Skip to content

docs(abi): PJ_toolbox_host_vtable_t slots are [stream-thread], not [main-thread] - #162

Merged
facontidavide merged 1 commit into
mainfrom
docs/write-host-thread-tags
Aug 2, 2026
Merged

docs(abi): PJ_toolbox_host_vtable_t slots are [stream-thread], not [main-thread]#162
facontidavide merged 1 commit into
mainfrom
docs/write-host-thread-tags

Conversation

@facontidavide

Copy link
Copy Markdown
Contributor

Comment-only. No ABI, layout, or code change. A recorded follow-up from the canonical layout-import arc.

The problem

Every implemented slot of PJ_toolbox_host_vtable_t is tagged [main-thread]. That is not what the host implements, and not how production plugins use it — so the tag actively discourages the shape we ship.

What the host actually does

Verified in PJ4's pj_datastore/src/plugin_data_host.cpp:

Slots Host-side serialisation
create_data_source, ensure_topic, ensure_field, append_record, append_bound_record, append_arrow_stream all bottom out in WriteCore under lockWriteEngines
acquire_catalog_snapshot, read_series_arrow hold engine.lockEngine() across the whole deep-copy / row-decode
register_object_topic, push_owned_object go through ObjectStore, internally thread-safe (store_mutex_ + a shared_mutex per series)

And toolbox_mosaico drives this vtable from its fetch worker today (fetch_worker.cpp:454), serialising with its own host_write_mu_ — precisely the shape the tag was denying.

Why [stream-thread] and not [thread-safe]

Each host object carries one shared error buffer, whose pointer is handed back through PJ_error_t*. Two threads failing concurrently inside slots of the same host object would race on a string the other is reassigning. So the guarantee is "any one thread at a time", not "concurrently".

That is exactly the shape PJ_source_write_host_vtable_t already has — and it is already tagged [stream-thread]. This makes the two consistent rather than inventing a new guarantee.

Legend sharpened

The three classes were too terse to carry the distinction, so they now spell it out: [stream-thread] states the one-thread-at-a-time rule, why off-GUI is sanctioned, and the serialise-if-you-fan-out obligation; [main-thread] says GUI ONLY; [thread-safe] says concurrently.

Deliberately NOT retagged

  • register_object_topic_on_dataset and set_object_topic_retention — the two ABI-appended tail slots. No host implements them; every known vtable leaves them NULL, so there is no implementation to audit. Loosening an unverified tag is how you sanction a race, so they keep the conservative [main-thread], the struct comment says why, and it asks whoever lands the first implementation to re-tag.
  • The colormap registry and settings-store slots — genuinely GUI/QSettings-bound.
  • PJ_object_read_host_vtable_t — its host wrapper was not audited here.

Testing

Header compile-checked standalone in both modes it is consumed in: gcc -std=c11 -Wall -Wextra and g++ -std=c++20 -Wall -Wextra, both clean.

🤖 Generated with Claude Code

…ain-thread]

Comment-only. No ABI, layout, or code change.

Every implemented slot of PJ_toolbox_host_vtable_t was tagged [main-thread],
which is not what the host implements and not how production plugins use it.
Verified in the PJ4 host (pj_datastore/src/plugin_data_host.cpp):

  - the write slots (create_data_source, ensure_topic, ensure_field,
    append_record, append_bound_record, append_arrow_stream) all bottom out in
    WriteCore under lockWriteEngines;
  - acquire_catalog_snapshot and read_series_arrow hold engine.lockEngine()
    across the whole deep-copy / row-decode;
  - register_object_topic and push_owned_object go through ObjectStore, which
    is internally thread-safe (store_mutex_ plus a shared_mutex per series).

And toolbox_mosaico drives this vtable from its fetch worker today
(fetch_worker.cpp:454), serialising with its own host_write_mu_ — the
sanctioned shape the tag was denying.

The correct class is [stream-thread], NOT [thread-safe]: each host object
carries one shared error buffer whose pointer is handed back through
PJ_error_t*, so two threads inside slots of the same host object would race on
it. That is exactly the shape PJ_source_write_host_vtable_t already has and is
already tagged with, so this makes the two consistent rather than inventing a
guarantee.

The tag legend is sharpened to carry that meaning: [stream-thread] now spells
out "any ONE thread at a time, typically the plugin's own worker" and the
serialise-if-you-fan-out rule, [main-thread] says GUI ONLY, and [thread-safe]
says concurrently.

Deliberately NOT retagged:
  - register_object_topic_on_dataset and set_object_topic_retention — the two
    ABI-appended tail slots. NO host implements them (every known vtable leaves
    them NULL), so there is no implementation to audit; loosening an unverified
    tag is how you sanction a race. The struct comment says so and asks whoever
    lands the first implementation to re-tag.
  - the colormap registry and settings-store slots — genuinely GUI/QSettings.
  - PJ_object_read_host_vtable_t — host wrapper not audited here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@facontidavide
facontidavide force-pushed the docs/write-host-thread-tags branch from 64de9d4 to 0ed1199 Compare August 2, 2026 16:25
@facontidavide
facontidavide merged commit f60da82 into main Aug 2, 2026
4 checks passed
@facontidavide
facontidavide deleted the docs/write-host-thread-tags branch August 2, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant