Written from a line-by-line source review; every example output is from a real run.
Introduction
The callbackudpserver plugin provides a minimal UDP server pattern: it opens a loopback port, and forwards incoming packets to a script-level callback. The server is driven by a background thread that watches for packets between `recvfrom` blockings; every packet reaches the given callback as a “borrowed” blob containing the sender's port, the IP host string, and the payload together. The callback may return a response blob, which the plugin immediately sends back to the sender — so a request-response pattern is supported.
Relationship to asyncmeta
The plugin uses the already documented asyncmeta constants: the State function returns asyncmeta.RouteStateOpen (=1) or RouteStateClosed (=0). StateText is the textual counterpart of the state (“open” or “closed”). This way the callback families use a uniform, shared vocabulary for the state.
Callback blob layout
For every incoming packet, the background thread invokes a script-level callback. The callback signature is `callback i32 NAME(ref blob $X)`. The borrowed blob has the following little-endian layout: 4 bytes port (sender's port), 4 bytes hostLen (host string length), then hostLen bytes of host string (usually “127.0.0.1”), and finally the actual payload (the remaining bytes). With the struct.assign pattern it is easy to process: define a struct with port + hostLen on the surface, and read the fields from it.
Loading the plugin
plugin "../plugins/print/PrintPlugin"; plugin "../plugins/callback_udp_server/CallbackUdpServerPlugin";
A typical flow — UDP echo server
callback i32 OnPacket(ref blob $X)
{
// Send the incoming packet's echo back (ref blob → response).
return(1);
}
i32 main()
{
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
// ... the background thread runs, callbacks fire ...
callbackudpserver.Close($sid);
return(0);
}What to know about every function (the basics)
Open always listens on LOOPBACK (localhost). It is NOT bound to an external IP.
port = 0 lets the OS pick a port; the actual port is returned by Port.
timeout = -1 means infinite wait for the callback; a positive value (ms) is the maximum wait after which the packet is DROPPED (Dropped counter goes up).
State returns RouteStateOpen (=1) or RouteStateClosed (=0); same constants as in asyncmeta.
serverId is a positive, monotonically increasing integer (i64); every subsequent operation (Close, Port, State, …) expects it.
The background thread stops on Close (the socket shutdown breaks the recvfrom), and only then does Close return.
OpenCount returns the number of currently open servers globally at the plugin level (regardless of which server they belong to).
An error (stopping the script) is caused by a wrong argument count or type, an out-of-range port (0..65535) or timeout, an invalid serverId, and a socket/thread startup failure.
How to read the signatures
callbackName is the script-level callback name. timeoutMs is the maximum wait for callback invocation in milliseconds; -1 = infinite. port is the TCP/IP UDP port (0..65535; 0 = OS picks). serverId is the positive id given by Open, expected by every subsequent operation. The type after the -> arrow is the return type.
Lifecycle
Opening the server on a loopback port and closing it. Open starts a background thread; Close waits for its shutdown.
callbackudpserver.Open
callbackudpserver.Open(callbackName, timeoutMs, port?) -> int
Opens a UDP server on loopback IP and forwards incoming packets to the callback through 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 maximum wait for callback invocation in milliseconds. -1 = infinite; otherwise 0..2147483647. |
| port | int | Optional UDP port to bind. 0..65535; 0 lets the OS pick a free port. Default: 0. |
The serverId (positive i64), expected by other operations. A bad argument or a bind failure raises a runtime error.
callback i32 OnPacket(ref blob $X) { return(1); }
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", ($sid > 0));1
For starting a tested UDP service (debug server, local echo, mock microservice). The 0-port is particularly handy in CI/test environments, because it does not clash with already-bound ports.
callbackudpserver.Close
callbackudpserver.Close(serverId) -> int
Closes the server: stops the background thread, shuts down the socket, and releases resources.
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The positive server id from Open. |
1 if the server was open and closed successfully; 0 if it was already closed. An unknown serverId or a non-positive value raises a runtime error.
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", callbackudpserver.Close($sid));1
For a clean server shutdown. Close waits for the background thread to actually stop, so the callback will no longer fire afterward — you can safely release resources tied to the callback.
State queries
Inspecting the server's current state: the bound port, and the “open/closed” state in both numeric and textual form.
callbackudpserver.Port
callbackudpserver.Port(serverId) -> int
Returns the server's actual bound port.
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The server id. |
The port (1..65535).
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", (callbackudpserver.Port($sid) > 0));1
Particularly useful when you passed port=0 to Open (asking for an OS-picked port). You query the returned port number with this — and you can advertise it to clients.
callbackudpserver.State
callbackudpserver.State(serverId) -> int
Returns the server's state using the asyncmeta route-state constants.
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The server id. |
asyncmeta.RouteStateOpen (=1) if open; asyncmeta.RouteStateClosed (=0) if closed.
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", callbackudpserver.State($sid));1
For programmatic state checking: comparing with asyncmeta.RouteStateOpen tells whether the server is alive. Same numeric code as the other callback_* plugins.
callbackudpserver.StateText
callbackudpserver.StateText(serverId) -> string
Returns the state in human-readable text.
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The server id. |
“open” if open; “closed” if closed.
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%s\n", callbackudpserver.StateText($sid));open
For logging or UI display. The textual counterpart of State's numeric value — same information in readable form.
Diagnostics
Counters and a key=value info string about the server's activity; a plugin-level OpenCount for the global view.
callbackudpserver.Handled
callbackudpserver.Handled(serverId) -> int
Returns the count of UDP packets successfully processed (forwarded to the callback) so far.
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The server id. |
The number of processed packets.
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", callbackudpserver.Handled($sid));0
For monitoring server throughput. Worth recording at the start of a given test or period, and observing the difference at the end — that gives the count of packets handled in that period.
callbackudpserver.Dropped
callbackudpserver.Dropped(serverId) -> int
Returns the count of dropped packets (due to callback timeout or buffer allocation failure).
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The server id. |
The number of dropped packets.
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", callbackudpserver.Dropped($sid));0
For an early signal of performance degradation: if it grows, either the callback does not fit in timeoutMs, or the buffer pool ran out. Raise timeoutMs, or write the callback faster.
callbackudpserver.Info
callbackudpserver.Info(serverId) -> string
Returns the server's complete key=value diagnostic state as a string.
| Parameter | Type | Description |
|---|---|---|
| serverId | int | The server id. |
A space-separated key=value list. Fields: id, state, stateCode, callback, timeout, port, handled, dropped.
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%s\n", callbackudpserver.Info($sid));id=1 state=open stateCode=1 callback=OnPacket timeout=-1 port=<auto> handled=0 dropped=0
For detailed diagnostics in a single call. The keys are stable; easy to parse (str.Find, str.Split-like patterns). Ideal for logging or a developer interface.
callbackudpserver.OpenCount
callbackudpserver.OpenCount() -> int
Returns the number of currently open UDP servers (plugin-level, global view).
This function takes no arguments.
The number of open servers (0 if none).
printf("%d\n", callbackudpserver.OpenCount());
i64 $sid;
$sid = callbackudpserver.Open("OnPacket", -1, 0);
printf("%d\n", callbackudpserver.OpenCount());
callbackudpserver.Close($sid);
printf("%d\n", callbackudpserver.OpenCount());0 1 0
For resource control and diagnostics. After Close it should return to 0; if it does not, that is a resource leak. For monitoring the real active count when managing multiple servers in parallel.
Practical notes
What the callbackudpserver plugin is good for
Starting a tested UDP service on loopback (debug server, local echo, mock).
Event-driven packet processing: every incoming datagram is a callback invocation; the script can focus on the content.
Request-response pattern: the callback's return blob is automatically sent back to the sender.
Simple telemetry: the Handled/Dropped counters give a snapshot of the server's state.
Processing the callback blob
The blob is little-endian: the first 4 bytes are the sender port (uint32), the next 4 bytes are the host string length (uint32), then the host string (usually “127.0.0.1”), and finally the payload. It can be declared as a struct: `struct UdpPacket packet { i32 port; i32 hostLen; }` — after `$X assign UdpPacket;`, `$X.port` and `$X.hostLen` are directly accessible. The host string and the payload can be read further via offset-based blob slicing.
Timeout and the Dropped counter
The timeoutMs parameter gives the callback's maximum wait time. If the callback takes longer (e.g. slow I/O), the background thread DROPS the packet and increments the Dropped counter. timeoutMs=-1 (infinite) is the most forgiving, but you may lose high-throughput capability, because the background thread only resumes receiving once the callback returns. For high traffic, a short (e.g. 50-200 ms) timeoutMs is recommended — a rising Dropped warns when intervention is needed.
Loopback-only binding
The plugin intentionally binds only to loopback (127.0.0.1) — a security default, optimized for sandbox-style, tested use. You CANNOT start a server reachable from the external network with this plugin; for that, the callback_listener or another suitable plugin (with the proper configuration) is the tool.
Error handling
An invalid argument (wrong type, empty callback name, port > 65535, timeout > INT_MAX, negative timeout != -1), a failed socket creation, a port conflict on bind, a failed background-thread start, and a call referring to an already-closed server are reported by the runtime as a runtime error, and the script stops. Close signals an open server with 1 and an already-closed one with 0 — but both are successful call results (not a runtime error).
The callbackudpclient UDP client plugin
Loopback UDP client, datagram sending, and reply callback — complete function reference