Haiku Teams, Threads, Ports, and the Kernel Object Model
A precise guide to Haiku teams, schedulable threads, bounded message ports, semaphores, shared areas, ownership, and hang diagnosis.
Haiku calls a process a team. A team provides the execution and resource context in which one or more threads run; a thread is the entity the scheduler can place on a CPU. Kernel ports carry discrete messages, semaphores coordinate waiters, and areas describe virtual-memory mappings that can be cloned into another team. These primitives are related, but they are not interchangeable.
The distinction becomes practical as soon as an application has a window, a worker, or interprocess communication. Closing one window does not necessarily terminate its team. Suspending one thread does not freeze every team resource in a consistent state. Sharing an area transfers access to memory but does not by itself notify the receiver that new data is ready. Correct designs combine primitives deliberately and diagnose failures at the same granularity.
A team is the process-level container
The public Kernel Kit uses team_id to identify a team and team_info to report process-level information. The API can enumerate teams, retrieve information for a particular ID, wait for a team, send signals, and launch an executable image. Tools such as ProcessController and debuggers build process views from this kernel state rather than treating a graphical window as the unit of execution.
A team contains threads and an address space, and it owns or references resources created during execution. The main thread is only one member. Haiku’s native application architecture commonly adds an application looper thread, window threads, service workers, and library-created threads. “The process is running” therefore says little about which thread is making progress.
Team IDs and thread IDs are kernel identifiers, not permanent identities. Once an object exits, a stored ID can become stale and may eventually be reused. Code must check API results and avoid treating a numeric ID from an old observation as proof that the same object still exists. Long-lived protocols need their own generation or operation identifier when identity must survive restarts.
Termination is a lifecycle event, not ordinary cleanup. Killing a team can stop threads while application invariants are incomplete, preventing them from saving state or releasing external resources gracefully. Use the application’s normal quit protocol first when it is responsive. Forced termination is justified for recovery, but diagnostic state—thread stacks, waits, and logs—should be captured beforehand when the failure matters.
Threads are schedulable execution contexts
The Kernel Kit exposes spawn_thread() to create a native thread with a name, priority, entry function, and argument. A newly spawned thread must be resumed before it runs. APIs can suspend, resume, rename, wait for, and change the priority of a thread, while thread_info reports its team, state, priority, CPU usage, and other metadata defined by the current interface.
Threads in one team share the address space, which makes data exchange cheap and races possible. A pointer is meaningful to sibling threads because they see the same mapping, but it is not automatically valid in another team. Shared access requires synchronization and a lifetime rule. A mutex or semaphore that protects an object is useful only if every path obeys the same rule.
Haiku documents thread states including running, ready, receiving, asleep, suspended, and waiting. A waiting thread may be healthy: a window looper without messages should block rather than burn CPU. A thread is suspicious when it waits for an event that can no longer occur, holds a lock needed by its producer, or remains ready without being scheduled because higher-priority work never yields.
Do not solve ordinary coordination by suspending arbitrary threads. A suspended thread can hold locks, own allocator state, or be halfway through an update. Cooperative cancellation is safer: signal a stop condition through a port, semaphore, message, or protected flag; let the worker reach a defined boundary; then join it and release resources in order.
Ports are bounded message queues
A kernel port is a queue with a name and fixed message capacity. create_port() receives the maximum count of queued messages. Writers send a numeric message code plus an optional data buffer; readers receive the next code and data. The Kernel Kit exposes port enumeration and information, queue-count inspection, timeout variants, ownership transfer, and deletion.
Capacity is count-based rather than a license for unlimited data. A full port can block or cause a timed/nonblocking write to fail according to the selected API flags. Producers therefore need a backpressure policy. Dropping redundant progress events, merging state updates, waiting with a bounded timeout, or moving bulk payloads to shared memory are explicit choices; silently blocking a UI thread behind a full port is usually not.
Message boundaries are preserved, unlike an undifferentiated byte stream. The code identifies the operation and the payload carries its data. Define byte layout, size, version, and ownership for every code. port_buffer_size() or its extended variant lets a receiver determine the next payload size before allocating a buffer, but the returned size is still untrusted input and must be bounded.
A port has an owning team, and ownership can be reassigned through the API. Deleting a port invalidates the communication endpoint and wakes or fails operations according to kernel semantics; it is not equivalent to sending a graceful shutdown message. Protocols should include an explicit shutdown code when participants need to flush state, then delete the port only after readers and writers have acknowledged or exited.
Higher-level Haiku messaging uses ports as part of its transport, but BMessage adds typed fields, handler tokens, routing, replies, and looper queues. Applications should not reimplement that machinery with raw ports when ordinary BMessenger semantics fit. Raw ports remain valuable for a small low-level protocol, service boundary, or carefully controlled high-throughput notification channel.
Semaphores coordinate availability and ownership
Haiku semaphores maintain a count and a queue of waiters. create_sem() establishes an initial count. Acquiring reduces available count or blocks until it can succeed; releasing increases availability and wakes waiters. Extended calls support timeouts, flags, and acquiring or releasing multiple units.
A binary semaphore can participate in mutual exclusion, while a counting semaphore can represent N available buffers or queued jobs. The number has meaning only through the protocol surrounding it. Releasing twice for one produced item invents capacity; failing to release after consuming an error path can deadlock the pipeline.
Semaphore ownership in kernel metadata controls lifecycle and accounting, not automatic mutual-exclusion ownership like a strict mutex. Code must not infer that only the creating thread may release it. Use BLocker, mutex facilities, or a documented higher-level primitive when lock ownership and recursive behavior are the actual requirement.
Timeouts make failure bounded but do not repair state. If an acquire times out, determine whether the protected operation was never started, is still running, or completed without signaling. Retrying blindly can duplicate work. Deleting a semaphore while participants may still use its ID is similarly unsafe; coordinate shutdown and join threads before destroying their wait objects.
Areas share memory, not synchronization
An area is a named virtual-memory region created with size, locking, placement, and protection attributes. create_area() maps new storage into a team. clone_area() maps the same underlying memory into another team, possibly at a different virtual address and with different allowed protection. Because addresses can differ, shared structures must not contain raw process-local pointers unless they are encoded as offsets and validated.
Areas are appropriate for payloads too large to copy repeatedly through a port: image buffers, media data, or a shared ring can live in an area while a small message reports which region is ready. The message or semaphore supplies ordering and ownership. The area itself does not tell another CPU that a partially written structure is complete.
Define the shared layout with fixed-width types, sizes, version fields, and bounds. Validate every offset and length before dereferencing. Specify who may write each region, when ownership transfers, and what memory-ordering or locking operation makes writes visible. Mapping writable shared memory from another team is a security boundary; the consumer must assume it can change unexpectedly unless the protocol provides immutability.
Deleting an area’s mapping from one team does not justify another team retaining a stale pointer to that mapping. Reference and shutdown rules must ensure no thread accesses a region after its local area is deleted. If a service can restart, clients need a reconnection path that discards old area and port IDs.
Compose primitives into a protocol
A robust worker queue might store descriptors in team-local memory, count ready jobs with a semaphore, and send completion as BMessage objects to a window. A cross-team media pipeline might place buffers in cloned areas and exchange buffer indices through ports. The exact combination follows data size, trust boundary, backpressure, latency, and discovery requirements.
Write the lifecycle before implementation: creator publishes endpoint; receiver validates version; producer obtains a free buffer; producer writes; synchronization publishes completion; consumer validates and reads; consumer returns ownership; both sides exchange shutdown; threads exit; endpoints and mappings are deleted. Every failure path must state who still owns the buffer and which waiters are awakened.
Names are convenient for diagnostics but not sufficient authentication. Kernel object names need not provide global uniqueness or authorization. Discover a trusted service through the appropriate system mechanism and validate protocol identity rather than attaching to the first port with familiar text.
Avoid unbounded waits in components that must remain responsive. Timeout and interruption errors are part of the protocol, not exceptional afterthoughts. A retry policy should distinguish transient backpressure from a dead peer, and cancellation should wake every thread that could otherwise wait forever.
Diagnose hangs by following the wait graph
Begin with the team, then enumerate its threads. Haiku’s Kernel Debugging Land documentation shows teams and threads commands that report IDs, states, priority, CPU, and the semaphore or condition variable associated with a wait. Stack crawls reveal the call site. Equivalent userland debugging can often gather the same central evidence more safely.
For each blocked thread, identify the object or event it awaits. Find the thread that owns the relevant lock or is supposed to write the port or release the semaphore. Repeat until reaching a running thread, an I/O operation, a deleted endpoint, or a cycle. A deadlock is a cycle in dependencies; a lost wake-up is a missing transition; starvation leaves work ready but insufficiently scheduled.
Port inspection adds queue depth and owner. A full port points to a receiver that is absent or too slow; an empty port with a waiting reader points upstream. Semaphore counts and last-acquisition diagnostics can narrow lock chains, although userland locking implementations may require stacks to identify logical ownership. Area lists confirm mappings but cannot prove shared data consistency.
Capture evidence before killing the team. Forced termination destroys the live wait graph and can turn a reproducible concurrency defect into “reboot fixed it.” Haiku’s kernel-object model is unusually inspectable; using team, thread, port, semaphore, and area evidence together converts a vague hang into a specific blocked relationship.
Related:
- The Haiku Kernel: A Modular, Pervasively Multithreaded Design
- Haiku’s Scheduler: Priorities, Real-Time Work, and Responsive Multimedia
Sources: