Native plugins › 08 Concurrency and operations
Write plugins that block safely, use platform primitives, and avoid the classic mistakes.
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.
loth_plat.h is the portability layer. Plugins may use it
directly:
| Group | Functions |
|---|---|
| Mutex | loth_mutex_new, loth_recursive_mutex_new, lock, trylock, unlock, free |
| Condition | loth_cond_new, wait, timedwait, signal, broadcast, free |
| Thread | loth_thread_create, join, detach, self, set_name |
| Time | loth_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.
gc_scan and finalize run on the collector thread.
They must not assume a particular caller.loth_thread_create plus the attach API
from the embedding track.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.
| Symptom | Cause |
|---|---|
| Crash or garbage after a GC | A Value was kept in a C local or in a handle without rooting. |
| Binding rejected | Name or descriptor mismatch, wrong kind, missing method, or slot names differ. Run loth native-info and compare. |
| "unimplemented" at call time | The library was not found or was rejected; the class is unbound. |
| Unexpected shared state between VMs | A C global is being used as per-object state; move it into the handle. |
| Deadlock in a shared object | The 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 wrong | Reading a Value without checking arg_types[i]; use the typed accessors. |