Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions doc/design/v2/runner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# UDF Runner v2 Design

This directory describes the runner-side architecture for protocol v2. It is a design document set; it does not
define an implementation or commit to a concrete C++ class layout.

The runner is organized into two source-level namespaces:

1. The API namespace contains the caller-facing C++ contracts.
2. The Internal namespace owns sockets, accepting, worker scheduling, protocol contexts, and implementation
dependencies.

`Runner` is the composition root for those components. A runner user transfers a `WorkerFactory` to a production
factory, which constructs `Runner` and its remaining owned components. Unit tests use the same construction seam with
owned test doubles.

The API namespace must not expose third-party symbols from the Internal namespace. A dependency may cross this
boundary only when it is vendored into an owned project namespace or uses a well-known interoperable ABI, such as the
Arrow C Data Interface.

## Documents

- [architecture.md](architecture.md) defines the namespace boundary and component responsibilities.
- [lifecycle.md](lifecycle.md) defines connection, worker, context, and shutdown lifecycles.

Mermaid `.mmd` files are the diagram sources of truth. Matching `.svg` files are rendered views linked from the
documents.

## Relationship to the protocol

The runner implements the protocol described in the [v2 protocol design](../README.md). In particular:

- [low_level.md](../protocol/low_level.md) defines framing, streams, transport bindings, and close behavior.
- [call_lifecycle.md](../protocol/call_lifecycle.md) defines the generic call abstraction.
- [high_level_calls.md](../protocol/high_level_calls.md) defines `Run`, Function operations, and callbacks.
- [high_level_payloads.md](../protocol/high_level_payloads.md) defines JSON and named payload contracts.

The runner `Context` is the implementation boundary for those protocol rules. This design does not redefine their
wire format.

## Initial transport

The first concrete transport is a Unix-domain stream socket. The socket and acceptor contracts are intentionally
transport-neutral enough to support a future TCP/TLS binding without changing the protocol context contract.
106 changes: 106 additions & 0 deletions doc/design/v2/runner/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Runner v2 Architecture

## Layering

```text
API namespace: caller-facing C++ contracts
|
v
Internal namespace: sockets, acceptor, worker pool, factory, context manager, context
|
v
Protocol v2
```

Related diagrams:

- [layered_architecture.svg](layered_architecture.svg)
- [component_relationships.svg](component_relationships.svg)

Dependencies point downward only. The API namespace contains only project-owned caller-facing contracts. The Internal
namespace may use C++ standard-library types, exceptions, and third-party libraries such as Arrow C++.

Third-party symbols must not leak from the Internal namespace through the API namespace. A dependency is allowed at
the API boundary only when it is vendored into an owned project namespace or communicated through a well-known ABI.
The current well-known ABI exception is the Arrow C Data Interface: `ArrowArray` and `ArrowSchema` may cross the
boundary under its release and ownership contract; Arrow C++ types remain internal.

## Components

### Runner

`Runner` is the composition root. A runner user transfers ownership of a `WorkerFactory` to a production factory.
The production factory constructs a `Runner` with that factory, transport configuration, limits, a `SocketAcceptor`,
a worker pool, and a `ContextManager`. `Runner` validates and connects those components, starts and stops them, and
destroys them in dependency-safe order after all work has completed.

Unit tests use the same construction seam to create a `Runner` with owned fakes, stubs, or instrumented component
implementations. The production factory is responsible only for choosing concrete production implementations; the
runner remains responsible for their configuration and composition.

### Socket

`Socket` represents one connected byte stream. It owns the accepted descriptor and provides the operations required
by the protocol transport: receive bytes, send bytes, observe closure, and close. The initial implementation uses a
Unix-domain stream descriptor. The abstraction must not expose Unix-specific address types to the protocol context.

The socket owns its descriptor after construction. Closing or destroying the socket makes the descriptor unusable;
ownership must not be duplicated implicitly.

### SocketAcceptor

`SocketAcceptor` owns the listening endpoint, performs bind/listen setup, and accepts connected sockets. It submits
each accepted descriptor to the worker scheduler. Accept failures are classified as retryable, shutdown-related, or
fatal and must not silently become worker failures.

The initial acceptor listens on a Unix-domain socket. The interface leaves endpoint configuration and accepted-socket
creation abstract so a TCP/TLS acceptor can be added later.

### WorkerFactory and worker pool

The runner user initially owns `WorkerFactory` and transfers it to the production factory, which transfers ownership
to `Runner`. The factory is used by reusable worker-pool resources to obtain a worker callable or worker object for
an accepted descriptor. `Runner` destroys the factory only after stopping and joining all worker activity.

The pool is reusable, but a worker invocation handles one accepted connection. A worker must not retain a protocol
context or descriptor after its invocation returns. Pool sizing, queue limits, and shutdown policy are explicit runner
configuration rather than hidden global state.

