Native plugins › 08 Concurrency and operations

Concurrency and operations

Write plugins that block safely, use platform primitives, and avoid the classic mistakes.

1. Blocking is allowed

While a native is running, the VM removes the thread from the collector's stop-the-world set. A native may therefore block — on I/O, on a mutex, or in loth_sleep_ms — without stalling the GC:

#include "loth_plat.h"

static void nd_pause(NativeCtx* ctx) {
    loth_sleep_ms((uint32_t)n_as_int(n_arg(ctx, 0), n_arg_type(ctx, 0)));
}

The trade-off is the root contract: while the native blocks or works, the GC may collect anything it cannot see. Values in the handle need gc_scan; values in C locals need vm_push_root.

2. Platform primitives

loth_plat.h is the portability layer. Plugins may use it directly:

GroupFunctions
Mutexloth_mutex_new, loth_recursive_mutex_new, lock, trylock, unlock, free
Conditionloth_cond_new, wait, timedwait, signal, broadcast, free
Threadloth_thread_create, join, detach, self, set_name
Timeloth_sleep_ms, loth_thread_yield, loth_monotonic_ms

Do not mix platforms: the same layer is used by the VM, so a plugin compiled against it behaves the same way on every supported system.

3. Thread safety of a plugin

4. Watchdog

The VM counts bytecode steps per thread. A host can cap them with vm_set_max_steps(vm, n); the CLI uses the LOTH_MAX_STEPS environment variable. When a thread exceeds its budget it panics with a step report. Native time is not counted, so a plugin that loops forever in C is not interrupted — check your own loops.

5. Pitfalls

SymptomCause
Crash or garbage after a GCA Value was kept in a C local or in a handle without rooting.
Binding rejectedName or descriptor mismatch, wrong kind, missing method, or slot names differ. Run loth native-info and compare.
"unimplemented" at call timeThe library was not found or was rejected; the class is unbound.
Unexpected shared state between VMsA C global is being used as per-object state; move it into the handle.
Deadlock in a shared objectThe handle's mutex was locked while calling back into the language, and the language tried to re-enter. Never hold your own lock across vm_call.
Argument values look wrongReading a Value without checking arg_types[i]; use the typed accessors.

6. Where to go next