1 | // [Blend2D] |
2 | // 2D Vector Graphics Powered by a JIT Compiler. |
3 | // |
4 | // [License] |
5 | // Zlib - See LICENSE.md file in the package. |
6 | |
7 | #include "./blapi-build_p.h" |
8 | #include "./blruntime_p.h" |
9 | #include "./blsupport_p.h" |
10 | #include "./blthreading_p.h" |
11 | |
12 | #ifdef _WIN32 |
13 | #include <process.h> |
14 | #endif |
15 | |
16 | // ============================================================================ |
17 | // [Globals] |
18 | // ============================================================================ |
19 | |
20 | static BLThreadVirt blThreadVirt; |
21 | |
22 | // ============================================================================ |
23 | // [BLThreadEvent - Windows] |
24 | // ============================================================================ |
25 | |
26 | #ifdef _WIN32 |
27 | BLResult blThreadEventCreate(BLThreadEvent* self, bool manualReset, bool signaled) noexcept { |
28 | HANDLE h = CreateEventW(nullptr, manualReset, signaled, nullptr); |
29 | if (BL_UNLIKELY(!h)) { |
30 | self->handle = -1; |
31 | return blTraceError(blResultFromWinError(GetLastError())); |
32 | } |
33 | |
34 | self->handle = (intptr_t)h; |
35 | return BL_SUCCESS; |
36 | } |
37 | |
38 | BLResult blThreadEventDestroy(BLThreadEvent* self) noexcept { |
39 | if (BL_UNLIKELY(self->handle == -1)) |
40 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
41 | |
42 | CloseHandle((HANDLE)self->handle); |
43 | self->handle = -1; |
44 | return BL_SUCCESS; |
45 | } |
46 | |
47 | bool blThreadEventIsSignaled(const BLThreadEvent* self) noexcept { |
48 | if (BL_UNLIKELY(self->handle == -1)) |
49 | return false; |
50 | return WaitForSingleObject((HANDLE)self->handle, 0) == WAIT_OBJECT_0; |
51 | } |
52 | |
53 | BLResult blThreadEventSignal(BLThreadEvent* self) noexcept { |
54 | if (BL_UNLIKELY(self->handle == -1)) |
55 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
56 | |
57 | SetEvent((HANDLE)self->handle); |
58 | return BL_SUCCESS; |
59 | } |
60 | |
61 | BLResult blThreadEventReset(BLThreadEvent* self) noexcept { |
62 | if (BL_UNLIKELY(self->handle == -1)) |
63 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
64 | |
65 | ResetEvent((HANDLE)self->handle); |
66 | return BL_SUCCESS; |
67 | } |
68 | |
69 | BLResult blThreadEventWaitInternal(BLThreadEvent* self, DWORD dwMilliseconds) noexcept { |
70 | if (BL_UNLIKELY(self->handle == -1)) |
71 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
72 | |
73 | DWORD result = WaitForSingleObject((HANDLE)self->handle, dwMilliseconds); |
74 | switch (result) { |
75 | case WAIT_OBJECT_0: |
76 | return BL_SUCCESS; |
77 | |
78 | case WAIT_FAILED: |
79 | return blTraceError(blResultFromWinError(GetLastError())); |
80 | |
81 | case WAIT_TIMEOUT: |
82 | return blTraceError(BL_ERROR_TIMED_OUT); |
83 | |
84 | default: |
85 | return blTraceError(BL_ERROR_INVALID_STATE); |
86 | }; |
87 | } |
88 | |
89 | BLResult blThreadEventWait(BLThreadEvent* self) noexcept { |
90 | return blThreadEventWaitInternal(self, INFINITE); |
91 | } |
92 | |
93 | BLResult blThreadEventTimedWait(BLThreadEvent* self, uint64_t microseconds) noexcept { |
94 | uint32_t milliseconds = uint32_t(blMin<uint64_t>(microseconds / 1000u, INFINITE)); |
95 | return blThreadEventWaitInternal(self, milliseconds); |
96 | } |
97 | #endif |
98 | |
99 | // ============================================================================ |
100 | // [BLThreadEvent - Posix] |
101 | // ============================================================================ |
102 | |
103 | #ifndef _WIN32 |
104 | struct BLThreadEventPosixImpl { |
105 | BLConditionVariable cond; |
106 | BLMutex mutex; |
107 | uint32_t manualReset; |
108 | uint32_t signaled; |
109 | |
110 | BL_INLINE BLThreadEventPosixImpl(bool manualReset, bool signaled) noexcept |
111 | : cond(), |
112 | mutex(), |
113 | manualReset(manualReset), |
114 | signaled(signaled) {} |
115 | }; |
116 | |
117 | BLResult blThreadEventCreate(BLThreadEvent* self, bool manualReset, bool signaled) noexcept { |
118 | self->handle = -1; |
119 | void* p = malloc(sizeof(BLThreadEventPosixImpl)); |
120 | |
121 | if (!p) |
122 | return BL_ERROR_OUT_OF_MEMORY; |
123 | |
124 | BLThreadEventPosixImpl* impl = new(p) BLThreadEventPosixImpl(manualReset, signaled); |
125 | self->handle = (intptr_t)impl; |
126 | return BL_SUCCESS; |
127 | } |
128 | |
129 | BLResult blThreadEventDestroy(BLThreadEvent* self) noexcept { |
130 | if (self->handle == -1) |
131 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
132 | |
133 | BLThreadEventPosixImpl* impl = (BLThreadEventPosixImpl*)self->handle; |
134 | impl->~BLThreadEventPosixImpl(); |
135 | free(impl); |
136 | |
137 | self->handle = -1; |
138 | return BL_SUCCESS; |
139 | } |
140 | |
141 | bool blThreadEventIsSignaled(const BLThreadEvent* self) noexcept { |
142 | if (self->handle == -1) |
143 | return false; |
144 | |
145 | BLThreadEventPosixImpl* impl = (BLThreadEventPosixImpl*)self->handle; |
146 | BLMutexGuard guard(impl->mutex); |
147 | |
148 | return impl->signaled != 0; |
149 | } |
150 | |
151 | BLResult blThreadEventSignal(BLThreadEvent* self) noexcept { |
152 | if (self->handle == -1) |
153 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
154 | |
155 | BLThreadEventPosixImpl* impl = (BLThreadEventPosixImpl*)self->handle; |
156 | BLMutexGuard guard(impl->mutex); |
157 | |
158 | if (!impl->signaled) { |
159 | impl->signaled = true; |
160 | if (impl->manualReset) |
161 | impl->cond.broadcast(); |
162 | else |
163 | impl->cond.signal(); |
164 | } |
165 | |
166 | return BL_SUCCESS; |
167 | } |
168 | |
169 | BLResult blThreadEventReset(BLThreadEvent* self) noexcept { |
170 | if (self->handle == -1) |
171 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
172 | |
173 | BLThreadEventPosixImpl* impl = (BLThreadEventPosixImpl*)self->handle; |
174 | BLMutexGuard guard(impl->mutex); |
175 | |
176 | impl->signaled = false; |
177 | return BL_SUCCESS; |
178 | } |
179 | |
180 | BLResult blThreadEventWait(BLThreadEvent* self) noexcept { |
181 | if (self->handle == -1) |
182 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
183 | |
184 | BLThreadEventPosixImpl* impl = (BLThreadEventPosixImpl*)self->handle; |
185 | BLMutexGuard guard(impl->mutex); |
186 | |
187 | while (!impl->signaled) |
188 | impl->cond.wait(impl->mutex); |
189 | |
190 | if (!impl->manualReset) |
191 | impl->signaled = false; |
192 | |
193 | return BL_SUCCESS; |
194 | } |
195 | |
196 | BLResult blThreadEventTimedWait(BLThreadEvent* self, uint64_t microseconds) noexcept { |
197 | if (self->handle == -1) |
198 | return blTraceError(BL_ERROR_INVALID_HANDLE); |
199 | |
200 | struct timespec absTime; |
201 | blGetAbsTimeForWaitCondition(absTime, microseconds); |
202 | |
203 | BLThreadEventPosixImpl* impl = (BLThreadEventPosixImpl*)self->handle; |
204 | BLMutexGuard guard(impl->mutex); |
205 | |
206 | while (!impl->signaled) |
207 | if (impl->cond.timedWait(impl->mutex, &absTime) == BL_ERROR_TIMED_OUT) |
208 | return BL_ERROR_TIMED_OUT; |
209 | |
210 | if (!impl->manualReset) |
211 | impl->signaled = false; |
212 | |
213 | return BL_SUCCESS; |
214 | } |
215 | #endif |
216 | |
217 | // ============================================================================ |
218 | // [BLThread - Internal] |
219 | // ============================================================================ |
220 | |
221 | class BLInternalThread : public BLThread { |
222 | public: |
223 | #ifdef _WIN32 |
224 | intptr_t handle; |
225 | #else |
226 | pthread_t handle; |
227 | #endif |
228 | |
229 | BLThreadEvent event; |
230 | volatile uint32_t internalStatus; |
231 | volatile uint32_t reserved; |
232 | |
233 | BLThreadFunc workFunc; |
234 | BLThreadFunc doneFunc; |
235 | void* workData; |
236 | |
237 | BLThreadFunc exitFunc; |
238 | void* exitData; |
239 | |
240 | BL_INLINE BLInternalThread(BLThreadFunc exitFunc, void* exitData) noexcept |
241 | : BLThread { &blThreadVirt }, |
242 | handle {}, |
243 | event(true, false), |
244 | internalStatus(BL_THREAD_STATUS_IDLE), |
245 | reserved(0), |
246 | workFunc(nullptr), |
247 | doneFunc(nullptr), |
248 | workData(nullptr), |
249 | exitFunc(exitFunc), |
250 | exitData(exitData) {} |
251 | |
252 | BL_INLINE ~BLInternalThread() noexcept { |
253 | #if _WIN32 |
254 | // The handle MUST be closed. |
255 | if (handle != 0) |
256 | CloseHandle((HANDLE)handle); |
257 | #endif |
258 | } |
259 | }; |
260 | |
261 | static BLInternalThread* blThreadNew(BLThreadFunc exitFunc, void* exitData) noexcept { |
262 | BLInternalThread* self = static_cast<BLInternalThread*>(malloc(sizeof(BLInternalThread))); |
263 | if (BL_UNLIKELY(!self)) |
264 | return nullptr; |
265 | return new(self) BLInternalThread(exitFunc, exitData); |
266 | } |
267 | |
268 | static BLResult BL_CDECL blThreadDestroy(BLThread* self_) noexcept { |
269 | BLInternalThread* self = static_cast<BLInternalThread*>(self_); |
270 | BL_ASSERT(self != nullptr); |
271 | |
272 | self->~BLInternalThread(); |
273 | free(self); |
274 | |
275 | return BL_SUCCESS; |
276 | } |
277 | |
278 | static BL_INLINE void blThreadEntryPoint(BLInternalThread* thread) noexcept { |
279 | for (;;) { |
280 | // Wait for some work to do. |
281 | thread->event.wait(); |
282 | blAtomicThreadFence(std::memory_order_acquire); |
283 | |
284 | BLThreadFunc workFunc = thread->workFunc; |
285 | BLThreadFunc doneFunc = thread->doneFunc; |
286 | void* workData = thread->workData; |
287 | |
288 | thread->workFunc = nullptr; |
289 | thread->doneFunc = nullptr; |
290 | thread->workData = nullptr; |
291 | |
292 | // If the compare-exchange fails and the function was not provided it means that this thread is quitting. |
293 | uint32_t value = BL_THREAD_STATUS_IDLE; |
294 | if (!std::atomic_compare_exchange_strong((std::atomic<uint32_t>*)&thread->internalStatus, &value, uint32_t(BL_THREAD_STATUS_RUNNING)) && !workFunc) |
295 | break; |
296 | |
297 | // Reset the event - more work can be queued from now... |
298 | thread->event.reset(); |
299 | |
300 | // Run the task. |
301 | workFunc(thread, workData); |
302 | |
303 | // Again, if the compare-exchange fails it means we are quitting. |
304 | value = BL_THREAD_STATUS_RUNNING; |
305 | bool res = !std::atomic_compare_exchange_strong((std::atomic<uint32_t>*)&thread->internalStatus, &value, uint32_t(BL_THREAD_STATUS_IDLE)); |
306 | |
307 | if (doneFunc) |
308 | doneFunc(thread, workData); |
309 | |
310 | if (!res && value == BL_THREAD_STATUS_QUITTING) |
311 | break; |
312 | } |
313 | |
314 | thread->exitFunc(thread, thread->exitData); |
315 | } |
316 | |
317 | static uint32_t BL_CDECL blThreadStatus(const BLThread* self_) noexcept { |
318 | const BLInternalThread* self = static_cast<const BLInternalThread*>(self_); |
319 | return blAtomicFetch(&self->internalStatus); |
320 | } |
321 | |
322 | static BLResult BL_CDECL blThreadRun(BLThread* self_, BLThreadFunc workFunc, BLThreadFunc doneFunc, void* data) noexcept { |
323 | BLInternalThread* self = static_cast<BLInternalThread*>(self_); |
324 | if (self->event.isSignaled()) |
325 | return blTraceError(BL_ERROR_BUSY); |
326 | |
327 | blAtomicThreadFence(std::memory_order_release); |
328 | self->workFunc = workFunc; |
329 | self->doneFunc = doneFunc; |
330 | self->workData = data; |
331 | self->event.signal(); |
332 | |
333 | return BL_SUCCESS; |
334 | } |
335 | |
336 | static BLResult BL_CDECL blThreadQuit(BLThread* self_) noexcept { |
337 | BLInternalThread* self = static_cast<BLInternalThread*>(self_); |
338 | |
339 | std::atomic_store((std::atomic<uint32_t>*)&self->internalStatus, uint32_t(BL_THREAD_STATUS_QUITTING)); |
340 | self->event.signal(); |
341 | |
342 | return BL_SUCCESS; |
343 | } |
344 | |
345 | // ============================================================================ |
346 | // [BLThread - Windows] |
347 | // ============================================================================ |
348 | |
349 | #ifdef _WIN32 |
350 | static unsigned BL_STDCALL blThreadEntryPointWrapper(void* arg) { |
351 | BLInternalThread* thread = static_cast<BLInternalThread*>(arg); |
352 | blThreadEntryPoint(thread); |
353 | return 0; |
354 | } |
355 | |
356 | BLResult BL_CDECL blThreadCreate(BLThread** threadOut, const BLThreadAttributes* attributes, BLThreadFunc exitFunc, void* exitData) noexcept { |
357 | BLInternalThread* thread = blThreadNew(exitFunc, exitData); |
358 | if (BL_UNLIKELY(!thread)) |
359 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
360 | |
361 | BLResult result = BL_SUCCESS; |
362 | if (!thread->event.isInitialized()) { |
363 | result = blTraceError(BL_ERROR_OUT_OF_MEMORY); |
364 | } |
365 | else { |
366 | uint32_t flags = 0; |
367 | uint32_t stackSize = attributes->stackSize; |
368 | |
369 | if (stackSize > 0) |
370 | flags = STACK_SIZE_PARAM_IS_A_RESERVATION; |
371 | |
372 | HANDLE handle = (HANDLE)_beginthreadex(nullptr, stackSize, blThreadEntryPointWrapper, thread, flags, nullptr); |
373 | if (handle == (HANDLE)-1) |
374 | result = BL_ERROR_BUSY; |
375 | else |
376 | thread->handle = (intptr_t)handle; |
377 | } |
378 | |
379 | if (result == BL_SUCCESS) { |
380 | *threadOut = thread; |
381 | return BL_SUCCESS; |
382 | } |
383 | else { |
384 | thread->~BLInternalThread(); |
385 | free(thread); |
386 | |
387 | *threadOut = nullptr; |
388 | return result; |
389 | } |
390 | } |
391 | #endif |
392 | |
393 | // ============================================================================ |
394 | // [BLThread - Posix] |
395 | // ============================================================================ |
396 | |
397 | #ifndef _WIN32 |
398 | static void* blThreadEntryPointWrapper(void* arg) { |
399 | blThreadEntryPoint(static_cast<BLInternalThread*>(arg)); |
400 | return nullptr; |
401 | } |
402 | |
403 | BLResult BL_CDECL blThreadCreate(BLThread** threadOut, const BLThreadAttributes* attributes, BLThreadFunc exitFunc, void* exitData) noexcept { |
404 | pthread_attr_t ptAttr; |
405 | int err = pthread_attr_init(&ptAttr); |
406 | |
407 | if (err) |
408 | return blResultFromPosixError(err); |
409 | |
410 | BLResult result = blThreadSetPtAttributes(&ptAttr, attributes); |
411 | if (result == BL_SUCCESS) |
412 | result = blThreadCreatePt(threadOut, &ptAttr, exitFunc, exitData); |
413 | |
414 | err = pthread_attr_destroy(&ptAttr); |
415 | BL_ASSERT(err == 0); |
416 | BL_UNUSED(err); |
417 | |
418 | return result; |
419 | } |
420 | |
421 | BLResult blThreadCreatePt(BLThread** threadOut, const pthread_attr_t* ptAttr, BLThreadFunc exitFunc, void* exitData) noexcept { |
422 | BLInternalThread* thread = blThreadNew(exitFunc, exitData); |
423 | if (BL_UNLIKELY(!thread)) |
424 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
425 | |
426 | int err; |
427 | if (!thread->event.isInitialized()) |
428 | err = ENOMEM; |
429 | else |
430 | err = pthread_create(&thread->handle, ptAttr, blThreadEntryPointWrapper, thread); |
431 | |
432 | if (!err) { |
433 | *threadOut = thread; |
434 | return BL_SUCCESS; |
435 | } |
436 | else { |
437 | thread->~BLInternalThread(); |
438 | free(thread); |
439 | |
440 | *threadOut = nullptr; |
441 | return blResultFromPosixError(err); |
442 | } |
443 | } |
444 | |
445 | BLResult blThreadSetPtAttributes(pthread_attr_t* ptAttr, const BLThreadAttributes* src) noexcept { |
446 | pthread_attr_setdetachstate(ptAttr, PTHREAD_CREATE_DETACHED); |
447 | if (src->stackSize) { |
448 | int err = pthread_attr_setstacksize(ptAttr, src->stackSize); |
449 | if (err) |
450 | return blResultFromPosixError(err); |
451 | } |
452 | return BL_SUCCESS; |
453 | } |
454 | #endif |
455 | |
456 | // ============================================================================ |
457 | // [BLThreading - RuntimeInit] |
458 | // ============================================================================ |
459 | |
460 | void blThreadingRtInit(BLRuntimeContext* rt) noexcept { |
461 | BL_UNUSED(rt); |
462 | |
463 | // BLThread virtual table. |
464 | blThreadVirt.destroy = blThreadDestroy; |
465 | blThreadVirt.status = blThreadStatus; |
466 | blThreadVirt.run = blThreadRun; |
467 | blThreadVirt.quit = blThreadQuit; |
468 | } |
469 | |