How to defer synchronous IPC responses
This page shows you how to defer synchronous IPC responses so that a gateway can perform asynchronous work while clients wait for a standard response. Deferred responses allow gateways to perform asynchronous work while appearing synchronous to clients that need to wait for a standard response.
Overview
- Suspending the automatic response
- Sending the deferred result
- Coordinating multiple workers
- Production considerations
Suspend the automatic response
Ideally, for concurrency, all messaging would be async. However, sync messaging is a convenient paradigm for client apps.
Deferred responses via -30! unblock the main event loop during .z.pg, letting you dispatch async work and respond later.
This is implemented by calling -30!(::) after capturing the client handle from .z.w. This prevents the process from sending the normal response expected in synchronous calls when the function exits.
.z.pg:{[query]
/ 1. Dispatch async work to workers
remoteFunction:{[clntHandle;query;st;sp]
neg[.z.w](`callback;clntHandle;@[(0b;)value@;query;{[errorString](1b;errorString)}];st;sp)
};
/ send the query to each worker
neg[workerHandles]@\:(remoteFunction;.z.w;query;st;sp);
/ 2. Suspend the automatic return
-30!(::);
}
Notice in the above implementation:
Send the deferred result
When the async work finishes, call the ternary form of -30! to complete the request: -30!(handle; isError; result).
handleis the waiting client handle you captured earlierisErroris0bfor data,1bto raise an error on the clientresultis the payload to send (error text whenisErroris1b)
/ Respond with data
-30!(handle; 0b; tableData)
/ Respond with an error (signals 'limit to client)
-30!(handle; 1b; "limit")
A realistic callback example of how to send the deferred result:
callback:{[clientHandle;result;st;sp]
pending[clientHandle],:enlist result;
if[count[workerHandles]=count pending clientHandle;
isError:0<sum pending[clientHandle][;0];
result:pending[clientHandle][;1];
reduceFunction:reduceFunctionDict[sp];
r:$[isError;{first x where 10h=type each x};reduceFunction]result;
-30!(clientHandle;isError;(r;.z.P-st));
pending[clientHandle]:()
]
}
Errors are raised from unexpected responses
Sending a deferred response to a handle that is no longer waiting will raise an error 'Handle n was not expecting a response msg.
Example: Multi-worker Gateway
Use deferred responses to fan out a request to multiple workers, collect their results, and return a single synchronous response to the client.
Gateway Script
workerHandles:hopen each 6000 6001 / open handles to worker processes
pending:()!() / keep track of received results for each clientHandle
/ this example function joins the results when all are received from the workers
reduceFunction:raze
/ each worker calls this with (0b;result) or (1b;errorString)
callback:{[clientHandle;result]
pending[clientHandle],:enlist result; / store the received result
/ check whether we have all expected results for this client
if[count[workerHandles]=count pending clientHandle;
/ test whether any response (0|1b;...) included an error
isError:0<sum pending[clientHandle][;0];
result:pending[clientHandle][;1]; / grab the error strings or results
/ send the first error or the reduced result
r:$[isError;{first x where 10h=type each x};reduceFunction]result;
-30!(clientHandle;isError;r);
pending[clientHandle]:(); / clear the temp results
]
}
.z.pg:{[query]
remoteFunction:{[clntHandle;query]
neg[.z.w](`callback;clntHandle;@[(0b;)value@;query;{[errorString](1b;errorString)}])
};
neg[workerHandles]@\:(remoteFunction;.z.w;query); / send the query to each worker
-30!(::); / defer sending a response message i.e. return value of .z.pg is ignored
}
Client Experience
From the client's perspective, the interaction is standard synchronous IPC. The complexity of the async fan-out is hidden.
/ Client blocks until gateway aggregates all worker responses
q)h:hopen `::gatewayPort
q)h"select sum size by sym from trade"
Production Considerations
To use deferred responses in production, you need to handle errors and clean up old requests. This keeps your gateway stable and ensures clients aren't blocked forever.
Handling Worker Errors
If a worker fails, the client shouldn't hang. Instead, the worker should catch the error and send it back to the gateway, which then passes it to the client.
- Worker: Use trap (
@[f;fx;e]or.[g;enlist gx;e]) to catch errors. Return a list with a success flag and the result (or error message). - Gateway: Check the flag. If it indicates an error, send that error to the client using
-30!(handle; 1b; errorString).
Worker Protocol Example
/ From remoteFunction in the above examples
/ Returns (0b; result) for success
/ Returns (1b; "error string") for failure
result: @[(0b;) value@; query; {[errorString](1b; errorString)}]
Cleaning Up
You need to clean up pending requests if clients disconnect or workers become unresponsive.
Client Disconnects
If a client disconnects while waiting, remove their request from your tracking list. You can do this in the .z.pc handler.
.z.pc:{[handle]
pending _: handle; / remove the handle from the pending dictionary
}
Timeouts
Use the .z.ts timer to check for requests that have been waiting too long. If a request times out, send an error to the client and clear the request.
/ Example timeout logic to be called within .z.ts
checkTimeouts:{
/ ... logic to identify stale handles ...
-30!(staleHandle; 1b; "timeout");
pending _: staleHandle;
}
Summary
In this guide, you:
- Learned how
-30!(::)defers.z.pgresponses - Used the ternary
-30!form to deliver success or error payloads - Applied the pattern to a multi-worker gateway
- Understood potential safeguards for disconnects, timeouts, and worker errors
You now have the essential steps to defer synchronous IPC responses in KDB-X gateways.
Further reading
- Check out the blog post on Deferred Response