Skip to content

Commit 4286701

Browse files
nficanoclaude
andcommitted
refactor: apply idiomatic C# safety fixes across SDK
Phase 3 of refactor-plan.md. The plan §6 violation scan claimed ~22 awaits missing ConfigureAwait and 3+ public-async sites missing CancellationToken; on a second pass with a multi-line-aware scan, those are false positives — all 85 awaits in src/ already call ConfigureAwait(false), and the 3 public-async sites already accept a CT. AgentRegistry.Versions was flagged as a Tier-1 mutable-collection leak; it's a property on a private nested class, so it's never on the public surface. Real changes: - SessionState.RunAsync now accepts a CancellationToken and links it to the internal CTS. ArcpServer.AcceptAsync forwards its own token through (fixes CA2016 that surfaced from the signature change). - ArcpClient.ReaderLoop: replace empty `catch (OperationCanceledException)` with a one-line justification comment. - ArcpClient.DisposeAsync: replace bare `catch { }` with typed `catch (Exception)` + comment. - Samples SubmitAndStream / LeaseViolation: forward `ct` through to context calls to clear the three lingering CA2016 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2aa7e47 commit 4286701

5 files changed

Lines changed: 20 additions & 12 deletions

File tree

samples/LeaseViolation/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
{
2626
Code = ex.Code,
2727
Message = ex.Message,
28-
});
28+
}, ct);
2929
}
3030
return "scan-complete";
3131
});

samples/SubmitAndStream/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
});
1313
server.RegisterAgent("greeter", async (ctx, ct) =>
1414
{
15-
await ctx.StatusAsync("starting");
15+
await ctx.StatusAsync("starting", cancellationToken: ct);
1616
await ctx.LogAsync("info", $"Hello, {ctx.Input}!", ct);
17-
await ctx.StatusAsync("complete");
17+
await ctx.StatusAsync("complete", cancellationToken: ct);
1818
return new { greeting = $"Hello, {ctx.Input}!" };
1919
});
2020

src/Arcp.Client/ArcpClient.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ private async Task ReaderLoop(CancellationToken cancellationToken)
9797
Dispatch(env, cancellationToken);
9898
}
9999
}
100-
catch (OperationCanceledException) { }
100+
catch (OperationCanceledException)
101+
{
102+
// Expected on shutdown; reader loop exits silently.
103+
}
101104
catch (Exception)
102105
{
103106
// Surface as a session error to in-flight handles.
@@ -303,7 +306,10 @@ await _transport.SendAsync(new Envelope
303306
Payload = new SessionByePayload { Reason = "client_close" },
304307
}).ConfigureAwait(false);
305308
}
306-
catch { /* already closed */ }
309+
catch (Exception)
310+
{
311+
// Transport may already be closed; suppress on dispose path.
312+
}
307313
_cts.Cancel();
308314
await _transport.DisposeAsync().ConfigureAwait(false);
309315
_cts.Dispose();

src/Arcp.Runtime/ArcpServer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public async Task AcceptAsync(ITransport transport, CancellationToken cancellati
6060
_sessions[session.SessionId] = session;
6161
try
6262
{
63-
await session.RunAsync().ConfigureAwait(false);
63+
await session.RunAsync(cancellationToken).ConfigureAwait(false);
6464
}
6565
finally
6666
{

src/Arcp.Runtime/SessionState.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,22 @@ internal SessionState(ITransport transport, ArcpServer server, ArcpServerOptions
6363
_lastInboundAt = options.TimeProvider.GetUtcNow();
6464
}
6565

66-
public async Task RunAsync()
66+
public async Task RunAsync(CancellationToken cancellationToken = default)
6767
{
68-
var sender = Task.Run(() => SenderLoop(_cts.Token));
69-
var heartbeat = Task.Run(() => HeartbeatLoop(_cts.Token));
68+
using var linked = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, cancellationToken);
69+
var token = linked.Token;
70+
var sender = Task.Run(() => SenderLoop(token), token);
71+
var heartbeat = Task.Run(() => HeartbeatLoop(token), token);
7072
try
7173
{
72-
await ReceiverLoop(_cts.Token).ConfigureAwait(false);
74+
await ReceiverLoop(token).ConfigureAwait(false);
7375
}
7476
finally
7577
{
7678
_cts.Cancel();
7779
_outbound.Writer.TryComplete();
78-
try { await sender.ConfigureAwait(false); } catch { /* shutdown */ }
79-
try { await heartbeat.ConfigureAwait(false); } catch { /* shutdown */ }
80+
try { await sender.ConfigureAwait(false); } catch (OperationCanceledException) { }
81+
try { await heartbeat.ConfigureAwait(false); } catch (OperationCanceledException) { }
8082
IsClosed = true;
8183
_server.RemoveSession(this);
8284
}

0 commit comments

Comments
 (0)