@@ -6,8 +6,46 @@ use capnp::capability::{FromClientHook, Promise};
66use capnp:: private:: capability:: { ClientHook , RequestHook } ;
77use futures:: TryFutureExt ;
88
9+ /// Trait implemented by the reconnecting client to set new connection out-of-band.
10+ ///
11+ /// When using [`auto_reconnect`] or [`lazy_auto_reconnect`] it is not always optimal
12+ /// to wait for a call to fail with [`Disconnected`](capnp::ErrorKind::Disconnected)
13+ /// before replacing the client that is wrapped with a new fresh one.
14+ ///
15+ /// Sometimes we know by other means that a client has gone away. It could be that we
16+ /// have clients that automatically sends us a new capability when it reconnects to us.
17+ ///
18+ /// For these situations you can use the implementation of this trait that you get from
19+ /// [`auto_reconnect`] or [`lazy_auto_reconnect`] to manually set the target of the
20+ /// wrapped client.
21+ ///
22+ /// # Example
23+ ///
24+ /// ```ignore
25+ /// // The reconnecting client that automatically calls connect
26+ /// let (foo_client, set_target) = auto_reconnect(|| {
27+ /// Ok(new_future_client(connect()))
28+ /// })?;
29+ ///
30+ /// // do work with foo_client
31+ /// ...
32+ ///
33+ /// // We become aware that the client has gone so reconnect manually
34+ /// set_target.set_target(new_future_client(connect()));
35+ ///
36+ /// // do more work with foo_client
37+ /// ...
38+ /// ```
939pub trait SetTarget < C > {
40+ /// Adds a new reference to this implementation of SetTarget.
41+ ///
42+ /// This is mostly to get around that `Clone` requires `Sized` and so you need this
43+ /// trick to get a copy of the `Box<dyn SetTarget<C>>` you got from making the
44+ /// reconneting client.
1045 fn add_ref ( & self ) -> Box < dyn SetTarget < C > > ;
46+
47+ /// Sets the target client of the reconecting client that this trait implementation is
48+ /// for.
1149 fn set_target ( & self , target : C ) ;
1250}
1351
@@ -218,6 +256,65 @@ where
218256 }
219257}
220258
259+ /// Creates a new client that reconnects when getting [`ErrorKind::Disconnected`](capnp::ErrorKind::Disconnected) errors.
260+ ///
261+ /// Usually when you get a [`Disconnected`](capnp::ErrorKind::Disconnected) error respoonse from calling a method on a capability
262+ /// it means the end of that capability for good. And so you can't call methods on that
263+ /// capability any more.
264+ ///
265+ /// When you have a way of getting the capability back: Be it from a bootstrap or because
266+ /// the capability is persistent this method can help you wrap that reconnnection logic into a client
267+ /// that automatically runs the logic whenever a method call returns [`Disconnected`](capnp::ErrorKind::Disconnected).
268+ ///
269+ /// The way it works is that you provide a closure that returns a fresh client or a permanent error and
270+ /// you get a new conected client and a [`SetTarget`] interface that you can optionally use to prematurely
271+ /// replace the client.
272+ ///
273+ /// There is one caveat though: The original request that got a [`Disconnected`](capnp::ErrorKind::Disconnected)
274+ /// will still get that response. It is up to the caller to retry the call if relevant. `auto_reconnect`` only
275+ /// deals with the calls that come after.
276+ ///
277+ /// # Example
278+ ///
279+ /// ```capnp
280+ /// # Cap'n Proto schema
281+ /// interface Foo {
282+ /// identity @0 (x: UInt32) -> (y: UInt32);
283+ /// }
284+ /// ```
285+ ///
286+ /// ```ignore
287+ /// // A simple bootstrapped tcp connection to remote.example.com
288+ /// async fn connect() -> capnp::Result<foo_client::Client> {
289+ /// let stream = tokio::net::TcpStream::connect(&"remote.example.com:3001").await?;
290+ /// stream.set_nodelay(true)?;
291+ /// let (reader, writer) = tokio_util::compat::TokioAsyncReadCompatExt::compat(stream).split();
292+ ///
293+ /// let network = Box::new(twoparty::VatNetwork::new(
294+ /// futures::io::BufReader::new(reader),
295+ /// futures::io::BufWriter::new(writer),
296+ /// rpc_twoparty_capnp::Side::Client,
297+ /// Default::default(),
298+ /// ));
299+ ///
300+ /// let mut rpc_system = RpcSystem::new(network, None);
301+ /// let foo_client: foo_client::Client = rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server);
302+ /// tokio::task::spawn_local(rpc_system);
303+ /// Ok(foo_client)
304+ /// }
305+ /// // The reconnecting client that automatically calls connect
306+ /// let (foo_client, _) = auto_reconnect(|| {
307+ /// // By using new_future_client we delay any calls until we have a new connection.
308+ /// Ok(new_future_client(connect()))
309+ /// })?;
310+ /// // Calling Foo like normally.
311+ /// let mut request = foo_client.identity_request();
312+ /// request.get().set_x(123);
313+ /// let promise = request.send().promise.and_then(|response| {
314+ /// println!("results = {}", response.get()?.get_y());
315+ /// Ok(())
316+ /// });
317+ /// ```
221318pub fn auto_reconnect < F , C > ( mut connect : F ) -> capnp:: Result < ( C , Box < dyn SetTarget < C > > ) >
222319where
223320 F : FnMut ( ) -> capnp:: Result < C > ,
@@ -232,6 +329,13 @@ where
232329 Ok ( ( FromClientHook :: new ( hook) , Box :: new ( c) ) )
233330}
234331
332+ /// Creates a new client that lazily connect and also reconnects when getting [`ErrorKind::Disconnected`](capnp::ErrorKind::Disconnected) errors.
333+ ///
334+ /// For explanation of how this functions see: [`auto_reconnect`]
335+ ///
336+ /// The main difference between [`auto_reconnect`] and this function is that while [`auto_reconnect`] will call
337+ /// the closure immediatly to get an inner client to wrap, this function starts out disconnected and only calls
338+ /// the closure to get the actual client when the capability is first used.
235339pub fn lazy_auto_reconnect < F , C > ( connect : F ) -> ( C , Box < dyn SetTarget < C > > )
236340where
237341 F : FnMut ( ) -> capnp:: Result < C > ,
0 commit comments