XXNU Source Handbook Download offline version from here
Part 06 · Chapter 06.09

Synchronous IPC dependencies and special reply ports

IPC guide · Message delivery · Runtime turnstiles · Reply policy

A synchronous caller needs the server to run before it can receive a reply. The reply capability authorizes that reply; a separate turnstile relationship tells the scheduler where the caller's dependency leads. In this checkout, a special reply port can redirect its receive turnstile toward the request destination, a workloop, or an intermediate knote turnstile. This chapter follows the ownership and reset rules in the architecture-independent IPC implementation, with no assumption about external libdispatch/XPC behavior. ipc_port_recv_update_inheritor.

The objects in one request and reply

Object State relevant to the dependency
Calling thread ith_special_reply_port for user RPC, ith_kernel_reply_port for kernel RPC; its donated turnstile must return at receive completion
Special reply ipc_port Receive queue, owner thread, ip_sync_link_state and state-dependent ip_sync_inheritor_*; this port remains distinct from the request destination
Request destination ipc_port Send turnstile can identify an interested workloop, a receive right in transit, or another supported receiver dependency
Request ipc_kmsg Owns the reply capability while queued/in transit; the server later receives that right
knote During event-driven receive, remembers the first port in kn_ext[3] or kn_ipc_port, and may hold another turnstile in kn_hook

Definitions and contracts: ipc_port, binding, knote protocol.

thread_get_special_reply_port() first unbinds any prior user special reply port, allocates IOT_SPECIAL_REPLY_PORT with a send right, then binds it to the current thread. Binding retains the port; the returned task-local name provides send and receive capabilities. Allocation failure returns MACH_PORT_NULL. A kernel caller instead uses a special port in ipc_space_reply. The legacy mach_reply_port() trap allocates ordinary IOT_PORT, so its name alone does not imply this scheduling protocol. Allocation and binding.

The receive-side helper checks that the supplied receive port is this thread's ith_special_reply_port, and rejects a syntactically invalid destination name with MACH_RCV_INVALID_NOTIFY. It translates the destination send right, temporarily retains the port, unlocks it, and attempts linkage. Failure to translate a previously valid destination does not itself fail the receive: the destination receive right may already have disappeared. mach_msg_rcv_link_special_reply_port.

ipc_port_link_special_reply_port() prepares the destination's send turnstile before acquiring the special reply port lock. It then requires a still-special port in PORT_SYNC_LINK_ANY without an existing destination. Successful publication takes an independent destination reference; rejected publication completes the unused prepared turnstile association. These are two lifetimes, not one reference count. Complete operation.

Source: osfmk/ipc/ipc_port.c, lines 1888–1899.

	if (!special_reply ||
	    special_reply_port->ip_sync_link_state != PORT_SYNC_LINK_ANY ||
	    special_reply_port->ip_sync_inheritor_port != IPC_PORT_NULL) {
		drop_turnstile_ref = TRUE;
	} else {
		/* take a reference on dest_port */
		ip_reference(dest_port);
		special_reply_port->ip_sync_inheritor_port = dest_port;
		special_reply_port->ip_sync_link_state = PORT_SYNC_LINK_PORT;
	}

	ip_mq_unlock(special_reply_port);
Synchronous IPC dependencies and special reply ports diagram
Synchronous IPC dependencies and special reply ports diagram · Open SVG
Mermaid source
flowchart LR
    C[Blocked client thread] -->|donates receive wait| R[Special reply receive turnstile]
    R -->|PORT state| S[Request destination send turnstile]
    S -->|interested receiver| W[Workloop turnstile]
    W -->|scheduler dependency| T[Server thread or workloop owner]
    R -->|KNOTE state after delivery| W
    R -->|STASH state after delivery| K[Knote intermediate turnstile]
    K -->|inherits toward| W

The arrows describe dependencies, not message routing or ownership of a server thread. The request still follows ordinary queue/direct-delivery selection. ipc_port_send_update_inheritor() has additional branches for in-transit receive rights, exec watchports and bootstrap check-in; its result may be a thread or another turnstile. A valid destination capability alone does not identify the runnable server. Send inheritor selection.

State determines which union member may be used

PORT_SYNC_LINK_* state Meaning and relevant ownership
ANY No destination/workloop link; new linkage is permitted
PORT ip_sync_inheritor_port retains the destination, with a prepared send-turnstile association
WORKLOOP_KNOTE ip_sync_inheritor_knote identifies a knote; that knote holds the associated port reference
WORKLOOP_STASH ip_sync_inheritor_ts holds an additional referenced intermediate turnstile
NO_LINKAGE Further linkage is suppressed until the receive-side reset permits it
RCV_THREAD A related receive-right/bootstrap state, using the receiving thread as send-turnstile inheritor; it is not another branch of special-reply receive inheritance

