fix(abcip): libplctag ErrorBadParam at tag create is a per-tag config error, no device backoff (#570, #593)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -37,6 +37,32 @@ internal static class LibplctagStatusTranslation
|
||||
? ToAbStatus(s)
|
||||
: AbStatus.BadConnection;
|
||||
|
||||
/// <summary>
|
||||
/// True when <paramref name="ex"/> is libplctag refusing the tag's own attributes (<c>ErrorBadParam</c>:
|
||||
/// the name or a create attribute does not parse), as opposed to a transport failure.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception.</param>
|
||||
public static bool IsTagParameterRefusal(LibPlcTagException ex) =>
|
||||
Enum.TryParse<Status>((ex.Message ?? string.Empty).Trim(), ignoreCase: true, out var s) && s == Status.ErrorBadParam;
|
||||
|
||||
/// <summary>
|
||||
/// Runs a tag's <b>initialisation</b>: <c>ErrorBadParam</c> there means libplctag rejected the tag's own
|
||||
/// name before any I/O, so it is raised as <see cref="AbCipTagConfigurationException"/> (per-tag
|
||||
/// <c>BadConfigurationError</c>, no backoff, no health signal — Gitea #570/#593) instead of falling through
|
||||
/// to the generic <see cref="AbStatus.BadConnection"/> default that backs the whole device off. Only the
|
||||
/// create phase: a BadParam from a read or write on a live handle is not a name problem.
|
||||
/// </summary>
|
||||
/// <param name="op">The libplctag initialisation.</param>
|
||||
/// <returns>A task completing when the initialisation does.</returns>
|
||||
public static async Task WrapInitialize(Func<Task> op)
|
||||
{
|
||||
try { await op().ConfigureAwait(false); }
|
||||
catch (LibPlcTagException ex) when (IsTagParameterRefusal(ex))
|
||||
{
|
||||
throw new AbCipTagConfigurationException($"libplctag rejected the tag's name or attributes: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Runs <paramref name="op"/>, rethrowing any <see cref="LibPlcTagException"/> as <see cref="AbTransportException"/>.</summary>
|
||||
/// <param name="op">The libplctag operation.</param>
|
||||
/// <returns>A task completing when the operation does.</returns>
|
||||
|
||||
@@ -52,7 +52,7 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task InitializeAsync(CancellationToken cancellationToken) =>
|
||||
RunReleasingOrphanAsync(() => _tag.InitializeAsync(cancellationToken));
|
||||
RunReleasingOrphanAsync(() => LibplctagStatusTranslation.WrapInitialize(() => _tag.InitializeAsync(cancellationToken)));
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>libplctag initialises an uninitialised tag on its first read, so this path can orphan one too.</remarks>
|
||||
|
||||
@@ -111,4 +111,41 @@ public sealed class LibplctagStatusTranslationTests
|
||||
ex.Status.ShouldBe(AbStatus.Timeout);
|
||||
ex.InnerException.ShouldBeSameAs(inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #593 / #570: <c>ErrorBadParam</c> during initialisation is libplctag refusing the tag's own name — a
|
||||
/// per-tag configuration error (the driver's no-backoff, no-health path), not a device connect failure.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task WrapInitialize_maps_ErrorBadParam_to_a_tag_configuration_exception()
|
||||
{
|
||||
var thrown = await Should.ThrowAsync<AbCipTagConfigurationException>(() =>
|
||||
LibplctagStatusTranslation.WrapInitialize(() => throw new LibPlcTagException(Status.ErrorBadParam)));
|
||||
thrown.InnerException.ShouldBeOfType<LibPlcTagException>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Falsification pair for the above: every OTHER initialisation failure is left alone (it still reaches
|
||||
/// <c>Wrap</c> and becomes a transport status that opens the backoff window).
|
||||
/// </summary>
|
||||
/// <param name="status">The libplctag status.</param>
|
||||
[Theory]
|
||||
[InlineData(Status.ErrorTimeout)]
|
||||
[InlineData(Status.ErrorBadGateway)]
|
||||
[InlineData(Status.ErrorNotFound)]
|
||||
public async Task WrapInitialize_leaves_other_statuses_untouched(Status status)
|
||||
{
|
||||
var thrown = await Should.ThrowAsync<LibPlcTagException>(() =>
|
||||
LibplctagStatusTranslation.WrapInitialize(() => throw new LibPlcTagException(status)));
|
||||
thrown.ShouldNotBeOfType<AbCipTagConfigurationException>();
|
||||
}
|
||||
|
||||
/// <summary>A BadParam on the read/write path (through <c>Wrap</c>) is not reclassified as configuration.</summary>
|
||||
[Fact]
|
||||
public async Task Wrap_still_maps_ErrorBadParam_to_a_transport_status()
|
||||
{
|
||||
var thrown = await Should.ThrowAsync<AbTransportException>(() =>
|
||||
LibplctagStatusTranslation.Wrap(() => throw new LibPlcTagException(Status.ErrorBadParam)));
|
||||
thrown.Status.ShouldBe(AbStatus.BadConnection);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user