CCI report: 333661b2-cfd0-4544-84cb-4bdf7f00cc33

Request message

{
  "request": "trigger",
  "version": 1,
  "event_type": "patch",
  "repository": {
    "id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
    "name": "heartwood",
    "description": "Radicle Heartwood Protocol & Stack",
    "private": false,
    "default_branch": "master",
    "delegates": [
      "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
      "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
      "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
    ]
  },
  "action": "Created",
  "patch": {
    "id": "39c3b193319e1e6bdba4663cd879b64b1026e583",
    "author": {
      "id": "did:key:z6MkrnXJWPndzPBxpBUaE3L3BnMeWpaQdT1V1FvkoCPFSFS3",
      "alias": null
    },
    "title": "Terminate transports whose socket is dead",
    "state": {
      "status": "open",
      "conflicts": []
    },
    "before": "0e5a56f25f51d07290613a9363bd8c0c9fc8e0be",
    "after": "df924ada68fb5015d7e41db27d7b7da642443593",
    "commits": [
      "df924ada68fb5015d7e41db27d7b7da642443593"
    ],
    "target": "0e5a56f25f51d07290613a9363bd8c0c9fc8e0be",
    "labels": [],
    "assignees": [],
    "revisions": [
      {
        "id": "39c3b193319e1e6bdba4663cd879b64b1026e583",
        "author": {
          "id": "did:key:z6MkrnXJWPndzPBxpBUaE3L3BnMeWpaQdT1V1FvkoCPFSFS3",
          "alias": null
        },
        "description": "A transport whose peer has gone away is never terminated, and the\nreactor then spins on it at 100% of a core. On every iteration the reactor\n[re-registers the socket with whatever interests the transport still\nreports][1], `poll` returns it as ready immediately, and the I/O\nhandlers do nothing with it. The service thread stops serving the\ncontrol socket, so `rad node status` times out and the node looks\noffline while its container is still up. Restarting a node only helps\nuntil the next peer disconnect.\n\nHow the transport loses track of the socket:\n\n1. A peer resets the connection between `poll` returning and the event\n   being handled. The event carries no error flag, so the reactor\n   [hands it to the transport instead of reaping it][2].\n2. On the writable event the transport [first asks whether the session\n   is established][3]. That answer is [derived from the session\n   artifact][4], which for a TCP stream [comes from `getpeername`][5].\n   On a reset socket that fails with ENOTCONN, so the transport takes\n   the \"handshake in progress\" branch.\n3. That branch [pokes the session with a zero-length write and\n   discards the result][6]. The write [passes straight through to the\n   socket][7], and a zero-length send is exactly what surfaces the\n   pending ECONNRESET. The one signal that the peer is gone is thrown\n   away, and the transport [re-arms its write interest][8] instead.\n4. On the readable event the read also [passes straight through to\n   the socket][9] and returns zero bytes, which the transport\n   [ignores as well][10].\n\nNothing ever [marks the transport terminated][11], so its [interest\nset][12] stays READABLE and WRITABLE on a closed socket forever.\n\nOn an affected node, strace shows thousands of failing `getpeername`\ncalls in 5 seconds, 5 per iteration, with `epoll_pwait` returning 1,200\ntimes a second.\n\nTwo changes:\n\n- Propagate a real error from the handshake-branch zero-length write\n  as a termination instead of discarding it. WouldBlock, Interrupted\n  and WriteZero stay non-fatal, matching the established branch. A\n  socket whose error was already consumed elsewhere reports EPIPE\n  here, so this also reaps transports that are already stuck.\n- Treat a zero-byte read as end-of-file once the transport is\n  [active][13]. From that point reads pass straight through to the\n  socket. During the handshake the protocol layer [returns zero after\n  consuming a handshake step][14], so that case keeps its current\n  behaviour. Without this a half-closed peer also spins, until the\n  service's [stale connection reaper][15] disconnects it about\n  [2 minutes][16] later.\n\nThe termination event [reaches the wire layer][17], which [unregisters\nthe transport whether or not it has a peer entry for it][18]. That\ncovers inbound connections that die mid-handshake, which have no node\nID and so are invisible to the stale reaper.\n\nTests use real loopback sockets: a peer that resets (SO_LINGER 0), the\nsame after the pending error has already been taken, and a peer that\nhalf-closes. All three failed before this change.\n\n[1]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor.rs#L350\n[2]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor.rs#L444\n[3]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L206\n[4]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L21\n[5]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L180\n[6]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L207\n[7]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L109\n[8]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L208\n[9]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L78\n[10]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L298\n[11]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L144\n[12]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L317\n[13]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/transport.rs#L45\n[14]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/reactor/session.rs#L99\n[15]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-protocol/src/service.rs#L2417\n[16]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-protocol/src/service.rs#L91\n[17]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/wire.rs#L819\n[18]: https://radicle.network/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/0e5a56f25f51d07290613a9363bd8c0c9fc8e0be/crates/radicle-node/src/wire.rs#L356",
        "base": "0e5a56f25f51d07290613a9363bd8c0c9fc8e0be",
        "oid": "df924ada68fb5015d7e41db27d7b7da642443593",
        "timestamp": 1790767734
      }
    ]
  }
}

Send response

{
  "response": "triggered",
  "run_id": {
    "id": "333661b2-cfd0-4544-84cb-4bdf7f00cc33"
  },
  "info_url": "https://cci.rad.levitte.org//333661b2-cfd0-4544-84cb-4bdf7f00cc33.html"
}

Checkout the source (in /opt/radcis/ci.rad.levitte.org/cci/state/333661b2-cfd0-4544-84cb-4bdf7f00cc33/w)

Started at: 2026-09-30 13:29:04.896107+02:00

Commands:

Read the repo configuration (.radicle/native.yaml in /opt/radcis/ci.rad.levitte.org/cci/state/333661b2-cfd0-4544-84cb-4bdf7f00cc33/w)

shell: 'export RUSTDOCFLAGS=''-D warnings''


  cargo --version

  rustc --version


  cargo fmt --check

  cargo clippy --all-targets --workspace -- --deny warnings

  cargo build --all-targets --workspace

  cargo doc --workspace --no-deps --all-features

  cargo test --workspace --no-fail-fast

  '

Run the script

Commands:

Send result

{
  "response": "finished",
  "result": "failure"
}