Call-Back Routine
A call-back routine inverts control: the client registers a function pointer (or closure) with the server, and the server invokes it when the result is ready, the client never polls, never redeems a ticket. It’s the push counterpart to futures’ pull.
Why call-back instead of ticket/future?
Because a future forces the client to block somewhere, if the event loop model is “don’t ever block,” you need a way for the server to notify the client without the client asking. Call-backs fit naturally into event-driven code (GUI, I/O reactor, signal handlers). The cost is inversion-of-control complexity: state that was local to a call is now smeared across the closure and the callback site.
Sketch
_Task Server {
struct Job { Request r; void (*cb)(Reply); };
Queue<Job> jobs;
public:
void submit( Request r, void (*cb)(Reply) ) {
jobs.push( { r, cb } );
}
private:
void main() {
for ( ;; ) {
_Accept( submit ) {}
while ( ! jobs.empty() ) {
Job j = jobs.pop();
Reply a = process( j.r );
j.cb( a ); // invoke client's callback
}
}
}
};Concurrency trap
The callback runs in the server’s thread unless explicitly dispatched back. If the client’s callback touches client-local state, you’ve just created a race. Two fixes:
- Server posts the callback to a per-client queue; client drains it from its own thread.
- Callback holds only thread-safe references, does its own synchronization.
Compared to futures
| Future | Call-back | |
|---|---|---|
| Control flow | client pulls | server pushes |
| Composition | easy (then/await) | nested (callback hell) |
| Thread of execution | client’s | server’s (by default) |
| Natural in | async/await code | event loops, GUIs |