refactor(runtime): delete the dead in-place ApplyDelta seam; config change = respawn only (#665)
Since #516 a DriverConfig change is stop + respawn, so DriverSpawnPlan.ToApplyDelta was always empty and the in-place path had no producer. #663 and #664 were both latent bugs in it. This deletes it: - host: ApplyChildDelta, _pendingDelta, HandleApplyResult and HandleDeltaApplied - plan: DriverSpawnPlan.ToApplyDelta - actor: the ApplyDelta, ApplyResult and DeltaApplied messages, the Connected reinit handler and the mid-connect AdoptConfigDuringInit - tests: the actor tests that drove the seam IDriver.ReinitializeAsync and each driver's implementation and unit tests stay. Comments and docs that cited AdoptConfigDuringInit as the reason a driver must release prior state at init now cite the IDriver contract and overlapping retries, which still apply. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+2
-2
@@ -5,8 +5,8 @@ using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// #630 — <c>InitializeAsync</c> on a live driver (<c>DriverInstanceActor.AdoptConfigDuringInit</c> runs
|
||||
/// it with no teardown first) overwrote <c>_devices[host]</c> and orphaned the previous state's
|
||||
/// #630 — <c>InitializeAsync</c> on a live driver with no teardown first (the <c>IDriver</c> contract
|
||||
/// allows it) overwrote <c>_devices[host]</c> and orphaned the previous state's
|
||||
/// probe loop + runtime. It now releases them first.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
|
||||
@@ -396,7 +396,7 @@ public sealed class CalculationDriverTests
|
||||
}
|
||||
|
||||
/// <summary>The skip ledger must be REBUILT on ReinitializeAsync, not just at construction — an
|
||||
/// ApplyDelta that drops a tag's scriptId (or adds a new tag with none) must be reflected immediately,
|
||||
/// in-place config change that drops a tag's scriptId (or adds a new tag with none) must be reflected immediately,
|
||||
/// since RebuildTagTable is the one routine both the ctor and reinit share.</summary>
|
||||
[Fact]
|
||||
public async Task GetSkippedTags_updates_after_reinitialize()
|
||||
@@ -414,9 +414,9 @@ public sealed class CalculationDriverTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #664 — <c>InitializeAsync</c> re-runs on a LIVE driver with a new config when an ApplyDelta lands
|
||||
/// mid-connect (<c>DriverInstanceActor.AdoptConfigDuringInit</c>). It used to ignore the JSON, so the
|
||||
/// driver kept its constructor tag set while the deploy reported the change applied. A config with a body
|
||||
/// Gitea #664 — <c>InitializeAsync</c> must serve the config document it is handed (the <c>IDriver</c>
|
||||
/// contract). It used to ignore the JSON, so a live driver re-initialised with a new config kept its
|
||||
/// constructor tag set. A config with a body
|
||||
/// now rebuilds the tag table (and the dependency-ref set the host re-registers from).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// #630 — <c>InitializeAsync</c> on a live driver (<c>DriverInstanceActor.AdoptConfigDuringInit</c> runs
|
||||
/// it with no teardown first) overwrote <c>_devices[host]</c> and orphaned the previous state's
|
||||
/// #630 — <c>InitializeAsync</c> on a live driver with no teardown first (the <c>IDriver</c> contract
|
||||
/// allows it) overwrote <c>_devices[host]</c> and orphaned the previous state's
|
||||
/// probe loop + client. It now releases them first.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
|
||||
@@ -209,9 +209,8 @@ public sealed class GalaxyDriverFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #664 — <c>InitializeAsync</c> re-runs on a live driver with a NEW config when an ApplyDelta lands
|
||||
/// mid-connect. Galaxy cannot hot-swap its options, and it used to ignore the JSON — connecting on the OLD
|
||||
/// endpoint while the deploy reported the change applied. A non-equivalent config is now refused, exactly
|
||||
/// Gitea #664 — <c>InitializeAsync</c> must not silently ignore the config it is handed. Galaxy cannot
|
||||
/// hot-swap its options, and it used to ignore the JSON — connecting on the OLD endpoint. A non-equivalent config is now refused, exactly
|
||||
/// as <c>ReinitializeAsync</c> refuses it; an equivalent one still initialises.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
|
||||
+3
-4
@@ -265,9 +265,8 @@ public sealed class MTConnectDriverLifecycleTests
|
||||
[Fact]
|
||||
public async Task Initialize_honours_the_config_json_over_the_constructor_options()
|
||||
{
|
||||
// The runtime delivers a config change ONLY through this argument (DriverInstanceActor
|
||||
// ApplyDelta -> ReinitializeAsync(json) on the LIVE instance). Ignoring it would make every
|
||||
// MTConnect config change a silent no-op that still seals the deployment green.
|
||||
// The IDriver contract: the driver serves the config document it is handed, not only its
|
||||
// constructor options — ignoring it would turn an in-place config change into a silent no-op.
|
||||
MTConnectDriverOptions? seen = null;
|
||||
var driver = new MTConnectDriver(Opts(), "mt1", o =>
|
||||
{
|
||||
@@ -533,7 +532,7 @@ public sealed class MTConnectDriverLifecycleTests
|
||||
[Fact]
|
||||
public async Task Reinitialize_with_an_unreadable_config_keeps_the_running_driver_serving()
|
||||
{
|
||||
// Blast radius: an unparseable config edit fails the DEPLOYMENT (ApplyResult(false)); it must
|
||||
// Blast radius: an unparseable in-place config edit fails the reinitialize call; it must
|
||||
// not down a driver that is happily serving its previous, valid configuration.
|
||||
var (driver, client) = await InitializedDriverAsync();
|
||||
|
||||
|
||||
@@ -225,9 +225,8 @@ public sealed class ModbusLifecycleHygieneTests
|
||||
// -------------------- Re-initialize transport hygiene --------------------
|
||||
|
||||
/// <summary>
|
||||
/// A second <c>InitializeAsync</c> on a live driver — the shape
|
||||
/// <c>DriverInstanceActor.AdoptConfigDuringInit</c> produces when an ApplyDelta lands while the
|
||||
/// driver is Connecting/Reconnecting — must dispose the transport the previous initialize
|
||||
/// A second <c>InitializeAsync</c> on a live driver with no teardown between (the <c>IDriver</c>
|
||||
/// contract allows it) must dispose the transport the previous initialize
|
||||
/// published before publishing a new one. Pre-fix <c>_transport</c> was simply overwritten,
|
||||
/// stranding a connected transport no later dispose could reach.
|
||||
/// </summary>
|
||||
@@ -251,7 +250,7 @@ public sealed class ModbusLifecycleHygieneTests
|
||||
});
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
// No ShutdownAsync in between — exactly what AdoptConfigDuringInit does.
|
||||
// No ShutdownAsync in between.
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
handed.Count.ShouldBe(2, "each initialize must build its own transport");
|
||||
@@ -415,8 +414,8 @@ public sealed class ModbusLifecycleHygieneTests
|
||||
|
||||
/// <summary>
|
||||
/// Two initializes racing on the same driver — the shape <c>DriverInstanceActor</c> produces by
|
||||
/// dispatching initialize on a fire-and-forget <c>Task.Run</c> with <c>AdoptConfigDuringInit</c>
|
||||
/// firing twice — must serialize on the lifecycle gate. Ungated, the teardown at the top of one
|
||||
/// dispatching initialize on a fire-and-forget <c>Task.Run</c>, where a reconnect retry tick can
|
||||
/// overlap a still-running attempt — must serialize on the lifecycle gate. Ungated, the teardown at the top of one
|
||||
/// initialize disposes the transport the other just published (or nulls <c>_transport</c>
|
||||
/// mid-connect), leaving the driver holding a disposed transport.
|
||||
/// </summary>
|
||||
|
||||
+3
-4
@@ -28,10 +28,9 @@ public sealed class MqttDriverSessionLifecycleTests
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Reachable with no teardown of the driver's own: <c>DriverInstanceActor</c> re-runs
|
||||
/// <c>InitializeAsync</c> with no <c>ShutdownAsync</c> when an <c>ApplyDelta</c> arrives while
|
||||
/// the driver is Connecting/Reconnecting (<c>AdoptConfigDuringInit</c>). The
|
||||
/// <c>teardownFirst</c> flag on the #527 retry path covers only the retry path.
|
||||
/// The <c>IDriver</c> contract allows re-initialising a live driver with no
|
||||
/// <c>ShutdownAsync</c>, and the #527 retry path's <c>teardownFirst</c> flag does not stop a
|
||||
/// retry overlapping a still-running attempt, so the driver must not rely on a teardown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The assertion is the broker's <b>live socket count</b>, not the driver's own view: a leak
|
||||
|
||||
@@ -483,8 +483,8 @@ public sealed class S7DriverReconnectTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #630 — a second <c>InitializeAsync</c> on a live driver (no <c>ShutdownAsync</c> between, as
|
||||
/// <c>DriverInstanceActor.AdoptConfigDuringInit</c> does) must stop the first probe loop and
|
||||
/// #630 — a second <c>InitializeAsync</c> on a live driver (no <c>ShutdownAsync</c> between — the
|
||||
/// <c>IDriver</c> contract allows it) must stop the first probe loop and
|
||||
/// dispose the first connection rather than orphan them.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user