Skip to content

Commit fc8daaf

Browse files
docs(mock): refresh CA2000 rationale on BowireMockHostManager.StartFromJson
Reverts the previous try/dispose restructure attempt — Roslyn can't model ownership transfer through an IAsyncDisposable factory + await, so the structural workaround just trades CA2000 for CA1508. Refreshed comment to spell out why the analyzer is wrong and why the dictionary write + record ctor between the suppressed line and ownership handoff are non-throwing in practice.
1 parent 3b0e81a commit fc8daaf

2 files changed

Lines changed: 25 additions & 25 deletions

File tree

src/Kuestenlogik.Bowire.Mock/Management/BowireMockHostManager.cs

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,34 +64,33 @@ public async Task<MockHostHandle> StartFromJson(string recordingJson, string rec
6464
$"No free TCP port found in the range {BasePort}..{BasePort + MaxProbes - 1}; close some mock hosts and try again.");
6565
}
6666

67+
// CA2000: MockServer ownership transfers into the dictionary
68+
// entry below; StopAsync / DisposeAsync tears it down. Roslyn
69+
// can't see the ownership move through an IAsyncDisposable
70+
// factory + await — wrapping the await in try/catch trades the
71+
// warning for CA1508 ("server is always null") because the
72+
// analyzer still can't model the null-out after handoff. The
73+
// dictionary write and Handle ctor are non-throwing in practice
74+
// (ConcurrentDictionary indexer never throws, MockHostHandle is
75+
// a value record with no validation), so the pragma is safe.
76+
#pragma warning disable CA2000
6777
var server = await MockServer.StartAsync(new MockServerOptions
6878
{
6979
RecordingPath = tempPath,
7080
Port = port,
7181
}, ct).ConfigureAwait(false);
72-
try
73-
{
74-
var handle = new MockHostHandle(
75-
MockId: mockId,
76-
RecordingId: recordingId,
77-
Label: label,
78-
Port: server.Port,
79-
Url: $"http://127.0.0.1:{server.Port}",
80-
StartedAtUtc: DateTime.UtcNow);
81-
82-
_entries[mockId] = new MockHostEntry(handle, server, tempPath);
83-
return handle;
84-
}
85-
catch
86-
{
87-
// Handle ctor / dictionary write threw before ownership of
88-
// `server` reached _entries — tear it down here so the
89-
// socket / file handle doesn't leak. Success path leaves
90-
// ownership with MockHostEntry, disposed via StopAsync or
91-
// DisposeAsync.
92-
try { await server.DisposeAsync().ConfigureAwait(false); } catch (ObjectDisposedException) { }
93-
throw;
94-
}
82+
#pragma warning restore CA2000
83+
84+
var handle = new MockHostHandle(
85+
MockId: mockId,
86+
RecordingId: recordingId,
87+
Label: label,
88+
Port: server.Port,
89+
Url: $"http://127.0.0.1:{server.Port}",
90+
StartedAtUtc: DateTime.UtcNow);
91+
92+
_entries[mockId] = new MockHostEntry(handle, server, tempPath);
93+
return handle;
9594
}
9695

9796
public async Task<bool> StopAsync(string mockId, CancellationToken ct)

src/Kuestenlogik.Bowire/Proxy/BowireProxyCertificateAuthority.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,13 @@ private X509Certificate2 MintLeaf(string hostname)
178178
var notAfter = notBefore.AddDays(90);
179179

180180
using var unsignedLeaf = req.Create(Certificate, notBefore, notAfter, serial);
181-
var signedLeaf = unsignedLeaf.CopyWithPrivateKey(rsa);
182181
// Round-trip through PFX export so the private key is bound for
183182
// SslStream consumption on all platforms (Windows in particular
184183
// requires this when the source cert is constructed in-memory).
184+
// `using` so an Export() throw still releases the unmanaged
185+
// handle the with-private-key copy holds (cs/dispose-not-called-on-throw).
186+
using var signedLeaf = unsignedLeaf.CopyWithPrivateKey(rsa);
185187
var pfxBytes = signedLeaf.Export(X509ContentType.Pkcs12);
186-
signedLeaf.Dispose();
187188
return X509CertificateLoader.LoadPkcs12(pfxBytes, password: null, keyStorageFlags: X509KeyStorageFlags.Exportable);
188189
}
189190

0 commit comments

Comments
 (0)