Files
logos-cpp-sdk/cpp-generator
Dario LipicarandClaude Opus 5 667990f28c feat(sdk): aboutToUnload — a module's chance to finish before teardown (#143)
* feat(sdk): aboutToUnload — a module's chance to finish before teardown

A module could not flush state or close handles on the way out: the host
stopped it, its destructors ran, and anything mid-flight was gone. This gives
LogosModuleContext the Qt Creator contract for exactly that problem.

    enum class LogosShutdown { Synchronous, Asynchronous };

    virtual LogosShutdown aboutToUnload() { return LogosShutdown::Synchronous; }
    void unloadFinished() const;   // any thread

Default Synchronous, so no existing module changes behaviour. A module with work
to finish returns Asynchronous and calls unloadFinished() when done; the host
waits, but only for a bounded grace period.

unloadFinished() is a NO-OP outside a framework context, and after the deadline
has passed. That matters more than it reads: a module needs no special case for
being torn down under a deadline it already missed.

Two SFINAE pairs mirror the existing maybeSet* helpers. maybeAboutToUnload
reports Synchronous for an impl that never inherited LogosModuleContext, which
is exactly right -- it has no hook, so there is nothing to wait for.

Both names join the reserved set beside onContextReady: an impl overriding
aboutToUnload is talking to the framework, not publishing API, and leaking
either would generate a consumer wrapper for a lifecycle hook (LogosShutdown
has no LIDL type to return anyway).

The cdylib backend emits the two optional C ABI exports. The completion
callback is installed BEFORE the impl is asked to unload, and that ordering is
the correctness of the whole async path: an impl that finishes INLINE would
otherwise signal into a slot that is still empty, and the host would wait out
its entire grace period for a module already done. There is a test for it,
because nothing about reading the code makes that failure visible.

294/294, 4 new. Requires logos-protocol#62; flake.lock pins that branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(deps): track logos-protocol master now that the teardown ABI has landed

logos-co/logos-protocol#62 merged as 9664ae2. The lock pointed at the PR branch
while it was open.

The narHash is unchanged across the move (sha256-JTREoJn2kjQmYyYHg2RQb4...), so
the merged tree is byte-identical to the branch this was built and tested
against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(generator): guard the teardown emission on protocol 0.5

The emitted exports named logos_module_unload_done_cb unconditionally, so a
module built with this generator against an older logos-protocol failed to
compile on a typedef it never asked for:

    error: 'logos_module_unload_done_cb' was not declared in this scope

in generated code the author never wrote and cannot see. That is what this PR's
doc-tests hit -- new generator, older protocol pin.

logos-protocol#63 gives the surface a MINOR (0.5) so it is detectable, and both
the statics and the exports now sit behind

    #if defined(LOGOS_PROTOCOL_VERSION_MINOR) && LOGOS_PROTOCOL_VERSION_MINOR >= 5

the same way the 0.3 trust-root surface is guarded a few lines below. A module
built against 0.4 simply has no teardown entry point, which is the same state as
a module that never overrode the hook -- and the glue that would call it is
generated alongside, so nothing goes looking for the missing symbol.

Both halves need the guard, not just the exports: the typedef is what an older
header lacks, and it is the statics that name it. The test asserts both.

295/295. flake.lock tracks protocol master (0d2a3c0), where 0.5 landed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:54:49 -03:00
..