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 }