Written from a line-by-line source review; every example output is from a real run.
Introduction
The callbacktcp plugin is a complete callback-based TCP package: an `Open` call opens a loopback TCP server (incoming connections are dispatched to a callback), and the SAME plugin can also act as a client — it can send a request to its own (or another) listener via `RequestText`/`RequestBlob` (synchronous) or `BeginTextRequest`/`BeginBlobRequest` (asynchronous) calls. The plugin supports the full asyncmeta vocabulary (Capabilities = 4095 = ProfileRouteService), and every job-level query has two names: the native `Request*` and the asyncmeta-compatible `Job*` synonym.
Server + client in one
This plugin is both a TCP server AND a TCP client. `Open(callback, timeoutMs)` opens a loopback TCP port and dispatches the RAW bytes of every incoming connection to the callback on a background thread. The callback signature is `callback i32 NAME(ref blob $Request)`; the callback may modify the request blob in place, and the modified bytes become the TCP response. Meanwhile, `RequestText(listenerId, text, timeoutMs)` synchronously and `BeginTextRequest(listenerId, text, timeoutMs)` asynchronously open a TCP connection to the listener's port, send the text/blob, and return the response (or an asynchronous requestId).
What is the client side for?
The `RequestText` / `Begin*Request` families exist primarily for TESTING: you call your own server with them to confirm the callback handles requests correctly. The loopback TCP channel has minimal overhead and works like a locally simulated incoming connection. They can also reach other loopback servers (e.g. another callback_tcp_listener listener, or another local TCP service).
Relationship to asyncmeta
The plugin is fully built on the asyncmeta constants. `Capabilities()` returns exactly the `asyncmeta.ProfileRouteService()` bitmask (=4095) — the entire vocabulary supported. `RouteState` returns `asyncmeta.RouteStateOpen` (=1) / `RouteStateClosed` (=0); `RouteMeta(listenerId, key)` knows the `asyncmeta.RouteMeta*` keys; `JobMeta(requestId, key)` knows the `asyncmeta.JobMeta*` keys.
Request* / Job* synonym pairs
Job-level (i.e., asynchronous request) query functions all exist under two names. `Request*` is the native, descriptive name (paints a “request”, HTTP-like image); `Job*` is the asyncmeta-vocabulary name. The two names map to the EXACT same handler — pick stylistically. Examples: `RequestPhase` ↔︎ `Phase`, `RequestStatus` ↔︎ `Status`, `RequestError` ↔︎ `ErrorText`, `RequestStatusText` ↔︎ `StatusText`, `RequestInfo` ↔︎ `JobInfo`, `RequestRoute` ↔︎ `JobRoute`, `RequestTimeout` ↔︎ `JobTimeout`, `RequestKnown` ↔︎ `JobKnown`, `RequestMeta` ↔︎ `JobMeta`, `RequestHasBlobResult` ↔︎ `JobHasBlobResult`, `RequestBlobResultSize` ↔︎ `JobBlobResultSize`.
Optional TLS layer
`EnableTls(listenerId, certFile, keyFile)` activates TLS on the listener: every subsequent incoming connection is wrapped in TLS by the tls plugin's provider. The listener itself does NOT link OpenSSL — that is resolved at runtime via the tls plugin. On a platform that doesn't support TLS (e.g. Windows pre-bundle), EnableTls returns a clean error and the listener keeps serving plaintext. TLS activation is IDEMPOTENT: calling it again with the same files just returns 0.
Sync vs async request
`RequestText(listenerId, text, timeoutMs)` and `RequestBlob(listenerId, blob, timeoutMs)` are SYNCHRONOUS: the call BLOCKS until the TCP connection is established, the request is sent, and the response arrives. `BeginTextRequest`/`BeginBlobRequest` are ASYNCHRONOUS: they return immediately with a requestId, and a background thread handles the connect/send/wait. Async jobs are managed with `WaitComplete(requestId, timeoutMs)`, `TakeTextResponse(requestId)`, and `TakeBlob(requestId)`.
Loading the plugin
plugin "../plugins/print/PrintPlugin"; plugin "../plugins/async_meta/AsyncMetaPlugin"; plugin "../plugins/callback_tcp_listener/CallbackTcpListenerPlugin";
A typical flow — synchronous echo on loopback
callback i32 Echo(ref blob $Request)
{
// the callback may modify the request bytes in place;
// these become the TCP response bytes
return(1);
}
i32 main()
{
i64 $lid;
string $resp[64];
$lid = callbacktcp.Open("Echo", 5000);
$resp = callbacktcp.RequestText($lid, "PING", 5000);
// ... process the response ...
callbacktcp.Close($lid);
return(0);
}A typical flow — asynchronous request with WaitComplete + TakeTextResponse
i64 $lid;
i64 $jobId;
string $resp[64];
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
// ... the script does other things ...
callbacktcp.WaitComplete($jobId, 5000);
if (callbacktcp.Status($jobId) == 0)
{
$resp = callbacktcp.TakeTextResponse($jobId);
// ... processing ...
}
callbacktcp.Close($lid);What to know about every function (the basics)
Capabilities is always ProfileRouteService = 4095 (every asyncmeta bit).
Open binds to loopback (127.0.0.1) on an OS-picked port (query with Port).
The `Request*` and `Job*` families are SYNONYMS — exactly the same handler. Stylistic choice for code style.
RequestText/RequestBlob are synchronous; BeginTextRequest/BeginBlobRequest are asynchronous.
WaitComplete takes a requestId (NOT listenerId), and does NOT destroy the request.
TakeTextResponse and TakeBlob destroy the request; the requestId is invalid afterward.
EnableTls is idempotent — calling it again with the same files just returns 0.
RouteClose and Close map to the same handler: close the listener, return the canceled queued requests count.
RouteCloseAccepted is a LATCH — once it's 1 it never resets.
An error (stopping the script) is caused by a wrong argument count or type, an out-of-range timeoutMs, an invalid listenerId/requestId, a closed plugin, TCP connect failure, and embedded NUL byte in a text response.
How to read the signatures
callbackName is the script-level callback name (`callback i32 NAME(ref blob $X)`). timeoutMs is the callback run-time (Open) or request I/O (Request*, Begin*) max in ms; -1 = infinite. listenerId is the positive id given by Open. requestId is the positive id given by Begin*. metaKey is one of the asyncmeta.RouteMeta* or JobMeta* values. The type after the -> arrow is the return type.
Lifecycle and TLS
Opening the listener on a loopback TCP port, optional TLS activation, and plugin shutdown.
callbacktcp.Capabilities
callbacktcp.Capabilities() -> int
Returns the plugin's capability bitmask matching the asyncmeta constants.
This function takes no arguments.
asyncmeta.ProfileRouteService (=4095): every asyncmeta bit (the entire vocabulary supported).
printf("%d\n", callbacktcp.Capabilities());
printf("%d\n", (callbacktcp.Capabilities() ==
asyncmeta.ProfileRouteService()));4095 1
For backend capability checking with the Supports* helpers. callback_tcp_listener supports the FULL vocabulary (like callback_listener) — the Supports* helpers return 1 for everything.
callbacktcp.Open
callbacktcp.Open(callbackName, timeoutMs) -> int
Opens a TCP listener on loopback IP and dispatches every incoming connection to the callback via a background thread.
| Parameter | Type | Description |
|---|---|---|
| callbackName | string | The script-level callback name to call. Signature: `callback i32 NAME(ref blob $X)`. |
| timeoutMs | int | The callback run-time limit in ms. -1 = infinite; otherwise 0..2147483647. |
The listenerId (positive i64). A bad argument, bind failure, or thread startup failure raises a runtime error.
callback i32 Echo(ref blob $X) { return(1); }
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", ($lid > 0));1
For starting a local TCP service (debug server, local RPC, tested protocol). The port is OS-picked; query it with Port($lid). The callback receives RAW TCP bytes — any text or binary protocol is freely implementable.
callbacktcp.EnableTls
callbacktcp.EnableTls(listenerId, certFile, keyFile) -> int
Activates TLS on the listener: every subsequent connection is wrapped in TLS by the tls plugin's provider.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| certFile | string | The server certificate path (PEM). |
| keyFile | string | The private key path (PEM). |
0 (success). Idempotent: calling it again with the same files just returns 0. If TLS is not supported on the platform, a clean runtime error.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
// callbacktcp.EnableTls($lid, "server.crt", "server.key");
printf("ok\n");ok
For a secure local service with a TLS layer. The listener itself does not link OpenSSL — it relies on the tls plugin's provider at runtime. The probe didn't activate TLS (we only document the verb's presence).
callbacktcp.OpenCount
callbacktcp.OpenCount() -> int
Returns the count of currently open listeners (plugin-wide, global view).
This function takes no arguments.
The number of open listeners (0 if none).
printf("%d\n", callbacktcp.OpenCount());
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.OpenCount());0 1
For resource control and diagnostics. If it does not return to 0 after Close calls, that signals a resource leak.
callbacktcp.Close
callbacktcp.Close(listenerId) -> int
Closes the given listener: stops the accept thread, shuts down the socket, releases resources.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id from Open. |
The count of queued requests canceled by the close. Typically 0 (clean close with nothing pending).
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.Close($lid));0
For clean listener shutdown. Close waits for in-progress callbacks to finish. If you called RouteClose first, Close returns 0 (listener no longer active).
Listener-level state
Current state of the listener: bound port, “open/closed” state, callback name, timeout, full diagnostic text. Route* names are equivalent to the State*/Info names.
callbacktcp.Port
callbacktcp.Port(listenerId) -> int
Returns the listener's actual, bound TCP port.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The port (1..65535).
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", (callbacktcp.Port($lid) > 0));1
For querying the OS-picked port. RequestText/Blob and BeginTextRequest/BeginBlobRequest use this port automatically via the listenerId.
callbacktcp.State
callbacktcp.State(listenerId) -> int
Returns the listener's state using the asyncmeta route-state constants.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
asyncmeta.RouteStateOpen (=1) or RouteStateClosed (=0).
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.State($lid));1
For programmatic state checking. Shared vocabulary with the callback_* family.
callbacktcp.RouteState
callbacktcp.RouteState(listenerId) -> int
Synonym of State — under the asyncmeta vocabulary.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
asyncmeta.RouteStateOpen (=1) or RouteStateClosed (=0).
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteState($lid));1
For asyncmeta-compatible code.
callbacktcp.StateText
callbacktcp.StateText(listenerId) -> string
The state as text.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
“open” or “closed”.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%s\n", callbacktcp.StateText($lid));open
For logging or UI display.
callbacktcp.RouteStateText
callbacktcp.RouteStateText(listenerId) -> string
Synonym of StateText.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
“open” or “closed”.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%s\n", callbacktcp.RouteStateText($lid));open
For logging, under the asyncmeta vocabulary.
callbacktcp.RouteCallback
callbacktcp.RouteCallback(listenerId) -> string
Returns the callback name passed at Open.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The callback name as text.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%s\n", callbacktcp.RouteCallback($lid));Echo
For reflective queries and diagnostics.
callbacktcp.RouteTimeout
callbacktcp.RouteTimeout(listenerId) -> int
Returns the timeout value passed at Open, in ms.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The timeoutMs value; -1 for infinite.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteTimeout($lid));5000
Reflective query of listener configuration.
callbacktcp.RouteKnown
callbacktcp.RouteKnown(listenerId) -> int
Tells whether the listenerId is known to the plugin.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if known (open); 0 if not.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteKnown($lid));1
Defensive check.
callbacktcp.RouteMeta
callbacktcp.RouteMeta(listenerId, metaKey) -> int
Returns a specific meta field of the listener by asyncmeta.RouteMeta* keys.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| metaKey | int | An asyncmeta.RouteMeta* value (State, Timeout, Pending, Queued, Running, Drained, Known, CloseAccepted, ClosedQueued). |
The value for the field.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteMeta($lid,
asyncmeta.RouteMetaState()));
printf("%d\n", callbacktcp.RouteMeta($lid,
asyncmeta.RouteMetaDrained()));1 1
Uniform asyncmeta query — for portable multi-backend code.
callbacktcp.Info
callbacktcp.Info(listenerId) -> string
Returns the listener's aggregated key=value diagnostic state as a string.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
A space-separated key=value list.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", (str.Len(callbacktcp.Info($lid)) > 0));1
Detailed diagnostics in a single call.
callbacktcp.RouteInfo
callbacktcp.RouteInfo(listenerId) -> string
Synonym of Info — key=value diagnostic state.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
A space-separated key=value list.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", (str.Len(callbacktcp.RouteInfo($lid)) > 0));1
For logging, under the asyncmeta vocabulary.
Listener-level traffic
Listener traffic metrics: active, pending, queued, running connections; drain state and blocking wait. Route* names are synonyms here too.
callbacktcp.Pending
callbacktcp.Pending(listenerId) -> int
Returns the number of pending (not yet started) requests.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of pending requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.Pending($lid));0
Early signal of congestion.
callbacktcp.RoutePending
callbacktcp.RoutePending(listenerId) -> int
Synonym of Pending.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of pending requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RoutePending($lid));0
Under the asyncmeta vocabulary.
callbacktcp.Queued
callbacktcp.Queued(listenerId) -> int
Returns the number of queued requests.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of queued requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.Queued($lid));0
For monitoring the internal queue length.
callbacktcp.RouteQueued
callbacktcp.RouteQueued(listenerId) -> int
Synonym of Queued.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of queued requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteQueued($lid));0
Under the asyncmeta vocabulary.
callbacktcp.Running
callbacktcp.Running(listenerId) -> int
Returns the number of currently running requests.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of running requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.Running($lid));0
For monitoring parallelism.
callbacktcp.RouteRunning
callbacktcp.RouteRunning(listenerId) -> int
Synonym of Running.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of running requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteRunning($lid));0
Under the asyncmeta vocabulary.
callbacktcp.Active
callbacktcp.Active(listenerId) -> int
Returns the number of currently active connections.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of active connections.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.Active($lid));0
For monitoring current server load.
callbacktcp.Drained
callbacktcp.Drained(listenerId) -> int
Tells whether the listener is drained (pending = queued = running = active = 0).
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if drained; 0 if something is in progress.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.Drained($lid));1
Before clean shutdown: ensure Drained=1 before calling Close.
callbacktcp.RouteDrained
callbacktcp.RouteDrained(listenerId) -> int
Synonym of Drained.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if drained; 0 if not.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteDrained($lid));1
Under the asyncmeta vocabulary.
callbacktcp.WaitDrained
callbacktcp.WaitDrained(listenerId, timeoutMs) -> int
Blocks until the listener is drained, or timeoutMs expires.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| timeoutMs | int | Max wait in ms; -1 = infinite. |
1 if drain occurred before timeoutMs expired; 0 if it expired.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.WaitDrained($lid, 50));1
In the two-step shutdown pattern: AFTER RouteClose, wait for drain.
callbacktcp.WaitRouteDrained
callbacktcp.WaitRouteDrained(listenerId, timeoutMs) -> int
Synonym of WaitDrained.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| timeoutMs | int | Max wait in ms. |
1 if drain occurred; 0 if expired.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.WaitRouteDrained($lid, 50));1
Under the asyncmeta vocabulary.
Request sending (client side)
Sending a TCP request to a listener's port (typically to test your own listener). Request* is synchronous, Begin* is asynchronous.
callbacktcp.RequestText
callbacktcp.RequestText(listenerId, text, timeoutMs) -> string
Sends a synchronous TCP request to the listener's port and returns the response text.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| text | string | The text to send (UTF-8 or ASCII; the response MUST NOT contain NUL bytes). |
| timeoutMs | int | Max time for the entire connection (connect + send + receive) in ms. |
The response text. If the response contains a NUL byte, a runtime error.
i64 $lid;
string $resp[64];
$lid = callbacktcp.Open("Echo", 5000);
$resp = callbacktcp.RequestText($lid, "PING", 5000);
printf("%d\n", str.Len($resp));0
For testing your own callback: send a specific request and check the response. A simple, synchronous pattern — if you need async, use BeginTextRequest.
callbacktcp.RequestBlob
callbacktcp.RequestBlob(listenerId, request, timeoutMs) -> blob
Sends a synchronous TCP request with a blob to the listener's port and returns the response blob.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| request | blob | The binary data to send. |
| timeoutMs | int | Max time for the entire connection in ms. |
The response blob (the callback-modified bytes). May be empty if the callback didn't write anything.
i64 $lid;
blob $req[3];
blob $resp[16];
$req[0] = 65;
$req[1] = 66;
$req[2] = 67;
$lid = callbacktcp.Open("Echo", 5000);
$resp = callbacktcp.RequestBlob($lid, $req, 5000);
printf("%d\n", $resp.Length);0
For binary protocols. NUL bytes are allowed (only RequestText restricts them).
callbacktcp.BeginTextRequest
callbacktcp.BeginTextRequest(listenerId, text, timeoutMs) -> int
Starts an asynchronous TCP request: the background thread connects, sends, and waits — the call returns immediately with a requestId.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| text | string | The text to send. |
| timeoutMs | int | Max I/O time in ms. |
The requestId (positive i64).
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", ($jobId > 0));1
For an async testing pattern: start a request, do other things, then WaitComplete + TakeTextResponse.
callbacktcp.BeginBlobRequest
callbacktcp.BeginBlobRequest(listenerId, request, timeoutMs) -> int
Starts an asynchronous TCP request with a blob payload.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| request | blob | The binary data to send. |
| timeoutMs | int | Max I/O time in ms. |
The requestId (positive i64).
i64 $lid;
i64 $jobId;
blob $req[3];
$req[0] = 65;
$req[1] = 66;
$req[2] = 67;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginBlobRequest($lid, $req, 5000);
printf("%d\n", ($jobId > 0));1
For async binary requests. Take the response with TakeBlob after WaitComplete.
Wait and response take
Waiting for an async request to complete and taking the response (text or blob).
callbacktcp.WaitComplete
callbacktcp.WaitComplete(requestId, timeoutMs) -> int
Blocks until the request completes, but does NOT destroy it — status and result are queryable afterward.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId from Begin*. |
| timeoutMs | int | Max wait in ms; -1 = infinite. |
1 if the request completed; 0 if the timeout expired.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", callbacktcp.WaitComplete($jobId, 5000));1
First step of the async two-step pattern: wait for completion, verify success via Status / RequestStatus, then TakeTextResponse / TakeBlob the response.
callbacktcp.TakeTextResponse
callbacktcp.TakeTextResponse(requestId) -> string
Returns the text response of a completed request, and destroys the request.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The response text. If the request has not completed, a runtime error. If it contains a NUL byte, a runtime error.
i64 $lid;
i64 $jobId;
string $resp[64];
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
$resp = callbacktcp.TakeTextResponse($jobId);
printf("%d\n", str.Len($resp));0
For a text response. If the callback didn't write anything, the response is an empty string. For binary, use TakeBlob.
callbacktcp.TakeBlob
callbacktcp.TakeBlob(requestId) -> blob
Returns the blob response of a completed request, and destroys the request.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The response blob.
i64 $lid;
i64 $jobId;
blob $req[3];
blob $resp[16];
$req[0] = 65;
$req[1] = 66;
$req[2] = 67;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginBlobRequest($lid, $req, 5000);
callbacktcp.WaitComplete($jobId, 5000);
$resp = callbacktcp.TakeBlob($jobId);
printf("%d\n", $resp.Length);0
For a binary response (may contain NUL bytes). The callback may modify the request bytes in place — these become the response.
Request-level state and metadata
Current state (phase, status, error) and metadata of an async request. Request* and Job* names are SYNONYMS — exactly the same handler, just stylistically interchangeable.
callbacktcp.RequestPhase
callbacktcp.RequestPhase(requestId) -> int
Returns the request phase from the asyncmeta.Phase* constants.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
asyncmeta.PhaseQueued (=0), PhaseRunning (=1), or PhaseCompleted (=2).
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.RequestPhase($jobId));2
For programmatic phase checks. Synonym of Phase.
callbacktcp.Phase
callbacktcp.Phase(requestId) -> int
Synonym of RequestPhase — under the asyncmeta vocabulary.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
asyncmeta.PhaseQueued / PhaseRunning / PhaseCompleted.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.Phase($jobId));2
For asyncmeta-compatible code.
callbacktcp.RequestPhaseText
callbacktcp.RequestPhaseText(requestId) -> string
The phase as text.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“queued”, “running”, or “completed”.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%s\n", callbacktcp.RequestPhaseText($jobId));completed
For logging or UI display.
callbacktcp.PhaseText
callbacktcp.PhaseText(requestId) -> string
Synonym of RequestPhaseText.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“queued”, “running”, or “completed”.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%s\n", callbacktcp.PhaseText($jobId));completed
Under the asyncmeta vocabulary.
callbacktcp.RequestStatus
callbacktcp.RequestStatus(requestId) -> int
Returns the request status: 0 = success, non-zero = error.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
0 if successful; non-zero is the error code.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.RequestStatus($jobId));0
Worth checking first after WaitComplete.
callbacktcp.Status
callbacktcp.Status(requestId) -> int
Synonym of RequestStatus.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
0 or error code.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.Status($jobId));0
Under the asyncmeta vocabulary.
callbacktcp.RequestError
callbacktcp.RequestError(requestId) -> string
Returns the request error message (empty on success).
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The error message; empty string on success.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", str.Len(callbacktcp.RequestError($jobId)));0
If Status is non-0, check this for the cause.
callbacktcp.ErrorText
callbacktcp.ErrorText(requestId) -> string
Synonym of RequestError.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The error message.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", str.Len(callbacktcp.ErrorText($jobId)));0
Under the asyncmeta vocabulary.
callbacktcp.RequestStatusText
callbacktcp.RequestStatusText(requestId) -> string
The textual counterpart of the status.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“success” on a successful request, other text depending on the error type.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%s\n", callbacktcp.RequestStatusText($jobId));success
For logging or user feedback.
callbacktcp.StatusText
callbacktcp.StatusText(requestId) -> string
Synonym of RequestStatusText.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“success” or error text.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%s\n", callbacktcp.StatusText($jobId));success
Under the asyncmeta vocabulary.
callbacktcp.RequestInfo
callbacktcp.RequestInfo(requestId) -> string
Returns the request state as a key=value diagnostic string.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
A space-separated key=value list.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", (str.Len(callbacktcp.RequestInfo($jobId)) >
0));1
Detailed request diagnostics in a single call.
callbacktcp.JobInfo
callbacktcp.JobInfo(requestId) -> string
Synonym of RequestInfo.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
A key=value diagnostic string.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", (str.Len(callbacktcp.JobInfo($jobId)) > 0));1
Under the asyncmeta vocabulary.
callbacktcp.RequestRoute
callbacktcp.RequestRoute(requestId) -> int
Returns the id of the listener the request belongs to.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The listenerId.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", (callbacktcp.RequestRoute($jobId) == $lid));1
Reflective query of the request-owner relation.
callbacktcp.JobRoute
callbacktcp.JobRoute(requestId) -> int
Synonym of RequestRoute.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The listenerId.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", (callbacktcp.JobRoute($jobId) == $lid));1
Under the asyncmeta vocabulary.
callbacktcp.RequestTimeout
callbacktcp.RequestTimeout(requestId) -> int
Returns the request's configured timeout in ms (from Begin*).
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The timeoutMs value.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", callbacktcp.RequestTimeout($jobId));5000
For logging or configuration checks.
callbacktcp.JobTimeout
callbacktcp.JobTimeout(requestId) -> int
Synonym of RequestTimeout.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The timeoutMs value.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", callbacktcp.JobTimeout($jobId));5000
Under the asyncmeta vocabulary.
callbacktcp.RequestKnown
callbacktcp.RequestKnown(requestId) -> int
Tells whether the requestId is known to the plugin.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if known; 0 if not (e.g. already destroyed by Take).
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", callbacktcp.RequestKnown($jobId));1
Defensive check.
callbacktcp.JobKnown
callbacktcp.JobKnown(requestId) -> int
Synonym of RequestKnown.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if known; 0 if not.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
printf("%d\n", callbacktcp.JobKnown($jobId));1
Under the asyncmeta vocabulary.
callbacktcp.RequestMeta
callbacktcp.RequestMeta(requestId, metaKey) -> int
Returns a meta field of the request by asyncmeta.JobMeta* keys.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
| metaKey | int | An asyncmeta.JobMeta* value (Phase, Status, Route, Timeout, Known, HasBlobResult, BlobResultSize). |
The value for the field.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.RequestMeta($jobId,
asyncmeta.JobMetaPhase()));2
Uniform asyncmeta query on the request state.
callbacktcp.JobMeta
callbacktcp.JobMeta(requestId, metaKey) -> int
Synonym of RequestMeta.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
| metaKey | int | An asyncmeta.JobMeta* value. |
The value for the field.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.JobMeta($jobId,
asyncmeta.JobMetaPhase()));2
Under the asyncmeta vocabulary — for portable multi-backend code.
callbacktcp.RequestHasBlobResult
callbacktcp.RequestHasBlobResult(requestId) -> int
Tells whether the request has a blob result.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if yes; 0 if no.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.RequestHasBlobResult($jobId));1
Take precondition: if 0, TakeBlob raises a runtime error.
callbacktcp.JobHasBlobResult
callbacktcp.JobHasBlobResult(requestId) -> int
Synonym of RequestHasBlobResult.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if yes; 0 if no.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.JobHasBlobResult($jobId));1
Under the asyncmeta vocabulary.
callbacktcp.RequestBlobResultSize
callbacktcp.RequestBlobResultSize(requestId) -> int
Returns the response blob size in bytes.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The blob size in bytes.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.RequestBlobResultSize($jobId));0
For memory pre-allocation: you know how big a blob to allocate for TakeBlob.
callbacktcp.JobBlobResultSize
callbacktcp.JobBlobResultSize(requestId) -> int
Synonym of RequestBlobResultSize.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The blob size in bytes.
i64 $lid;
i64 $jobId;
$lid = callbacktcp.Open("Echo", 5000);
$jobId = callbacktcp.BeginTextRequest($lid, "HELLO", 5000);
callbacktcp.WaitComplete($jobId, 5000);
printf("%d\n", callbacktcp.JobBlobResultSize($jobId));0
Under the asyncmeta vocabulary.
Listener shutdown
Diagnostics of the listener (route) shutdown process and explicit shutdown. RouteClose and Close do the same thing under different names.
callbacktcp.RouteCloseAccepted
callbacktcp.RouteCloseAccepted(listenerId) -> int
Tells whether a close request has been accepted on the listener.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if yes; 0 if no RouteClose / Close has happened yet.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
callbacktcp.RouteClose($lid);
printf("%d\n", callbacktcp.RouteCloseAccepted($lid));1
LATCH: once 1, it never resets. Useful for diagnostics.
callbacktcp.RouteClosedQueued
callbacktcp.RouteClosedQueued(listenerId) -> int
Returns the count of queued requests canceled by the most recent RouteClose / Close.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The count of canceled queued requests.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
callbacktcp.RouteClose($lid);
printf("%d\n", callbacktcp.RouteClosedQueued($lid));0
For diagnostics: how many requests were lost during shutdown.
callbacktcp.RouteClose
callbacktcp.RouteClose(listenerId) -> int
Synonym of Close — exact same handler.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The count of canceled queued requests (0 = clean close).
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", callbacktcp.RouteClose($lid));0
Under the asyncmeta vocabulary. Equivalent to Close — stylistic choice.
callbacktcp.RouteCloseInfo
callbacktcp.RouteCloseInfo(listenerId) -> string
Returns the listener shutdown process as a key=value diagnostic string.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
A space-separated key=value list.
i64 $lid;
$lid = callbacktcp.Open("Echo", 5000);
printf("%d\n", (str.Len(callbacktcp.RouteCloseInfo($lid)) >
0));1
Detailed shutdown diagnostics in a single call.
Practical notes
What the callbacktcp plugin is good for
Starting a local TCP service on a loopback port (debug server, local RPC, tested protocol).
Event-driven processing: every incoming TCP connection is a callback invocation; the callback gets RAW bytes and can implement any protocol.
Built-in client side for testing your own server: `RequestText`/`RequestBlob` are synchronous, `BeginTextRequest`/`BeginBlobRequest` are asynchronous — both open a loopback TCP connection.
Optional TLS layer with EnableTls: incoming connections are TLS-wrapped, the callback receives the decoded bytes unchanged.
asyncmeta compatibility with the full vocabulary: Phase, Status, JobMeta, RouteMeta, WaitDrained, RouteClose — every bit supported (Capabilities = 4095).
Server + client in one — the testing pattern
A key feature of the plugin is that a complete TCP exchange is reachable from one script: Open opens a server, and RequestText immediately calls the server itself. This is ideal for testing a callback implementation: you can invoke your own logic through a real TCP channel, ensuring the endpoints communicate properly. A minimal test: open a listener → send RequestText → check the response → close.
Request* vs Job* — synonym pairs
Every job-level (= queryable per async request) function has two names: the native `Request*` (paints the HTTP/RPC request image) and the asyncmeta-vocabulary `Job*`. The two forms are EXACTLY the same handler — pick stylistically. If your code works with other asyncmeta-compatible backends, `Job*` is more consistent; if only callbacktcp, `Request*` is more expressive.
Sync vs async request
`RequestText`/`RequestBlob` are SYNCHRONOUS: the call blocks until the TCP connection + send + receive finishes. Simple, but the script does nothing else meanwhile. `BeginTextRequest`/`BeginBlobRequest` are ASYNCHRONOUS: they return immediately with a requestId, the background thread handles I/O. Then WaitComplete + TakeTextResponse / TakeBlob finish and collect. For high-throughput or parallel testing, the async form is recommended.
TLS activation
`EnableTls(listenerId, certFile, keyFile)` is IDEMPOTENT: calling it again with the same files just returns 0. The TLS layer runs through the tls plugin's provider — resolved at runtime, so the callbacktcp plugin itself does not link OpenSSL. If the platform doesn't support TLS (e.g. a Windows pre-bundle build), EnableTls returns a clean error and the listener keeps serving plaintext.
Callback signature
The callback signature is `callback i32 NAME(ref blob $Request)`. The incoming `$Request` blob contains the RAW bytes received over the TCP connection — the plugin does not interpret HTTP, does not look for protocol framing. The callback may modify the blob bytes in place, and the modified content becomes the TCP response. If the callback writes nothing, the response is empty (as in the probe — the Echo callback returns 1 without modifying the blob).
Listener shutdown
`RouteClose(listenerId)` and `Close(listenerId)` are the SAME handler — two names, identical behavior. The return value is NOT a 1/0 success flag, but the COUNT of queued requests canceled by the close. Typically 0 (clean close with no pending requests). The two-step shutdown pattern: RouteClose → WaitDrained → forced cleanup (if needed). A single Close performs all steps.
Error handling
An invalid listenerId, requestId, wrong argument count or type, out-of-range timeoutMs, touching an already-closed listener or plugin are reported by the runtime as a runtime error, and the script stops. A TCP connect failure (e.g. connecting to a closed listener's port) gives an error code in the Status field; `RequestError` returns the error text. `TakeTextResponse` raises a runtime error if the response contains an embedded NUL byte — use TakeBlob instead.
The random number plugin
General-purpose pseudo-random number generator — xoshiro256++ algorithm, 8 verbs