1 /**
2     POSIX Implementation for nulib.threading.internal.thread
3 
4     Copyright:
5         Copyright © 2025, Kitsunebi Games
6         Copyright © 2025, Inochi2D Project
7     
8     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9     Authors:   Luna Nielsen
10 */
11 module nulib.posix.threading.thread;
12 import nulib.threading.internal.thread;
13 import nulib.posix.threading.semaphore;
14 import nulib.posix.pthread;
15 import numem;
16 
17 struct PosixThreadContext {
18     ThreadContext ctx;
19     PosixSemaphore semaphore;
20 }
21 
22 class PosixThread : NativeThread {
23 private:
24 @nogc:
25     uint runRequests_;
26     shared(PosixThreadContext) ctx_;
27     pthread_t handle_;
28 
29 public:
30 
31     ~this() nothrow {
32         try {
33             PosixSemaphore sem = cast(PosixSemaphore)nu_atomic_load_ptr(cast(void**)&ctx_.semaphore);
34             if (sem) {
35                 nogc_delete(sem);
36                 nu_atomic_store_ptr(cast(void**)&ctx_.semaphore, null);
37             }
38         } catch (Exception ex) {
39             // Let it leak.
40         }
41     }
42     
43     this(ThreadContext ctx) nothrow {
44         this.ctx_ = cast(shared(PosixThreadContext))PosixThreadContext(
45             ctx: ctx,
46             semaphore: nogc_new!PosixSemaphore(0),
47         );
48     }
49 
50     /**
51         ID of the thread.
52     */
53     override
54     @property ThreadId tid() @trusted => cast(ThreadId)cast(void*)handle_;
55 
56     /**
57         Whether the thread is currently running.
58     */
59     override
60     @property bool isRunning() @trusted => nu_atomic_load_32(runRequests_) > 0;
61 
62     /**
63         Starts the given thread.
64     */
65     override
66     void start() @trusted {
67         if (nu_atomic_load_32(runRequests_) == 0) {
68             nu_atomic_add_32(runRequests_, 1);
69 
70             pthread_attr_t pattr = void;
71             int err = pthread_attr_init(&pattr);
72             assert(err == 0, "Failed to create thread!");
73             pthread_create(&handle_, &pattr, &_nu_thread_posix_entry, cast(void*)&ctx_);
74             pthread_attr_destroy(&pattr);
75         }
76     }
77 
78     /**
79         Forcefully cancels the thread, stopping execution.
80 
81         This is not a safe operation, as it may lead to memory
82         leaks and corrupt state. Only use when ABSOLUTELY neccesary.
83     */
84     override
85     void cancel() @system {
86         if (nu_atomic_load_32(runRequests_) > 0) {
87             if (nu_atomic_sub_32(runRequests_, 1) == 1) {
88                 pthread_cancel(handle_);
89                 this.join(0, false);
90             }
91         }
92     }
93 
94     /**
95         Waits for the thread to finish execution.
96     
97         Params:
98             timeout = How long to wait for the thread to exit.
99             rethrow = Whether execptions thrown in the thread should be rethrown.
100     */
101     override
102     bool join(uint timeout, bool rethrow) @trusted {
103         if (!isRunning)
104             return true;
105         
106         void* retv;
107         if (timeout == 0) {
108             if (pthread_join(handle_, &retv) == 0) {
109                 if (rethrow && ctx_.ctx.ex)
110                     throw cast(Exception)ctx_.ctx.ex;
111 
112                 return true;
113             }
114             return false;
115         }
116 
117         PosixSemaphore sem = cast(PosixSemaphore)nu_atomic_load_ptr(cast(void**)&ctx_.semaphore);
118         if (sem && sem.await(timeout)) {
119             if (pthread_join(handle_, &retv) == 0) {
120                 if (rethrow && ctx_.ctx.ex)
121                     throw cast(Exception)ctx_.ctx.ex;
122 
123                 return true;
124             }
125             return false;
126         }
127         return false;
128     }
129 }
130 
131 extern(C) export
132 NativeThread _nu_thread_new(ThreadContext ctx) @trusted @nogc nothrow {
133     return nogc_new!PosixThread(ctx);
134 }
135 
136 extern(C) export
137 ThreadId _nu_thread_current_tid() @trusted @nogc nothrow {
138     return cast(ThreadId)cast(void*)pthread_self();
139 }
140 
141 extern(C) export
142 void _nu_thread_sleep(uint ms) @trusted @nogc nothrow {
143     cast(void)usleep(cast(int)ms);
144 }
145 
146 //
147 //          BINDINGS
148 //
149 extern(C) @nogc nothrow:
150 
151 alias fp2_t = extern(D) void function(void* userData) @nogc;
152 
153 void* _nu_thread_posix_entry(void* threadContext) @trusted nothrow @nogc {
154     PosixThreadContext* context = cast(PosixThreadContext*)(threadContext);
155 
156     // Signal semaphore if it's still alive.
157     PosixSemaphore sem = cast(PosixSemaphore)nu_atomic_load_ptr(cast(void**)&context.semaphore);
158     try {
159         (cast(fp2_t)context.ctx.callback)(context.ctx.userData);
160         if (sem)
161             sem.signal();
162     } catch(Exception ex) {
163         context.ctx.ex = ex;
164     }
165     return null;
166 }
167 
168 extern int usleep(int) @trusted @nogc nothrow;