Reputation: 16765
We are currently looking for RPC
frameworks and were unable to find any with signal functionality unfortunately however we need it. We looked at gRPC
, Apache Thrift
, Cap-n-Proto
and found that no one of them provide such functionality out of the box as DBus does. Worth mentioning, we need it as IPC. Also, we need to monitor 1 more socket so one is for RPC server and another is for another server. In DBus
we could add it into the glib's mainloop. Our target RPC must allow this.
P.S. DBus
is not really what we need because we need only client-server architecture instead of client-bus-daemon.
P.P.S. About off-topic
- I see nothing in this question which needs opinionated answer. Answer should contain facts but not opinions.
Upvotes: 7
Views: 2984
Reputation: 45286
Signals can be implemented on top of Cap'n Proto in a few different ways.
Chain of objects
There is no problem with a Cap'n Proto RPC call taking a very long time to complete. Other calls on the same connection can continue normally, and you can have many outstanding calls at a time. Therefore, one strategy for receiving signals is to have a call that waits for the signal before returning.
Many RPC systems support hanging calls, but there's an additional challenge: If you have a stream of signals, and it's important that the client observe every signal in the stream, then things get complicated if new signals are being generated faster than the client calls the RPC to read them. You would need to keep a buffer for each client. But what if the client dies and stops making requests? Now you need some sort of timeout after which you clear it.
Unlike most other RPC systems, Cap'n Proto supports generating new objects on-the-fly. You can therefore represent your stream of signals as a chain of objects. For example:
struct MyPayload { ... }
interface MyInterface {
subscribe @0 () -> (firstSignal :Signal(MyPayload));
# Subscribe to signals from this interface.
}
interface Signal(Type) {
# One signal in a stream of signals. Has a payload, and lets you
# wait for the next signal.
get @0 () -> (value :Type);
# Gets the payload value of this signal. (Returns immediately.)
waitForNext @1 () -> (nextSignal :Signal(Type));
# Waits for the next signal in the sequence, returning a new
# `Signal` object representing it.
}
This greatly simplifies state management on the server side since Cap'n Proto will automatically call each object's destructor as soon as all clients have indicated that they are done with it (by destroying the client-side reference, aka "dropping" it). If a client disconnects, all its references are implicitly dropped.
Callbacks
Because Cap'n Proto allows RPC calls in both directions (client -> server and server -> client), you can implement a "signal" or publish/subscribe mechanism using callbacks:
struct MyPayload { ... }
interface MyInterface {
subscribe @0 (cb :Callback(MyPayload)) -> (handle :Handle);
}
interface Callback(Type) {
call @0 (value :Type);
}
interface Handle {}
The client calls subscribe()
and passes a callback object cb
. The server can then call back to the client any time there is a signal.
Note that subscribe()
returns a Handle
, which is an object with no methods. The purpose of this is to detect when the client unsubscribes. If the client drops handle
, the server will be notified (the server-side object's destructor will run), and the server can then unregister the callback. This also handles the case where the client disconnects -- all object references are implicitly dropped on a disconnect.
At first glance this solution probably looks much better than the chain-of-objects solution, due to its simplicity. However, it has the problem that you now have object references pointing in both directions, which can lead to cycles. Inside your client code, you have to be careful to make sure the callback implementation does not "own" the handle which keeps it registered, otherwise it will never get cleaned up (except when the connection closes). You also have to make sure that the callback can still be called for a short period after dropping the handle, while you wait for the server to unregister the callback. These issues aren't present in the chain-of-objects solution, which may make that solution cleaner to implement.
Other RPC systems
I discussed Cap'n Proto above because I'm the author and because it provides more options than most RPC systems.
If you use gRPC, you can use its "streaming" feature to support something like signals. A streaming RPC can return multiple responses over time.
I'm not sure about Thrift. The last time I tried it, requests had to be FIFO, which meant that long-running RPCs were a no-no. However, that was a long time ago and maybe it has changed since then.
Upvotes: 10