Reconnecting to a Service

A service can go away: its process crashes or restarts, or — over RPC — its session ends because one connection failed, a reply deadline expired or the server closed it. Every proxy to it then returns DeadObject, and getting back means looking the service up again, on a new session for RPC, and registering your callbacks again. rsbinder::Reconnecting does that for one service.

use rsbinder::{Endpoint, Reconnecting};

let listener = listener.clone();
let hello = Reconnecting::<dyn IHello>::builder(Endpoint::unix("/run/hello.sock").with_service("hello"))
    .on_connect(move |conn| {
        // Every (re)connection, before callers get the new proxy.
        conn.proxy().register_listener(&listener)?;
        Ok(())
    })
    .build()?;

let reply = hello.with(|h| h.echo("hi"))?;

The Uri is any the entry API takes: a kernel endpoint with a service name (Uri::kernel().with_service(name), binder://name) for the service manager, or an RPC endpoint with one (#name). Leave the service name out to connect to a server's root object — the form a libbinder RpcServer::setRootObject server needs, since only an rsbinder server has a directory of names.

What happens when the service goes away

The helper notices, starts a thread of its own, looks the service up again — backing off from 10 ms to at most 1 s between failed attempts — runs on_connect, and only then hands the new proxy to callers. generation() counts the connections made.

How it notices depends on what the lookup returned:

Looked-up serviceNoticed by
a kernel binder proxyits death notification, at once
RPC, the session has incoming connections or a serve loopits death notification, as the session ends
RPC otherwise — the default unix://…#namethe next call: before it is sent where the socket reports the close (Unix sockets; TCP on Linux/Android), otherwise after that call fails
a service in the same processnever: it cannot die on its own

It never decides from a status code. The call that ended a session can return TimedOut, and a live server's handler can return DeadObject.

Calls while it reconnects

with runs your closure once and returns its result. A failed call is never sent again: only the service knows whether a call may run twice.

With no connection, with returns DeadObject — but if a reconnect attempt is in progress it first waits for it, up to ReconnectPolicy::call_wait (1 s). A server that is already back answers an attempt within milliseconds, so the call runs on the new connection. Between attempts — the server still down — calls fail at once instead of each waiting a second. Inside a binder transaction or inside on_connect nothing waits: the reconnect may be waiting on that very thread.

To wait across attempts, use wait_connected(timeout), or connected().await with tokio. current() returns the proxy without waiting, for async code that must not block.

Starting before the server

build() tries once without waiting. It fails only for what another attempt cannot change — a Uri that serve / connect would refuse, options that do not fit it, a missing feature, a kernel endpoint without a service name, no binder device for a kernel endpoint (NoInit), a kernel service of another interface (BadType), an RPC name lookup on a server without a directory of names (BadType), a name the service manager refuses to watch, or ConnectError::stop from on_connect. A server that is not up yet is not an error: the helper connects in the background, so a client may start first. Set ClientOptions::timeout through options(...) if the server could accept a connection and never answer.

on_connect

Register callbacks, subscribe, restore what the server lost. Return Err(ConnectError::stop(code)) to give up for good; anything else — including a ? on a call that failed — counts as a failed attempt and is retried. A callback the server makes while the hook runs should use conn.proxy(): the helper hands out the new proxy only after the hook returns.

Lifetime

Dropping the Reconnecting (or close()) closes the current RPC session and stops any reconnect in progress after its current step, without waiting for it. A callback object that needs the helper should hold helper.downgrade() — a WeakReconnecting — not the helper: a strong handle there keeps the helper, its session and the callback alive in a cycle.

The helper does not register services. A service that outlives a restarted service manager must add itself again; on Android, init restarts the services that depend on servicemanager.