| 1 | module executor |
| 2 | |
| 3 | import time |
| 4 | |
| 5 | fn test_run_sync_from_foreign_thread_waits_until_owner_executes_job() { |
| 6 | mut ex := new(queue_size: 4)! |
| 7 | sync_started := chan bool{cap: 1} |
| 8 | sync_ran := chan bool{cap: 1} |
| 9 | sync_done := chan bool{cap: 1} |
| 10 | run_result := chan string{cap: 1} |
| 11 | |
| 12 | runner := spawn fn [mut ex, run_result] () { |
| 13 | ex.run() or { |
| 14 | run_result <- err.msg() |
| 15 | return |
| 16 | } |
| 17 | run_result <- 'ok' |
| 18 | }() |
| 19 | |
| 20 | caller := spawn fn [mut ex, sync_started, sync_ran, sync_done] () { |
| 21 | sync_started <- true |
| 22 | ex.run_sync(fn [sync_ran] () ! { |
| 23 | sync_ran <- true |
| 24 | }) or { |
| 25 | sync_done <- false |
| 26 | return |
| 27 | } |
| 28 | sync_done <- true |
| 29 | }() |
| 30 | |
| 31 | assert wait_for_bool(sync_started, 'run_sync caller did not start') |
| 32 | assert wait_for_bool(sync_ran, 'run_sync job did not run on owner pump') |
| 33 | assert wait_for_bool(sync_done, 'run_sync caller did not finish') |
| 34 | ex.stop() |
| 35 | assert wait_for_string(run_result, 'run did not stop') == 'ok' |
| 36 | ex.wait()! |
| 37 | caller.wait() |
| 38 | runner.wait() |
| 39 | } |
| 40 | |
| 41 | fn test_run_sync_returns_job_error_to_foreign_caller() { |
| 42 | mut ex := new(queue_size: 2)! |
| 43 | run_result := chan string{cap: 1} |
| 44 | sync_result := chan string{cap: 1} |
| 45 | |
| 46 | runner := spawn fn [mut ex, run_result] () { |
| 47 | ex.run() or { |
| 48 | run_result <- err.msg() |
| 49 | return |
| 50 | } |
| 51 | run_result <- 'ok' |
| 52 | }() |
| 53 | |
| 54 | caller := spawn fn [mut ex, sync_result] () { |
| 55 | ex.run_sync(fn () ! { |
| 56 | return error('sync job failed') |
| 57 | }) or { |
| 58 | sync_result <- err.msg() |
| 59 | return |
| 60 | } |
| 61 | sync_result <- 'ok' |
| 62 | }() |
| 63 | |
| 64 | assert wait_for_string(sync_result, 'run_sync error was not returned') == 'sync job failed' |
| 65 | assert wait_for_string(run_result, 'run did not return after sync error') == 'sync job failed' |
| 66 | ex.wait() or { assert err.msg() == 'sync job failed' } |
| 67 | caller.wait() |
| 68 | runner.wait() |
| 69 | } |
| 70 | |
| 71 | fn test_run_sync_from_owner_runs_inline_without_deadlock() { |
| 72 | mut ex := new(queue_size: 4)! |
| 73 | events := chan int{cap: 4} |
| 74 | |
| 75 | ex.try_post(fn [mut ex, events] () ! { |
| 76 | events <- 1 |
| 77 | ex.run_sync(fn [events] () ! { |
| 78 | events <- 100 |
| 79 | })! |
| 80 | events <- 2 |
| 81 | })! |
| 82 | ex.try_post(fn [events] () ! { |
| 83 | events <- 3 |
| 84 | })! |
| 85 | |
| 86 | assert ex.drain_pending(2)! == 2 |
| 87 | assert wait_for_int(events, 'outer job did not start') == 1 |
| 88 | assert wait_for_int(events, 'inline run_sync job did not run') == 100 |
| 89 | assert wait_for_int(events, 'outer job did not finish') == 2 |
| 90 | assert wait_for_int(events, 'second queued job did not run') == 3 |
| 91 | |
| 92 | ex.stop() |
| 93 | assert !ex.run_one()! |
| 94 | ex.wait()! |
| 95 | } |
| 96 | |
| 97 | fn test_run_sync_owner_inline_error_caught_closes_admission_and_wait_returns_error() { |
| 98 | mut ex := new(queue_size: 4)! |
| 99 | events := chan int{cap: 3} |
| 100 | post_result := chan string{cap: 1} |
| 101 | run_result := chan string{cap: 1} |
| 102 | |
| 103 | ex.try_post(fn [mut ex, events, post_result] () ! { |
| 104 | events <- 1 |
| 105 | ex.run_sync(fn () ! { |
| 106 | return error('inline sync failed') |
| 107 | }) or { events <- 2 } |
| 108 | ex.try_post(fn () ! {}) or { |
| 109 | post_result <- err.msg() |
| 110 | return |
| 111 | } |
| 112 | post_result <- 'accepted' |
| 113 | ex.stop() |
| 114 | })! |
| 115 | ex.try_post(fn [events] () ! { |
| 116 | events <- 3 |
| 117 | })! |
| 118 | |
| 119 | runner := spawn fn [mut ex, run_result] () { |
| 120 | ex.run() or { |
| 121 | run_result <- err.msg() |
| 122 | return |
| 123 | } |
| 124 | run_result <- 'ok' |
| 125 | }() |
| 126 | |
| 127 | assert wait_for_int(events, 'outer job did not start') == 1 |
| 128 | assert wait_for_int(events, 'inline run_sync error was not caught') == 2 |
| 129 | assert wait_for_string(post_result, 'try_post after inline error did not return') == 'executor: executor is closed' |
| 130 | assert wait_for_int(events, 'accepted job after inline error was not drained') == 3 |
| 131 | assert wait_for_string(run_result, 'run did not return inline first error') == 'inline sync failed' |
| 132 | ex.wait() or { |
| 133 | assert err.msg() == 'inline sync failed' |
| 134 | runner.wait() |
| 135 | return |
| 136 | } |
| 137 | assert false, 'wait returned successfully after inline run_sync error' |
| 138 | } |
| 139 | |
| 140 | fn test_run_one_returns_owner_inline_run_sync_error_caught_by_outer_job() { |
| 141 | mut ex := new(queue_size: 4)! |
| 142 | events := chan int{cap: 2} |
| 143 | |
| 144 | ex.try_post(fn [mut ex, events] () ! { |
| 145 | ex.run_sync(fn () ! { |
| 146 | return error('inline sync failed') |
| 147 | }) or { events <- 1 } |
| 148 | })! |
| 149 | ex.try_post(fn [events] () ! { |
| 150 | events <- 2 |
| 151 | })! |
| 152 | |
| 153 | mut saw_inline_error := false |
| 154 | ex.run_one() or { |
| 155 | assert err.msg() == 'inline sync failed' |
| 156 | saw_inline_error = true |
| 157 | } |
| 158 | assert saw_inline_error |
| 159 | assert wait_for_int(events, 'outer job did not catch inline run_sync error') == 1 |
| 160 | |
| 161 | mut saw_closed := false |
| 162 | ex.try_post(fn () ! {}) or { |
| 163 | assert err.msg() == 'executor: executor is closed' |
| 164 | saw_closed = true |
| 165 | } |
| 166 | assert saw_closed |
| 167 | |
| 168 | ex.run_one() or { assert err.msg() == 'inline sync failed' } |
| 169 | assert wait_for_int(events, 'accepted job after inline error was not drained') == 2 |
| 170 | ex.wait() or { |
| 171 | assert err.msg() == 'inline sync failed' |
| 172 | return |
| 173 | } |
| 174 | assert false, 'wait returned successfully after inline run_sync error' |
| 175 | } |
| 176 | |
| 177 | fn test_drain_pending_limit_returns_owner_inline_run_sync_error_caught_by_outer_job() { |
| 178 | mut ex := new(queue_size: 4)! |
| 179 | events := chan int{cap: 2} |
| 180 | |
| 181 | ex.try_post(fn [mut ex, events] () ! { |
| 182 | ex.run_sync(fn () ! { |
| 183 | return error('inline sync failed') |
| 184 | }) or { events <- 1 } |
| 185 | })! |
| 186 | ex.try_post(fn [events] () ! { |
| 187 | events <- 2 |
| 188 | })! |
| 189 | |
| 190 | ex.drain_pending(1) or { assert err.msg() == 'inline sync failed' } |
| 191 | assert wait_for_int(events, 'outer job did not catch inline run_sync error') == 1 |
| 192 | |
| 193 | ex.drain_pending(8) or { assert err.msg() == 'inline sync failed' } |
| 194 | assert wait_for_int(events, 'accepted job after inline error was not drained') == 2 |
| 195 | ex.wait() or { |
| 196 | assert err.msg() == 'inline sync failed' |
| 197 | return |
| 198 | } |
| 199 | assert false, 'wait returned successfully after inline run_sync error' |
| 200 | } |
| 201 | |
| 202 | fn test_run_sync_from_owner_after_stop_returns_closed_without_running_nested_job() { |
| 203 | mut ex := new(queue_size: 2)! |
| 204 | result := chan string{cap: 1} |
| 205 | nested_ran := chan bool{cap: 1} |
| 206 | |
| 207 | ex.try_post(fn [mut ex, result, nested_ran] () ! { |
| 208 | ex.stop() |
| 209 | ex.run_sync(fn [nested_ran] () ! { |
| 210 | nested_ran <- true |
| 211 | }) or { |
| 212 | result <- err.msg() |
| 213 | return |
| 214 | } |
| 215 | result <- 'ok' |
| 216 | })! |
| 217 | |
| 218 | assert ex.run_one()! |
| 219 | assert wait_for_string(result, 'owner run_sync after stop did not return') == 'executor: executor is closed' |
| 220 | select { |
| 221 | _ := <-nested_ran { |
| 222 | assert false, 'nested run_sync job ran after stop' |
| 223 | } |
| 224 | else {} |
| 225 | } |
| 226 | ex.wait()! |
| 227 | } |
| 228 | |
| 229 | fn test_run_sync_from_owner_after_prior_job_error_is_refused_without_nested_execution() { |
| 230 | mut ex := new(queue_size: 3)! |
| 231 | result := chan string{cap: 1} |
| 232 | nested_ran := chan bool{cap: 1} |
| 233 | |
| 234 | ex.try_post(fn () ! { |
| 235 | return error('first job failed') |
| 236 | })! |
| 237 | ex.try_post(fn [mut ex, result, nested_ran] () ! { |
| 238 | ex.run_sync(fn [nested_ran] () ! { |
| 239 | nested_ran <- true |
| 240 | }) or { |
| 241 | result <- err.msg() |
| 242 | return |
| 243 | } |
| 244 | result <- 'ok' |
| 245 | })! |
| 246 | |
| 247 | mut run_result := 'ok' |
| 248 | ex.run() or { run_result = err.msg() } |
| 249 | assert run_result == 'first job failed' |
| 250 | assert wait_for_string(result, 'owner run_sync after first error did not return') == 'executor: executor is closed' |
| 251 | select { |
| 252 | _ := <-nested_ran { |
| 253 | assert false, 'nested run_sync job ran after first error' |
| 254 | } |
| 255 | else {} |
| 256 | } |
| 257 | ex.wait() or { |
| 258 | assert err.msg() == 'first job failed' |
| 259 | return |
| 260 | } |
| 261 | assert false, 'wait returned successfully after first job error' |
| 262 | } |
| 263 | |
| 264 | fn test_run_sync_after_stop_returns_error() { |
| 265 | mut ex := new(queue_size: 1)! |
| 266 | ex.stop() |
| 267 | ex.run_sync(fn () ! {}) or { |
| 268 | assert err.msg() == 'executor: executor is closed' |
| 269 | return |
| 270 | } |
| 271 | assert false, 'run_sync accepted after stop' |
| 272 | } |
| 273 | |
| 274 | fn test_run_sync_rejects_nil_job() { |
| 275 | mut ex := new(queue_size: 1)! |
| 276 | nil_job := unsafe { JobFn(nil) } |
| 277 | ex.run_sync(nil_job) or { |
| 278 | assert err.msg() == 'executor: job function is nil' |
| 279 | ex.stop() |
| 280 | return |
| 281 | } |
| 282 | assert false, 'run_sync accepted a nil job' |
| 283 | } |
| 284 | |
| 285 | fn wait_for_bool(signal chan bool, message string) bool { |
| 286 | select { |
| 287 | value := <-signal { |
| 288 | return value |
| 289 | } |
| 290 | 1 * time.second { |
| 291 | assert false, message |
| 292 | } |
| 293 | } |
| 294 | return false |
| 295 | } |
| 296 | |
| 297 | fn wait_for_int(signal chan int, message string) int { |
| 298 | select { |
| 299 | value := <-signal { |
| 300 | return value |
| 301 | } |
| 302 | 1 * time.second { |
| 303 | assert false, message |
| 304 | } |
| 305 | } |
| 306 | return 0 |
| 307 | } |
| 308 | |
| 309 | fn wait_for_string(signal chan string, message string) string { |
| 310 | select { |
| 311 | value := <-signal { |
| 312 | return value |
| 313 | } |
| 314 | 1 * time.second { |
| 315 | assert false, message |
| 316 | } |
| 317 | } |
| 318 | return '' |
| 319 | } |
| 320 | |