Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
Application Manager

init/execute/shutdown lifecycle entry point for applications More...

Files

file  src/app/appmanager/sapi_appmanager.c
 Application Manager implementation.

Data Structures

struct  sapi_appmanager_operations_t
 Application lifecycle operations. More...
struct  sapi_appmanager_checkpoint_config_t
 Optional built-in checkpoint rendezvous configuration (ADR-019/ADR-034). More...
struct  sapi_appmanager_config_t
 Application manager configuration. More...
struct  sapi_appmanager_state_t
 Application manager runtime state. More...

Macros

#define SAPI_APPMANAGER_CHECKPOINT_SIGNATURE_SEED   ((uint64_t)0xFFFFFFFFFFFFFFFFULL)
 Reset value of the per-cycle checkpoint signature before any marks are folded into it (ADR-034) - see sapi_appmanager_checkpoint_fold_signature()'s own doc. A cycle in which no SAPI_CHECKPOINT_MARK() call is ever made therefore always computes checkpoint_id as sapi_appmanager_checkpoint_fold_signature(SAPI_APPMANAGER_CHECKPOINT_SIGNATURE_SEED).
#define SAPI_CHECKPOINT_MARK()
 Records a checkpoint mark for this call site (ADR-034) - see sapi_appmanager_checkpoint_mark()'s own doc.
#define SAPI_CHECKPOINT_MARK_LABEL(label_str)
 Records a checkpoint mark identified by a caller-supplied label instead of this call site's file:line (ADR-034) - see sapi_appmanager_checkpoint_mark()'s own doc.

Enumerations

enum  sapi_app_state_t {
  SAPI_APP_STATE_UNINITIALIZED = 0 , SAPI_APP_STATE_INITIALIZING = 1 , SAPI_APP_STATE_RUNNING = 2 , SAPI_APP_STATE_SHUTTING_DOWN = 3 ,
  SAPI_APP_STATE_SHUTDOWN = 4 , SAPI_APP_STATE_ERROR = 5
}
 Application state enumeration. More...

Functions

uint32_t sapi_appmanager_checkpoint_fold_signature (uint64_t signature)
 Pure helper: folds a 64-bit checkpoint signature down to the uint32_t sapi_checkpoint_config_t::checkpoint_id expects (ADR-034).
void sapi_appmanager_checkpoint_mark (const char *file, int32_t line, const char *label)
 Folds a hash of this call site into the current cycle's checkpoint signature (ADR-034).
int sapi_appmanager_run (const sapi_appmanager_config_t *config)
 Run application with lifecycle management.
sapi_app_state_t sapi_appmanager_get_state (void)
 Get current application state.
sapi_status_t sapi_appmanager_get_stats (sapi_appmanager_state_t *state)
 Get application statistics.
void sapi_appmanager_request_shutdown (void)
 Request application shutdown.
sapi_status_t sapi_appmanager_install_default_signal_handlers (void)
 POSIX-only convenience: installs SIGINT and SIGTERM handlers that call sapi_appmanager_request_shutdown(), so an operator (Ctrl+C) or process manager (SIGTERM) can stop a sapi_appmanager_run() loop gracefully - shutdown() still runs, this is not a hard kill.
void sapi_appmanager_reset_state (void)
 Forcibly resets the application manager's own bookkeeping (lifecycle state, iteration/error counters, shutdown-request flag, and the ADR-026 setup-phase lock) back to its initial, pre-run condition.

Detailed Description

init/execute/shutdown lifecycle entry point for applications

Macro Definition Documentation

◆ SAPI_APPMANAGER_CHECKPOINT_SIGNATURE_SEED

#define SAPI_APPMANAGER_CHECKPOINT_SIGNATURE_SEED   ((uint64_t)0xFFFFFFFFFFFFFFFFULL)

Reset value of the per-cycle checkpoint signature before any marks are folded into it (ADR-034) - see sapi_appmanager_checkpoint_fold_signature()'s own doc. A cycle in which no SAPI_CHECKPOINT_MARK() call is ever made therefore always computes checkpoint_id as sapi_appmanager_checkpoint_fold_signature(SAPI_APPMANAGER_CHECKPOINT_SIGNATURE_SEED).

  • a fixed, deterministic value, not zero by coincidence.

