xref: /plan9/sys/src/liboventi/plan9-thread.c (revision 6ca6a3e703ee2ec4aed99c2177f71d7f127da6d9)
1 #include <u.h>
2 #include <libc.h>
3 #include <oventi.h>
4 
5 enum
6 {
7 	QueuingW,	/* queuing for write lock */
8 	QueuingR,	/* queuing for read lock */
9 };
10 
11 
12 typedef struct Thread Thread;
13 
14 struct Thread {
15 	int pid;
16 	int ref;
17 	char *error;
18 	int state;
19 	Thread *next;
20 };
21 
22 struct VtLock {
23 	Lock lk;
24 	Thread *writer;		/* thread writering write lock */
25 	int readers;		/* number writering read lock */
26 	Thread *qfirst;
27 	Thread *qlast;
28 	uintptr	pc;
29 };
30 
31 struct VtRendez {
32 	VtLock *lk;
33 	Thread *wfirst;
34 	Thread *wlast;
35 };
36 
37 enum {
38 	ERROR = 0,
39 };
40 
41 static Thread **vtRock;
42 
43 static void	vtThreadInit(void);
44 static void	threadSleep(Thread*);
45 static void	threadWakeup(Thread*);
46 
47 int
vtThread(void (* f)(void *),void * rock)48 vtThread(void (*f)(void*), void *rock)
49 {
50 	int tid;
51 
52 	tid = rfork(RFNOWAIT|RFMEM|RFPROC);
53 	switch(tid){
54 	case -1:
55 		vtOSError();
56 		return -1;
57 	case 0:
58 		break;
59 	default:
60 		return tid;
61 	}
62 	vtAttach();
63 	(*f)(rock);
64 	vtDetach();
65 	_exits(0);
66 	return 0;
67 }
68 
69 static Thread *
threadLookup(void)70 threadLookup(void)
71 {
72 	return *vtRock;
73 }
74 
75 void
vtAttach(void)76 vtAttach(void)
77 {
78 	int pid;
79 	Thread *p;
80 	static int init;
81 	static Lock lk;
82 
83 	lock(&lk);
84 	if(!init) {
85 		rfork(RFREND);
86 		vtThreadInit();
87 		init = 1;
88 	}
89 	unlock(&lk);
90 
91 	pid = getpid();
92 	p = *vtRock;
93 	if(p != nil && p->pid == pid) {
94 		p->ref++;
95 		return;
96 	}
97 	p = vtMemAllocZ(sizeof(Thread));
98 	p->ref = 1;
99 	p->pid = pid;
100 	*vtRock = p;
101 }
102 
103 void
vtDetach(void)104 vtDetach(void)
105 {
106 	Thread *p;
107 
108 	p = *vtRock;
109 	assert(p != nil);
110 	p->ref--;
111 	if(p->ref == 0) {
112 		vtMemFree(p->error);
113 		vtMemFree(p);
114 		*vtRock = nil;
115 	}
116 }
117 
118 char *
vtGetError(void)119 vtGetError(void)
120 {
121 	char *s;
122 
123 	if(ERROR)
124 		fprint(2, "vtGetError: %s\n", threadLookup()->error);
125 	s = threadLookup()->error;
126 	if(s == nil)
127 		return "unknown error";
128 	return s;
129 }
130 
131 char*
vtSetError(char * fmt,...)132 vtSetError(char* fmt, ...)
133 {
134 	Thread *p;
135 	char *s;
136 	va_list args;
137 
138 	p = threadLookup();
139 
140 	va_start(args, fmt);
141 	s = vsmprint(fmt, args);
142 	vtMemFree(p->error);
143 	p->error = s;
144 	va_end(args);
145 	if(ERROR)
146 		fprint(2, "vtSetError: %s\n", p->error);
147 	werrstr("%s", p->error);
148 	return p->error;
149 }
150 
151 static void
vtThreadInit(void)152 vtThreadInit(void)
153 {
154 	static Lock lk;
155 
156 	lock(&lk);
157 	if(vtRock != nil) {
158 		unlock(&lk);
159 		return;
160 	}
161 	vtRock = privalloc();
162 	if(vtRock == nil)
163 		vtFatal("can't allocate thread-private storage");
164 	unlock(&lk);
165 }
166 
167 VtLock*
vtLockAlloc(void)168 vtLockAlloc(void)
169 {
170 	return vtMemAllocZ(sizeof(VtLock));
171 }
172 
173 /*
174  * RSC: I think the test is backward.  Let's see who uses it.
175  *
176 void
177 vtLockInit(VtLock **p)
178 {
179 	static Lock lk;
180 
181 	lock(&lk);
182 	if(*p != nil)
183 		*p = vtLockAlloc();
184 	unlock(&lk);
185 }
186  */
187 
188 void
vtLockFree(VtLock * p)189 vtLockFree(VtLock *p)
190 {
191 	if(p == nil)
192 		return;
193 	assert(p->writer == nil);
194 	assert(p->readers == 0);
195 	assert(p->qfirst == nil);
196 	vtMemFree(p);
197 }
198 
199 VtRendez*
vtRendezAlloc(VtLock * p)200 vtRendezAlloc(VtLock *p)
201 {
202 	VtRendez *q;
203 
204 	q = vtMemAllocZ(sizeof(VtRendez));
205 	q->lk = p;
206 	setmalloctag(q, getcallerpc(&p));
207 	return q;
208 }
209 
210 void
vtRendezFree(VtRendez * q)211 vtRendezFree(VtRendez *q)
212 {
213 	if(q == nil)
214 		return;
215 	assert(q->wfirst == nil);
216 	vtMemFree(q);
217 }
218 
219 int
vtCanLock(VtLock * p)220 vtCanLock(VtLock *p)
221 {
222 	Thread *t;
223 
224 	lock(&p->lk);
225 	t = *vtRock;
226 	if(p->writer == nil && p->readers == 0) {
227 		p->writer = t;
228 		unlock(&p->lk);
229 		return 1;
230 	}
231 	unlock(&p->lk);
232 	return 0;
233 }
234 
235 
236 void
vtLock(VtLock * p)237 vtLock(VtLock *p)
238 {
239 	Thread *t;
240 
241 	lock(&p->lk);
242 	p->pc = getcallerpc(&p);
243 	t = *vtRock;
244 	if(p->writer == nil && p->readers == 0) {
245 		p->writer = t;
246 		unlock(&p->lk);
247 		return;
248 	}
249 
250 	/*
251 	 * venti currently contains code that assume locks can be passed between threads :-(
252 	 * assert(p->writer != t);
253 	 */
254 
255 	if(p->qfirst == nil)
256 		p->qfirst = t;
257 	else
258 		p->qlast->next = t;
259 	p->qlast = t;
260 	t->next = nil;
261 	t->state = QueuingW;
262 	unlock(&p->lk);
263 
264 	threadSleep(t);
265 	assert(p->writer == t && p->readers == 0);
266 }
267 
268 int
vtCanRLock(VtLock * p)269 vtCanRLock(VtLock *p)
270 {
271 	lock(&p->lk);
272 	if(p->writer == nil && p->qfirst == nil) {
273 		p->readers++;
274 		unlock(&p->lk);
275 		return 1;
276 	}
277 	unlock(&p->lk);
278 	return 0;
279 }
280 
281 void
vtRLock(VtLock * p)282 vtRLock(VtLock *p)
283 {
284 	Thread *t;
285 
286 	lock(&p->lk);
287 	t = *vtRock;
288 	if(p->writer == nil && p->qfirst == nil) {
289 		p->readers++;
290 		unlock(&p->lk);
291 		return;
292 	}
293 
294 	/*
295 	 * venti currently contains code that assumes locks can be passed between threads
296 	 * assert(p->writer != t);
297 	 */
298 	if(p->qfirst == nil)
299 		p->qfirst = t;
300 	else
301 		p->qlast->next = t;
302 	p->qlast = t;
303 	t->next = nil;
304 	t->state = QueuingR;
305 	unlock(&p->lk);
306 
307 	threadSleep(t);
308 	assert(p->writer == nil && p->readers > 0);
309 }
310 
311 void
vtUnlock(VtLock * p)312 vtUnlock(VtLock *p)
313 {
314 	Thread *t, *tt;
315 
316 	lock(&p->lk);
317 	/*
318 	 * venti currently has code that assumes lock can be passed between threads :-)
319  	 * assert(p->writer == *vtRock);
320 	 */
321  	assert(p->writer != nil);
322 	assert(p->readers == 0);
323 	t = p->qfirst;
324 	if(t == nil) {
325 		p->writer = nil;
326 		unlock(&p->lk);
327 		return;
328 	}
329 	if(t->state == QueuingW) {
330 		p->qfirst = t->next;
331 		p->writer = t;
332 		unlock(&p->lk);
333 
334 		threadWakeup(t);
335 		return;
336 	}
337 
338 	p->writer = nil;
339 	while(t != nil && t->state == QueuingR) {
340 		tt = t;
341 		t = t->next;
342 		p->readers++;
343 
344 		threadWakeup(tt);
345 	}
346 	p->qfirst = t;
347 	unlock(&p->lk);
348 }
349 
350 void
vtRUnlock(VtLock * p)351 vtRUnlock(VtLock *p)
352 {
353 	Thread *t;
354 
355 	lock(&p->lk);
356 	assert(p->writer == nil && p->readers > 0);
357 	p->readers--;
358 	t = p->qfirst;
359 	if(p->readers > 0 || t == nil) {
360 		unlock(&p->lk);
361 		return;
362 	}
363 	assert(t->state == QueuingW);
364 
365 	p->qfirst = t->next;
366 	p->writer = t;
367 	unlock(&p->lk);
368 
369 	threadWakeup(t);
370 }
371 
372 int
vtSleep(VtRendez * q)373 vtSleep(VtRendez *q)
374 {
375 	Thread *s, *t, *tt;
376 	VtLock *p;
377 
378 	p = q->lk;
379 	lock(&p->lk);
380 	s = *vtRock;
381 	/*
382 	 * venti currently contains code that assume locks can be passed between threads :-(
383 	 * assert(p->writer != s);
384 	 */
385 	assert(p->writer != nil);
386 	assert(p->readers == 0);
387 	t = p->qfirst;
388 	if(t == nil) {
389 		p->writer = nil;
390 	} else if(t->state == QueuingW) {
391 		p->qfirst = t->next;
392 		p->writer = t;
393 		threadWakeup(t);
394 	} else {
395 		p->writer = nil;
396 		while(t != nil && t->state == QueuingR) {
397 			tt = t;
398 			t = t->next;
399 			p->readers++;
400 			threadWakeup(tt);
401 		}
402 	}
403 
404 	if(q->wfirst == nil)
405 		q->wfirst = s;
406 	else
407 		q->wlast->next = s;
408 	q->wlast = s;
409 	s->next = nil;
410 	unlock(&p->lk);
411 
412 	threadSleep(s);
413 	assert(p->writer == s);
414 	return 1;
415 }
416 
417 int
vtWakeup(VtRendez * q)418 vtWakeup(VtRendez *q)
419 {
420 	Thread *t;
421 	VtLock *p;
422 
423 	/*
424 	 * take off wait and put on front of queue
425 	 * put on front so guys that have been waiting will not get starved
426 	 */
427 	p = q->lk;
428 	lock(&p->lk);
429 	/*
430 	 * venti currently has code that assumes lock can be passed between threads :-)
431  	 * assert(p->writer == *vtRock);
432 	 */
433 	assert(p->writer != nil);
434 	t = q->wfirst;
435 	if(t == nil) {
436 		unlock(&p->lk);
437 		return 0;
438 	}
439 	q->wfirst = t->next;
440 	if(p->qfirst == nil)
441 		p->qlast = t;
442 	t->next = p->qfirst;
443 	p->qfirst = t;
444 	t->state = QueuingW;
445 	unlock(&p->lk);
446 
447 	return 1;
448 }
449 
450 int
vtWakeupAll(VtRendez * q)451 vtWakeupAll(VtRendez *q)
452 {
453 	int i;
454 
455 	for(i=0; vtWakeup(q); i++)
456 		;
457 	return i;
458 }
459 
460 static void
threadSleep(Thread * t)461 threadSleep(Thread *t)
462 {
463 	if(rendezvous(t, (void*)0x22bbdfd6) != (void*)0x44391f14)
464 		sysfatal("threadSleep: rendezvous failed: %r");
465 }
466 
467 static void
threadWakeup(Thread * t)468 threadWakeup(Thread *t)
469 {
470 	if(rendezvous(t, (void*)0x44391f14) != (void*)0x22bbdfd6)
471 		sysfatal("threadWakeup: rendezvous failed: %r");
472 }
473