ci: Modbus framing test lost its FC16 PDU to a List race with the probe loop
Hosted run 2243 (dc0a24f2, job 3882) failed in
ModbusWritePduFramingTests.FC16_pdu_is_exactly_header_plus_byte_count with
"Sequence contains no matching element" at transport.Sent.Single(fc == 0x10),
one line after the write had already returned status Good: the driver sent the
FC16 PDU, but the record lost it.
The fixture left Probe at its default (Enabled = true), so InitializeAsync
started ProbeLoopAsync on a pool thread, which sends an FC03 at once. Both that
send and the write's send did List<byte[]>.Add on the capturing transport
concurrently; List is not thread-safe and one Add can overwrite the other.
- Disable the probe in this fixture, as the sibling capturing-transport
fixtures (ModbusProtocolOptionsTests, ModbusCoalescingDefaultsTests) do.
- Record PDUs in a ConcurrentQueue so a background send can never drop one.
- Dispose the driver (await using) so no loop outlives the test.
Test-only; not related to the #729/#727 changes (2243 predates them, and the
run that includes them, 2260, passed Driver.Modbus.Tests 1055/1055).
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
@@ -16,16 +17,20 @@ public sealed class ModbusWritePduFramingTests
|
||||
{
|
||||
private const string Ref = "arr";
|
||||
|
||||
/// <summary>Records every PDU; answers reads with zeros and multi-writes with the standard echo.</summary>
|
||||
/// <summary>
|
||||
/// Records every PDU; answers reads with zeros and multi-writes with the standard echo. The record is a
|
||||
/// concurrent queue: a plain <c>List</c> lost the FC16 PDU on hosted CI (run 2243, "Sequence contains no
|
||||
/// matching element") when the default-on probe loop's FC03 <c>Add</c> raced the write's <c>Add</c>.
|
||||
/// </summary>
|
||||
private sealed class CapturingTransport : IModbusTransport
|
||||
{
|
||||
public readonly List<byte[]> Sent = [];
|
||||
public readonly ConcurrentQueue<byte[]> Sent = new();
|
||||
|
||||
public Task ConnectAsync(CancellationToken ct) => Task.CompletedTask;
|
||||
|
||||
public Task<byte[]> SendAsync(byte unitId, byte[] pdu, CancellationToken ct)
|
||||
{
|
||||
Sent.Add(pdu);
|
||||
Sent.Enqueue(pdu);
|
||||
var qty = (pdu[3] << 8) | pdu[4];
|
||||
return Task.FromResult(pdu[0] switch
|
||||
{
|
||||
@@ -42,10 +47,13 @@ public sealed class ModbusWritePduFramingTests
|
||||
private static async Task<byte[]> WriteAndCaptureAsync(string tagConfig, object value, byte functionCode, bool fc23 = false)
|
||||
{
|
||||
var transport = new CapturingTransport();
|
||||
var drv = new ModbusDriver(
|
||||
// The probe is off: these tests assert on the write's own PDU, and a background probe loop sending FC03
|
||||
// concurrently from a pool thread is noise at best (and was a lost-PDU race against the old List record).
|
||||
await using var drv = new ModbusDriver(
|
||||
new ModbusDriverOptions
|
||||
{
|
||||
Host = "fake", RawTags = [new RawTagEntry(Ref, tagConfig, false)], UseFC23ForMultiRegisterWrites = fc23,
|
||||
Probe = new ModbusProbeOptions { Enabled = false },
|
||||
},
|
||||
"modbus-framing", _ => transport);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
Reference in New Issue
Block a user