Definition at line 341 of file sapi_appmanager.h.

◆ SAPI_CHECKPOINT_MARK

#define SAPI_CHECKPOINT_MARK ( )
Value:
sapi_appmanager_checkpoint_mark(__FILE__, (int32_t)__LINE__, NULL)
void sapi_appmanager_checkpoint_mark(const char *file, int32_t line, const char *label)
Folds a hash of this call site into the current cycle's checkpoint signature (ADR-034).

Records a checkpoint mark for this call site (ADR-034) - see sapi_appmanager_checkpoint_mark()'s own doc.

Definition at line 398 of file sapi_appmanager.h.

◆ SAPI_CHECKPOINT_MARK_LABEL

#define SAPI_CHECKPOINT_MARK_LABEL ( label_str)
Value:
sapi_appmanager_checkpoint_mark(__FILE__, (int32_t)__LINE__, (label_str))

Records a checkpoint mark identified by a caller-supplied label instead of this call site's file:line (ADR-034) - see sapi_appmanager_checkpoint_mark()'s own doc.

Definition at line 406 of file sapi_appmanager.h.

Enumeration Type Documentation

◆ sapi_app_state_t

Application state enumeration.

Tracks the lifecycle state of an application managed by the app manager.

Enumerator
SAPI_APP_STATE_UNINITIALIZED 

Not yet initialized

SAPI_APP_STATE_INITIALIZING 

Initialization in progress

SAPI_APP_STATE_RUNNING 

Normal execution

SAPI_APP_STATE_SHUTTING_DOWN 

Shutdown in progress

SAPI_APP_STATE_SHUTDOWN 

Shutdown complete

SAPI_APP_STATE_ERROR 

Error state

Definition at line 96 of file sapi_appmanager.h.

Function Documentation

◆ sapi_appmanager_checkpoint_fold_signature()

uint32_t sapi_appmanager_checkpoint_fold_signature ( uint64_t signature)

Pure helper: folds a 64-bit checkpoint signature down to the uint32_t sapi_checkpoint_config_t::checkpoint_id expects (ADR-034).

An explicit, checked fold - XOR of the signature's two 32-bit halves (CLAUDE.md: no implicit truncating cast) - not a bare narrowing assignment. Exposed as a pure, stateless function (no reference to any in-progress cycle) so a test, or an integrator's own diagnostic logging, can independently compute the expected checkpoint_id for a known signature value without needing to be inside an active sapi_appmanager_run() cycle - e.g. sapi_appmanager_checkpoint_fold_signature(SAPI_APPMANAGER_CHECKPOINT_SIGNATURE_SEED) gives the checkpoint_id for a cycle that made no marks at all.

Parameters
signatureThe 64-bit signature to fold (e.g. the seed, for a no-marks cycle).
Returns
The folded 32-bit checkpoint_id.

Definition at line 159 of file sapi_appmanager.c.

◆ sapi_appmanager_checkpoint_mark()

void sapi_appmanager_checkpoint_mark ( const char * file,
int32_t line,
const char * label )

Folds a hash of this call site into the current cycle's checkpoint signature (ADR-034).

Intended to be called via SAPI_CHECKPOINT_MARK() / SAPI_CHECKPOINT_MARK_LABEL() below, not directly, so file/line are captured at the real call site. Each call computes a CRC64 hash of either label (if non-NULL) or file:line, then chains it into the running per-cycle signature: signature = crc64(encode_le(signature) || encode_le(mark_hash)) - the same explicit-little-endian-byte-pack convention sapi_checkpoint.c's own build_arrival_message() already uses, so the fold is well-defined regardless of the two channels' CPU architectures.

A no-op (does not touch the running signature) if both file and label are NULL, and outside a sapi_appmanager_run() cycle (before the loop starts, or after it ends) - safe to call unconditionally from shared code paths that might run during init()/shutdown() too.

