Resolve all high-severity architecture review findings
This commit is contained in:
@@ -58,6 +58,7 @@ internal sealed class AuditingDbCommand : DbCommand
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private DbConnection? _wrappingConnection;
|
||||
private DbTransaction? _wrappingTransaction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new auditing database command decorator.
|
||||
@@ -171,8 +172,13 @@ internal sealed class AuditingDbCommand : DbCommand
|
||||
/// <inheritdoc />
|
||||
protected override DbTransaction? DbTransaction
|
||||
{
|
||||
get => _inner.Transaction;
|
||||
set => _inner.Transaction = value;
|
||||
get => _inner.Transaction is null ? null : _wrappingTransaction;
|
||||
set
|
||||
{
|
||||
// Preserve the public wrapper, while giving the provider its own transaction.
|
||||
_inner.Transaction = value is AuditingDbTransaction auditing ? auditing.Inner : value;
|
||||
_wrappingTransaction = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -17,8 +17,9 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// All other <see cref="DbConnection"/> members forward to the inner connection
|
||||
/// unchanged so the script keeps full ADO.NET semantics (transactions, state
|
||||
/// Transactions and command connection readback retain this wrapper so they cannot
|
||||
/// expose an unaudited provider connection. Other members forward to the inner connection
|
||||
/// so the script keeps full ADO.NET semantics (transactions, state
|
||||
/// transitions, server-version queries, etc.). Disposing the wrapper disposes
|
||||
/// the inner connection — the caller is still responsible for disposal per
|
||||
/// the <see cref="IDatabaseGateway"/> contract.
|
||||
@@ -147,7 +148,13 @@ internal sealed class AuditingDbConnection : DbConnection
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
|
||||
=> _inner.BeginTransaction(isolationLevel);
|
||||
=> new AuditingDbTransaction(_inner.BeginTransaction(isolationLevel), this);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask<DbTransaction> BeginDbTransactionAsync(
|
||||
IsolationLevel isolationLevel, CancellationToken cancellationToken)
|
||||
=> new AuditingDbTransaction(
|
||||
await _inner.BeginTransactionAsync(isolationLevel, cancellationToken).ConfigureAwait(false), this);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DbCommand CreateDbCommand()
|
||||
@@ -168,7 +175,7 @@ internal sealed class AuditingDbConnection : DbConnection
|
||||
// threaded alongside _executionId. Null for non-routed runs.
|
||||
_parentExecutionId,
|
||||
// Flow Debugger: the DbWrite rows ride the inline collector.
|
||||
_onAudit);
|
||||
_onAudit) { Connection = this };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps transaction-derived commands inside the auditing connection boundary.
|
||||
/// Only the command decorator can unwrap the provider transaction for assignment.
|
||||
/// </summary>
|
||||
internal sealed class AuditingDbTransaction(DbTransaction inner, AuditingDbConnection connection) : DbTransaction
|
||||
{
|
||||
/// <summary>The provider transaction, for internal command assignment only.</summary>
|
||||
internal DbTransaction Inner => inner;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DbConnection? DbConnection => inner.Connection is null ? null : connection;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IsolationLevel IsolationLevel => inner.IsolationLevel;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool SupportsSavepoints => inner.SupportsSavepoints;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Commit() => inner.Commit();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task CommitAsync(CancellationToken cancellationToken = default) => inner.CommitAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Rollback() => inner.Rollback();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task RollbackAsync(CancellationToken cancellationToken = default) => inner.RollbackAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Save(string savepointName) => inner.Save(savepointName);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SaveAsync(string savepointName, CancellationToken cancellationToken = default)
|
||||
=> inner.SaveAsync(savepointName, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Rollback(string savepointName) => inner.Rollback(savepointName);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task RollbackAsync(string savepointName, CancellationToken cancellationToken = default)
|
||||
=> inner.RollbackAsync(savepointName, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Release(string savepointName) => inner.Release(savepointName);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task ReleaseAsync(string savepointName, CancellationToken cancellationToken = default)
|
||||
=> inner.ReleaseAsync(savepointName, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) inner.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueTask DisposeAsync()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
return inner.DisposeAsync();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
/// <para>
|
||||
/// Thread-model note: the indexer get/set block synchronously
|
||||
/// on the Instance Actor Ask (and, for data-connected attributes, the DCL
|
||||
/// round-trip). This is safe because script bodies execute on the dedicated
|
||||
/// round-trip). Runtime helper continuations use ConfigureAwait(false), so they
|
||||
/// can complete even when every dedicated worker is blocked in an indexer.
|
||||
/// Script bodies execute on the dedicated
|
||||
/// <see cref="ScriptExecutionScheduler"/> threads, not the
|
||||
/// shared <see cref="System.Threading.ThreadPool"/> — so a blocked accessor
|
||||
/// cannot starve unrelated Akka dispatchers or HTTP request handling. The async
|
||||
|
||||
@@ -701,7 +701,7 @@ public class ScriptRuntimeContext
|
||||
var request = new GetAttributeRequest(
|
||||
correlationId, _instanceName, attributeName, DateTimeOffset.UtcNow);
|
||||
|
||||
var response = await _instanceActor.Ask<GetAttributeResponse>(request, _askTimeout);
|
||||
var response = await _instanceActor.Ask<GetAttributeResponse>(request, _askTimeout).ConfigureAwait(false);
|
||||
|
||||
if (!response.Found)
|
||||
{
|
||||
@@ -762,7 +762,7 @@ public class ScriptRuntimeContext
|
||||
public async Task<bool> WaitAttribute(
|
||||
string name, string? targetValueEncoded, Func<object?, bool>? predicate, TimeSpan timeout,
|
||||
bool requireGoodQuality = false)
|
||||
=> (await WaitInternal(name, targetValueEncoded, predicate, timeout, requireGoodQuality)).Matched;
|
||||
=> (await WaitInternal(name, targetValueEncoded, predicate, timeout, requireGoodQuality).ConfigureAwait(false)).Matched;
|
||||
|
||||
/// <summary>
|
||||
/// WaitForAttribute (spec §3): the richer overload backing <c>Attributes.WaitForAsync</c>
|
||||
@@ -780,7 +780,7 @@ public class ScriptRuntimeContext
|
||||
string name, string? targetValueEncoded, Func<object?, bool>? predicate, TimeSpan timeout,
|
||||
bool requireGoodQuality = false)
|
||||
{
|
||||
var r = await WaitInternal(name, targetValueEncoded, predicate, timeout, requireGoodQuality);
|
||||
var r = await WaitInternal(name, targetValueEncoded, predicate, timeout, requireGoodQuality).ConfigureAwait(false);
|
||||
return new WaitResult(r.Matched, r.Value, r.Quality, r.TimedOut);
|
||||
}
|
||||
|
||||
@@ -816,7 +816,7 @@ public class ScriptRuntimeContext
|
||||
try
|
||||
{
|
||||
response = await _instanceActor.Ask<WaitForAttributeResponse>(
|
||||
req, timeout + _askTimeout, _scriptTimeoutToken);
|
||||
req, timeout + _askTimeout, _scriptTimeoutToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (AskTimeoutException)
|
||||
{
|
||||
@@ -911,7 +911,7 @@ public class ScriptRuntimeContext
|
||||
{
|
||||
// Ask — mutation serialized through the Instance Actor mailbox; the reply
|
||||
// carries the device-write outcome for data-connected attributes.
|
||||
response = await _instanceActor.Ask<SetStaticAttributeResponse>(command, _askTimeout);
|
||||
response = await _instanceActor.Ask<SetStaticAttributeResponse>(command, _askTimeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex) when (sw is not null)
|
||||
{
|
||||
@@ -979,7 +979,7 @@ public class ScriptRuntimeContext
|
||||
Guid? stepId = _isTraced ? Guid.NewGuid() : null;
|
||||
using var step = stepId is { } batchStepId ? TraceStepScope.Enter(batchStepId) : null;
|
||||
var batchResp = await _instanceActor.Ask<WriteAttributeBatchResponse>(
|
||||
batchReq, TimeSpan.FromSeconds(35), _scriptTimeoutToken);
|
||||
batchReq, TimeSpan.FromSeconds(35), _scriptTimeoutToken).ConfigureAwait(false);
|
||||
|
||||
if (sw is not null)
|
||||
{
|
||||
@@ -1003,7 +1003,7 @@ public class ScriptRuntimeContext
|
||||
throw new InvalidOperationException(
|
||||
$"WriteBatchAndWait write failed: {batchResp.ErrorMessage}");
|
||||
|
||||
return await WaitAttribute(responseAttr, responseEncoded, null, timeout);
|
||||
return await WaitAttribute(responseAttr, responseEncoded, null, timeout).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user