Dispatch returns a result envelope

dpm_execute fills a dpm_result on every call: status and error_code
from the module's return value, and a payload the module hands back
through dpm_set_result. The payload's layout belongs to the module and
is documented per command; its release function is carried in the
envelope and invoked by dpm_result_release. The context keeps a stack
of the envelopes in progress, so a module calling a peer receives the
peer's payload in its own envelope and its caller sees only what the
module sets itself.

The good fixture returns a payload and counts its releases, and the
modules test covers the envelope fields, the payload round trip, single
release, discard on a NULL envelope, and a payload set outside any
dispatch. The prose documents describe the envelope and the payload
header a module ships.
This commit is contained in:
2026-09-07 14:53:24 -04:00
parent 0291e61fd8
commit 55c852586b
13 changed files with 404 additions and 39 deletions

View File

@@ -134,6 +134,49 @@ enum {
DPM_LOG_DEBUG = 4
};
/* ------------------------------------------------------------------ */
/* Results */
/* ------------------------------------------------------------------ */
/**
* @brief Outcome of a dispatch, carried in dpm_result.status
*/
enum {
/** The module returned 0. */
DPM_RESULT_OK = 0,
/** The module returned nonzero; error_code carries the value. */
DPM_RESULT_ERROR = 1
};
/**
* @brief What a dispatch returns
*
* Filled by dpm_execute() on every call. status and error_code are the
* envelope, the same for every module and every command. data is the
* payload: what the module chose to return for the command, in a
* layout that module documents for that command at that version. The
* caller that dispatched the command knows the layout it asked for.
*
* The payload belongs to the module that produced it, and release is
* how that module frees it. dpm_result_release() calls release and
* clears both fields. A module that returns no payload leaves data and
* release NULL.
*/
typedef struct dpm_result {
/** DPM_RESULT_OK or DPM_RESULT_ERROR. */
int status;
/** The module's return value; 0 when status is DPM_RESULT_OK. */
int error_code;
/** The payload, or NULL. */
void* data;
/** Frees data; NULL when data needs no release. */
void (*release)(void* data);
} dpm_result;
/* ------------------------------------------------------------------ */
/* Context configuration overrides */
/* ------------------------------------------------------------------ */
@@ -285,21 +328,40 @@ int dpm_get_module_info(dpm_ctx* ctx, dpm_module* mod,
* argc > 0; semantics beyond that are the module's to define.
*
* This is the only path into module code. A caller addresses a module
* by name and a capability by command string, so it holds no
* compile-time knowledge of the module it is calling — the same call
* by name and a capability by command string, and the same call is what
* a module makes to reach a peer.
*
* The envelope at `out` is filled on every call: status and error_code
* from the module's return value, and data and release from what the
* module handed to dpm_set_result() during the call. The caller owns
* the envelope's storage and releases the payload with
* dpm_result_release() when it is done with it. Passing NULL for `out`
* discards the payload before this call returns.
*
* @param ctx The libdpm-core.so context
* @param mod A module handle from dpm_require()
* @param command The command name; NULL or empty behaves as the
* module's help command
* @param argc Number of arguments
* @param argv Argument vector
* @param out Receives the result envelope; NULL discards the payload
* @return The module's return value verbatim; 0 on success
*/
DPM_PUBLIC_ABI_EXPORT
int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
int argc, char** argv);
int argc, char** argv, dpm_result* out);
/**
* @brief Releases a result's payload
*
* Calls the release function the module supplied, when there is one,
* and clears data and release. status and error_code are left as they
* were.
*
* @param result The envelope whose payload is released; NULL is a no-op
*/
DPM_PUBLIC_ABI_EXPORT
void dpm_result_release(dpm_result* result);
/* ------------------------------------------------------------------ */
/* Installation */
@@ -520,6 +582,28 @@ void dpm_set_last_error(dpm_ctx* ctx, const char* msg);
DPM_PUBLIC_ABI_EXPORT
const char* dpm_get_last_error(dpm_ctx* ctx);
/**
* @brief Hands a payload to the dispatch in progress
*
* A module returns data to its caller by calling this from inside
* dpm_module_execute. The payload lands in the envelope the caller
* passed to dpm_execute(), and release is what frees it when the caller
* is done. Nested dispatches each have their own envelope, so a payload
* set by a peer the module called does not reach the module's own
* caller unless the module sets it again itself.
*
* A second call during the same dispatch releases the earlier payload
* and replaces it. Outside any dispatch there is no envelope to receive
* the payload, so it is released at once.
*
* @param ctx The libdpm-core.so context handed to the module's entry
* point; NULL is a no-op
* @param data The payload; NULL clears it
* @param release Frees data; NULL when data needs no release
*/
DPM_PUBLIC_ABI_EXPORT
void dpm_set_result(dpm_ctx* ctx, void* data, void (*release)(void* data));
/* ------------------------------------------------------------------ */
/* Module contract (implemented by modules, called by libdpm-core.so) */
/* ------------------------------------------------------------------ */
@@ -534,9 +618,11 @@ const char* dpm_get_last_error(dpm_ctx* ctx);
* const char* dpm_module_aliases(void);
*
* dpm_module_execute is the only entry through which a module performs
* work. The other three are what it reports about itself; the library
* reads them at load, serves the version and description through
* dpm_get_module_info, and records the aliases at installation.
* work. Its return value becomes the envelope's status and error_code,
* and a payload for the caller is handed to dpm_set_result before it
* returns. The other three are what it reports about itself; the
* library reads them at load, serves the version and description
* through dpm_get_module_info, and records the aliases at installation.
*
* dpm_module_aliases returns a comma-separated list of alternate names
* the module answers to, or NULL to declare none. NULL is an answer; the