### Worker

A worker receives one accepted descriptor, asks the `ContextManager` to create a protocol context, runs that context
until normal close, peer disconnect, cancellation, or error, and then releases the context and descriptor. The worker
does not parse protocol messages outside the context boundary.

### ContextManager

`ContextManager` converts an owned connected descriptor into a connection-scoped `Context`. It centralizes context
construction, protocol dependencies, limits, cancellation, and cleanup. Context creation failure must close or reclaim
the descriptor according to the documented ownership handoff.

### Context

`Context` is the runner-side interface to protocol v2. It owns one connection and exposes the operations needed by the
protocol implementation: framing, control-stream initialization, stream dispatch, call/data handling, callbacks,
keepalive, and close/error processing.

The context is not an application callback object and is not shared between worker invocations. It is the sole owner
of protocol state for its connection and must enforce the validation, ordering, flow-control, and close rules from the
protocol documents.

## Data and dependency flow

```text
runner user --moves WorkerFactory--> production factory
|
v
Runner owns factory, acceptor, pool, and context manager
|
SocketAcceptor -> worker pool -> Worker(fd) -> ContextManager.create(fd) -> protocol Context
|
Socket bytes + Arrow C data values
```

Arrow record batches and schemas may cross the API boundary as `ArrowArray` and `ArrowSchema` under the Arrow C Data
Interface. Arrow C++ objects remain Internal-namespace implementation details and are released according to that
interface's ownership rules.
15 changes: 15 additions & 0 deletions doc/design/v2/runner/component_relationships.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
flowchart LR
User[Runner user] -->|transfers WorkerFactory| Production[Production factory]
Production -->|constructs| Runner[Runner\ncomposition root]
Runner -->|owns| Factory[WorkerFactory]
Runner -->|owns| Acceptor[SocketAcceptor\nlistening endpoint]
Runner -->|owns| Queue[Reusable worker pool]
Runner -->|owns| Manager[ContextManager]
Acceptor -->|accepted fd| Queue
Factory -->|creates worker| Queue
Queue -->|one fd per invocation| Worker[Worker callable]
Worker -->|create context| Manager[ContextManager]
Manager -->|owns connection context| Context[Protocol Context]
Context --> Socket[Socket abstraction]
Socket --> Transport[Unix stream socket\nfuture TCP/TLS binding]
Context --> V2[Protocol v2 interface]
1 change: 1 addition & 0 deletions doc/design/v2/runner/component_relationships.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions doc/design/v2/runner/connection_lifecycle.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
sequenceDiagram
participant User as Runner user
participant Production as Production factory
participant Runner
participant Acceptor as SocketAcceptor
participant Pool as Worker pool
participant Factory as WorkerFactory
participant Worker
participant Manager as ContextManager
participant Context

User->>Production: transfer WorkerFactory
Production->>Runner: construct with owned components
Runner->>Acceptor: configure and listen
Acceptor->>Acceptor: accept connection
Acceptor->>Pool: enqueue owned fd
Pool->>Factory: create worker for fd
Factory-->>Pool: worker callable
Pool->>Worker: invoke(fd)
Worker->>Manager: create_context(fd)
Manager-->>Worker: Context(connection)
Worker->>Context: run protocol v2
Context-->>Context: close / disconnect / cancel / error
Context-->>Worker: return and release connection
Worker-->>Pool: invocation complete

alt queue, worker, or context creation failure
Acceptor-->>Acceptor: close fd exactly once
end
1 change: 1 addition & 0 deletions doc/design/v2/runner/connection_lifecycle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions doc/design/v2/runner/descriptor_ownership.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
stateDiagram-v2
[*] --> AcceptorOwned: accept()
AcceptorOwned --> QueueOwned: enqueue succeeds
AcceptorOwned --> Released: queue rejects
QueueOwned --> WorkerOwned: worker starts
QueueOwned --> Released: worker unavailable
WorkerOwned --> ContextOwned: context creation succeeds
WorkerOwned --> Released: context creation fails
ContextOwned --> Released: close / disconnect / cancellation / error
Released --> [*]

note right of AcceptorOwned
The descriptor has one owner
at every handoff.
end note
1 change: 1 addition & 0 deletions doc/design/v2/runner/descriptor_ownership.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions doc/design/v2/runner/layered_architecture.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
flowchart TB
Api[API namespace\ncaller-facing C++ contracts]
Internal[Internal namespace\nsockets, acceptor, worker pool, contexts]
Arrow[Arrow C Data Interface\nArrowArray and ArrowSchema]
Protocol[Protocol v2\nframing, streams, calls, payloads]

Api --> Internal
Api --- Arrow
Internal --> Protocol
Loading