network: socket options (TCP_NODELAY, keepalive) #120

Closed
opened 2026-10-05 21:07:22 +00:00 by orion · 7 comments
Collaborator

Context

Long-lived database connections: a dead peer is noticed only by keepalive. Listed among what std is missing for the
first-party packages. TCP_NODELAY is already on for every stream (docs/design/NETWORK.md section 4); what is missing
is a way to switch it and to turn TCP keepalive on, with its timings.

Source

  • FIRST-PARTY.md gap 22
  • docs/design/NETWORK.md section 4: the setter for TCP_NODELAY and the socket options are named there as later
    additions, each when a program needs it.

Design (decided by the loop)

This is an epic of two steps, because the change needs a new native and the seed that builds the compiler compiles
std/network (compiler/src/debugger/adapter.trb imports it): the two-commit rule of CLAUDE.md applies, and the second
commit cannot bootstrap until the published seed knows the natives.

  • Package: std/network, on TcpStream only. UDP options and listener options stay later additions.
  • API (step 2): methods, no settings object. fn setNoDelay(enabled: Bool): Result<Void, NetworkError> and
    fn setKeepAlive(idle: Duration?, interval: Duration? = None, count: Int? = None): Result<Void, NetworkError>:
    None for idle turns keepalive off, Some(idle) turns it on, and the two other parameters set the probe spacing
    and count where given (whole seconds, at least 1; a smaller Duration is rounded up, zero is refused). Plus the
    reads fn noDelay(): Result<Bool, NetworkError> and fn keepAlive(): Result<Duration?, NetworkError>. Defaults stay:
    TCP_NODELAY on, keepalive off.
  • Natives (step 1): one pair, networkSetOption and networkOption, with the option as a number.
  • No sandbox change: options of an already open socket are not a new capability (NETWORK.md section 10).

Steps

  • 1. Runtime natives and their manifest entries (touches the seed; one commit feat(runtime): ...): #426, landed (PR #427)
  • 2. std/network API on TcpStream, tests, docs (docs/standard-library/network.md, NETWORK.md section 4, the
    generated skills/) and FIRST-PARTY.md gap 22 marked done. Commit feat(network): TcpStream.setNoDelay and setKeepAlive: #473 (filed once the published seed c4a8443532aa knew the natives).

Acceptance criteria (of the epic)

  • Step 1 landed
  • Step 2 landed: the two setters and two readers exist on TcpStream, tested in std/network against a loopback
    connection (set, read back, a closed stream fails with isClosed()), natively and in the VM
  • FIRST-PARTY.md gap 22 marked done

Dependencies

None known; the steps depend on each other as listed.

Moved from the planning lists of the docs; the design record stays the specification.

