Skip to content

S11.10: Retrofit SolidSyslog core onto PoolAllocator (handle-taking API) #413

Description

@DavidCozens

Parent epic: #29

Context

S11.09 (#412) closes the per-class migration sweep — every stateful
_Create/_Destroy class outside the SolidSyslog core itself now lives on
SolidSyslogPoolAllocator. This story retrofits the core: SolidSyslog.c becomes
the canonical 3-TU split with PoolAllocator and a handle-taking API.

Today (Core/Source/SolidSyslog.c):

  • File-scope static struct SolidSyslog instance initialised from a NilInstance
    template at file load, plus a static bool instanceInitialised flag.
  • _Create(const SolidSyslogConfig*) returns void; double-Create reports
    SolidSyslog_Error(ERROR, ALREADY_INITIALISED) and ignores.
  • _Destroy(void) resets the live instance to NilInstance, re-arms the
    Nil-collaborator once-only reporter flags.
  • _Log / _Service reach into the file-static instance directly — there is no
    vtable downcast (no SelfFromBase) because SolidSyslog isn't dispatched through a
    vtable, it owns the algorithm.
  • Five private "Nil collaborators" live in the same TU:
    • NilStore — silent no-ops (functionally identical to SolidSyslogNullStore_Get).
    • NilBuffer — reports NIL_BUFFER_USED once on first Write, then silent.
    • NilSender — reports NIL_SENDER_USED once on first Send, then silent.
    • NilClock / NilStringFunction — TU-local no-op callbacks; no public Null
      sibling exists for the function-pointer typedefs.

The retrofit (a) gets SolidSyslog onto the same canonical shape as every other
E11 class, (b) admits multi-instance SolidSyslog by passing a handle on every
runtime call, and (c) collapses the Nil → Null collaborator duplication.

Decisions locked in pre-raise

1. Handle-taking API. Public signatures change to mirror every other pool
class. _Create returns the slot handle; _Destroy, _Log, _Service take it.
Pool default size is 1U; the change preserves the single-instance use case
unchanged for integrators who only ever create one, but admits multi-instance
where it's useful.

Signature Before After
_Create void _Create(const SolidSyslogConfig*) struct SolidSyslog* _Create(const SolidSyslogConfig*)
_Destroy void _Destroy(void) void _Destroy(struct SolidSyslog*)
_Log void _Log(const SolidSyslogMessage*) void _Log(struct SolidSyslog*, const SolidSyslogMessage*)
_Service void _Service(void) void _Service(struct SolidSyslog*)

The five process-global library functions stay process-global:
SolidSyslog_SetErrorHandler, SolidSyslog_Error, SolidSyslog_SetConfigLock,
SolidSyslog_LockConfig, SolidSyslog_UnlockConfig. Multi-instance does not
change library-wide error/lock semantics; the ConfigLock-protects-the-Pool
chicken-and-egg forces them global anyway.

2. Three-TU split. Same shape as every other E11 class:

  • Core/Source/SolidSyslog.c — algorithm + SolidSyslog_Initialise(self, config) +
    SolidSyslog_Cleanup(self). Public functions _Log / _Service reach through
    the passed handle rather than a file-static instance pointer.
  • Core/Source/SolidSyslogPrivate.h — concrete struct SolidSyslog definition +
    private signatures. TU-internal.
  • Core/Source/SolidSyslogStatic.c — Pool, Allocator, _Create, _Destroy,
    IndexFromHandle, CleanupAtIndex, and the NilInstance exhaustion-fallback.

3. Pool size tunable — SOLIDSYSLOG_POOL_SIZE, default 1U. Lands in
Core/Interface/SolidSyslogTunablesDefaults.h alongside every other class's tunable.

4. Pool exhaustion → NilInstance fallback. No new public Null SolidSyslog
class. The TU-private NilInstance template (in SolidSyslogStatic.c) holds
the same struct shape as a real SolidSyslog instance, with Buffer/Sender/
Store wired to SolidSyslogNullBuffer_Get() / SolidSyslogNullSender_Get() /
SolidSyslogNullStore_Get() and the function-pointer fields wired to the
TU-local nil callbacks. On exhaustion, _Create fires
SolidSyslog_Error(ERROR, POOL_EXHAUSTED) and returns &NilInstance. Caller code
keeps running — _Log(&NilInstance, msg) and _Service(&NilInstance) execute
through the Null vtables, dropping the message and the service tick silently.

5. _Destroy(unknown_handle) and _Destroy(&NilInstance) → WARNING + ignore.
Standard per the bad-setup contract (feedback_bad_setup_contract). The
IndexFromHandle invalid-pointer path covers both cases naturally — &NilInstance
sits outside the Pool[] address range, so IndexFromHandle returns invalid.

6. NULL-handle defensive on _Log / _Service → ERROR + return. Integrator
bug; report and no-op. Same severity as a NULL-config to _Create today.

7. Nil → Null collaborator collapse. Replace the three private Nil
collaborator instances with their public Null siblings inside the NilInstance
template:

  • NilStoreSolidSyslogNullStore_Get() (identical behaviour today; pure dedup).
  • NilBufferSolidSyslogNullBuffer_Get() (loud-once NIL_BUFFER_USED removed
    _InstallBuffer's Create-time SolidSyslog_Error(ERROR, NULL_BUFFER) is the
    authoritative signal).
  • NilSenderSolidSyslogNullSender_Get() (same as Buffer — loud-once
    NIL_SENDER_USED removed; Create-time NULL_SENDER is the signal).

Deletes ~120 lines from SolidSyslog.c (the entire Nil-collaborators section)
plus the SOLIDSYSLOG_ERROR_MSG_NIL_BUFFER_USED and
SOLIDSYSLOG_ERROR_MSG_NIL_SENDER_USED strings from SolidSyslogErrorMessages.h.
NilClock and NilStringFunction stay TU-local — no public Null equivalent
exists for function-pointer typedefs.

8. No new MISRA suppressions. SolidSyslog has no storage cast today (it's
file-static, not caller-supplied-storage), so no D.002 entries evaporate. But
the retrofit must not introduce any new suppressions either — the pattern is
already MISRA-clean per S11.02 / S11.07 / S11.08 precedent.

9. File-scope statics carry the class-prefix shapeSolidSyslog_InUse,
SolidSyslog_Pool, SolidSyslog_Allocator, SolidSyslog_NilInstance
matching every other E11 class.

Sequencing on the work branch

Recommended commit shape on refactor/s11-10-solidsyslog-pool:

  1. refactor: S11.10 collapse Nil collaborators onto public Null siblings
  2. feat!: S11.10 migrate SolidSyslog core onto PoolAllocator with handle API
  3. refactor: S11.10 update BDD targets and tests for handle API
  4. docs: update DEVLOG for S11.10

Squash-merge title: feat!: S11.10 SolidSyslog core onto PoolAllocator (handle API).
The ! marks the breaking change for release-please.

Commit 1 is the Nil→Null cleanup standalone — large diff, mechanical, no API
change. Commit 2 is the architectural change (3-TU split + handle parameter).
Commit 3 is the consumer-side update for the new API. Splitting (2) from (3)
means (2) compiles but doesn't link end-to-end until (3); reviewers see the
core change isolated from the caller blast radius.

Scope

  1. Core/Source/SolidSyslog.c — strip instance file-static, strip Nil
    collaborators, public functions become handle-dispatching, introduce
    _Initialise/_Cleanup helpers.
  2. Core/Source/SolidSyslogPrivate.h — TU-internal struct + private signatures.
  3. Core/Source/SolidSyslogStatic.c — Pool, Allocator, _Create, _Destroy,
    IndexFromHandle, CleanupAtIndex, NilInstance exhaustion-fallback.
  4. Core/Interface/SolidSyslog.h_Log / _Service signatures updated.
  5. Core/Interface/SolidSyslogConfig.h_Create / _Destroy signatures
    updated.
  6. Core/Interface/SolidSyslogTunablesDefaults.hSOLIDSYSLOG_POOL_SIZE
    default 1U.
  7. Core/Source/SolidSyslogErrorMessages.h — add MSG_POOL_EXHAUSTED,
    MSG_DESTROY_UNKNOWN_HANDLE, MSG_LOG_NULL_HANDLE,
    MSG_SERVICE_NULL_HANDLE; remove MSG_NIL_BUFFER_USED, MSG_NIL_SENDER_USED.
  8. Core/CMakeLists.txt — add SolidSyslogStatic.c.
  9. All callers updated to pass a handle:
    • Bdd/Targets/Linux/main.c
    • Bdd/Targets/Windows/BddTargetWindows.c
    • Bdd/Targets/FreeRtos/main.c
    • Bdd/Targets/Common/*.c (every TU that calls _Log or _Service)
    • All Tests/SolidSyslogTest.cpp tests
  10. New Pool TEST_GROUP — 9 tests mirroring SolidSyslogWindowsMutexPool.
  11. Existing SolidSyslog tests — update for handle API; drop assertions on the
    removed loud-once reporters; add NULL-handle defensive coverage on _Log /
    _Service.
  12. CLAUDE.md audience-table rows for SolidSyslog.h and SolidSyslogConfig.h
    updated.
  13. feedback_simple_runtime_api memory entry refreshed (app code calls
    _Log(handle, msg) and _Service(handle) — still only two runtime
    functions, plus the Create-time handle to thread).

Public API changes

/* Before */
void                       SolidSyslog_Create(const struct SolidSyslogConfig* config);
void                       SolidSyslog_Destroy(void);
void                       SolidSyslog_Log(const struct SolidSyslogMessage* message);
void                       SolidSyslog_Service(void);

/* After */
struct SolidSyslog*        SolidSyslog_Create(const struct SolidSyslogConfig* config);
void                       SolidSyslog_Destroy(struct SolidSyslog* handle);
void                       SolidSyslog_Log(struct SolidSyslog* handle, const struct SolidSyslogMessage* message);
void                       SolidSyslog_Service(struct SolidSyslog* handle);

SOLIDSYSLOG_POOL_SIZE tunable added (default 1U).

MSG_NIL_BUFFER_USED and MSG_NIL_SENDER_USED error-message strings removed.

Acceptance criteria

  1. SolidSyslog core lives on SolidSyslogPoolAllocator with the canonical 3-TU
    split. SolidSyslogStatic.c lands at ≤ ~3 file-scope functions +
    CleanupAtIndex + NilInstance template, mirroring S11.07 / S11.08 baseline.
  2. Public SolidSyslog_Create returns a handle; _Destroy, _Log, _Service
    take a handle. The five global library functions (_SetErrorHandler,
    _Error, _SetConfigLock, _LockConfig, _UnlockConfig) are unchanged.
  3. Pool exhaustion: _Create fires ERROR / POOL_EXHAUSTED and returns
    &NilInstance. _Log / _Service against &NilInstance are silent no-ops
    (Null vtables on Buffer/Sender/Store).
  4. NULL handle to _Log / _Service fires ERROR and returns; no crash.
  5. _Destroy(unknown) and _Destroy(&NilInstance) fire WARNING and return;
    no crash, no slot mutation.
  6. The three Nil collaborators (NilStore, NilBuffer, NilSender) are gone
    from SolidSyslog.c; the NilInstance template wires those slots to the
    public SolidSyslogNull*_Get() siblings. NIL_BUFFER_USED and
    NIL_SENDER_USED error-message strings are gone. NilClock and
    NilStringFunction stay TU-local.
  7. Pool TEST_GROUP — 9 tests mirroring SolidSyslogWindowsMutexPool.
  8. All previously existing tests continue to pass under the handle API.
  9. All BDD targets (Linux, Windows, FreeRTOS, Common) and BDD-target tests
    compile and run against the handle API.
  10. All host gates green from the gcc devcontainer: debug, sanitize, coverage,
    analyze-tidy, analyze-cppcheck, analyze-format. Clang-debug + IWYU via the
    clang container; Windows MSVC + integration jobs + BDD jobs via CI.
  11. cppcheck-misra produces no new findings vs main.
  12. Coverage at 100% line for SolidSyslog.c, SolidSyslogStatic.c, and any
    consumer that gained NULL-handle / fallback paths.
  13. Validation step: re-run the full suite at SOLIDSYSLOG_POOL_SIZE=3 via
    SOLIDSYSLOG_USER_TUNABLES_FILE to exercise multi-instance.
  14. DEVLOG entry on the work branch BEFORE the PR per feedback_devlog_in_pr,
    documenting the migration plus an explicit "breaking change" note for the
    release-please changelog.

Out of scope

  • SolidSyslog_SetErrorHandler, SolidSyslog_SetConfigLock — single global
    function-pointer slots, not stateful Created objects. Pattern doesn't apply.
  • SolidSyslogFormatter — transient stack-built object; lifecycle is
    fundamentally different. Stays unchanged.
  • Wholesale MISRA storage-cast deviation removal + docs sweep — S11.11.
  • Dynamic (heap) allocation TU — possible future epic.

Memory pointers for the next session

  • CLAUDE.md + SKILL.md.
  • MEMORY.md, especially:
    • project_e11_static_pool_design
    • project_e11_three_tu_split
    • project_pool_allocator_helper
    • project_configlock_injection
    • feedback_bad_setup_contract — fallback severity vocabulary
    • feedback_simple_runtime_api — refresh during this story
    • feedback_discuss_production_changes
    • feedback_tdd_and_review_style
    • feedback_no_premature_generalisation
    • feedback_cppcheck_misra_invariant
    • feedback_devlog_in_pr
    • feedback_pr_template
    • feedback_no_git_commit_dash_s
    • feedback_no_merge
  • E11 epic body (E11: Static-Allocation Variant #29).
  • S11.07 / S11.08 / S11.09 DEVLOG entries (recent per-class precedents).

Open questions for the next session

  • NilInstance naming post-Nil→Null collapse — once its Buffer/Sender/Store
    slots wire to public Null siblings, "Nil" is a historical name. Rename to
    NullInstance for consistency, or keep NilInstance for stability against
    git history? Default: keep, decide at review.
  • SOLIDSYSLOG_ERROR_MSG_POOL_EXHAUSTED wording — every other class fires
    the same severity but with class-specific wording. Decide a uniform format
    vs class-specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    storyStory issue

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions