Skip to content
FreeBSDDeep Dive Published Updated 8 min readViews unavailable

FreeBSD POSIX Message Queues: Inspect Capacity and Reap Queue Names Safely

Operate FreeBSD POSIX message queues with mqueuefs, inspect capacity, understand persistence and limits, and recover stale queue names safely.

POSIX message queues provide named, kernel-managed channels for processes that exchange discrete messages. They are separate from System V message queues, pipes, Unix-domain sockets, and shared memory. On FreeBSD, system calls operate on kernel queue objects; mqueuefs(4) provides a filesystem view that can help an operator inspect those objects. Mounting that view is not required for applications to use the queue system calls.

This distinction matters during diagnosis. A missing mount point does not prove that message queues are unavailable, and a file visible through a mounted mqueuefs is not an ordinary disk file. Queue name, message capacity, message size, current count, permissions, and lifecycle are kernel state. Treat this as IPC state with explicit ownership and cleanup, not as a directory where an operator can safely remove arbitrary files.

Verify the facility and its limits

Record the release and inspect the tunables supported by the installed kernel:

freebsd-version -kru
sysctl kern.mqueue
kldstat -v | grep -i mqueue

mqueuefs(4) documents a kernel option and a loadable module named mqueuefs. A kernel may include the facility statically, so absence from kldstat alone is not conclusive. If a system call returns ENOSYS or a module is unavailable, check the kernel configuration, release documentation, and boot messages instead of repeatedly retrying the application.

The current mq_open(2) manual documents kern.mqueue.maxmq, kern.mqueue.maxmsgsize, and kern.mqueue.maxmsg as system-wide controls, with defaults that are release-specific. Read the local values rather than hard-coding defaults into monitoring:

sysctl kern.mqueue.maxmq kern.mqueue.maxmsgsize kern.mqueue.maxmsg

These are ceilings for queue creation and capacity, not promises that every process can allocate the maximum. Memory pressure, per-queue attributes, permissions, and other kernel resources can still cause creation or send operations to fail. Tuning a global maximum affects the whole host; collect workload evidence and plan rollback before changing it.

Use the filesystem view as an inspection aid

Create a temporary mount point and mount the queue filesystem only when you need to inspect queue names and attributes:

install -d -m 0700 /var/run/mqueue-view
mount -t mqueuefs null /var/run/mqueue-view
ls -la /var/run/mqueue-view

The FreeBSD manual shows mount -t mqueuefs null /mnt/mqueue. It describes the filesystem as a view of system queues and says a permanent mount point is not advised because the intended use has been temporary inspection. Use the installed manual and site policy before adding a boot-time fstab entry.

The manual demonstrates reading a queue entry to see its attributes:

cat /var/run/mqueue-view/worker.jobs

Do not create a queue by using touch on the mounted view. The manual says that doing so creates a kernel queue with default attributes and advises using mq_open(2) instead, because that API lets the application request its intended attributes. The filesystem is useful for observation and some file-style operations, but it is not a substitute for the application API or a documented queue ownership process.

When finished, leave the mount namespace predictable:

mount -p | grep mqueuefs
umount /var/run/mqueue-view

If unmount reports the filesystem is busy, inspect processes and working directories that may be inside the mount. Do not force an unmount while an application is using the view. Unmounting the view does not unlink queue names or discard queued messages.

Create queues with explicit attributes

An application should choose a short, documented queue name beginning with a slash. FreeBSD mq_open(2) imposes a strict naming rule: the name may begin with a slash and contain no other slash characters. The same name refers to the same queue object until that name is removed. Avoid deriving names from untrusted input without validation, and define whether multiple service instances share one queue or use distinct names.

A creation call should specify access mode, creation mode, and bounded queue attributes. This is an API fragment, not a complete program; production code must check every result and define its cleanup policy:

struct mq_attr attr = {
    .mq_flags = 0,
    .mq_maxmsg = 16,
    .mq_msgsize = 1024,
    .mq_curmsgs = 0
};
mqd_t q = mq_open("/worker.jobs",
    O_CREAT | O_EXCL | O_RDWR, 0600, &attr);

O_CREAT | O_EXCL makes unexpected reuse visible by failing if the name already exists. For an intentional shared queue, a process may open an existing name without O_EXCL, then call mq_getattr and compare actual values with its contract. Do not assume attributes passed to mq_open replace attributes of an existing queue.

