forked from microsoft/coyote
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFailoverDriver.cs
149 lines (130 loc) · 5.9 KB
/
FailoverDriver.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Threading.Tasks;
using Microsoft.Coyote.Actors;
using Microsoft.Coyote.Actors.Timers;
using Microsoft.Coyote.Samples.Common;
namespace Microsoft.Coyote.Samples.CoffeeMachineActors
{
/// <summary>
/// This class is designed to test how the CoffeeMachine handles "failover" or specifically, can it
/// correctly "restart after failure" without getting into a bad state. The CoffeeMachine will be
/// randomly terminated. The only thing the CoffeeMachine can depend on is the persistence of the
/// state provided by the MockSensors.
/// </summary>
internal class FailoverDriver : StateMachine
{
private ActorId WaterTankId;
private ActorId CoffeeGrinderId;
private ActorId DoorSensorId;
private ActorId CoffeeMachineId;
private bool RunForever;
private int Iterations;
private TimerInfo HaltTimer;
private readonly LogWriter Log = LogWriter.Instance;
internal class StartTestEvent : Event { }
[Start]
[OnEntry(nameof(OnInit))]
[OnEventGotoState(typeof(StartTestEvent), typeof(Test))]
internal class Init : State { }
internal void OnInit(Event e)
{
var evt = e as ConfigEvent;
this.RunForever = evt.RunSlowly;
// Create the persistent sensor state.
this.WaterTankId = this.CreateActor(typeof(MockWaterTank), new ConfigEvent(this.RunForever));
this.CoffeeGrinderId = this.CreateActor(typeof(MockCoffeeGrinder), new ConfigEvent(this.RunForever));
this.DoorSensorId = this.CreateActor(typeof(MockDoorSensor), new ConfigEvent(this.RunForever));
}
[OnEntry(nameof(OnStartTest))]
[OnEventDoAction(typeof(TimerElapsedEvent), nameof(HandleTimer))]
[OnEventGotoState(typeof(CoffeeMachine.CoffeeCompletedEvent), typeof(Stop))]
internal class Test : State { }
internal void OnStartTest()
{
this.Log.WriteLine("#################################################################");
this.Log.WriteLine("starting new CoffeeMachine.");
// Create a new CoffeeMachine instance
this.CoffeeMachineId = this.CreateActor(typeof(CoffeeMachine), new CoffeeMachine.ConfigEvent(this.WaterTankId,
this.CoffeeGrinderId, this.DoorSensorId, this.Id));
// Request a coffee!
var shots = this.RandomInteger(3) + 1;
this.SendEvent(this.CoffeeMachineId, new CoffeeMachine.MakeCoffeeEvent(shots));
// Setup a timer to randomly kill the coffee machine. When the timer fires we
// will restart the coffee machine and this is testing that the machine can
// recover gracefully when that happens.
this.HaltTimer = this.StartTimer(TimeSpan.FromSeconds(this.RandomInteger(7) + 1));
}
private void HandleTimer()
{
this.RaiseGotoStateEvent<Stop>();
}
internal void OnStopTest(Event e)
{
if (this.HaltTimer != null)
{
this.StopTimer(this.HaltTimer);
this.HaltTimer = null;
}
if (e is CoffeeMachine.CoffeeCompletedEvent ce)
{
if (ce.Error)
{
this.Log.WriteWarning("CoffeeMachine reported an error.");
this.Log.WriteWarning("Test is complete, press ENTER to continue...");
// No point trying to make more coffee.
this.RunForever = false;
}
else
{
this.Log.WriteLine("CoffeeMachine completed the job.");
}
this.RaiseGotoStateEvent<Stopped>();
}
else
{
// Halt the CoffeeMachine. HaltEvent is async and we must ensure the CoffeeMachine
// is really halted before we create a new one because MockSensors will get confused
// if two CoffeeMachines are running at the same time. So we've implemented a terminate
// handshake here. We send event to the CoffeeMachine to terminate, and it sends back
// a HaltedEvent when it really has been halted.
this.Log.WriteLine("forcing termination of CoffeeMachine.");
this.SendEvent(this.CoffeeMachineId, new CoffeeMachine.TerminateEvent());
}
}
[OnEntry(nameof(OnStopTest))]
[OnEventDoAction(typeof(CoffeeMachine.HaltedEvent), nameof(OnCoffeeMachineHalted))]
[IgnoreEvents(typeof(CoffeeMachine.CoffeeCompletedEvent))]
internal class Stop : State { }
internal void OnCoffeeMachineHalted()
{
// OK, the CoffeeMachine really is halted now, so we can go to the stopped state.
this.RaiseGotoStateEvent<Stopped>();
}
[OnEntry(nameof(OnStopped))]
internal class Stopped : State { }
private void OnStopped()
{
if (this.RunForever || this.Iterations == 0)
{
this.Iterations += 1;
// Run another CoffeeMachine instance!
this.RaiseGotoStateEvent<Test>();
}
else
{
// Test is done, halt the mock sensors.
this.SendEvent(this.DoorSensorId, HaltEvent.Instance);
this.SendEvent(this.WaterTankId, HaltEvent.Instance);
this.SendEvent(this.CoffeeGrinderId, HaltEvent.Instance);
this.RaiseHaltEvent();
}
}
protected override Task OnEventUnhandledAsync(Event e, string state)
{
this.Log.WriteLine("### Unhandled event {0} in state {1}", e.GetType().FullName, state);
return base.OnEventUnhandledAsync(e, state);
}
}
}