| 1 | module executor |
| 2 | |
| 3 | import time |
| 4 | |
| 5 | fn test_run_drains_accepted_jobs_then_wait_returns() { |
| 6 | mut ex := new(queue_size: 4)! |
| 7 | ran := chan int{cap: 3} |
| 8 | result := chan string{cap: 1} |
| 9 | |
| 10 | for i in 0 .. 3 { |
| 11 | ex.try_post(fn [ran, i] () ! { |
| 12 | ran <- i |
| 13 | })! |
| 14 | } |
| 15 | |
| 16 | runner := spawn fn [mut ex, result] () { |
| 17 | ex.run() or { |
| 18 | result <- err.msg() |
| 19 | return |
| 20 | } |
| 21 | result <- 'ok' |
| 22 | }() |
| 23 | |
| 24 | assert wait_for_int(ran, 'first run job missing') == 0 |
| 25 | assert wait_for_int(ran, 'second run job missing') == 1 |
| 26 | assert wait_for_int(ran, 'third run job missing') == 2 |
| 27 | ex.stop() |
| 28 | assert wait_for_string(result, 'run did not return after stop') == 'ok' |
| 29 | ex.wait()! |
| 30 | runner.wait() |
| 31 | } |
| 32 | |
| 33 | fn test_stop_from_owner_callback_does_not_deadlock() { |
| 34 | mut ex := new(queue_size: 2)! |
| 35 | ran := chan bool{cap: 1} |
| 36 | result := chan string{cap: 1} |
| 37 | |
| 38 | ex.try_post(fn [mut ex, ran] () ! { |
| 39 | ex.stop() |
| 40 | ran <- true |
| 41 | })! |
| 42 | |
| 43 | runner := spawn fn [mut ex, result] () { |
| 44 | ex.run() or { |
| 45 | result <- err.msg() |
| 46 | return |
| 47 | } |
| 48 | result <- 'ok' |
| 49 | }() |
| 50 | |
| 51 | assert wait_for_bool(ran, 'owner callback did not run') |
| 52 | assert wait_for_string(result, 'run did not return after owner stop') == 'ok' |
| 53 | ex.wait()! |
| 54 | runner.wait() |
| 55 | } |
| 56 | |
| 57 | fn test_wait_from_owner_callback_after_stop_returns_error_without_consuming_wait() { |
| 58 | mut ex := new(queue_size: 2)! |
| 59 | wait_result := chan string{cap: 1} |
| 60 | run_result := chan string{cap: 1} |
| 61 | |
| 62 | ex.try_post(fn [mut ex, wait_result] () ! { |
| 63 | ex.stop() |
| 64 | ex.wait() or { |
| 65 | wait_result <- err.msg() |
| 66 | return |
| 67 | } |
| 68 | wait_result <- 'ok' |
| 69 | })! |
| 70 | |
| 71 | runner := spawn fn [mut ex, run_result] () { |
| 72 | ex.run() or { |
| 73 | run_result <- err.msg() |
| 74 | return |
| 75 | } |
| 76 | run_result <- 'ok' |
| 77 | }() |
| 78 | |
| 79 | assert wait_for_string(wait_result, 'owner wait did not return') == 'executor: wait cannot be called from executor owner thread' |
| 80 | assert wait_for_string(run_result, 'run did not return after owner stop') == 'ok' |
| 81 | ex.wait()! |
| 82 | runner.wait() |
| 83 | } |
| 84 | |
| 85 | fn test_wait_before_terminal_pump_returns_error() { |
| 86 | mut ex := new(queue_size: 1)! |
| 87 | ex.wait() or { |
| 88 | assert err.msg() == 'executor: wait requires a running pump or stopped executor' |
| 89 | ex.stop() |
| 90 | return |
| 91 | } |
| 92 | assert false, 'wait returned successfully before a terminal owner pump' |
| 93 | } |
| 94 | |
| 95 | fn test_wait_before_runner_run_does_not_consume_wait() { |
| 96 | mut ex := new(queue_size: 2)! |
| 97 | start_run := chan bool{cap: 1} |
| 98 | started := chan bool{cap: 1} |
| 99 | result := chan string{cap: 1} |
| 100 | |
| 101 | ex.try_post(fn [started] () ! { |
| 102 | started <- true |
| 103 | })! |
| 104 | |
| 105 | runner := spawn fn [mut ex, start_run, result] () { |
| 106 | _ := <-start_run |
| 107 | ex.run() or { |
| 108 | result <- err.msg() |
| 109 | return |
| 110 | } |
| 111 | result <- 'ok' |
| 112 | }() |
| 113 | |
| 114 | mut saw_precondition := false |
| 115 | ex.wait() or { |
| 116 | assert err.msg() == 'executor: wait requires a running pump or stopped executor' |
| 117 | saw_precondition = true |
| 118 | } |
| 119 | assert saw_precondition |
| 120 | |
| 121 | start_run <- true |
| 122 | assert wait_for_bool(started, 'runner did not start owner pump') |
| 123 | ex.stop() |
| 124 | assert wait_for_string(result, 'run did not return after stop') == 'ok' |
| 125 | ex.wait()! |
| 126 | mut saw_second_wait := false |
| 127 | ex.wait() or { |
| 128 | assert err.msg() == 'executor: wait was already called' |
| 129 | saw_second_wait = true |
| 130 | } |
| 131 | assert saw_second_wait |
| 132 | runner.wait() |
| 133 | } |
| 134 | |
| 135 | fn test_run_after_stop_closes_admission_and_second_run_is_stopped() { |
| 136 | mut ex := new(queue_size: 1)! |
| 137 | ex.stop() |
| 138 | |
| 139 | mut rejected_after_stop := false |
| 140 | ex.try_post(fn () ! {}) or { |
| 141 | assert err.msg() == 'executor: executor is closed' |
| 142 | rejected_after_stop = true |
| 143 | } |
| 144 | assert rejected_after_stop |
| 145 | |
| 146 | ex.run() or { |
| 147 | assert err.msg() == 'executor: executor is stopped' |
| 148 | return |
| 149 | } |
| 150 | assert false, 'run after stop returned successfully' |
| 151 | } |
| 152 | |
| 153 | fn test_second_wait_returns_error() { |
| 154 | mut ex := new(queue_size: 1)! |
| 155 | started := chan bool{cap: 1} |
| 156 | release := chan bool{cap: 1} |
| 157 | result := chan string{cap: 1} |
| 158 | |
| 159 | ex.try_post(fn [started, release] () ! { |
| 160 | started <- true |
| 161 | _ := <-release |
| 162 | })! |
| 163 | |
| 164 | runner := spawn fn [mut ex, result] () { |
| 165 | ex.run() or { |
| 166 | result <- err.msg() |
| 167 | return |
| 168 | } |
| 169 | result <- 'ok' |
| 170 | }() |
| 171 | |
| 172 | assert wait_for_bool(started, 'run did not start before wait test') |
| 173 | release <- true |
| 174 | ex.stop() |
| 175 | assert wait_for_string(result, 'run did not stop') == 'ok' |
| 176 | ex.wait()! |
| 177 | ex.wait() or { |
| 178 | assert err.msg() == 'executor: wait was already called' |
| 179 | runner.wait() |
| 180 | return |
| 181 | } |
| 182 | assert false, 'second wait returned successfully' |
| 183 | } |
| 184 | |
| 185 | fn test_second_concurrent_run_returns_error() { |
| 186 | mut ex := new(queue_size: 2)! |
| 187 | started := chan bool{cap: 1} |
| 188 | release := chan bool{cap: 1} |
| 189 | first_result := chan string{cap: 1} |
| 190 | second_result := chan string{cap: 1} |
| 191 | |
| 192 | ex.try_post(fn [started, release] () ! { |
| 193 | started <- true |
| 194 | _ := <-release |
| 195 | })! |
| 196 | |
| 197 | first := spawn fn [mut ex, first_result] () { |
| 198 | ex.run() or { |
| 199 | first_result <- err.msg() |
| 200 | return |
| 201 | } |
| 202 | first_result <- 'ok' |
| 203 | }() |
| 204 | assert wait_for_bool(started, 'first run did not start owner job') |
| 205 | |
| 206 | second := spawn fn [mut ex, second_result] () { |
| 207 | ex.run() or { |
| 208 | second_result <- err.msg() |
| 209 | return |
| 210 | } |
| 211 | second_result <- 'ok' |
| 212 | }() |
| 213 | |
| 214 | msg := wait_for_string(second_result, 'second run did not return') |
| 215 | assert msg == 'executor: owner pump is already running' |
| 216 | |
| 217 | release <- true |
| 218 | ex.stop() |
| 219 | assert wait_for_string(first_result, 'first run did not stop') == 'ok' |
| 220 | ex.wait()! |
| 221 | first.wait() |
| 222 | second.wait() |
| 223 | } |
| 224 | |
| 225 | fn wait_for_bool(signal chan bool, message string) bool { |
| 226 | select { |
| 227 | value := <-signal { |
| 228 | return value |
| 229 | } |
| 230 | 1 * time.second { |
| 231 | assert false, message |
| 232 | } |
| 233 | } |
| 234 | return false |
| 235 | } |
| 236 | |
| 237 | fn wait_for_int(signal chan int, message string) int { |
| 238 | select { |
| 239 | value := <-signal { |
| 240 | return value |
| 241 | } |
| 242 | 1 * time.second { |
| 243 | assert false, message |
| 244 | } |
| 245 | } |
| 246 | return 0 |
| 247 | } |
| 248 | |
| 249 | fn wait_for_string(signal chan string, message string) string { |
| 250 | select { |
| 251 | value := <-signal { |
| 252 | return value |
| 253 | } |
| 254 | 1 * time.second { |
| 255 | assert false, message |
| 256 | } |
| 257 | } |
| 258 | return '' |
| 259 | } |
| 260 | |