The mq_open manual identifies librt as the POSIX realtime library. A typical build command is:

cc -Wall -Wextra worker.c -lrt -o worker

Validate linkage with the installed manual. The application should define queue permissions, owner, maximum message size, message count, and name ownership before deployment. A mode such as 0600 is a restrictive example, not a substitute for a multi-process access design.

Design send, receive, and completion behavior

POSIX queues preserve message boundaries and support message priorities. A receiver supplies a buffer large enough for the queue’s maximum message size; otherwise mq_receive fails with EMSGSIZE. Use mq_getattr to learn actual mq_msgsize and mq_curmsgs instead of allocating from a guess. The queue count is a snapshot and can change immediately when another process sends or receives.

With blocking descriptors, mq_send waits when the queue is full and mq_receive waits when it is empty. With O_NONBLOCK, those calls return EAGAIN instead. Either policy needs bounded operational behavior. A producer should apply backpressure or record a durable failure rather than retry in a tight loop. A consumer should distinguish “currently empty” from a transport or process failure. For work that must survive host failure, POSIX MQ alone is not a durable business queue: its messages are kernel state, not a replicated on-disk log.

FreeBSD implements a message-queue descriptor using a file descriptor. The mq_open manual documents inheritance across fork, closure across a new image after exec, and support for select and kevent. This can make integration with an event loop practical, but the application must still handle message ordering, priority policy, descriptor closure, and restart semantics. Do not assume the same descriptor lifecycle on another operating system without checking its implementation.

Understand names, descriptors, and cleanup

mq_close closes one process’s descriptor. It does not remove the shared queue name. mq_unlink removes the name so future opens cannot find it; existing open descriptors can continue to refer to the object until they close. This resembles unlinking a filesystem name while references remain, not a safe command to run whenever mq_open reports EEXIST.

Choose one lifecycle owner. A service may create a named queue at startup and unlink it on administrative shutdown, or use a deployment manager to create and clean up versioned names. A consumer that sees a queue left by a crashed predecessor should inspect its attributes and recovery rules. Unlinking can discard messages after the last open descriptor closes, so determine whether messages are safe to lose, replayable from upstream, or require an application-specific drain procedure.

Avoid using rm on the mqueuefs view as an unreviewed incident shortcut. Although the filesystem exposes common operations, removal affects a named kernel queue. Confirm the exact name and owner, capture attributes and message count, coordinate with producer and consumer processes, and use the documented API or controlled operation approved for that service. Never remove all visible queues on a production host to “reset IPC.”

Diagnose common failure modes

For ENOENT, check that the process uses the intended name and that creation flags match the expected lifecycle. For EEXIST, determine which process owns the name; do not automatically unlink. For ENOSPC or EMFILE/ENFILE, inspect queue limits, current queue count, process descriptor limits, and host load. For EAGAIN, confirm that nonblocking mode is intended and the consumer is healthy. For EMSGSIZE, compare the receive buffer against actual mq_msgsize.

If the application cannot mount or inspect mqueuefs, distinguish lack of a view from lack of queue system calls. The syscall facility can work without the filesystem mounted. If the application creates queues but cannot see them in the expected directory, verify that the view is mounted in the same mount namespace and at the intended location. A name’s absence from the view does not prove it never existed; another process may have unlinked it while holding an open descriptor.

Monitor queue depth, send failures, receive latency, process identity, and message age at the application layer. The filesystem view and mq_getattr expose useful state but do not tell whether a message is valid, whether a consumer committed a side effect, or whether processing is exactly once. Include an application-level message identifier and idempotency strategy when duplicate or retried work matters.

Acceptance criteria

A queue deployment is ready when its name and owner are documented, attributes fit a measured memory and throughput budget, producers have bounded backpressure, consumers validate message lengths, all descriptors have a cleanup path, and restart behavior is explicit. Verify that the expected queue exists, read its attributes, send and receive a harmless test message, and demonstrate full and empty behavior in a non-production environment.

Preserve the distinction between API availability, filesystem visibility, and durable work semantics. FreeBSD POSIX queues are a useful local IPC primitive. They are not a filesystem-backed broker, network transport, or substitute for application-level delivery guarantees.

Related:

Sources:

Comments