Parameters
fileSource file of the call site (normally FILE); ignored if label is non-NULL.
lineSource line of the call site (normally LINE); ignored if label is non-NULL.
labelOptional caller-supplied identity for this mark instead of file:line (e.g. when several call sites should be treated as the same logical mark, or a single call site's mark identity should depend on which branch was taken). May be NULL to use file:line.

Definition at line 171 of file sapi_appmanager.c.

◆ sapi_appmanager_run()

int sapi_appmanager_run ( const sapi_appmanager_config_t * config)

Run application with lifecycle management.

This is the single entry point for all applications. It manages the complete lifecycle:

  1. Initialize (init)
  2. Per-cycle loop, in order (ADR-034 - checkpoint moved to the end, see sapi_appmanager_checkpoint_config_t's own doc for why): a. Reset this cycle's checkpoint-mark signature to a fixed seed b. pre_execute(), if ops->pre_execute is non-NULL c. execute() d. post_execute(), if ops->post_execute is non-NULL e. Checkpoint rendezvous, if config->checkpoint is non-NULL, using this cycle's own folded mark signature as checkpoint_id f. ops->on_checkpoint_result(), if non-NULL
  3. Shutdown (shutdown)

Error handling:

  • REQ-APPMANAGER-009 (ADR-026): if a PREVIOUS call to this function is still mid-lifecycle (its own INITIALIZING/RUNNING/SHUTTING_DOWN) when this one is entered, it is refused immediately with EXIT_FAILURE and none of that previous call's state is touched - this is the single entry point and cannot be concurrently re-entered. A NEW call made only after a previous one has fully returned (state SHUTDOWN/ERROR) is unaffected - this framework's own test suite relies on exactly that sequential-call pattern.
  • If init() fails, shutdown() is still called and EXIT_FAILURE is returned
  • If the pre_execute(), execute(), post_execute(), or checkpoint stage of a cycle returns non-OK, that is logged and counted against error_count exactly the same way regardless of which stage failed; any remaining stage of that same cycle before the checkpoint stage is skipped, and the loop continues to the next cycle (unless error_threshold is reached) - EXCEPT ops->on_checkpoint_result(), which (per ADR-034) is still called even when the checkpoint stage itself failed, precisely so a staging application always learns the outcome (committed=false) and can discard what it staged this cycle; see that field's own doc
  • If error_threshold is reached, application shuts down
  • shutdown() is always called, even on error
Parameters
configApplication manager configuration
Returns
0 (EXIT_SUCCESS) if application completed normally 1 (EXIT_FAILURE) if initialization failed or errors exceeded threshold

Example (single-stage, unchanged from before ADR-019):

.init = my_app_init,
.execute = my_app_execute,
.shutdown = my_app_shutdown,
.get_name = my_app_get_name,
.get_version = my_app_get_version
};
.ops = &ops,
.context = &my_app_context,
.max_iterations = 0, // Run forever
.error_threshold = 10 // Stop after 10 errors
};
return sapi_appmanager_run(&config);
int sapi_appmanager_run(const sapi_appmanager_config_t *config)
Run application with lifecycle management.
Application manager configuration.
Application lifecycle operations.

Example (pre/post hooks plus a built-in cross-channel checkpoint, ADR-019/ADR-034 - my_app_prepare_outputs() marks and stages, never sends directly; my_app_commit_outputs() only runs after the checkpoint confirms both channels took the same path this cycle):

.init = my_app_init,
.pre_execute = my_app_read_inputs,
.execute = my_app_decide,
.post_execute = my_app_prepare_outputs, // calls SAPI_CHECKPOINT_MARK(), stages, does not transmit
.on_checkpoint_result = my_app_commit_outputs, // committed=true -> flush staged output; false -> discard it
.shutdown = my_app_shutdown,
.get_name = my_app_get_name,
.get_version = my_app_get_version
};
static const sapi_appmanager_checkpoint_config_t checkpoint_cfg = {
.voter = &my_voter, // already has its channels sapi_voter_register_channel()-ed
.max_delay_ms = 200,
.expected_node_count = 1,
.watchdog = NULL
};
.ops = &ops,
.context = &my_app_context,
.max_iterations = 0,
.error_threshold = 10,
.checkpoint = &checkpoint_cfg
};
return sapi_appmanager_run(&config);
Optional built-in checkpoint rendezvous configuration (ADR-019/ADR-034).

