Reduce the frequency of connection-related renderer crashes (see #3664)

- Use ResetWithReason to report intentional browser side disconnects of
  existing Mojo connections. Don't retry for those disconnects.
- Add set_disconnect_with_reason_and_result_handler in Chromium/Mojo to
  expose the MojoResult code for failed connections, allowing
  identification of connections that are intentionally unbound on the
  browser side.
- Optimize initial reconnect delay for known disconnect cases such as
  navigation-related and bfcache changes.
- Remove connection timeout and increase total connection deadline by
  100% to further reduce crash rates on slower machines.
- Only fail fatally for main frames (not sub-frames) in cases where the
  connection fails or disconnects for unknown reasons.
- Improve connection debug logging when running with
  `--enable-logging --vmodule=*frame*=1 --log-file=C:\temp\log.txt`
This commit is contained in:
Marshall Greenblatt
2024-11-22 16:12:52 -05:00
parent a02960b2fc
commit 7f253f83a2
11 changed files with 691 additions and 190 deletions

View File

@@ -11,6 +11,7 @@
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/threading/thread_checker.h"
#include "cef/libcef/common/frame_util.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
@@ -19,47 +20,36 @@
#include "mojo/public/cpp/bindings/receiver.h"
#include "url/origin.h"
namespace content {
// Base class for mojo interface implementations tied to a document's lifetime.
// The service will be destroyed when any of the following happens:
// 1. mojo interface connection error happened,
// 2. the RenderFrameHost was deleted, or
// 3. navigation was committed on the RenderFrameHost (not same document) and
// ShouldCloseOnFinishNavigation() returns true.
//
// WARNING: To avoid race conditions, subclasses MUST only get the origin via
// origin() instead of from |render_frame_host| passed in the constructor.
// See https://crbug.com/769189 for an example of such a race.
// Base class for mojo interface implementations tied to a RenderFrameHost
// lifetime. The service will be destroyed on mojo interface connection error
// or RFH deletion.
//
// Based on the old implementation of DocumentServiceBase that existed prior to
// https://crrev.com/2809effa24. CEF requires the old implementation to support
// bindings that outlive navigation.
template <typename Interface>
class FrameServiceBase : public Interface, public WebContentsObserver {
class CefFrameServiceBase : public Interface,
public content::WebContentsObserver {
public:
FrameServiceBase(RenderFrameHost* render_frame_host,
mojo::PendingReceiver<Interface> pending_receiver)
: WebContentsObserver(
WebContents::FromRenderFrameHost(render_frame_host)),
CefFrameServiceBase(content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<Interface> pending_receiver)
: content::WebContentsObserver(
content::WebContents::FromRenderFrameHost(render_frame_host)),
render_frame_host_(render_frame_host),
origin_(render_frame_host_->GetLastCommittedOrigin()),
receiver_(this, std::move(pending_receiver)) {
// |this| owns |receiver_|, so unretained is safe.
receiver_.set_disconnect_handler(
base::BindOnce(&FrameServiceBase::Close, base::Unretained(this)));
base::BindOnce(&CefFrameServiceBase::Close, base::Unretained(this)));
}
protected:
// Make the destructor private since |this| can only be deleted by Close().
~FrameServiceBase() override = default;
// All subclasses should use this function to obtain the origin instead of
// trying to get it from the RenderFrameHost pointer directly.
const url::Origin& origin() const { return origin_; }
~CefFrameServiceBase() override = default;
// Returns the RenderFrameHost held by this object.
RenderFrameHost* render_frame_host() const { return render_frame_host_; }
content::RenderFrameHost* render_frame_host() const {
return render_frame_host_;
}
// Subclasses can use this to check thread safety.
// For example: DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -72,44 +62,23 @@ class FrameServiceBase : public Interface, public WebContentsObserver {
// Use WebContents::From(render_frame_host()) instead, but please keep in mind
// that the render_frame_host() might not be active. See
// RenderFrameHost::IsActive() for details.
using WebContentsObserver::web_contents;
using content::WebContentsObserver::web_contents;
// WebContentsObserver implementation.
void RenderFrameDeleted(RenderFrameHost* render_frame_host) final {
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) final {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (render_frame_host == render_frame_host_) {
DVLOG(1) << __func__ << ": RenderFrame destroyed.";
DVLOG(1) << __func__ << ": RenderFrameHost destroyed.";
if (receiver_.is_bound()) {
receiver_.ResetWithReason(
static_cast<uint32_t>(frame_util::ResetReason::kDeleted),
"Deleted");
}
Close();
}
}
void DidFinishNavigation(NavigationHandle* navigation_handle) final {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!ShouldCloseOnFinishNavigation()) {
return;
}
if (!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument() ||
navigation_handle->IsPageActivation()) {
return;
}
if (navigation_handle->GetRenderFrameHost() == render_frame_host_) {
// FrameServiceBase is destroyed either when RenderFrameHost is
// destroyed (covered by RenderFrameDeleted) or when a new document
// commits in the same RenderFrameHost (covered by DidFinishNavigation).
// Only committed non-same-document non-bfcache non-prerendering
// activation navigations replace a document in existing RenderFrameHost.
DVLOG(1) << __func__ << ": Close connection on navigation.";
Close();
}
}
// Used for CEF bindings that outlive navigation.
virtual bool ShouldCloseOnFinishNavigation() const { return true; }
// Stops observing WebContents and delete |this|.
void Close() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -117,11 +86,8 @@ class FrameServiceBase : public Interface, public WebContentsObserver {
delete this;
}
const raw_ptr<RenderFrameHost> render_frame_host_ = nullptr;
const url::Origin origin_;
const raw_ptr<content::RenderFrameHost> render_frame_host_ = nullptr;
mojo::Receiver<Interface> receiver_;
};
} // namespace content
#endif // CEF_LIBCEF_BROWSER_FRAME_SERVICE_BASE_H_