vxx2 / vlib / x / executor / executor.v
170 lines · 148 sloc · 3.35 KB · e80386b6af8a071a2e13fd654ebcdb2105fb9202
Raw
1module executor
2
3import sync
4
5// JobFn is the function signature executed by an Executor on the owner thread.
6pub type JobFn = fn () !
7
8// ExecutorConfig configures a bounded owner-thread executor.
9@[params]
10pub struct ExecutorConfig {
11pub:
12 queue_size int
13}
14
15struct ExecutorJobResult {
16mut:
17 err IError = none
18}
19
20struct 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]
32pub struct Executor {
33mut:
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.
60pub 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
75fn (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
86fn (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
93fn (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
102fn (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
110fn (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
121fn (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
128fn (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
134fn (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
141fn (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
164fn (mut e Executor) finish_job() {
165 e.mutex.lock()
166 if e.active > 0 {
167 e.active--
168 }
169 e.mutex.unlock()
170}
171