The idea is so that forker thread can catch and handle exceptions thrown in the forked (forkee) threads, to provide another control option for structured concurrency.
Đ: {
Đ| 1: method asyncRisk(n) {
Đ| 2: defer {
Đ| 3: ;-1<| 'async defer cleanup #' ++ n
Đ| 4: }
Đ| 5:
Đ| 6: throw '!AsyncFailure#' ++ n
Đ| 7:
Đ| 8: ;-1<| 'not a chance to see this'
Đ| 9: }
Đ| 10:
Đ| 11: {
Đ| 12: for i from range(3) do {
Đ| 13: go asyncRisk(i)
Đ| 14: }
Đ| 15: } $=> { # this exception handler will be triggered to run
Đ| 16: # in multiple forked threads (forkees), but its recover
Đ| 17: # continuation will only run by the thread installed it,
Đ| 18: # i.e. the forker thread.
Đ| 19: { exc } -> {
Đ| 20: ;-1<| 'handling ' ++ exc
Đ| 21: }
Đ| 22: }
Đ| 23:
Đ| 24: # too obviously, but it could really go wrong with,
Đ| 25: # a continuation based implementation
Đ| 26: ;-1<| 'this should run only once by forker thread'
Đ| 27:
Đ| 28: for _ from console.everyMillis(10) do { break }
Đ| 29:
Đ| 30: ;-1<| 'all done.'
Đ| 31: }
🐞 ThreadId 3 👉 <console>:26:4 ❗ this should run only once by forker thread
🐞 ThreadId 20 👉 <console>:20:8 ❗ handling !AsyncFailure#0
🐞 ThreadId 20 👉 <console>:3:8 ❗ async defer cleanup #0
🐞 ThreadId 21 👉 <console>:20:8 ❗ handling !AsyncFailure#1
🐞 ThreadId 21 👉 <console>:3:8 ❗ async defer cleanup #1
🐞 ThreadId 22 👉 <console>:20:8 ❗ handling !AsyncFailure#2
🐞 ThreadId 22 👉 <console>:3:8 ❗ async defer cleanup #2
🐞 ThreadId 3 👉 <console>:30:4 ❗ all done.
Đ:
Đ:
Đ: # Note:
Đ: # we use negative log level to trigger debug trace, so thread
Đ: # id is shown as well;
Đ: # and the minus sign (-) will parse as infix subtraction
Đ: # operator if following some expression, so we prefix it with
Đ: # a semicolon (;) to disambiguate;
Đ: # then `;-1<| 'xxx'` reads `trace "xxx"` with extra info
Đ: