1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 This file contains functional *watchdog*s. 11 */ 12 module dextool.plugin.mutate.backend.watchdog; 13 14 import logger = std.experimental.logger; 15 16 version (unittest) { 17 import unit_threaded : shouldEqual, shouldBeFalse, shouldBeTrue; 18 } 19 20 import core.time : Duration; 21 22 @safe: 23 24 /** Watchdog that signal *timeout* after a static time. 25 */ 26 struct StaticTime(WatchT) { 27 private: 28 import std.datetime.stopwatch : StopWatch; 29 30 enum State { 31 // watchdog must be initialized before it can be used 32 initialize, 33 // waiting to be activated 34 waiting, 35 // it is activate 36 active, 37 // the timeout has occured 38 timeout, 39 // finished with no problem 40 done, 41 42 } 43 44 State st; 45 Duration timeout; 46 WatchT watch; 47 48 public: 49 this(Duration timeout) { 50 st = State.waiting; 51 this.timeout = timeout; 52 } 53 54 // Start the watchdog. 55 void start() { 56 import std.algorithm : among; 57 58 assert(st.among(State.waiting, State.done)); 59 st = State.active; 60 watch.start; 61 } 62 63 /// Stop the watchdog. 64 void stop() { 65 assert(st == State.active); 66 st = State.done; 67 watch.stop; 68 } 69 70 /// The timeout has not trigged. 71 bool isOk() { 72 if (watch.peek > timeout) { 73 st = State.timeout; 74 } 75 76 return st != State.timeout; 77 } 78 } 79 80 /** Watchdog that signal *timeout* after a static time. 81 * 82 * Progressive watchdog 83 */ 84 struct ProgressivWatchdog { 85 nothrow: 86 private: 87 static immutable double constant_factor = 1.5; 88 static immutable double scale_factor = 2.0; 89 90 Duration base_timeout; 91 double n = 0; 92 93 public: 94 this(Duration timeout) { 95 this.base_timeout = timeout; 96 } 97 98 void incrTimeout() { 99 ++n; 100 } 101 102 Duration timeout() { 103 import std.math : sqrt; 104 105 double scale = constant_factor + sqrt(n) * scale_factor; 106 return (1L + (cast(long)(base_timeout.total!"msecs" * scale))).dur!"msecs"; 107 } 108 } 109 110 private: 111 112 import core.time : dur; 113 114 version (unittest) { 115 struct FakeWatch { 116 Duration d; 117 void start() { 118 } 119 120 void stop() { 121 } 122 123 void reset() { 124 } 125 126 auto peek() { 127 return d; 128 } 129 } 130 } 131 132 @("shall signal timeout when the watch has passed the timeout") 133 unittest { 134 // arrange 135 auto wd = StaticTime!FakeWatch(10.dur!"seconds"); 136 137 // assert 138 wd.isOk.shouldBeTrue; 139 140 wd.start; 141 wd.isOk.shouldBeTrue; 142 143 // at the limit so should still be ok 144 wd.watch.d = 9.dur!"seconds"; 145 wd.isOk.shouldBeTrue; 146 147 // just past the limit for a ProgressivWatchdog so should trigger 148 wd.watch.d = 11.dur!"seconds"; 149 wd.isOk.shouldBeFalse; 150 } 151 152 @("shall increment the timeout") 153 unittest { 154 import unit_threaded; 155 156 auto pwd = ProgressivWatchdog(2.dur!"seconds"); 157 auto wd = StaticTime!FakeWatch(pwd.timeout); 158 159 wd.isOk.shouldBeTrue; 160 161 wd.start; 162 wd.isOk.shouldBeTrue; 163 164 // shall be not OK because the timer have just passed the timeout 165 wd.watch.d = 5.dur!"seconds"; 166 wd.isOk.shouldBeFalse; 167 168 // arrange 169 pwd.incrTimeout; 170 wd = StaticTime!FakeWatch(pwd.timeout); 171 172 // assert 173 wd.isOk.shouldBeTrue; 174 175 // shall be OK because it is just at the timeout 176 wd.watch.d = 6.dur!"seconds"; 177 wd.isOk.shouldBeTrue; 178 179 // shall trigger because it just passed the timeout 180 wd.watch.d = 8.dur!"seconds"; 181 wd.isOk.shouldBeFalse; 182 }