## Context Long-lived database connections: a dead peer is noticed only by keepalive. Listed among what `std` is missing for the first-party packages. `TCP_NODELAY` is already on for every stream (`docs/design/NETWORK.md` section 4); what is missing is a way to switch it and to turn TCP keepalive on, with its timings. ## Source - [FIRST-PARTY.md gap 22](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/FIRST-PARTY.md#L282) - `docs/design/NETWORK.md` section 4: the setter for `TCP_NODELAY` and the socket options are named there as later additions, each when a program needs it. ## Design (decided by the loop) This is an epic of two steps, because the change needs a new native and the seed that builds the compiler compiles `std/network` (`compiler/src/debugger/adapter.trb` imports it): the two-commit rule of CLAUDE.md applies, and the second commit cannot bootstrap until the published seed knows the natives. - **Package:** `std/network`, on `TcpStream` only. UDP options and listener options stay later additions. - **API (step 2):** methods, no settings object. `fn setNoDelay(enabled: Bool): Result<Void, NetworkError>` and `fn setKeepAlive(idle: Duration?, interval: Duration? = None, count: Int? = None): Result<Void, NetworkError>`: `None` for `idle` turns keepalive off, `Some(idle)` turns it on, and the two other parameters set the probe spacing and count where given (whole seconds, at least 1; a smaller `Duration` is rounded up, zero is refused). Plus the reads `fn noDelay(): Result<Bool, NetworkError>` and `fn keepAlive(): Result<Duration?, NetworkError>`. Defaults stay: `TCP_NODELAY` on, keepalive off. - **Natives (step 1):** one pair, `networkSetOption` and `networkOption`, with the option as a number. - **No sandbox change:** options of an already open socket are not a new capability (NETWORK.md section 10). ## Steps - [x] 1. Runtime natives and their manifest entries (touches the seed; one commit `feat(runtime): ...`): #426, landed (PR #427) - [x] 2. `std/network` API on `TcpStream`, tests, docs (`docs/standard-library/network.md`, NETWORK.md section 4, the generated `skills/`) and FIRST-PARTY.md gap 22 marked done. Commit `feat(network): TcpStream.setNoDelay and setKeepAlive`: #473 (filed once the published seed `c4a8443532aa` knew the natives). ## Acceptance criteria (of the epic) - [x] Step 1 landed - [x] Step 2 landed: the two setters and two readers exist on `TcpStream`, tested in `std/network` against a loopback connection (set, read back, a closed stream fails with `isClosed()`), natively and in the VM - [x] FIRST-PARTY.md gap 22 marked done ## Dependencies None known; the steps depend on each other as listed. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop), source FIRST-PARTY.md gap 22, not a duplicate. No dependencies. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z

[loop:torbscript] intake: in scope (author orion, label loop), source FIRST-PARTY.md gap 22, not a duplicate. No dependencies. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T14:56Z

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T14:56Z
Author
Collaborator

[loop:torbscript] decided by the loop: #120 is an epic of two steps, not one PR. Why: TCP_NODELAY is already on (NETWORK.md section 4) and only needs a setter, but keepalive needs a new native, and the compiler imports std/network (compiler/src/debugger/adapter.trb), so the seed compiles it: the two-commit rule of CLAUDE.md applies and a PR with the native and the std/ API together cannot bootstrap before the published seed knows the native. Package std/network, on TcpStream only; methods (setNoDelay, setKeepAlive(idle:, interval:, count:) plus two readers), defaults unchanged; one native pair networkSetOption/networkOption with the option as a number, so a later option costs no manifest entry; no sandbox change (an option of an open socket is no new capability). Details are in the rewritten body.

Refine result: no duplicate in the open issues (keyword search: keepalive, nodelay, socket option). First step filed as #426 (runtime natives, touches the seed: yes, label loop and ready) and linked in the checklist. Step 2 (the std/network API, tests, docs, gap 22 done) is filed just in time once step 1 has landed and the published seed knows the natives. The seed refresh stays Torben's/the merge gate's. #120 stays without ready.

Refine worker usage: roughly 85k tokens (estimate, sonnet).
next: waiting (epic waits on its steps)

[loop:torbscript] decided by the loop: #120 is an epic of two steps, not one PR. Why: `TCP_NODELAY` is already on (NETWORK.md section 4) and only needs a setter, but keepalive needs a new native, and the compiler imports `std/network` (`compiler/src/debugger/adapter.trb`), so the seed compiles it: the two-commit rule of CLAUDE.md applies and a PR with the native and the `std/` API together cannot bootstrap before the published seed knows the native. Package `std/network`, on `TcpStream` only; methods (`setNoDelay`, `setKeepAlive(idle:, interval:, count:)` plus two readers), defaults unchanged; one native pair `networkSetOption`/`networkOption` with the option as a number, so a later option costs no manifest entry; no sandbox change (an option of an open socket is no new capability). Details are in the rewritten body. Refine result: no duplicate in the open issues (keyword search: keepalive, nodelay, socket option). First step filed as #426 (runtime natives, touches the seed: yes, label `loop` and `ready`) and linked in the checklist. Step 2 (the `std/network` API, tests, docs, gap 22 done) is filed just in time once step 1 has landed and the published seed knows the natives. The seed refresh stays Torben's/the merge gate's. #120 stays without `ready`. Refine worker usage: roughly 85k tokens (estimate, sonnet). next: waiting (epic waits on its steps)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261025 until 2026-10-24T24:00:00Z

