1 /**
2     POSIX Implementation for nulib.threading.internal.semaphore
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.semaphore;
12 import nulib.threading.internal.semaphore;
13 import nulib.posix.common;
14 import numem;
15 
16 version (OSX)
17     version = Darwin;
18 else version (iOS)
19     version = Darwin;
20 else version (TVOS)
21     version = Darwin;
22 else version (WatchOS)
23     version = Darwin;
24 else version (VisionOS)
25     version = Darwin;
26 
27 /**
28     Native implementation of a semaphore.
29 */
30 class PosixSemaphore : NativeSemaphore {
31 private:
32 @nogc:
33     version(Darwin) semaphore_t sem_;
34     else version(Posix) sem_t sem_;
35 
36 public:
37 
38     /// Destructor
39     ~this() nothrow {
40         version(Darwin) {
41             auto rc = semaphore_destroy(mach_task_self(), sem_);
42             assert(rc == KERN_SUCCESS, "Failed to destroy semaphore!");
43         } else {
44             auto rc = sem_destroy(&sem_);
45             assert(rc == 0, "Failed to destroy semaphore!");
46         }
47     }
48 
49     /**
50         Constructs a new Posix Semaphore.
51     */
52     this(uint count) nothrow {
53         version(Darwin) {
54             auto rc = semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, count);
55             assert(rc == KERN_SUCCESS, "Failed to create semaphore!");
56         } else {
57             auto rc = sem_init(&sem_, 0, count);
58             assert(rc == 0, "Failed to create semaphore!");
59         }
60     }
61 
62     /**
63         Signals the semaphore.
64 
65         Note:
66             Control is not transferred to the waiter.
67     */
68     override
69     void signal() {
70         version(Darwin) {
71             auto rc = semaphore_signal(sem_);
72             assert(rc == KERN_SUCCESS, "Failed to signal semaphore!");
73         } else {
74             auto rc = sem_post(&sem_);
75             assert(rc == 0, "Failed to signal semaphore!");
76         }
77     }
78 
79     /**
80         Suspends the thread until the semaphore is signaled,
81         or the timeout is reached.
82 
83         Params:
84             timeout =   Timeout in miliseconds to block the 
85                         calling thread before giving up.
86 
87         Returns:
88             $(D true) if the semaphore was signaled in time,
89             $(D false) otherwise.
90     */
91     override
92     bool await(ulong timeout = 0) {
93         version(Darwin) {
94             if (timeout == 0) {
95                 while(true) {
96                     auto rc = semaphore_wait(sem_);
97                     if (!rc)
98                         return true;
99                     
100                     if (rc == KERN_ABORTED && errno == EINTR)
101                         continue;
102                     
103                     assert(0, "Unable to wait for semaphore!");
104                     return false;
105                 }
106             }
107 
108             mach_timespec_t t;
109             t.tv_sec = cast(uint)(timeout / 1000);
110             t.tv_nsec = cast(uint)(timeout % 1000);
111             while(true) {
112                 auto rc = semaphore_timedwait(sem_, t);
113                 if (!rc)
114                     return true;
115 
116                 if (rc == KERN_OPERATION_TIMED_OUT)
117                     return false;
118                 
119                 if (rc == KERN_ABORTED && errno == EINTR)
120                     continue;
121                 
122                 assert(0, "Unable to wait for semaphore!");
123                 return false;
124             }
125         } else {
126             if (timeout == 0) {
127                 auto rc = sem_wait(&sem_);
128                 if (!rc)
129                     return true;
130                 
131                 assert(errno == EINTR, "Unable to wait for semaphore!");
132                 return false;
133             }
134 
135             timespec t;
136             cast(void)clock_gettime(0, &t);
137             t.secs += (timeout / 1000);
138             t.nsecs = (t.nsecs + timeout) % 1000;
139             while(true) {
140                 if (!sem_timedwait(&sem_, &t))
141                     return true;
142                 
143                 if (errno == ETIMEDOUT)
144                     return false;
145                     
146                 if (errno == EINTR)
147                     continue;
148 
149                 assert(0, "Unable to wait for semaphore!");
150                 return false;
151             }
152         }
153     }
154 
155     /**
156         Checks if the semaphore is signalled then
157         awaits on it if is.
158 
159         Returns:
160             $(D true) if the semaphore was signalled,
161             $(D false) otherwise.
162     */
163     override
164     bool tryAwait() {
165         version(Darwin) {
166             mach_timespec_t t;
167             t.tv_sec = 0;
168             t.tv_nsec = 0;
169             while(true) {
170                 auto rc = semaphore_timedwait(sem_, t);
171                 if (!rc)
172                     return true;
173 
174                 if (rc == KERN_OPERATION_TIMED_OUT)
175                     return false;
176                 
177                 if (rc == KERN_ABORTED && __error() == EINTR)
178                     continue;
179                 
180                 assert(0, "Unable to wait for semaphore!");
181                 return false;
182             }
183         } else {
184             while(true) {
185                 if (!sem_trywait(&sem_))
186                     return true;
187                 
188                 if (errno == EAGAIN)
189                     return false;
190                         
191                 if (errno == EINTR)
192                     continue;
193 
194                 assert(0, "Unable to wait for semaphore!");
195                 return false;
196             }
197         }
198     }
199 }
200 
201 
202 extern(C) export
203 NativeSemaphore _nu_semaphore_new(uint count) @trusted @nogc nothrow {
204     return nogc_new!PosixSemaphore(count);
205 }
206 
207 //
208 //          BINDINGS
209 //
210 extern(C) @nogc nothrow:
211 
212 version(Darwin) {
213     alias semaphore_t = uint;
214     alias task_t = uint;
215 
216     alias kern_return_t = int;
217     enum kern_return_t KERN_SUCCESS = 0;
218     enum SYNC_POLICY_FIFO           = 0x0;
219     enum KERN_ABORTED               = 14; // Aborted (Kernel)
220     enum KERN_OPERATION_TIMED_OUT   = 49; // Timed Out (Kernel)
221 
222     alias clock_res_t = int;
223     struct mach_timespec_t {
224         uint        tv_sec;
225         clock_res_t tv_nsec;
226     }
227 
228     extern task_t mach_task_self();
229     extern kern_return_t semaphore_create(task_t, semaphore_t*, int, int);
230     extern kern_return_t semaphore_destroy(task_t, semaphore_t);
231     extern kern_return_t semaphore_signal(semaphore_t);
232     extern kern_return_t semaphore_wait(semaphore_t);
233     extern kern_return_t semaphore_timedwait(semaphore_t, mach_timespec_t);
234 
235 } else version(Posix) {
236 
237     struct sem_t {
238         void[32] internal_;
239     }
240 
241     struct timespec {
242         long secs;
243         long nsecs;
244     }
245 
246     enum SEM_FAILED = cast(sem_t*) null;
247 
248     extern int sem_destroy(sem_t*);
249     extern int sem_init(sem_t*, int, uint);
250     extern int sem_post(sem_t*);
251     extern int sem_trywait(sem_t*);
252     extern int sem_timedwait(sem_t*, const scope timespec*);
253     extern int sem_wait(sem_t*);
254     extern int clock_gettime(int, const scope timespec*);
255 }