Skip to content

Commit 2e4cda2

Browse files
chore(greptile): iteration 2 — applied 1, rejected 0
Extend the connectTCPInterface write-after-success + rollback pattern to the three remaining tcp-server init sites: both initialize() overloads and reinitializeConnection(). Without this, an addInterface throw during init left tcpInterfaces["tcp-server"] and tcpEndpoints["tcp-server"] populated with a dead interface; reconnectTCPOnly delegates to connectTCPInterface(entityId: "tcp-server", ...) which then silently no-op'd on a same-address retry through the new idempotency guard. For the two initialize overloads, the catch block preserves the "non-fatal" semantics (init proceeds without TCP, no rethrow) but now also clears the partial dictionary writes so a later reconnectTCPOnly retry isn't stuck. For reinitializeConnection — which had no catch and propagates errors to its caller — the new do/catch rolls back and rethrows, mirroring connectTCPInterface. Co-Authored-By: Claude claude-opus-4-7[1m] <noreply@anthropic.com>
1 parent 792ebd4 commit 2e4cda2

1 file changed

Lines changed: 40 additions & 4 deletions

File tree

Sources/ColumbaApp/Services/AppServices.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,22 @@ public final class AppServices {
443443
do {
444444
let newInterface = try TCPInterface(config: config)
445445
tcpInterfaces["tcp-server"] = newInterface
446-
tcpEndpoints["tcp-server"] = TCPEndpoint(host: host, port: port)
447446
try await newTransport.addInterface(newInterface)
447+
// Record the applied endpoint only after the interface
448+
// has been successfully attached. See the matching catch
449+
// block below for why this ordering matters.
450+
tcpEndpoints["tcp-server"] = TCPEndpoint(host: host, port: port)
448451
} catch {
452+
// Initialization is "non-fatal" with respect to TCP — the
453+
// rest of init proceeds without it, and the user can
454+
// retry via reconnectTCPOnly. But that retry routes
455+
// through connectTCPInterface, whose new idempotency
456+
// guard would silently no-op if a stale tcpEndpoints
457+
// entry survived this catch. Roll back any partial
458+
// dictionary writes so a same-address retry isn't
459+
// stuck.
460+
tcpInterfaces.removeValue(forKey: "tcp-server")
461+
tcpEndpoints.removeValue(forKey: "tcp-server")
449462
logger.warning("TCP interface failed (non-fatal): \(error.localizedDescription, privacy: .public)")
450463
}
451464
}
@@ -559,9 +572,20 @@ public final class AppServices {
559572
do {
560573
let newInterface = try TCPInterface(config: config)
561574
tcpInterfaces["tcp-server"] = newInterface
562-
tcpEndpoints["tcp-server"] = TCPEndpoint(host: host, port: port)
563575
try await newTransport.addInterface(newInterface)
576+
// Record the applied endpoint only after the interface
577+
// has been successfully attached. See the matching catch
578+
// block below — same rationale as the first overload.
579+
tcpEndpoints["tcp-server"] = TCPEndpoint(host: host, port: port)
564580
} catch {
581+
// Non-fatal: init proceeds without TCP. But roll back
582+
// any partial dictionary writes so a later
583+
// reconnectTCPOnly retry with the same address doesn't
584+
// hit a stuck idempotency guard in connectTCPInterface
585+
// and silently no-op. See the first initialize overload
586+
// for the full rationale.
587+
tcpInterfaces.removeValue(forKey: "tcp-server")
588+
tcpEndpoints.removeValue(forKey: "tcp-server")
565589
logger.warning("TCP interface failed (non-fatal): \(error.localizedDescription, privacy: .public)")
566590
}
567591
}
@@ -1404,10 +1428,22 @@ public final class AppServices {
14041428
)
14051429
let newInterface = try TCPInterface(config: config)
14061430
tcpInterfaces["tcp-server"] = newInterface
1407-
tcpEndpoints["tcp-server"] = TCPEndpoint(host: host, port: port)
14081431

14091432
// Add interface to transport (connects it)
1410-
try await newTransport.addInterface(newInterface)
1433+
do {
1434+
try await newTransport.addInterface(newInterface)
1435+
} catch {
1436+
// addInterface failed — roll back the dictionary write so a
1437+
// retry via reconnectTCPOnly with the same address isn't
1438+
// silently no-op'd by connectTCPInterface's idempotency
1439+
// guard. See connectTCPInterface's catch block for the full
1440+
// rationale.
1441+
tcpInterfaces.removeValue(forKey: "tcp-server")
1442+
throw error
1443+
}
1444+
// Only record the applied endpoint after the interface has been
1445+
// successfully attached to the transport.
1446+
tcpEndpoints["tcp-server"] = TCPEndpoint(host: host, port: port)
14111447

14121448
// Set transport on router and re-register delivery destination
14131449
if let router = router {

0 commit comments

Comments
 (0)