The shared constants cover both special-reply and receive-right protocols. Read the union using the actual port role. Constants, receive inheritors, bootstrap receive-right state.

Event-driven receipt transfers the scheduling dependency

When receive copyout installs a special-reply send-once right through a relevant knote, ipc_right_copyout_any_send() requests IPC_PORT_ADJUST_SR_LINK_WORKLOOP. This is an actual capability-copyout boundary: donation changes as the server receives the reply endpoint. Call site.

filt_machport_stash_port() uses the workloop turnstile directly for the first remembered port. A later eligible port uses the intermediate turnstile in kn_hook; filt_machport_turnstile_prepare_lazily() allocates it before the port's message-queue lock is held. Eligibility for the Mach-port knote requires the relevant receive/sync-peek options and EV_DISPATCH. An unsupported knote yields NO_LINKAGE, not an invented runnable receiver. Stashing and lazy preparation.

This design also supports abandoning a delivered request. Re-enabling the knote completes its saved links: the first port is checked under its port lock to ensure it still points to this knote, because it may already have replied and been reused. The intermediate turnstile's inheritor is cleared, its association completed, and its reference released. Several dependent ports can therefore be disconnected through one intermediate object without walking all their state at once. Completion.

Reset, interruption and port death

ipc_port_adjust_special_reply_port_locked() consumes the port lock. It saves the old destination or stashed-turnstile pointer, clears the corresponding union member, installs the selected state, and either returns the thread's donated turnstile or updates the existing turnstile's inheritor. Completion/cleanup and saved-reference release occur after unlocking. This prevents treating a changed dependency graph as if all its old edges remain owned. State change and cleanup.

Source: osfmk/ipc/ipc_port.c, lines 2161–2188.

		ts = ipc_port_rcv_turnstile(special_reply_port);
		if (ts) {
			turnstile_reference(ts);
			ipc_port_recv_update_inheritor(special_reply_port, ts,
			    TURNSTILE_IMMEDIATE_UPDATE);
		}
	}

	ip_mq_unlock(special_reply_port);

	if (get_turnstile) {
		turnstile_cleanup();
	} else if (ts) {
		/* Call turnstile cleanup after dropping the interlock */
		turnstile_update_inheritor_complete(ts, TURNSTILE_INTERLOCK_NOT_HELD);
		turnstile_deallocate(ts);
	}

	if (port_stashed_turnstile) {
		turnstile_deallocate(port_stashed_turnstile);
	}

	/* Release the ref on the dest port and its turnstile */
	if (dest_port) {
		ipc_port_send_turnstile_complete(dest_port);
		/* release the reference on the dest port, space lock might be held */
		ip_release_safe(dest_port);
	}

mach_msg_receive_results_complete() resets permitted linkage before the receive object reference is dropped. An interrupted/timed-out receive and MACH_RCV_TOO_LARGE with MACH_RCV_LARGE do not mean a reply was consumed: the helper consequently distinguishes resetting the link from marking a received message. The message-sent/lost-link diagnostic bits are development/debug features, not universal production bookkeeping. Receive completion, diagnostic implementations.

Port destruction marks the endpoint inactive, updates send inheritance and unlinks special-reply state before finishing queued-message destruction. Unbinding a thread's reply port is another lifetime boundary; merely removing a scheduler edge does not destroy the receive right or every send-once right. Death ordering, thread unbinding.

A worked RPC and its independent checks

Consider an illustrative client A sending to service S with a send-once reply right for R. A waits on R and links R to S. If S is monitored by a participating workloop, A's wait can influence that workloop before its server executes. Delivery transfers the reply right and can replace the S dependency with a knote/workloop dependency. Sending the reply, receive completion, knote re-enablement without reply, and port destruction have different cleanup paths; none can leave an old destination reference merely because the next RPC will reuse R.

If strict reply is requested, receipt may separately record a bank persona in ip_reply_context. The server's adopted voucher is checked when replying. That identity check neither creates the turnstile linkage nor proves the server adopted the voucher simply by receiving its right. See strict reply and copyout policy. The same separation applies to exception RPC, whose kernel special-reply transport is traced in exception delivery.

This source review covers the visible state transitions and selected knote integration. External dispatch libraries, every combined workloop event race, scheduler policy choices and generated MIG client code remain separate boundaries; no kernel execution is claimed.

Search the handbook

Search every chapter, interface and code excerpt.

    Diagram

    Source preview

    Open full file