RPC: How shall we define RPC methods on TorClient<R>?
We'd like to be able to have RPC methods that work on TorClient<R>
for all R:Runtime
. But for now, our dispatch system only lets us define methods on types one at a time.
So for now, as a temporary measure, we're going to have the RPC system only work for TorClient<PreferredRuntime>
. But we'd like a better solution. (Not just for TorClient<R>
but for all cases where we'd like FooMgr<R>
to get exposed as an Object in our RPC system.)
We've discussed a bunch of such solutions on IRC; I'll try to summarize here and ask @Diziet to expand or edit where my memory or summary is faulty.
Even cleverer dispatch!
These solutions involve changes to the RPC subsystem only.
One entry per concrete type
When defining a method that works on TorClient<R>
, we could somehow make sure that an entry is added to our dispatch table for every R
. This means a larger dispatch table, which would not be the end of the world. It could maybe involve having to stick an inventory::submit!
inside a trait implementation: who knows if that would work? (Maybe with something like the old const _:() = {...}
trick?)
Have each Object know its method table.
We could change our dispatch system so that every object can provide its own method-to-function dispatch table, and then define Object for TorClient<R>
with a list of methods. @Diziet has some clever ideas for this case. (Long-term we would probably want to make these per-object dispatch tables shared; having one per TorClient
is not a big deal, but one per Stream
would be a lot.)
If we do this it might also be neat to let simpler objects participate in the old system, if that makes them easier to declare?
Doing this could make it tricky to define a method against an object that we are not defining. That's not a big problem for TorClient, which is itself very high level, but it could be a problem elsewhere.
If an object knows its method table, then it can either register that table itself, or give that table when asked, or just have a "dispatch(Box)" function.
Too many traits!
Make a trait for the parts of TorClient we want.
We could define a TorClientTrait
that provides access to all the APIs that don't expose anything that depends on Runtime
. Then we could define all of our RPC functionality against some type wrapping Arc<dyn TorClientTrait>
.
(There are variations of this where we break existing users and move all Arti APIs to the TorClientTrait
.)
Change the TorClient API to only use runtimes internally.
In addition to making a TorClientTrait for TorClient<R>
, we make a NewTorClient
that wraps Arc<TorClientTrait>
. Then we only expose NewTorClient
, and no longer expose generic TorClient<R>
.
This would be a breaking change, but it's one we've thought about making previously.
Sledgehammer solutions.
Make Runtime dyn-safe
We could make Runtime a dyn-safe trait (or make a dyn-safe wrapper for it) and then use that in TorClient
(or more places!), replacing TorClient<R>
with just TorClient
in our APIs.
This could make "get time" annoying, since we want to do that a LOT. But maybe it only needs to be efficient in tor-proto
, and we don't mind if it's slow in arti-client
?
Abolish Runtimes
We could make Arti tokio-only, and declare that we only support building with one runtime at a time. This would make our testing story a bit nastier, since we do use custom Runtimes right now for testing against simulated broken networks, for no-network mocking, time simulation, etc. Also we'd need a way to support user-provided Runtime kludges, since IIRC VPN tools use Runtimes to set magic "don't " flags on our outbound TCP stream.