Definition at line 203 of file sapi_appmanager.c.

◆ sapi_appmanager_get_state()

sapi_app_state_t sapi_appmanager_get_state ( void )

Get current application state.

Returns the current state of a running application manager. Note: This is intended for monitoring, not for control.

Returns
Current application state

Definition at line 457 of file sapi_appmanager.c.

◆ sapi_appmanager_get_stats()

sapi_status_t sapi_appmanager_get_stats ( sapi_appmanager_state_t * state)

Get application statistics.

Returns runtime statistics (iterations, errors, etc.).

Parameters
statePointer to sapi_appmanager_state_t to populate
Returns
SAPI_STATUS_OK on success

Definition at line 462 of file sapi_appmanager.c.

◆ sapi_appmanager_request_shutdown()

void sapi_appmanager_request_shutdown ( void )

Request application shutdown.

Signals the application to begin shutdown. The execute loop will terminate after the current iteration.

Note: This function requests shutdown but does not guarantee immediate termination. The shutdown may take one iteration to complete.

Definition at line 472 of file sapi_appmanager.c.

◆ sapi_appmanager_install_default_signal_handlers()

sapi_status_t sapi_appmanager_install_default_signal_handlers ( void )

POSIX-only convenience: installs SIGINT and SIGTERM handlers that call sapi_appmanager_request_shutdown(), so an operator (Ctrl+C) or process manager (SIGTERM) can stop a sapi_appmanager_run() loop gracefully - shutdown() still runs, this is not a hard kill.

This is the one function in this module with an OS dependency - see this header's own file-level

Note
for why that is an intentional, scoped exception rather than a general pattern for this framework. Safe to call unconditionally on any target: on a non-POSIX build this compiles to a no-op that returns SAPI_STATUS_NOT_SUPPORTED, so portable integration code does not need its own #ifdef around the call site.

Call this before sapi_appmanager_run() (or from init()); calling it more than once re-installs the same handlers (idempotent, matches sapi_safestate_register_handler()'s "registering again replaces the previous" convention).

Returns
SAPI_STATUS_OK if both handlers were installed; SAPI_STATUS_NOT_SUPPORTED on a non-POSIX build; SAPI_STATUS_INTERNAL_ERROR if the underlying sigaction() call itself failed (see errno at the call site for detail - not surfaced here, consistent with this framework not using errno in its own public API, CLAUDE.md's "no use of errno in production paths").

Definition at line 532 of file sapi_appmanager.c.

◆ sapi_appmanager_reset_state()

void sapi_appmanager_reset_state ( void )

Forcibly resets the application manager's own bookkeeping (lifecycle state, iteration/error counters, shutdown-request flag, and the ADR-026 setup-phase lock) back to its initial, pre-run condition.

Normal use of sapi_appmanager_run() never requires this: every one of its own entry/exit paths already resets this same state on its own (see REQ-APPMANAGER-009). This function exists for the one case that bypasses those paths entirely: an application-level fault handler that itself performs a non-local jump (e.g. longjmp()) out of a SAPI_SAFESTATE_LEVEL_SAFE/_REBOOT reaction instead of the framework's own documented never-returns contract (REQ-COMMON-SAFESTATE-002). After such a jump, sapi_appmanager_run() never reaches its own SHUTDOWN phase, and this module's internal state would otherwise stay permanently stuck mid-lifecycle - causing every subsequent sapi_appmanager_run() call to be rejected by the single-entry-point guard. Call this once, immediately after regaining control via such a non-local jump, before calling sapi_appmanager_run() again.

Safety:
Calling this while a sapi_appmanager_run() call is genuinely still executing (not abandoned via a non-local jump) corrupts that call's own state. This function exists specifically for the abandoned-via-non-local-jump case, not general use - MISRA C:2012 Rule 21.4 already prohibits <setjmp.h> in production code, so this situation should never arise there; it exists because this framework's own test suite has no other way to exercise a SAPI_SAFESTATE_LEVEL_SAFE reaction (which never returns) without one.

Definition at line 477 of file sapi_appmanager.c.