Skip to content

fix: marshal provider events onto the source thread#68

Merged
iurimatias merged 3 commits into
masterfrom
fix/marshal-provider-events-to-source-thread
May 25, 2026
Merged

fix: marshal provider events onto the source thread#68
iurimatias merged 3 commits into
masterfrom
fix/marshal-provider-events-to-source-thread

Conversation

@igor-sirotin
Copy link
Copy Markdown
Contributor

@igor-sirotin igor-sirotin commented May 22, 2026

Addresses logos-co/logos-delivery-module#44

Problem

A module method that emits an event mid-call never returns to the caller over remoting, while a method that emits nothing returns fine.

Concretely: delivery_module.start() emits connectionStateChanged as the node connects; the module logs start completed with success, but the caller's RPC times out. createNode() (no event) works.

Cause

ModuleProxy's event listener emits eventResponse directly on whatever thread the module fired the event from — its worker/FFI thread, not the remoting source's thread. QtRemoteObjects then serializes and sends that event from the foreign thread, racing the source socket against the method reply being sent from the source thread, which silently drops the reply.

Fix

Marshal the emission onto the ModuleProxy's own thread (QMetaObject::invokeMethod(this, …, Qt::QueuedConnection)) so events and replies are serialized on the single thread QtRemoteObjects expects to own the source.

Found by bisecting a downstream consumer hang to the universal-interface migration; verified the asymmetry (event-emitting call hangs, event-free call works) matches this race.

igor-sirotin and others added 3 commits May 23, 2026 00:32
ModuleProxy's event listener emitted eventResponse directly on whatever thread
the module fired the event from (its worker/FFI thread). QtRemoteObjects then
serialized and sent the event from that foreign thread, racing the source
socket against a method reply being sent from the source thread, which silently
dropped the reply.

This is why a method that emits an event mid-call never returns to the caller
(e.g. delivery_module start(), which emits connectionStateChanged as the node
connects) while a method that emits nothing (createNode) returns fine.

Marshal the emission onto the ModuleProxy's own thread via a queued invocation
so events and method replies are serialized on the single thread
QtRemoteObjects expects to own the source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QueuedConnection deferred every emission, breaking same-thread callers that
emit-then-assert and crashing when a queued lambda outlived the object.
AutoConnection invokes synchronously when already on the source thread and only
queues cross-thread emissions (the actual fix); passing 'this' as context
cancels a queued call if the object is destroyed first.
@iurimatias iurimatias merged commit d77c3dd into master May 25, 2026
1 check passed
@igor-sirotin igor-sirotin deleted the fix/marshal-provider-events-to-source-thread branch May 25, 2026 13:38
iurimatias pushed a commit to logos-co/logos-view-module-runtime that referenced this pull request May 25, 2026
Picks up logos-co/logos-cpp-sdk#68 — marshals provider event emission onto the
remoting source thread. Part of propagating the fix to the host runtime.
iurimatias pushed a commit to logos-co/logos-liblogos that referenced this pull request May 25, 2026
Picks up logos-co/logos-cpp-sdk#68 — marshals provider event emission onto the
remoting source thread. Host-runtime step toward fixing the start hang.
iurimatias pushed a commit to logos-co/logos-standalone-app that referenced this pull request May 25, 2026
…eading fix)

Picks up logos-co/logos-cpp-sdk#68 across the host runtime (the ui-host +
logoscore where ModuleProxy runs), via the merged view-module-runtime and
liblogos bumps. Completes the host chain for the start hang.
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.

2 participants