source: mainline/uspace/lib/c/generic/fibril_synch.c@ c20cccb

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c20cccb was 82453b29, checked in by Jiří Zárevúcky <jiri.zarevucky@…>, 7 years ago

Detect when printf() printing deadlock deadlocks.

  • Property mode set to 100644
File size: 16.7 KB
Line 
1/*
2 * Copyright (c) 2009 Jakub Jermar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libc
30 * @{
31 */
32/** @file
33 */
34
35#include <fibril_synch.h>
36#include <fibril.h>
37#include <async.h>
38#include <adt/list.h>
39#include <futex.h>
40#include <sys/time.h>
41#include <errno.h>
42#include <assert.h>
43#include <stacktrace.h>
44#include <stdlib.h>
45#include <stdio.h>
46#include <io/kio.h>
47
48#include "private/async.h"
49#include "private/fibril.h"
50
51static fibril_local bool deadlocked = false;
52
53static void optimize_execution_power(void)
54{
55 /*
56 * When waking up a worker fibril previously blocked in fibril
57 * synchronization, chances are that there is an idle manager fibril
58 * waiting for IPC, that could start executing the awakened worker
59 * fibril right away. We try to detect this and bring the manager
60 * fibril back to fruitful work.
61 */
62 async_poke();
63}
64
65static void print_deadlock(fibril_owner_info_t *oi)
66{
67 fibril_t *f = (fibril_t *) fibril_get_id();
68
69 if (deadlocked) {
70 kio_printf("Deadlock detected while printing deadlock. Aborting.\n");
71 abort();
72 }
73 deadlocked = true;
74
75 printf("Deadlock detected.\n");
76 stacktrace_print();
77
78 printf("Fibril %p waits for primitive %p.\n", f, oi);
79
80 while (oi && oi->owned_by) {
81 printf("Primitive %p is owned by fibril %p.\n",
82 oi, oi->owned_by);
83 if (oi->owned_by == f)
84 break;
85 stacktrace_print_fp_pc(
86 context_get_fp(&oi->owned_by->ctx),
87 context_get_pc(&oi->owned_by->ctx));
88 printf("Fibril %p waits for primitive %p.\n",
89 oi->owned_by, oi->owned_by->waits_for);
90 oi = oi->owned_by->waits_for;
91 }
92}
93
94
95static void check_for_deadlock(fibril_owner_info_t *oi)
96{
97 while (oi && oi->owned_by) {
98 if (oi->owned_by == (fibril_t *) fibril_get_id()) {
99 print_deadlock(oi);
100 abort();
101 }
102 oi = oi->owned_by->waits_for;
103 }
104}
105
106
107void fibril_mutex_initialize(fibril_mutex_t *fm)
108{
109 fm->oi.owned_by = NULL;
110 fm->counter = 1;
111 list_initialize(&fm->waiters);
112}
113
114void fibril_mutex_lock(fibril_mutex_t *fm)
115{
116 fibril_t *f = (fibril_t *) fibril_get_id();
117
118 futex_lock(&async_futex);
119 if (fm->counter-- <= 0) {
120 awaiter_t wdata;
121
122 awaiter_initialize(&wdata);
123 wdata.fid = fibril_get_id();
124 wdata.wu_event.inlist = true;
125 list_append(&wdata.wu_event.link, &fm->waiters);
126 check_for_deadlock(&fm->oi);
127 f->waits_for = &fm->oi;
128 fibril_switch(FIBRIL_FROM_BLOCKED);
129 } else {
130 fm->oi.owned_by = f;
131 }
132 futex_unlock(&async_futex);
133}
134
135bool fibril_mutex_trylock(fibril_mutex_t *fm)
136{
137 bool locked = false;
138
139 futex_lock(&async_futex);
140 if (fm->counter > 0) {
141 fm->counter--;
142 fm->oi.owned_by = (fibril_t *) fibril_get_id();
143 locked = true;
144 }
145 futex_unlock(&async_futex);
146
147 return locked;
148}
149
150static void _fibril_mutex_unlock_unsafe(fibril_mutex_t *fm)
151{
152 if (fm->counter++ < 0) {
153 link_t *tmp;
154 awaiter_t *wdp;
155 fibril_t *f;
156
157 tmp = list_first(&fm->waiters);
158 assert(tmp != NULL);
159 wdp = list_get_instance(tmp, awaiter_t, wu_event.link);
160 wdp->active = true;
161 wdp->wu_event.inlist = false;
162
163 f = (fibril_t *) wdp->fid;
164 fm->oi.owned_by = f;
165 f->waits_for = NULL;
166
167 list_remove(&wdp->wu_event.link);
168 fibril_add_ready(wdp->fid);
169 optimize_execution_power();
170 } else {
171 fm->oi.owned_by = NULL;
172 }
173}
174
175void fibril_mutex_unlock(fibril_mutex_t *fm)
176{
177 assert(fibril_mutex_is_locked(fm));
178 futex_lock(&async_futex);
179 _fibril_mutex_unlock_unsafe(fm);
180 futex_unlock(&async_futex);
181}
182
183bool fibril_mutex_is_locked(fibril_mutex_t *fm)
184{
185 bool locked = false;
186
187 futex_lock(&async_futex);
188 if (fm->counter <= 0)
189 locked = true;
190 futex_unlock(&async_futex);
191
192 return locked;
193}
194
195void fibril_rwlock_initialize(fibril_rwlock_t *frw)
196{
197 frw->oi.owned_by = NULL;
198 frw->writers = 0;
199 frw->readers = 0;
200 list_initialize(&frw->waiters);
201}
202
203void fibril_rwlock_read_lock(fibril_rwlock_t *frw)
204{
205 fibril_t *f = (fibril_t *) fibril_get_id();
206
207 futex_lock(&async_futex);
208 if (frw->writers) {
209 awaiter_t wdata;
210
211 awaiter_initialize(&wdata);
212 wdata.fid = (fid_t) f;
213 wdata.wu_event.inlist = true;
214 f->is_writer = false;
215 list_append(&wdata.wu_event.link, &frw->waiters);
216 check_for_deadlock(&frw->oi);
217 f->waits_for = &frw->oi;
218 fibril_switch(FIBRIL_FROM_BLOCKED);
219 } else {
220 /* Consider the first reader the owner. */
221 if (frw->readers++ == 0)
222 frw->oi.owned_by = f;
223 }
224 futex_unlock(&async_futex);
225}
226
227void fibril_rwlock_write_lock(fibril_rwlock_t *frw)
228{
229 fibril_t *f = (fibril_t *) fibril_get_id();
230
231 futex_lock(&async_futex);
232 if (frw->writers || frw->readers) {
233 awaiter_t wdata;
234
235 awaiter_initialize(&wdata);
236 wdata.fid = (fid_t) f;
237 wdata.wu_event.inlist = true;
238 f->is_writer = true;
239 list_append(&wdata.wu_event.link, &frw->waiters);
240 check_for_deadlock(&frw->oi);
241 f->waits_for = &frw->oi;
242 fibril_switch(FIBRIL_FROM_BLOCKED);
243 } else {
244 frw->oi.owned_by = f;
245 frw->writers++;
246 }
247 futex_unlock(&async_futex);
248}
249
250static void _fibril_rwlock_common_unlock(fibril_rwlock_t *frw)
251{
252 futex_lock(&async_futex);
253 if (frw->readers) {
254 if (--frw->readers) {
255 if (frw->oi.owned_by == (fibril_t *) fibril_get_id()) {
256 /*
257 * If this reader firbril was considered the
258 * owner of this rwlock, clear the ownership
259 * information even if there are still more
260 * readers.
261 *
262 * This is the limitation of the detection
263 * mechanism rooted in the fact that tracking
264 * all readers would require dynamically
265 * allocated memory for keeping linkage info.
266 */
267 frw->oi.owned_by = NULL;
268 }
269 goto out;
270 }
271 } else {
272 frw->writers--;
273 }
274
275 assert(!frw->readers && !frw->writers);
276
277 frw->oi.owned_by = NULL;
278
279 while (!list_empty(&frw->waiters)) {
280 link_t *tmp = list_first(&frw->waiters);
281 awaiter_t *wdp;
282 fibril_t *f;
283
284 wdp = list_get_instance(tmp, awaiter_t, wu_event.link);
285 f = (fibril_t *) wdp->fid;
286
287 f->waits_for = NULL;
288
289 if (f->is_writer) {
290 if (frw->readers)
291 break;
292 wdp->active = true;
293 wdp->wu_event.inlist = false;
294 list_remove(&wdp->wu_event.link);
295 fibril_add_ready(wdp->fid);
296 frw->writers++;
297 frw->oi.owned_by = f;
298 optimize_execution_power();
299 break;
300 } else {
301 wdp->active = true;
302 wdp->wu_event.inlist = false;
303 list_remove(&wdp->wu_event.link);
304 fibril_add_ready(wdp->fid);
305 if (frw->readers++ == 0) {
306 /* Consider the first reader the owner. */
307 frw->oi.owned_by = f;
308 }
309 optimize_execution_power();
310 }
311 }
312out:
313 futex_unlock(&async_futex);
314}
315
316void fibril_rwlock_read_unlock(fibril_rwlock_t *frw)
317{
318 assert(fibril_rwlock_is_read_locked(frw));
319 _fibril_rwlock_common_unlock(frw);
320}
321
322void fibril_rwlock_write_unlock(fibril_rwlock_t *frw)
323{
324 assert(fibril_rwlock_is_write_locked(frw));
325 _fibril_rwlock_common_unlock(frw);
326}
327
328bool fibril_rwlock_is_read_locked(fibril_rwlock_t *frw)
329{
330 bool locked = false;
331
332 futex_lock(&async_futex);
333 if (frw->readers)
334 locked = true;
335 futex_unlock(&async_futex);
336
337 return locked;
338}
339
340bool fibril_rwlock_is_write_locked(fibril_rwlock_t *frw)
341{
342 bool locked = false;
343
344 futex_lock(&async_futex);
345 if (frw->writers) {
346 assert(frw->writers == 1);
347 locked = true;
348 }
349 futex_unlock(&async_futex);
350
351 return locked;
352}
353
354bool fibril_rwlock_is_locked(fibril_rwlock_t *frw)
355{
356 return fibril_rwlock_is_read_locked(frw) ||
357 fibril_rwlock_is_write_locked(frw);
358}
359
360void fibril_condvar_initialize(fibril_condvar_t *fcv)
361{
362 list_initialize(&fcv->waiters);
363}
364
365errno_t
366fibril_condvar_wait_timeout(fibril_condvar_t *fcv, fibril_mutex_t *fm,
367 suseconds_t timeout)
368{
369 awaiter_t wdata;
370
371 assert(fibril_mutex_is_locked(fm));
372
373 if (timeout < 0)
374 return ETIMEOUT;
375
376 awaiter_initialize(&wdata);
377 wdata.fid = fibril_get_id();
378 wdata.to_event.inlist = timeout > 0;
379 wdata.wu_event.inlist = true;
380
381 futex_lock(&async_futex);
382 if (timeout) {
383 getuptime(&wdata.to_event.expires);
384 tv_add_diff(&wdata.to_event.expires, timeout);
385 async_insert_timeout(&wdata);
386 }
387 list_append(&wdata.wu_event.link, &fcv->waiters);
388 _fibril_mutex_unlock_unsafe(fm);
389 fibril_switch(FIBRIL_FROM_BLOCKED);
390 futex_unlock(&async_futex);
391
392 // XXX: This could be replaced with an unlocked version to get rid
393 // of the unlock-lock pair. I deliberately don't do that because
394 // further changes would most likely need to revert that optimization.
395 fibril_mutex_lock(fm);
396
397 futex_lock(&async_futex);
398 if (wdata.to_event.inlist)
399 list_remove(&wdata.to_event.link);
400 if (wdata.wu_event.inlist)
401 list_remove(&wdata.wu_event.link);
402 futex_unlock(&async_futex);
403
404 return wdata.to_event.occurred ? ETIMEOUT : EOK;
405}
406
407void fibril_condvar_wait(fibril_condvar_t *fcv, fibril_mutex_t *fm)
408{
409 errno_t rc;
410
411 rc = fibril_condvar_wait_timeout(fcv, fm, 0);
412 assert(rc == EOK);
413}
414
415static void _fibril_condvar_wakeup_common(fibril_condvar_t *fcv, bool once)
416{
417 link_t *tmp;
418 awaiter_t *wdp;
419
420 futex_lock(&async_futex);
421 while (!list_empty(&fcv->waiters)) {
422 tmp = list_first(&fcv->waiters);
423 wdp = list_get_instance(tmp, awaiter_t, wu_event.link);
424 list_remove(&wdp->wu_event.link);
425 wdp->wu_event.inlist = false;
426 if (!wdp->active) {
427 wdp->active = true;
428 fibril_add_ready(wdp->fid);
429 optimize_execution_power();
430 if (once)
431 break;
432 }
433 }
434 futex_unlock(&async_futex);
435}
436
437void fibril_condvar_signal(fibril_condvar_t *fcv)
438{
439 _fibril_condvar_wakeup_common(fcv, true);
440}
441
442void fibril_condvar_broadcast(fibril_condvar_t *fcv)
443{
444 _fibril_condvar_wakeup_common(fcv, false);
445}
446
447/** Timer fibril.
448 *
449 * @param arg Timer
450 */
451static errno_t fibril_timer_func(void *arg)
452{
453 fibril_timer_t *timer = (fibril_timer_t *) arg;
454 errno_t rc;
455
456 fibril_mutex_lock(timer->lockp);
457
458 while (timer->state != fts_cleanup) {
459 switch (timer->state) {
460 case fts_not_set:
461 case fts_fired:
462 fibril_condvar_wait(&timer->cv, timer->lockp);
463 break;
464 case fts_active:
465 rc = fibril_condvar_wait_timeout(&timer->cv,
466 timer->lockp, timer->delay);
467 if (rc == ETIMEOUT && timer->state == fts_active) {
468 timer->state = fts_fired;
469 timer->handler_fid = fibril_get_id();
470 fibril_mutex_unlock(timer->lockp);
471 timer->fun(timer->arg);
472 fibril_mutex_lock(timer->lockp);
473 timer->handler_fid = 0;
474 }
475 break;
476 case fts_cleanup:
477 case fts_clean:
478 assert(false);
479 break;
480 }
481 }
482
483 /* Acknowledge timer fibril has finished cleanup. */
484 timer->state = fts_clean;
485 fibril_condvar_broadcast(&timer->cv);
486 fibril_mutex_unlock(timer->lockp);
487
488 return 0;
489}
490
491/** Create new timer.
492 *
493 * @return New timer on success, @c NULL if out of memory.
494 */
495fibril_timer_t *fibril_timer_create(fibril_mutex_t *lock)
496{
497 fid_t fid;
498 fibril_timer_t *timer;
499
500 timer = calloc(1, sizeof(fibril_timer_t));
501 if (timer == NULL)
502 return NULL;
503
504 fid = fibril_create(fibril_timer_func, (void *) timer);
505 if (fid == 0) {
506 free(timer);
507 return NULL;
508 }
509
510 fibril_mutex_initialize(&timer->lock);
511 fibril_condvar_initialize(&timer->cv);
512
513 timer->fibril = fid;
514 timer->state = fts_not_set;
515 timer->lockp = (lock != NULL) ? lock : &timer->lock;
516
517 fibril_add_ready(fid);
518 return timer;
519}
520
521/** Destroy timer.
522 *
523 * @param timer Timer, must not be active or accessed by other threads.
524 */
525void fibril_timer_destroy(fibril_timer_t *timer)
526{
527 fibril_mutex_lock(timer->lockp);
528 assert(timer->state == fts_not_set || timer->state == fts_fired);
529
530 /* Request timer fibril to terminate. */
531 timer->state = fts_cleanup;
532 fibril_condvar_broadcast(&timer->cv);
533
534 /* Wait for timer fibril to terminate */
535 while (timer->state != fts_clean)
536 fibril_condvar_wait(&timer->cv, timer->lockp);
537 fibril_mutex_unlock(timer->lockp);
538
539 free(timer);
540}
541
542/** Set timer.
543 *
544 * Set timer to execute a callback function after the specified
545 * interval.
546 *
547 * @param timer Timer
548 * @param delay Delay in microseconds
549 * @param fun Callback function
550 * @param arg Argument for @a fun
551 */
552void fibril_timer_set(fibril_timer_t *timer, suseconds_t delay,
553 fibril_timer_fun_t fun, void *arg)
554{
555 fibril_mutex_lock(timer->lockp);
556 fibril_timer_set_locked(timer, delay, fun, arg);
557 fibril_mutex_unlock(timer->lockp);
558}
559
560/** Set locked timer.
561 *
562 * Set timer to execute a callback function after the specified
563 * interval. Must be called when the timer is locked.
564 *
565 * @param timer Timer
566 * @param delay Delay in microseconds
567 * @param fun Callback function
568 * @param arg Argument for @a fun
569 */
570void fibril_timer_set_locked(fibril_timer_t *timer, suseconds_t delay,
571 fibril_timer_fun_t fun, void *arg)
572{
573 assert(fibril_mutex_is_locked(timer->lockp));
574 assert(timer->state == fts_not_set || timer->state == fts_fired);
575 timer->state = fts_active;
576 timer->delay = delay;
577 timer->fun = fun;
578 timer->arg = arg;
579 fibril_condvar_broadcast(&timer->cv);
580}
581
582/** Clear timer.
583 *
584 * Clears (cancels) timer and returns last state of the timer.
585 * This can be one of:
586 * - fts_not_set If the timer has not been set or has been cleared
587 * - fts_active Timer was set but did not fire
588 * - fts_fired Timer fired
589 *
590 * @param timer Timer
591 * @return Last timer state
592 */
593fibril_timer_state_t fibril_timer_clear(fibril_timer_t *timer)
594{
595 fibril_timer_state_t old_state;
596
597 fibril_mutex_lock(timer->lockp);
598 old_state = fibril_timer_clear_locked(timer);
599 fibril_mutex_unlock(timer->lockp);
600
601 return old_state;
602}
603
604/** Clear locked timer.
605 *
606 * Clears (cancels) timer and returns last state of the timer.
607 * This can be one of:
608 * - fts_not_set If the timer has not been set or has been cleared
609 * - fts_active Timer was set but did not fire
610 * - fts_fired Timer fired
611 * Must be called when the timer is locked.
612 *
613 * @param timer Timer
614 * @return Last timer state
615 */
616fibril_timer_state_t fibril_timer_clear_locked(fibril_timer_t *timer)
617{
618 fibril_timer_state_t old_state;
619
620 assert(fibril_mutex_is_locked(timer->lockp));
621
622 while (timer->handler_fid != 0) {
623 if (timer->handler_fid == fibril_get_id()) {
624 printf("Deadlock detected.\n");
625 stacktrace_print();
626 printf("Fibril %zx is trying to clear timer %p from "
627 "inside its handler %p.\n",
628 fibril_get_id(), timer, timer->fun);
629 abort();
630 }
631
632 fibril_condvar_wait(&timer->cv, timer->lockp);
633 }
634
635 old_state = timer->state;
636 timer->state = fts_not_set;
637
638 timer->delay = 0;
639 timer->fun = NULL;
640 timer->arg = NULL;
641 fibril_condvar_broadcast(&timer->cv);
642
643 return old_state;
644}
645
646/**
647 * Initialize a semaphore with initial count set to the provided value.
648 *
649 * @param sem Semaphore to initialize.
650 * @param count Initial count. Must not be negative.
651 */
652void fibril_semaphore_initialize(fibril_semaphore_t *sem, long count)
653{
654 /*
655 * Negative count denotes the length of waitlist,
656 * so it makes no sense as an initial value.
657 */
658 assert(count >= 0);
659 sem->count = count;
660 list_initialize(&sem->waiters);
661}
662
663/**
664 * Produce one token.
665 * If there are fibrils waiting for tokens, this operation satisfies
666 * exactly one waiting `fibril_semaphore_down()`.
667 * This operation never blocks the fibril.
668 *
669 * @param sem Semaphore to use.
670 */
671void fibril_semaphore_up(fibril_semaphore_t *sem)
672{
673 futex_lock(&async_futex);
674 sem->count++;
675
676 if (sem->count > 0) {
677 futex_unlock(&async_futex);
678 return;
679 }
680
681 link_t *tmp = list_first(&sem->waiters);
682 assert(tmp);
683 list_remove(tmp);
684
685 futex_unlock(&async_futex);
686
687 awaiter_t *wdp = list_get_instance(tmp, awaiter_t, wu_event.link);
688 fibril_add_ready(wdp->fid);
689 optimize_execution_power();
690}
691
692/**
693 * Consume one token.
694 * If there are no available tokens (count <= 0), this operation blocks until
695 * another fibril produces a token using `fibril_semaphore_up()`.
696 *
697 * @param sem Semaphore to use.
698 */
699void fibril_semaphore_down(fibril_semaphore_t *sem)
700{
701 futex_lock(&async_futex);
702 sem->count--;
703
704 if (sem->count >= 0) {
705 futex_unlock(&async_futex);
706 return;
707 }
708
709 awaiter_t wdata;
710 awaiter_initialize(&wdata);
711
712 wdata.fid = fibril_get_id();
713 list_append(&wdata.wu_event.link, &sem->waiters);
714
715 fibril_switch(FIBRIL_FROM_BLOCKED);
716 futex_unlock(&async_futex);
717}
718
719/** @}
720 */
Note: See TracBrowser for help on using the repository browser.