Files
logos-plugin-qt/cpp/logos_plugin_unload.h
T
Dario Gabriel LipicarandClaude Opus 5 ef11c2108a feat(qt-host): share the teardown helper, and guard the glue on MAJOR too
Two changes to the same surface, both about the by-name teardown hook this
repo's qt-host-generator emits.

1. logos::runPluginAboutToUnload(), ported verbatim from
   logos-module-loader-qt's logos_host.cpp, where it was a file-local
   helper. ui-host (logos-view-module-runtime) needs the identical algorithm
   before `delete pluginObject`, and a second hand-written copy of a nested
   event loop plus deadline is not a thing to have twice.

   Both load-bearing properties are intact: the hook is invoked BY NAME
   rather than through the vtable (PluginInterface is compiled separately
   into every plugin, so a new virtual would shift the vtable under every
   plugin already built), and `deadline.isActive()` remains the only bit
   distinguishing "finished" from "gave up".

   The grace CONSTANT deliberately does not move with it. It stays a
   parameter because the two hosts have different budgets: the container
   gives logos_host 5000ms and it carves out 3000, while ViewModuleHost::stop()
   gives ui-host 3000ms total. Porting the constant would have consumed
   ui-host's entire budget and hard-killed every async view mid-teardown —
   exactly what the hook exists to prevent.

   It lives HERE because this repo emits the surface it reaches. Both halves
   under one roof is what makes checks.unload-contract possible: it generates
   real glue, scrapes the symbol names out of it, and greps the consumer for
   those same names. Nothing in either build tied them together before.

2. The two version guards this generator emits tested
   LOGOS_PROTOCOL_VERSION_MINOR and ignored LOGOS_PROTOCOL_VERSION_MAJOR. At
   protocol 1.0.0 the MINOR resets to 0, both go false, and the glue stops
   emitting the grant_host_services call (0.3) and the teardown pair (0.5).

   Not a link error and not a dlopen failure: logos-cpp-sdk guards the
   matching DEFINITIONS the same way, so the calls and the definitions
   disappear together and everything still builds and loads. Modules just
   quietly stop having teardown. logos-rust-sdk would keep emitting all ten
   exports, because it compares the (major, minor) tuple — so the two sides
   would silently disagree.

   The arithmetic is emitted expanded rather than behind a function-like
   macro, because the generated sources are resolved by unifdef in
   logos-cpp-sdk's ABI check and unifdef silently no-ops on what it cannot
   parse. Matching logos-cpp-sdk#145 character for character.

unload-contract, qt-host-generator, qt-host and vanilla-plugin all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 18:08:50 -03:00

52 lines
2.2 KiB
C++

#ifndef LOGOS_PLUGIN_UNLOAD_H
#define LOGOS_PLUGIN_UNLOAD_H
// The host side of the module teardown hook.
//
// A Logos Qt plugin may expose, on its plugin class:
//
// Q_INVOKABLE int aboutToUnload(); // 0 = Synchronous, 1 = Asynchronous
// Q_SIGNALS:
// void unloadFinished();
//
// modelled on Qt Creator's IPlugin::aboutToShutdown()/ShutdownFlag. Returning
// Asynchronous buys the module a bounded GRACE PERIOD, not a veto: the host
// waits for unloadFinished() or the deadline, whichever comes first, and then
// proceeds to tear the plugin down regardless.
//
// WHY THIS LIVES HERE. Two hosts drive this hook -- logos_host (the core-module
// loader) and ui-host (the view-module runtime) -- and the dance below is
// subtle enough that two copies of it would drift. This repo is the one that
// also EMITS the surface it reaches (qt-host-generator/lidl_gen_cdylib_glue.cpp
// writes both member declarations above), so keeping the consumer beside the
// emitter puts both halves of a BY-NAME contract under one test:
// tests/test-unload-contract.nix extracts the names from freshly generated glue
// and greps this helper's implementation for the same ones, so renaming either
// half alone turns red.
//
// The GRACE PERIOD is deliberately NOT a constant here. It is a policy each
// host owns, because each host is carved out of a different hard-kill budget:
// logos_host has 5s from the container before SIGKILL, ui-host has 3s from
// ViewModuleHost::stop() before kill(). Passing it in is what keeps one
// algorithm serving two budgets.
class QObject;
namespace logos {
// Give `plugin` its chance to finish, then return.
//
// Call this AFTER the application event loop has returned and BEFORE deleting
// the plugin -- the nested event loop below is only safe once the outer
// exec() is done.
//
// Returns as soon as the plugin says it is already quiescent, which is the
// common case and costs one meta-call. Blocks for at most `graceMs` otherwise.
// A null plugin, a plugin with no such meta-method, or a plugin that returns
// Synchronous are all no-ops.
void runPluginAboutToUnload(QObject* plugin, int graceMs);
} // namespace logos
#endif // LOGOS_PLUGIN_UNLOAD_H