| 1 | module executor |
| 2 | |
| 3 | import sync |
| 4 | |
| 5 | // JobFn is the function signature executed by an Executor on the owner thread. |
| 6 | pub type JobFn = fn () ! |
| 7 | |
| 8 | // ExecutorConfig configures a bounded owner-thread executor. |
| 9 | @[params] |
| 10 | pub struct ExecutorConfig { |
| 11 | pub: |
| 12 | queue_size int |
| 13 | } |
| 14 | |
| 15 | struct ExecutorJobResult { |
| 16 | mut: |
| 17 | err IError = none |
| 18 | } |
| 19 | |
| 20 | struct ExecutorJob { |
| 21 | f JobFn @[required] |
| 22 | result_ch chan ExecutorJobResult |
| 23 | has_result bool |
| 24 | report_error bool |
| 25 | } |
| 26 | |
| 27 | // Executor serializes accepted jobs on whichever thread actively pumps it. |
| 28 | // |
| 29 | // The executor does not own a thread. The owner identity is recorded only while |
| 30 | // run(), run_one(), or drain_pending() is actively pumping callbacks. |
| 31 | @[heap] |
| 32 | pub struct Executor { |
| 33 | mut: |
| 34 | queue_size int |
| 35 | queue []ExecutorJob |
| 36 | active int |
| 37 | |
| 38 | mutex &sync.Mutex = sync.new_mutex() |
| 39 | job_ready chan int |
| 40 | submit_ready chan int |
| 41 | done chan int |
| 42 | |
| 43 | accepting bool = true |
| 44 | stopped bool |
| 45 | waited bool |
| 46 | |
| 47 | pumping bool |
| 48 | run_active bool |
| 49 | |
| 50 | owner_active bool |
| 51 | owner_thread_id u64 |
| 52 | |
| 53 | first_err IError = none |
| 54 | } |
| 55 | |
| 56 | // new creates an Executor with a fixed bounded queue. |
| 57 | // |
| 58 | // The owner thread is not captured at construction time. It is recorded only |
| 59 | // while an owner pump method is actively running. |
| 60 | pub fn new(config ExecutorConfig) !&Executor { |
| 61 | if config.queue_size <= 0 { |
| 62 | return error(err_queue_size_invalid) |
| 63 | } |
| 64 | return &Executor{ |
| 65 | queue_size: config.queue_size |
| 66 | queue: []ExecutorJob{cap: config.queue_size} |
| 67 | mutex: sync.new_mutex() |
| 68 | job_ready: chan int{cap: 1} |
| 69 | submit_ready: chan int{cap: 1} |
| 70 | done: chan int{cap: 1} |
| 71 | accepting: true |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | fn (mut e Executor) pop_job_locked() ?ExecutorJob { |
| 76 | if e.queue.len == 0 { |
| 77 | return none |
| 78 | } |
| 79 | job := e.queue[0] |
| 80 | e.queue.delete(0) |
| 81 | e.active++ |
| 82 | e.wake_submitters_locked() |
| 83 | return job |
| 84 | } |
| 85 | |
| 86 | fn (mut e Executor) wake_job_ready_locked() { |
| 87 | if !e.job_ready.closed { |
| 88 | e.job_ready.close() |
| 89 | } |
| 90 | e.job_ready = chan int{cap: 1} |
| 91 | } |
| 92 | |
| 93 | fn (mut e Executor) wake_submitters_locked() { |
| 94 | if !e.submit_ready.closed { |
| 95 | e.submit_ready.close() |
| 96 | } |
| 97 | if e.accepting { |
| 98 | e.submit_ready = chan int{cap: 1} |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | fn (mut e Executor) close_admission_locked() { |
| 103 | if e.accepting { |
| 104 | e.accepting = false |
| 105 | e.wake_submitters_locked() |
| 106 | e.wake_job_ready_locked() |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | fn (mut e Executor) finish_terminal_locked() IError { |
| 111 | if !e.stopped { |
| 112 | e.stopped = true |
| 113 | e.close_admission_locked() |
| 114 | if !e.done.closed { |
| 115 | e.done.close() |
| 116 | } |
| 117 | } |
| 118 | return e.first_err |
| 119 | } |
| 120 | |
| 121 | fn (mut e Executor) get_first_error() IError { |
| 122 | e.mutex.lock() |
| 123 | err := e.first_err |
| 124 | e.mutex.unlock() |
| 125 | return err |
| 126 | } |
| 127 | |
| 128 | fn (mut e Executor) set_first_error(err IError) { |
| 129 | e.mutex.lock() |
| 130 | e.set_first_error_locked(err) |
| 131 | e.mutex.unlock() |
| 132 | } |
| 133 | |
| 134 | fn (mut e Executor) set_first_error_locked(err IError) { |
| 135 | if e.first_err is none { |
| 136 | e.first_err = err |
| 137 | } |
| 138 | e.close_admission_locked() |
| 139 | } |
| 140 | |
| 141 | fn (mut e Executor) execute_job(job ExecutorJob) IError { |
| 142 | defer { |
| 143 | e.finish_job() |
| 144 | } |
| 145 | first_err_before := e.get_first_error() |
| 146 | had_first_err := first_err_before !is none |
| 147 | mut result := ExecutorJobResult{} |
| 148 | job.f() or { result.err = err } |
| 149 | if result.err is none && !had_first_err { |
| 150 | err := e.get_first_error() |
| 151 | if err !is none { |
| 152 | result.err = err |
| 153 | } |
| 154 | } |
| 155 | if result.err !is none && job.report_error { |
| 156 | e.set_first_error(result.err) |
| 157 | } |
| 158 | if job.has_result { |
| 159 | job.result_ch <- result |
| 160 | } |
| 161 | return result.err |
| 162 | } |
| 163 | |
| 164 | fn (mut e Executor) finish_job() { |
| 165 | e.mutex.lock() |
| 166 | if e.active > 0 { |
| 167 | e.active-- |
| 168 | } |
| 169 | e.mutex.unlock() |
| 170 | } |
| 171 | |