fix(review): resolve deferred code-review findings; S7.Cli verdict, browser CP release, MemberLeft/Exited/Downed, Sql Detach hoist (#625)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
|
||||
|
||||
/// <summary>
|
||||
/// Shared helper for work the driver stops waiting on (a timed-out liveness probe or poll group).
|
||||
/// </summary>
|
||||
internal static class AbandonedTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Observes an abandoned task's eventual fault so it never surfaces as an unobserved task
|
||||
/// exception. The task still owns — and disposes — its own connection, and still releases any
|
||||
/// concurrency slot when it finally completes; that is what keeps a frozen database from
|
||||
/// accumulating connections.
|
||||
/// </summary>
|
||||
/// <param name="work">The task being abandoned.</param>
|
||||
internal static void Detach(Task work)
|
||||
=> _ = work.ContinueWith(
|
||||
static t => _ = t.Exception,
|
||||
CancellationToken.None,
|
||||
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
@@ -1048,7 +1048,7 @@ public sealed class SqlDriver
|
||||
/// <para>Both the linked CTS deadline and an outer <see cref="TaskExtensions"/> wait are used, because
|
||||
/// each covers a case the other does not: the CTS handles a provider that <em>does</em> honour
|
||||
/// cancellation, and the outer wait handles one that does not. An abandoned attempt keeps ownership of
|
||||
/// its own connection and disposes it; <see cref="Detach"/> observes its eventual fault.</para>
|
||||
/// its own connection and disposes it; <see cref="AbandonedTask.Detach"/> observes its eventual fault.</para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The work's result type.</typeparam>
|
||||
/// <param name="work">The operation to bound. Receives the deadline-linked token.</param>
|
||||
@@ -1089,13 +1089,13 @@ public sealed class SqlDriver
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Detach(running);
|
||||
AbandonedTask.Detach(running);
|
||||
throw new TimeoutException($"{what} within {(int)budget.TotalMilliseconds} ms.");
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Our deadline fired and the provider DID honour the linked token.
|
||||
Detach(running);
|
||||
AbandonedTask.Detach(running);
|
||||
throw new TimeoutException($"{what} within {(int)budget.TotalMilliseconds} ms.");
|
||||
}
|
||||
}
|
||||
@@ -1123,19 +1123,6 @@ public sealed class SqlDriver
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes an abandoned liveness attempt's eventual fault so it never surfaces as an unobserved
|
||||
/// task exception. The task still owns — and disposes — its own connection.
|
||||
/// <para>TODO(M2): a verbatim duplicate of <c>SqlPollReader.Detach</c>; both live in Driver.Sql and
|
||||
/// could hoist to one internal helper. Left in place here to keep this change off the reader.</para>
|
||||
/// </summary>
|
||||
private static void Detach(Task work)
|
||||
=> _ = work.ContinueWith(
|
||||
static t => _ = t.Exception,
|
||||
CancellationToken.None,
|
||||
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
|
||||
// ---- endpoint description ----
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -329,7 +329,7 @@ public sealed class SqlPollReader
|
||||
// Either the caller pulled the plug, or our deadline fired and the provider DID honour the
|
||||
// linked token. Both leave the work running with nobody to observe its fault.
|
||||
if (!cancellationToken.IsCancellationRequested) return TimedOut(plan, work);
|
||||
Detach(work);
|
||||
AbandonedTask.Detach(work);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -352,22 +352,10 @@ public sealed class SqlPollReader
|
||||
plan.Model, plan.Members[0].Table, plan.Members.Count,
|
||||
(int)_operationTimeout.TotalMilliseconds);
|
||||
|
||||
Detach(work);
|
||||
AbandonedTask.Detach(work);
|
||||
return GroupResult.TimedOut;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes an abandoned group's eventual fault so it never surfaces as an unobserved task
|
||||
/// exception. The task still owns its connection and still releases the concurrency slot when it
|
||||
/// finally completes — that is what keeps a frozen database from accumulating connections.
|
||||
/// </summary>
|
||||
private static void Detach(Task work)
|
||||
=> _ = work.ContinueWith(
|
||||
static t => _ = t.Exception,
|
||||
CancellationToken.None,
|
||||
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a connection, executes the plan's single command, and materialises the result set.
|
||||
/// <para>The rows are read into memory <b>before</b> the reader is disposed — the slice-back needs
|
||||
|
||||
Reference in New Issue
Block a user