Skip to content

Record background-thread host calls and support nested MockHost installs#1698

Open
jax-0n-git wants to merge 1 commit into
osaurus-ai:mainfrom
jax-0n-git:fix/mockhost-background-thread-and-nested-installs
Open

Record background-thread host calls and support nested MockHost installs#1698
jax-0n-git wants to merge 1 commit into
osaurus-ai:mainfrom
jax-0n-git:fix/mockhost-background-thread-and-nested-installs

Conversation

@jax-0n-git

Copy link
Copy Markdown
Contributor

Summary

MockHost resolved every host-API trampoline through a thread-local install slot, which had two gaps versus the behavior OsaurusPluginTestKit documents:

  1. Background-thread host calls were dropped. Host-global callbacks (log / config_set / config_delete / dispatch / http_request) made from a plugin-spawned background thread — an explicitly supported frame per osaurus_plugin.h, and the reason the recorders are NSLock-guarded — found no thread-local slot in current() and were silently dropped. A plugin that persists config or logs on an async path recorded nothing, so a passing host.configWrites.lastValue(...) != nil / host.logs.contains(...) assertion was a false negative.

  2. withInstalled couldn't nest. The docstrings promised a thread-local install "stack" that "restores the previous host," and the precondition message said to "nest with withInstalled," but hostAPIPointer trapped on any second install and uninstall unconditionally cleared the slot — nesting was impossible.

Approach

  • New recordingHost() resolves host-global callbacks to the calling thread's install slot if present, else — for a background thread with no slot — the uniquely-installed host from a small process-wide registry. When zero or ≥2 hosts are installed the background call is ambiguous and is dropped, never misrouted. (A @convention(c) trampoline carries no per-host context, so a contextless background call can only be attributed via process-wide state; single-host is the well-defined common case.)
  • get_active_agent_id keeps using the thread-local slot, so it still returns NULL outside a per-agent frame (including a background thread), matching the ABI.
  • hostAPIPointer saves the displaced host and uninstall restores it, so installs nest (LIFO, as withInstalled's defer guarantees). Re-installing the same host without an intervening uninstall still traps.

Changes

  • Bug fix
  • Tests

Test Plan

swift test in Packages/OsaurusPluginTestKit (15 tests, all green) + swift-format lint --strict clean. New Threading suite covers background-thread log/config recording, the get_active_agent_id NULL-on-background-thread rule, the ambiguous-multi-host drop, and nested install/restore. The suite is nested under a now-.serialized MockHostTests so host installs never overlap (the background-thread fallback's single-host attribution is only well-defined when installs don't run concurrently); verified stable across 20 consecutive full-suite runs. No MLX/GUI dependency.

MockHost resolved every host-API trampoline through a thread-local install
slot, which had two gaps versus the behavior the kit documents:

1. Host-global callbacks (log / config_set / config_delete / dispatch /
   http_request) made from a plugin-spawned background thread — an
   explicitly supported frame per osaurus_plugin.h, and the reason the
   recorders are NSLock-guarded — found no thread-local slot and were
   silently dropped. A plugin that persists config or logs on an async path
   recorded nothing, so a passing assertion was a false negative.

2. The docstrings promised a thread-local install "stack" that "restores
   the previous host," and the trap message said to "nest with
   withInstalled," but hostAPIPointer trapped on any second install and
   uninstall unconditionally cleared the slot — nesting was impossible.

Resolve host-global callbacks through a new recordingHost(): the thread's
install slot if present, else the uniquely-installed host from a
process-wide registry, so a background-thread call is recorded when one
host is installed and dropped — never misrouted — when attribution is
ambiguous. get_active_agent_id keeps using the thread-local slot so it
still returns NULL outside a per-agent frame, per the ABI. hostAPIPointer
now saves the displaced host and uninstall restores it, so installs nest;
re-installing the same host still traps, and uninstall traps on a
non-LIFO/cross-thread unwind (which would otherwise leak the displaced host
and leave the slot pointing at it) — withInstalled's defer keeps the
blessed path LIFO.

Adds a Threading suite (nested under a now-.serialized MockHostTests so
host installs never overlap) covering background-thread log/config
recording, the get_active_agent_id NULL-on-background-thread rule, the
ambiguous-multi-host drop (joining its worker so it can't perturb the next
test), nested install/restore, a weak-reference regression that the LIFO
unwind neither leaks a host nor poisons the slot, and an exit-test
regression that a non-LIFO uninstall traps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jax-0n-git jax-0n-git force-pushed the fix/mockhost-background-thread-and-nested-installs branch from 203b182 to 9e3772f Compare June 25, 2026 03:07
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