The biggest remaining issue right now is how the message processing loop is implemented; with the current structure, I need to pick from one of either:
1. Risk of deadlocking when making a query from an event handler
2. Swallowing an error in a number of edgecases, and leaving a client hanging
3. Processing incoming events out of order
None of these are really acceptable, so it's time to rework the message processing loop instead! The new approach will be to split inbound query replies and events; events will be guaranteed ordered, but query replies will not be (as they are tied to a specific query anyway, so any ordering naturally happens in user code).
This way, a query reply cannot be held up by an event that's being processed (case 1), and I also won't need weird error-buffering techniques to make the receiving loop continue (case 2), while still leaving ordering intact for those messages where that might actually matter (namely, events).