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