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

FutureCall-back
Control flowclient pullsserver pushes
Compositioneasy (then/await)nested (callback hell)
Thread of executionclient’sserver’s (by default)
Natural inasync/await codeevent loops, GUIs