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 }