// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_EXTERNAL_MOJO_EXTERNAL_SERVICE_SUPPORT_RECONNECTING_REMOTE_H_
#define CHROMECAST_EXTERNAL_MOJO_EXTERNAL_SERVICE_SUPPORT_RECONNECTING_REMOTE_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "chromecast/external_mojo/external_service_support/external_connector.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace chromecast {
// A class which wraps a mojo::Remote with automatic reconnection logic.
//
// Two reconnection methods are supported: (1) Provide a service name and an
// ExternalConnector to reconnect, or (2) provide a callback to rebind the
// remote.
//
// Clients can register observer callbacks to be notified of reconnect events so
// that they can re-initialize some state in the remote process. Observers are
// notified in the same order they were registered. Observers should use WeakPtr
// if they expect to outlive the ReconnectingRemote.
//
// This class can also be used to wrap a local implementation. This can be used
// for (1) Client code which can exist in both in and out-of-process, and (2)
// Injecting a mock implementation. Since the impl is called directly, this
// allows for synchronous method call validation, as opposed to asynchronously
// posting mojo calls which require a base::RunLoop to verify in unit tests.
//
template <typename Interface>
class ReconnectingRemote {
public:
// Reconnect option 1: Provide an ExternalConnector to request the interface
// from a named service.
ReconnectingRemote(const std::string& service_name,
external_service_support::ExternalConnector* connector)
: service_name_(service_name), connector_(connector) {
DCHECK(connector_);
Connect();
}
// Reconnect option 2: Provide a callback to re-bind |remote_|. |remote_| is
// always in an unbound state before |connect_callback_| is run.
explicit ReconnectingRemote(
base::RepeatingCallback<void(mojo::Remote<Interface>* remote)>
connect_callback)
: connect_callback_(std::move(connect_callback)) {
Connect();
}
// Option 3: Inject an implementation directly to wrap a local implementation.
// Reconnection is not necessary since a local instance will always exist.
explicit ReconnectingRemote(Interface* impl) : remote_proxy_(impl) {}
ReconnectingRemote(const ReconnectingRemote&) = delete;
ReconnectingRemote& operator=(const ReconnectingRemote&) = delete;
~ReconnectingRemote() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
Interface* get() const { return remote_proxy_; }
Interface* operator->() const { return get(); }
Interface& operator*() const { return *get(); }
void OnReconnect(base::RepeatingClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observer_callbacks_.push_back(std::move(callback));
}
private:
void Connect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
remote_.reset();
if (connector_) {
connector_->BindInterface(service_name_,
remote_.BindNewPipeAndPassReceiver());
} else {
connect_callback_.Run(&remote_);
}
DCHECK(remote_.is_bound());
remote_proxy_ = remote_.get();
remote_.set_disconnect_handler(
base::BindOnce(&ReconnectingRemote::Connect, base::Unretained(this)));
for (auto& callback : observer_callbacks_) {
callback.Run();
}
}
const std::string service_name_;
external_service_support::ExternalConnector* const connector_ = nullptr;
base::RepeatingCallback<void(mojo::Remote<Interface>* remote)>
connect_callback_;
mojo::Remote<Interface> remote_;
Interface* remote_proxy_ = nullptr;
std::vector<base::RepeatingClosure> observer_callbacks_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<ReconnectingRemote> weak_factory_{this};
};
} // namespace chromecast
#endif // CHROMECAST_EXTERNAL_MOJO_EXTERNAL_SERVICE_SUPPORT_RECONNECTING_REMOTE_H_