1 module tagion.testbench.actor.supervisor;
2 
3 import tagion.testbench.actor.util;
4 
5 // This test is disabled because the restart() functionality of the supervisor
6 // Was never used and removed, however other parts of the tests are still relevant like the taskfailure handling.
7 // So it needs to be updated
8 version (none)  :  // Default import list for bdd
9 import core.time;
10 import std.exception : assumeWontThrow;
11 import std.format : format;
12 import std.meta;
13 import std.stdio;
14 import std.typecons : Tuple;
15 import tagion.actor.actor;
16 import tagion.actor.exceptions : TaskFailure;
17 import tagion.basic.tagionexceptions : TagionException;
18 import tagion.behaviour;
19 import tagion.hibon.Document;
20 import tagion.testbench.tools.Environment;
21 import tagion.utils.pretend_safe_concurrency;
22 
23 enum feature = Feature(
24             "Actor supervisor test",
25             [
26         "This feature should check that when a child catches an exception is sends it up as a failure.",
27         "The supervisour has the abillity to decide whether or not to restart i depending on the exception."
28 ]);
29 
30 alias FeatureContext = Tuple!(
31         SupervisorWithFailingChild, "SupervisorWithFailingChild",
32         FeatureGroup*, "result"
33 );
34 
35 @safe
36 class Recoverable : TagionException {
37     this(immutable(char)[] msg, string file = __FILE__, size_t line = __LINE__) pure {
38         super(msg, file, line);
39     }
40 }
41 
42 @safe
43 class Fatal : TagionException {
44     this(immutable(char)[] msg, string file = __FILE__, size_t line = __LINE__) pure {
45         super(msg, file, line);
46     }
47 }
48 
49 /// Child Actor
50 @safe
51 struct SetUpForFailure {
52     void recoverable(Msg!"recoverable") {
53         writeln("oh nose");
54         throw new Recoverable("I am fail");
55     }
56 
57     void fatal(Msg!"fatal") {
58         writeln("oh noes");
59         throw new Fatal("I am big fail");
60     }
61 
62     void task() nothrow {
63         run(&recoverable, &fatal);
64     }
65 }
66 
67 alias ChildHandle = ActorHandle!SetUpForFailure;
68 
69 enum supervisor_task_name = "supervisor";
70 enum child_task_name = "childUno";
71 
72 alias reRecoverable = Msg!"reRecoverable";
73 alias reFatal = Msg!"reFatal";
74 
75 /// Supervisor Actor
76 @safe
77 struct SetUpForDisappointment {
78     //SetUpForFailure child;
79     static ChildHandle childHandle;
80 
81     void task() @safe nothrow {
82         childHandle = spawn!SetUpForFailure(child_task_name);
83         waitforChildren(Ctrl.ALIVE);
84         run(failHandler);
85     }
86 
87     // Override the default fail handler
88     auto failHandler = (TaskFailure tf) @trusted {
89         writefln("Received the taskfailure from overrid taskfail type: %s", typeid(tf.throwable));
90 
91         if (cast(Recoverable) tf.throwable !is null) {
92             writeln(typeof(tf.throwable).stringof);
93             writeln("This is Recoverable, just let it run");
94             sendOwner(reRecoverable());
95         }
96         else if (cast(Fatal) tf.throwable !is null) {
97             writeln(typeof(tf.throwable).stringof, tf.task_name, locate(tf.task_name));
98             childHandle = respawn(childHandle);
99             waitforChildren(Ctrl.ALIVE);
100             writefln("This is fatal, we need to restart %s", tf.task_name);
101             sendOwner(reFatal());
102         }
103         else if (cast(MessageMismatch) tf.throwable !is null) {
104             writeln("The actor does not handle this type of message");
105         }
106         else {
107             if (ownerTid !is Tid.init) {
108                 assumeWontThrow(ownerTid.prioritySend(tf));
109             }
110         }
111     };
112 }
113 
114 alias SupervisorHandle = ActorHandle!SetUpForDisappointment;
115 
116 @safe @Scenario("Supervisor with failing child",
117         [])
118 class SupervisorWithFailingChild {
119     SupervisorHandle supervisorHandle;
120     ChildHandle childHandle;
121 
122     @Given("a actor #super")
123     Document aActorSuper() {
124         supervisorHandle = spawn!SetUpForDisappointment(supervisor_task_name);
125         check(waitforChildren(Ctrl.ALIVE), "Supervisor is not alive");
126 
127         return result_ok;
128     }
129 
130     @When("the #super and the #child has started")
131     Document hasStarted() {
132         childHandle = handle!SetUpForFailure(child_task_name);
133         check(childHandle.tid !is Tid.init, "Child is not running");
134 
135         return result_ok;
136     }
137 
138     @Then("the #super should send a message to the #child which results in a fail")
139     Document aFail() {
140         childHandle.send(Msg!"fatal"());
141         return result_ok;
142     }
143 
144     @Then("the #super actor should catch the #child which failed")
145     Document whichFailed() {
146         writeln("Returned ", receiveOnly!reFatal);
147         return result_ok;
148     }
149 
150     @Then("the #super actor should stop #child and restart it")
151     Document restartIt() @trusted {
152         childHandle = handle!SetUpForFailure(child_task_name); // FIX: actor handle should be transparent
153         import core.thread, core.time;
154 
155         Thread.sleep(100.msecs);
156         check(childHandle.tid !is Tid.init, "Child thread is not running");
157         return result_ok;
158     }
159 
160     @Then("the #super should send a message to the #child which results in a different fail")
161     Document differentFail() {
162         childHandle.send(Msg!"recoverable"());
163         check(childHandle.tid !is Tid.init, "Child thread is not running");
164         return result_ok;
165     }
166 
167     @Then("the #super actor should let the #child keep running")
168     Document keepRunning() {
169         writeln("Returned ", receiveOnly!reRecoverable);
170         check(childHandle.tid !is Tid.init, "Child thread is not running");
171 
172         return result_ok;
173     }
174 
175     @Then("the #super should stop")
176     Document superShouldStop() {
177         supervisorHandle.send(Sig.STOP);
178         check(waitforChildren(Ctrl.END), "Supervisor did not end");
179         return result_ok;
180     }
181 
182 }