fix(focas): PMC coalescing treats any rc refusal as per-tag fallback, skips transient busy memo, one connect per batch (#266)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -634,6 +634,20 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
{
|
||||
if (items.Count < 2) continue;
|
||||
var plans = FocasPmcCoalescer.Plan(items, _options.PmcCoalesce.MaxSpanBytes, _options.PmcCoalesce.MaxGapBytes);
|
||||
if (!plans.Any(p => p.Members.Count >= 2 && !device.CoalesceRejected.ContainsKey(p.Key))) continue;
|
||||
|
||||
// One connect attempt per device per batch: if it fails, every PMC tag of the device fails with
|
||||
// that status here instead of paying a connect attempt per plan and again per tag.
|
||||
IFocasClient client;
|
||||
try { client = await EnsureConnectedAsync(device, ct).ConfigureAwait(false); }
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; }
|
||||
catch (Exception)
|
||||
{
|
||||
FailMembers(items, FocasStatusMapper.BadCommunicationError, references, results, now, ref served,
|
||||
$"FOCAS connect failed reading PMC tags of {device.Options.HostAddress}");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var plan in plans)
|
||||
{
|
||||
if (plan.Members.Count < 2) continue;
|
||||
@@ -642,7 +656,6 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
var members = plan.Members;
|
||||
try
|
||||
{
|
||||
var client = await EnsureConnectedAsync(device, ct).ConfigureAwait(false);
|
||||
var (slots, status) = await client
|
||||
.ReadPmcSlotsAsync(plan.Letter, plan.SlotType, plan.Start, plan.End, ct)
|
||||
.ConfigureAwait(false);
|
||||
@@ -659,44 +672,41 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
}
|
||||
Volatile.Write(ref _health, new DriverHealth(DriverState.Healthy, now, null));
|
||||
}
|
||||
else if (status == FocasStatusMapper.BadCommunicationError)
|
||||
{
|
||||
FailMembers(plan, status, references, results, now, ref served);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The CNC refused the merged range. Don't guess which member is at fault: remember the
|
||||
// range and let the per-tag loop give each member its own answer.
|
||||
device.CoalesceRejected[plan.Key] = 0;
|
||||
// The CNC REFUSED the merged range (any return code, mapped or not). Don't guess which
|
||||
// member is at fault: let the per-tag loop give each its own answer. Remember the range
|
||||
// so later polls skip the doomed read — except for a transient refusal (EW_BUSY maps to
|
||||
// BadDeviceFailure), which says nothing about the range.
|
||||
if (status != FocasStatusMapper.BadDeviceFailure)
|
||||
device.CoalesceRejected[plan.Key] = 0;
|
||||
_logger.LogDebug(
|
||||
"FOCAS PMC range {Key} on {Host} rejected (0x{Status:X8}) — reading its {Count} tags individually",
|
||||
plan.Key, device.Options.HostAddress, status, members.Count);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; }
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
FailMembers(plan, FocasStatusMapper.BadCommunicationError, references, results, now, ref served);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
FailMembers(plan, FocasStatusMapper.BadCommunicationError, references, results, now, ref served);
|
||||
// Transport failure / timeout: the link is suspect, so fail the members without per-tag retries.
|
||||
FailMembers(members, FocasStatusMapper.BadCommunicationError, references, results, now, ref served,
|
||||
$"FOCAS transport failure reading PMC range {plan.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return served;
|
||||
|
||||
void FailMembers(FocasPmcRangePlan plan, uint status, IReadOnlyList<string> refs,
|
||||
DataValueSnapshot[] res, DateTime stamp, ref bool[]? servedRef)
|
||||
void FailMembers(IEnumerable<FocasPmcReadItem> failed, uint status, IReadOnlyList<string> refs,
|
||||
DataValueSnapshot[] res, DateTime stamp, ref bool[]? servedRef, string reason)
|
||||
{
|
||||
foreach (var m in plan.Members)
|
||||
foreach (var m in failed)
|
||||
{
|
||||
res[m.Index] = new DataValueSnapshot(null, status, null, stamp);
|
||||
(servedRef ??= new bool[refs.Count])[m.Index] = true;
|
||||
}
|
||||
Volatile.Write(ref _health, new DriverHealth(DriverState.Degraded,
|
||||
Volatile.Read(ref _health).LastSuccessfulRead,
|
||||
$"FOCAS status 0x{status:X8} reading PMC range {plan.Key}"));
|
||||
$"{reason} (0x{status:X8})"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user