[loop:torbscript] claim refine by session:loop-torbscript-20261025 until 2026-10-24T24:00:00Z
Author
Collaborator

[loop:torbscript] decided by the loop: step 1 (#426, the natives networkSetOption/networkOption) landed with PR #427, so the first step of the earlier brief (a TCP_NODELAY setter without a native) is moot: the native pair serves every option, and the setter and keepalive belong in one std/ commit. Step 2 starts next and is filed just in time as #473 (label loop and ready): TcpStream.setNoDelay/noDelay/setKeepAlive/keepAlive, one commit feat(network): TcpStream.setNoDelay and setKeepAlive. Why one step and not two: both halves are a few lines over the same two natives and the same tests, far below the ~1 h of an issue of its own. Seed: the published seed c4a8443532aa (2026-10-24) was built from a commit after the natives landed, so this is the safe second commit of the two-commit rule and std/network can use them; no further seed change. No duplicate found (keywords: keepalive, nodelay, socket option, TcpStream). Owner-reserved matters: none (no new capability, no public contract with users, no cost). The checklist above links #473.

[loop:torbscript] decided by the loop: step 1 (#426, the natives `networkSetOption`/`networkOption`) landed with PR #427, so the first step of the earlier brief (a `TCP_NODELAY` setter without a native) is moot: the native pair serves every option, and the setter and keepalive belong in one `std/` commit. Step 2 starts next and is filed just in time as #473 (label `loop` and `ready`): `TcpStream.setNoDelay`/`noDelay`/`setKeepAlive`/`keepAlive`, one commit `feat(network): TcpStream.setNoDelay and setKeepAlive`. Why one step and not two: both halves are a few lines over the same two natives and the same tests, far below the ~1 h of an issue of its own. Seed: the published seed `c4a8443532aa` (2026-10-24) was built from a commit after the natives landed, so this is the safe second commit of the two-commit rule and `std/network` can use them; no further seed change. No duplicate found (keywords: keepalive, nodelay, socket option, TcpStream). Owner-reserved matters: none (no new capability, no public contract with users, no cost). The checklist above links #473.
Author
Collaborator

[loop:torbscript] refine: checked #120 against main, NETWORK.md section 4, FIRST-PARTY.md gap 22 and the published seeds. Step 1 (#426) had already landed (PR #427) and the published seed knows its natives, so no first step was filed; step 2 is now filed as #473 with label loop and ready (goal, testable acceptance criteria, design record point, seed impact: none further, size: one commit). The epic checklist is updated; #120 stays without ready. next: waiting (epic waits on step 2, #473 is ready for implement). Refine worker usage: roughly 70k tokens (estimate, sonnet).

[loop:torbscript] refine: checked #120 against main, NETWORK.md section 4, FIRST-PARTY.md gap 22 and the published seeds. Step 1 (#426) had already landed (PR #427) and the published seed knows its natives, so no first step was filed; step 2 is now filed as #473 with label `loop` and `ready` (goal, testable acceptance criteria, design record point, seed impact: none further, size: one commit). The epic checklist is updated; #120 stays without `ready`. next: waiting (epic waits on step 2, #473 is ready for implement). Refine worker usage: roughly 70k tokens (estimate, sonnet).
orion closed this issue 2026-10-25 15:33:39 +00:00
Author
Collaborator

[loop:torbscript] close: both steps landed (#426 via PR 427, #473 via PR 477, merge commit 9bc0ec86f3 on main), FIRST-PARTY.md gap 22 is marked done on main, main CI green. Epic checklist ticked, closing. next: closed

[loop:torbscript] close: both steps landed (#426 via PR 427, #473 via PR 477, merge commit 9bc0ec86f3ff50597b4e6f1375ee6d7084d256c8 on main), FIRST-PARTY.md gap 22 is marked done on main, main CI green. Epic checklist ticked, closing. next: closed
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#120
No description provided.