Linux-Wackeltests: Instanz endet erst auf die Stopp-Bitte, Lesetest schreibt bis genug gelesen ist (#722)
Drei Manager-Tests liessen den Prozess vor der Stopp-Bitte enden; unter Last sah der Aufsichtstakt das zuerst und meldete Crashed statt Stopped. Die FakeChannel bekommt OnStopRequest, die Tests beenden darin. Der Drain-Test bekommt 20 s statt 2 s Luft. AtomicFile schreibt, bis der Leser 20 Blicke hatte, statt fest 40 Mal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e42ec100b0
commit
753d633cf3
@@ -119,6 +119,9 @@ public sealed class AtomicFileTests : IDisposable
|
|||||||
|
|
||||||
AtomicFile.WriteAllText(path, klein);
|
AtomicFile.WriteAllText(path, klein);
|
||||||
|
|
||||||
|
var fehler = 0;
|
||||||
|
var gelesen = 0;
|
||||||
|
|
||||||
// Harte Zeitgrenze, damit ein Fehler im Schreiber den Test nicht hängen lässt.
|
// Harte Zeitgrenze, damit ein Fehler im Schreiber den Test nicht hängen lässt.
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||||
|
|
||||||
@@ -126,7 +129,9 @@ public sealed class AtomicFileTests : IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 40; i++)
|
// #722: Geschrieben wird, bis der Leser genug gesehen hat — nicht eine feste
|
||||||
|
// Zahl von Malen. Unter Parallellast kam der Leser sonst nur auf fuenf Blicke.
|
||||||
|
for (var i = 0; (i < 40 || Volatile.Read(ref gelesen) < 20) && !cts.IsCancellationRequested; i++)
|
||||||
{
|
{
|
||||||
AtomicFile.WriteAllText(path, i % 2 == 0 ? gross : klein);
|
AtomicFile.WriteAllText(path, i % 2 == 0 ? gross : klein);
|
||||||
await Task.Delay(1);
|
await Task.Delay(1);
|
||||||
@@ -138,9 +143,6 @@ public sealed class AtomicFileTests : IDisposable
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var fehler = 0;
|
|
||||||
var gelesen = 0;
|
|
||||||
|
|
||||||
var leser = Task.Run(async () =>
|
var leser = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
while (!cts.IsCancellationRequested)
|
while (!cts.IsCancellationRequested)
|
||||||
@@ -150,7 +152,7 @@ public sealed class AtomicFileTests : IDisposable
|
|||||||
// AtomicFile.ReadAllText, damit der Leser den Schreiber nicht
|
// AtomicFile.ReadAllText, damit der Leser den Schreiber nicht
|
||||||
// blockiert — genau dafür gibt es den Helfer.
|
// blockiert — genau dafür gibt es den Helfer.
|
||||||
var inhalt = AtomicFile.ReadAllText(path);
|
var inhalt = AtomicFile.ReadAllText(path);
|
||||||
gelesen++;
|
Interlocked.Increment(ref gelesen);
|
||||||
|
|
||||||
// Muss immer ein vollständiges JSON-Array sein.
|
// Muss immer ein vollständiges JSON-Array sein.
|
||||||
JsonSerializer.Deserialize<string[]>(inhalt);
|
JsonSerializer.Deserialize<string[]>(inhalt);
|
||||||
|
|||||||
@@ -172,9 +172,18 @@ internal sealed class FakeChannel : IInstanceChannel
|
|||||||
: Answer);
|
: Answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// #722: What the instance does when the request arrives — typically: end. A test that
|
||||||
|
/// ended the process before asking raced the supervisor's own check: under load the check
|
||||||
|
/// saw the process gone first and called it a crash, not a stop.
|
||||||
|
/// </summary>
|
||||||
|
public Action<string>? OnStopRequest { get; set; }
|
||||||
|
|
||||||
public Task<bool> RequestStopAsync(string instanceDirectory, CancellationToken ct = default)
|
public Task<bool> RequestStopAsync(string instanceDirectory, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
StopRequests++;
|
StopRequests++;
|
||||||
|
if (StopRequestArrives)
|
||||||
|
OnStopRequest?.Invoke(instanceDirectory);
|
||||||
return Task.FromResult(StopRequestArrives);
|
return Task.FromResult(StopRequestArrives);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ public sealed class InstanceRegistryTests : IAsyncDisposable
|
|||||||
register.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
register.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
||||||
|
|
||||||
// Die Instanz endet auf die Bitte hin.
|
// Die Instanz endet auf die Bitte hin.
|
||||||
_launcher.Last!.Exit(0);
|
var prozess = _launcher.Last!;
|
||||||
_kanal.Answer = null;
|
_kanal.OnStopRequest = _ => { _kanal.Answer = null; prozess.Exit(0); };
|
||||||
|
|
||||||
(await register.StopAsync(pfad)).ShouldBeTrue();
|
(await register.StopAsync(pfad)).ShouldBeTrue();
|
||||||
register.Instances.Single().Condition.ShouldBe(InstanceCondition.Stopped);
|
register.Instances.Single().Condition.ShouldBe(InstanceCondition.Stopped);
|
||||||
@@ -230,8 +230,8 @@ public sealed class InstanceRegistryTests : IAsyncDisposable
|
|||||||
var a = register.Create("Alpha");
|
var a = register.Create("Alpha");
|
||||||
await register.StartAsync(a);
|
await register.StartAsync(a);
|
||||||
|
|
||||||
_launcher.Last!.Exit(0);
|
var prozess = _launcher.Last!;
|
||||||
_kanal.Answer = null;
|
_kanal.OnStopRequest = _ => { _kanal.Answer = null; prozess.Exit(0); };
|
||||||
|
|
||||||
await register.StopAllAsync("Lizenz widerrufen");
|
await register.StopAllAsync("Lizenz widerrufen");
|
||||||
|
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ public sealed class InstanceSupervisorTests : IDisposable
|
|||||||
catch { /* Ein liegengebliebener Testordner faerbt keinen Lauf rot. */ }
|
catch { /* Ein liegengebliebener Testordner faerbt keinen Lauf rot. */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
private InstanceSupervisor Aufsicht()
|
private InstanceSupervisor Aufsicht(SupervisionOptions? options = null)
|
||||||
{
|
{
|
||||||
var supervisor = new InstanceSupervisor(
|
var supervisor = new InstanceSupervisor(
|
||||||
_dir, _launcher, _kanal, NullLogger.Instance, Options);
|
_dir, _launcher, _kanal, NullLogger.Instance, options ?? Options);
|
||||||
|
|
||||||
supervisor.Observed += _ereignisse.Add;
|
supervisor.Observed += _ereignisse.Add;
|
||||||
return supervisor;
|
return supervisor;
|
||||||
@@ -307,16 +307,18 @@ public sealed class InstanceSupervisorTests : IDisposable
|
|||||||
{
|
{
|
||||||
_kanal.Answer = Test;
|
_kanal.Answer = Test;
|
||||||
|
|
||||||
await using var aufsicht = Aufsicht();
|
// #722: Viel Luft bis MaxDrainWait — unter Parallellast kam die Verzoegerung von 700 ms
|
||||||
|
// sonst erst nach den 2 s an, und der Test sah einen Abschuss, wo keiner hingehoert.
|
||||||
|
await using var aufsicht = Aufsicht(Options with { MaxDrainWait = TimeSpan.FromSeconds(20) });
|
||||||
await aufsicht.StartAsync();
|
await aufsicht.StartAsync();
|
||||||
|
|
||||||
var prozess = _launcher.Last.ShouldNotBeNull();
|
var prozess = _launcher.Last.ShouldNotBeNull();
|
||||||
|
|
||||||
// Sie antwortet und meldet: Ich raeume auf. Laenger als StopGrace (300ms), aber
|
// Sie antwortet und meldet: Ich raeume auf. Laenger als StopGrace (300ms), aber
|
||||||
// kuerzer als MaxDrainWait (2s).
|
// kuerzer als MaxDrainWait.
|
||||||
_kanal.Answer = Test with { Phase = ShutdownPhase.Draining, OpenWork = 1 };
|
_kanal.Answer = Test with { Phase = ShutdownPhase.Draining, OpenWork = 1 };
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_kanal.OnStopRequest = _ => Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(700);
|
await Task.Delay(700);
|
||||||
_kanal.Answer = null;
|
_kanal.Answer = null;
|
||||||
|
|||||||
@@ -182,8 +182,8 @@ public sealed class ManagerCoreTests : IAsyncDisposable
|
|||||||
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
||||||
|
|
||||||
// Die Instanz endet auf die Bitte hin.
|
// Die Instanz endet auf die Bitte hin.
|
||||||
_launcher.Last!.Exit(0);
|
var prozess = _launcher.Last!;
|
||||||
_kanal.AnswerFor[Path.GetFullPath(pfad)] = null;
|
_kanal.OnStopRequest = _ => { _kanal.AnswerFor[Path.GetFullPath(pfad)] = null; prozess.Exit(0); };
|
||||||
|
|
||||||
await manager.HandleLicenseRevokedAsync("Widerrufen durch den Hersteller");
|
await manager.HandleLicenseRevokedAsync("Widerrufen durch den Hersteller");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user