lahja
lahja copied to clipboard
Add an explicit Request/Response mechanism
What was wrong?
Trinity has a lot of code which looks like this:
async def handle_should_connect_to_requests(self) -> None:
async for req in self.wait_iter(self.event_bus.stream(ShouldConnectToPeerRequest)):
self.logger.debug2('Received should connect to request: %s', req.remote)
should_connect = self.tracker.should_connect_to(req.remote)
self.event_bus.broadcast(
ShouldConnectToPeerResponse(should_connect),
req.broadcast_config()
)
That's a lot of overhead if all you want to do is expose a method for other processes!
It's also a little dangerous. If multiple coroutines are subscribed like this, then every request will trigger all of them. The caller will accept the first response and drop the rest. You might actually want this in some situations, but I think in most situations it's unexpected and unwanted behavior.
How can it be fixed?
Maybe something like this?
# callee
self.event_bus.register_method(
'should-connect-to-peer',
self.tracker.should_connect_to
)
# caller
should_connect = await self.event_bus.call_method('should-connect-to-peer', remote)
call_method triggers a special event, endpoints which are listening for the given event call the registered method with the given args (in this case, remote). Once something like #42 lands Endpoints will inform each other what they're listening for, if you call register_method for an event which another Endpoint is already listening for it could throw an exception.
mypy would be very unhappy if we used the above interface, though I'm not sure what would be better. With this interface you could:
connect_to_peer = cast(
Callable[[XXX], XXX],
functools.partial(self.event_bus.call_method, 'should-connect-to')
)
but I'm sure there's a better way, possibly involving a mypy plugin?
I played a little with trying to do with this vanilla mypy but didn't find anything satisfying. Here's as close as I was able to get with a couple hours of exploration: https://gist.github.com/lithp/0e2b4a8619c4f8e9dae3dad7bda2d093
The things I'm least happy with about that solution (in descending order to terribleness)
- kwargs don't work
- you have to make a version of call_method for every possible number of args
- you have to call
make_handleand keep track of what it gives you
I think the first two can be solved with a mypy plugin and get_method_hook. The last one might actually be okay but I think you could get rid of it with some creativity and get_additional_deps. If you can force the register_method calls to be analyzed first and hold onto the types which were registered, you can then use those to type check the call_method calls.