1 /* $Id: nhextnb.c,v 1.1 2003/10/25 18:06:01 j_ali Exp $ */
2 /* Copyright (c) Slash'EM Development Team 2003 */
3 /* NetHack may be freely redistributed. See license for details. */
4
5 /* NhExt: Non-blocking support using threads & mutexes */
6
7 /* #define DEBUG */
8
9 #include <stdlib.h>
10 #ifdef DEBUG
11 #include <stdio.h>
12 #endif
13 #include "nhxdr.h"
14
15 #ifdef WIN32
16 #include <windows.h>
17 #include <process.h>
18
check_res(int res)19 static int check_res(int res)
20 {
21 return res == WAIT_ABANDONED || res == WAIT_OBJECT_0 ? 1 :
22 res == WAIT_TIMEOUT ? 0 : -1;
23 }
24 #define DEFINE_LOCK(mutex) HANDLE mutex
25 #define INIT_LOCK(mutex) (mutex = CreateMutex(NULL, FALSE, NULL))
26 #define AQUIRE_LOCK_(mutex) check_res(WaitForSingleObject(mutex, INFINITE))
27 #define TRY_LOCK_(mutex) check_res(WaitForSingleObject(mutex, 0))
28 #define RELEASE_LOCK_(mutex) ReleaseMutex(mutex)
29 #define FREE_LOCK(mutex) CloseHandle(mutex)
30 #else /* WIN32 */
31 #include <pthread.h>
32 #include <unistd.h>
33 #include <errno.h>
34
35 #define DEFINE_LOCK(mutex) pthread_mutex_t mutex
36 #define INIT_LOCK(mutex) (!pthread_mutex_init(&(mutex), NULL))
37 #define AQUIRE_LOCK_(mutex) (!pthread_mutex_lock(&(mutex)))
38 #define TRY_LOCK_(mutex) (!pthread_mutex_trylock(&(mutex)))
39 #define RELEASE_LOCK_(mutex) (!pthread_mutex_unlock(&(mutex)))
40 #define FREE_LOCK(mutex) (!pthread_mutex_destroy(&(mutex)))
41 #endif
42
43 #ifdef DEBUG
44 #ifdef WIN32
45 #define debug_line(str) fprintf(stderr, "[%X] %s\n", \
46 GetCurrentThreadId(), str)
47 #else
48 #define debug_line(str) fprintf(stderr, "%s\n", str)
49 #endif
debug_line_res(int res,const char * mutex,const char * verb)50 static int debug_line_res(int res, const char *mutex, const char *verb)
51 {
52 char buf[100];
53 if (res == 1)
54 sprintf(buf, "Lock %s %sd", mutex, verb);
55 else if (res == 0)
56 sprintf(buf, "Lock %s not %sd", mutex, verb);
57 else {
58 sprintf(buf, "%s of lock %s produced error", verb, mutex);
59 if (buf[0] >= 'a' && buf[0] <= 'z') {
60 buf[0] -= 'a';
61 buf[0] += 'A';
62 }
63 }
64 debug_line(buf);
65 return res;
66 }
67 #define AQUIRE_LOCK(mutex) (debug_line("Aquiring lock " #mutex), \
68 debug_line_res(AQUIRE_LOCK_(mutex), \
69 #mutex, "aquire"))
70 #define TRY_LOCK(mutex) debug_line_res(TRY_LOCK_(mutex), \
71 #mutex, "aquire")
72 #define RELEASE_LOCK(mutex) debug_line_res(RELEASE_LOCK_(mutex), \
73 #mutex, "release")
74 #else
75 #define AQUIRE_LOCK(mutex) AQUIRE_LOCK_(mutex)
76 #define TRY_LOCK(mutex) TRY_LOCK_(mutex)
77 #define RELEASE_LOCK(mutex) RELEASE_LOCK_(mutex)
78 #endif
79
80 #define NHEXT_NB_PENDING 1
81 #define NHEXT_NB_CLOSED 2
82 #define NHEXT_NB_ERROR 4
83
84 struct NhExtNB_ {
85 unsigned int flags;
86 nhext_io_func func;
87 void *handle;
88 #ifdef WIN32
89 HANDLE thread;
90 #else
91 pthread_t thread;
92 #endif
93 DEFINE_LOCK(m_A);
94 DEFINE_LOCK(m_B);
95 DEFINE_LOCK(m_C);
96 struct {
97 void *buffer;
98 int bytes;
99 } cmd;
100 int res;
101 };
102
103 #ifdef WIN32
read_thread(void * data)104 static unsigned __stdcall read_thread(void *data)
105 {
106 NhExtNB *nb = (NhExtNB *)data;
107 #else
108 static void *read_thread(void *data)
109 {
110 NhExtNB *nb = (NhExtNB *)data;
111 #endif
112 void *buffer = data; /* Any non-zero value */
113 int bytes;
114 #ifdef DEBUG
115 debug_line("read_thread starts");
116 #endif
117 if (AQUIRE_LOCK(nb->m_B)) {
118 for(;;) {
119 if (!AQUIRE_LOCK(nb->m_C))
120 break;
121 buffer = nb->cmd.buffer;
122 bytes = nb->cmd.bytes;
123 if (!RELEASE_LOCK(nb->m_B))
124 break;
125 if (!AQUIRE_LOCK(nb->m_A))
126 break;
127 if (!RELEASE_LOCK(nb->m_C))
128 break;
129 if (!buffer)
130 break;
131 if (!AQUIRE_LOCK(nb->m_B))
132 break;
133 #ifdef DEBUG
134 debug_line("Issuing read call");
135 #endif
136 nb->res = (*nb->func)(nb->handle, buffer, bytes);
137 #ifdef DEBUG
138 debug_line("Read call returns");
139 #endif
140 if (!RELEASE_LOCK(nb->m_A))
141 break;
142 }
143 }
144 (void)RELEASE_LOCK(nb->m_A);
145 if (!buffer) {
146 /* Controlled exit - we're responsible for cleaning up */
147 (void)FREE_LOCK(nb->m_A);
148 (void)FREE_LOCK(nb->m_B);
149 (void)FREE_LOCK(nb->m_C);
150 #ifdef WIN32
151 CloseHandle(nb->thread);
152 #endif
153 #ifdef DEBUG
154 debug_line("read_thread terminates");
155 #endif
156 free(nb);
157 return 0;
158 } else {
159 (void)RELEASE_LOCK(nb->m_B);
160 (void)RELEASE_LOCK(nb->m_C);
161 #ifdef DEBUG
162 debug_line("read_thread aborts");
163 #endif
164 #ifdef WIN32
165 return 1;
166 #else
167 return (void *)1;
168 #endif
169 }
170 }
171
172 /*
173 * Sequence of events for a non-blocking read:
174 *
175 * Read thread Main thread
176 * ----------- -----------
177 * B C Waiting for cmd AC - nhext_nb_read called
178 * B C Waiting for cmd AC - Writes cmd
179 * BC - Copying cmd A B Issues cmd
180 * C A AB -
181 * AC - B C Waiting for cmd to be actioned
182 * A B Ready to action cmd BC -
183 * AB - In underlying system C - Returns to caller
184 * AB - In underlying system C A nhext_nb_read re-called
185 * B - Result recorded AC - Result available to caller
186 *
187 * (The first column for each thread is the locks currently aquired. The second
188 * column is the locks currently being waited for.)
189 *
190 * Sequence of events for a blocking read:
191 *
192 * Read thread Main thread
193 * ----------- -----------
194 * B C Waiting for cmd AC - nhext_nb_read called
195 * B C Waiting for cmd AC - Writes cmd
196 * BC - Copying cmd A B Issues cmd
197 * C A AB -
198 * AC - B C Waiting for cmd to be actioned
199 * A B Ready to action cmd BC -
200 * AB - In underlying system C A Waiting for results
201 * B - Result recorded AC - Returns to caller
202 *
203 * The mutexes also protect certain fields as follows:
204 * A - res
205 * B -
206 * C - cmd
207 */
208
209 NhExtNB *nhext_nb_open(nhext_io_func func, void *handle)
210 {
211 NhExtNB *nb;
212 int retval;
213 nb = malloc(sizeof(*nb));
214 if (!nb) {
215 #ifdef DEBUG
216 debug_line("nhext_nb_open failing (not enough memory)");
217 #endif
218 return NULL;
219 }
220 nb->func = func;
221 nb->handle = handle;
222 if (!INIT_LOCK(nb->m_A)) {
223 free(nb);
224 #ifdef DEBUG
225 debug_line("nhext_nb_open failing (can't init lock A)");
226 #endif
227 return NULL;
228 }
229 if (!INIT_LOCK(nb->m_B)) {
230 FREE_LOCK(nb->m_A);
231 #ifdef DEBUG
232 debug_line("nhext_nb_open failing (can't init lock B)");
233 #endif
234 free(nb);
235 return NULL;
236 }
237 if (!INIT_LOCK(nb->m_C)) {
238 FREE_LOCK(nb->m_A);
239 FREE_LOCK(nb->m_B);
240 #ifdef DEBUG
241 debug_line("nhext_nb_open failing (can't init lock C)");
242 #endif
243 free(nb);
244 return NULL;
245 }
246 if (!AQUIRE_LOCK(nb->m_A) || !AQUIRE_LOCK(nb->m_C)) {
247 #ifdef DEBUG
248 debug_line("nhext_nb_open failing (can't aquire locks A & C)");
249 #endif
250 goto out;
251 }
252 nb->flags = 0;
253 #ifdef WIN32
254 nb->thread = (HANDLE)_beginthreadex(NULL, 0, read_thread, nb, 0, NULL);
255 if (!nb->thread) {
256 #else
257 if (pthread_create(&nb->thread, NULL, read_thread, nb) == EAGAIN) {
258 #endif
259 #ifdef DEBUG
260 debug_line("nhext_nb_open failing (can't create read thread)");
261 #endif
262 out: RELEASE_LOCK(nb->m_A);
263 RELEASE_LOCK(nb->m_C);
264 FREE_LOCK(nb->m_A);
265 FREE_LOCK(nb->m_B);
266 FREE_LOCK(nb->m_C);
267 free(nb);
268 return NULL;
269 }
270 /*
271 * We must wait for the read thread to start and aquire the B mutex
272 * or the synchronization will fail.
273 */
274 while ((retval = TRY_LOCK(nb->m_B)) > 0) {
275 if (!RELEASE_LOCK(nb->m_B)) {
276 #ifdef WIN32
277 TerminateThread(nb->thread, -1);
278 CloseHandle(nb->thread);
279 #else
280 pthread_cancel(nb->thread);
281 #endif
282 #ifdef DEBUG
283 debug_line("nhext_nb_open failing (can't release lock B)");
284 #endif
285 goto out;
286 }
287 #ifdef WIN32
288 Sleep(0); /* Relinquish time slice */
289 #else
290 # ifdef _POSIX_PRIORITY_SCHEDULING
291 sched_yield(); /* Relinquish time slice */
292 # else
293 sleep(0);
294 # endif
295 #endif
296 }
297 if (retval < 0) {
298 nhext_nb_close(nb);
299 #ifdef DEBUG
300 debug_line("nhext_nb_open failing (error in trying to aquire lock B)");
301 #endif
302 return NULL;
303 }
304 return nb;
305 }
306
307 int nhext_nb_close(NhExtNB *nb)
308 {
309 int retval;
310 if (nb->flags & NHEXT_NB_CLOSED)
311 return -1;
312 nb->flags |= NHEXT_NB_CLOSED;
313 /*
314 * Mutex B should always be owned by the read thread unless we have aborted
315 * in the middle of a call to nhext_nb_read.
316 */
317 retval = TRY_LOCK(nb->m_B);
318 if (retval == 1) {
319 #ifdef WIN32
320 DWORD code;
321 if (GetExitCodeThread(nb->thread, &code) == STILL_ACTIVE) {
322 /*
323 * Something has gone drastically wrong. Clean up as best we can.
324 */
325 TerminateThread(nb->thread, 1);
326 code = 1;
327 }
328 #else
329 void *code;
330 if (pthread_join(nb->thread, &code)) {
331 /*
332 * Something has gone drastically wrong. Clean up as best we can.
333 */
334 pthread_cancel(nb->thread);
335 code = (void *)1;
336 }
337 #endif
338 if (code) {
339 /* Read thread has aborted. Clean up */
340 #ifdef WIN32
341 CloseHandle(nb->thread);
342 #endif
343 RELEASE_LOCK(nb->m_A);
344 RELEASE_LOCK(nb->m_B);
345 RELEASE_LOCK(nb->m_C);
346 FREE_LOCK(nb->m_A);
347 FREE_LOCK(nb->m_B);
348 FREE_LOCK(nb->m_C);
349 free(nb);
350 } else {
351 /* read thread has terminated and cleaned up (shouldn't happen) */
352 RELEASE_LOCK(nb->m_B);
353 }
354 return -1;
355 }
356 nb->cmd.buffer = NULL;
357 nb->cmd.bytes = 0;
358 if (!(nb->flags & NHEXT_NB_PENDING))
359 RELEASE_LOCK(nb->m_A);
360 RELEASE_LOCK(nb->m_C);
361 /* The read thread is responsible for cleaning up - if a read is pending
362 * this will be after it finishes.
363 */
364 return 0;
365 }
366
367 int nhext_nb_read(NhExtNB *nb, char *buf, int bytes, int blocking)
368 {
369 int retval;
370 #ifdef DEBUG
371 debug_line("nhext_nb_read called");
372 #endif
373 if (nb->flags & NHEXT_NB_ERROR)
374 return -1;
375 if (!(nb->flags & NHEXT_NB_PENDING)) {
376 nb->cmd.buffer = buf;
377 nb->cmd.bytes = bytes;
378 if (!RELEASE_LOCK(nb->m_C) || !AQUIRE_LOCK(nb->m_B) ||
379 !RELEASE_LOCK(nb->m_A) || !AQUIRE_LOCK(nb->m_C) ||
380 !RELEASE_LOCK(nb->m_B)) {
381 nb->flags |= NHEXT_NB_ERROR;
382 #ifdef DEBUG
383 debug_line("nhext_nb_read failing with hard error");
384 #endif
385 return -1;
386 }
387 if (!blocking) {
388 nb->flags |= NHEXT_NB_PENDING;
389 #ifdef DEBUG
390 debug_line("nhext_nb_read returning PENDING");
391 #endif
392 return -2;
393 } else {
394 if (!AQUIRE_LOCK(nb->m_A)) {
395 nb->flags |= NHEXT_NB_ERROR;
396 #ifdef DEBUG
397 debug_line("nhext_nb_read failing with hard error");
398 #endif
399 return -1;
400 }
401 #ifdef DEBUG
402 debug_line("nhext_nb_read returns result");
403 #endif
404 return nb->res >= 0 ? nb->res : -1;
405 }
406 } else {
407 if (buf != nb->cmd.buffer || bytes < nb->cmd.bytes) {
408 #ifdef DEBUG
409 debug_line("nhext_nb_read failing with soft error (INVALID)");
410 #endif
411 return -1;
412 }
413 if (!blocking) {
414 retval = TRY_LOCK(nb->m_A);
415 if (retval == 1) {
416 nb->flags &= ~NHEXT_NB_PENDING;
417 #ifdef DEBUG
418 debug_line("nhext_nb_read returns result");
419 #endif
420 return nb->res >= 0 ? nb->res : -1;
421 } else if (retval) {
422 nb->flags |= NHEXT_NB_ERROR;
423 #ifdef DEBUG
424 debug_line("nhext_nb_read failing with hard error");
425 #endif
426 return -1;
427 }
428 else {
429 #ifdef DEBUG
430 debug_line("nhext_nb_read returning PENDING");
431 #endif
432 return -2;
433 }
434 } else {
435 if (!AQUIRE_LOCK(nb->m_A)) {
436 nb->flags |= NHEXT_NB_ERROR;
437 #ifdef DEBUG
438 debug_line("nhext_nb_read failing with hard error");
439 #endif
440 return -1;
441 }
442 nb->flags &= ~NHEXT_NB_PENDING;
443 #ifdef DEBUG
444 debug_line("nhext_nb_read returns result");
445 #endif
446 return nb->res >= 0 ? nb->res : -1;
447 }
448 }
449 }
450