Overhaul lock_test.R
[akaros.git] / kern / src / rendez.c
1 /* Copyright (c) 2013 The Regents of the University of California
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details.
4  *
5  * Plan9 style Rendezvous (http://plan9.bell-labs.com/sys/doc/sleep.html)
6  *
7  * We implement it with CVs, and it can handle multiple sleepers/wakers. */
8
9 #include <rendez.h>
10 #include <kthread.h>
11 #include <alarm.h>
12 #include <assert.h>
13 #include <smp.h>
14 #include <err.h>
15
16 void rendez_init(struct rendez *rv)
17 {
18         cv_init_irqsave(&rv->cv);
19 }
20
21 void rendez_sleep(struct rendez *rv, int (*cond)(void*), void *arg)
22 {
23         int8_t irq_state = 0;
24         struct cv_lookup_elm cle;
25
26         assert(can_block(this_pcpui_ptr()));
27         /* Do a quick check before registering and sleeping.  this is the
28          * 'check, signal, check again' pattern, where the first check is an
29          * optimization.  Many rendezes will already be satisfied, so we want to
30          * avoid excessive locking associated with reg/dereg. */
31         cv_lock_irqsave(&rv->cv, &irq_state);
32         if (cond(arg)) {
33                 cv_unlock_irqsave(&rv->cv, &irq_state);
34                 return;
35         }
36         __reg_abortable_cv(&cle, &rv->cv);
37         /* Mesa-style semantics, which is definitely what you want.  See the
38          * discussion at the end of the URL above. */
39         while (!cond(arg)) {
40                 /* it's okay if we miss the ABORT flag; we hold the cv lock, so
41                  * an aborter's broadcast is waiting until we unlock. */
42                 if (should_abort(&cle)) {
43                         cv_unlock_irqsave(&rv->cv, &irq_state);
44                         dereg_abortable_cv(&cle);
45                         error(EINTR, "syscall aborted");
46                 }
47                 cv_wait(&rv->cv);
48                 cpu_relax();
49         }
50         cv_unlock_irqsave(&rv->cv, &irq_state);
51         dereg_abortable_cv(&cle);
52 }
53
54 /* Force a wakeup of all waiters on the rv, including non-timeout users.  For
55  * those, they will just wake up, see the condition is still false (probably)
56  * and go back to sleep. */
57 static void rendez_alarm_handler(struct alarm_waiter *awaiter)
58 {
59         struct rendez *rv = (struct rendez*)awaiter->data;
60
61         rendez_wakeup(rv);
62 }
63
64 void rendez_debug_waiter(struct alarm_waiter *awaiter)
65 {
66         struct rendez *rv = (struct rendez*)awaiter->data;
67         struct cond_var *cv = &rv->cv;
68         struct kthread *kth;
69         int8_t irq_state = 0;
70
71         cv_lock_irqsave(cv, &irq_state);
72         TAILQ_FOREACH(kth, &cv->waiters, link) {
73                 print_lock();
74                 printk("-------- kth %s ----------\n", kth->name);
75                 backtrace_kthread(kth);
76                 printk("-----------------\n");
77                 print_unlock();
78         }
79         cv_unlock_irqsave(cv, &irq_state);
80 }
81
82 /* Like sleep, but it will timeout in 'usec' microseconds. */
83 bool rendez_sleep_timeout(struct rendez *rv, int (*cond)(void*), void *arg,
84                           uint64_t usec)
85 {
86         int8_t irq_state = 0;
87         struct alarm_waiter awaiter;
88         struct cv_lookup_elm cle;
89         struct timer_chain *pcpui_tchain = &per_cpu_info[core_id()].tchain;
90         bool ret;
91
92         assert(can_block(this_pcpui_ptr()));
93         if (!usec)
94                 return false;
95         /* Doing this cond check early, but then unlocking again.  Mostly just
96          * to avoid weird issues with the CV lock and the alarm tchain lock. */
97         cv_lock_irqsave(&rv->cv, &irq_state);
98         if (cond(arg)) {
99                 cv_unlock_irqsave(&rv->cv, &irq_state);
100                 return true;
101         }
102         cv_unlock_irqsave(&rv->cv, &irq_state);
103         /* The handler will call rendez_wake, but won't mess with the condition
104          * state.  It's enough to break us out of cv_wait() to see .on_tchain is
105          * clear, which is a proxy for "has my alarm fired or will it soon." */
106         init_awaiter(&awaiter, rendez_alarm_handler);
107         awaiter.data = rv;
108         set_awaiter_rel(&awaiter, usec);
109         /* Set our alarm on this cpu's tchain.  Note that when we sleep in
110          * cv_wait, we could be migrated, and later on we could be unsetting the
111          * alarm remotely. */
112         set_alarm(pcpui_tchain, &awaiter);
113         cv_lock_irqsave(&rv->cv, &irq_state);
114         __reg_abortable_cv(&cle, &rv->cv);
115         /* We could wake early for a few reasons.  Legit wakeups after a changed
116          * condition (and we should exit), other alarms with different timeouts
117          * (and we should go back to sleep), etc.  Note it is possible for our
118          * alarm to fire immediately upon setting it: before we even cv_lock. */
119         while (1) {
120                 if (alarm_expired(&awaiter)) {
121                         ret = false;
122                         break;
123                 }
124                 if (cond(arg)) {
125                         ret = true;
126                         break;
127                 }
128                 if (should_abort(&cle)) {
129                         cv_unlock_irqsave(&rv->cv, &irq_state);
130                         unset_alarm(pcpui_tchain, &awaiter);
131                         dereg_abortable_cv(&cle);
132                         error(EINTR, "syscall aborted");
133                 }
134                 cv_wait(&rv->cv);
135                 cpu_relax();
136         }
137         cv_unlock_irqsave(&rv->cv, &irq_state);
138         dereg_abortable_cv(&cle);
139         /* Turn off our alarm.  If it already fired, this is a no-op.  Note this
140          * could be cross-core. */
141         unset_alarm(pcpui_tchain, &awaiter);
142         return ret;
143 }
144
145 /* plan9 rendez returned a pointer to the proc woken up.  we return "true" if we
146  * woke someone up. */
147 bool rendez_wakeup(struct rendez *rv)
148 {
149         int8_t irq_state = 0;
150         bool ret;
151
152         /* The plan9 style "one sleeper, one waker" could get by with a signal
153          * here.  But we want to make sure all potential waiters are woken up.
154          */
155         cv_lock_irqsave(&rv->cv, &irq_state);
156         ret = rv->cv.nr_waiters ? TRUE : FALSE;
157         __cv_broadcast(&rv->cv);
158         cv_unlock_irqsave(&rv->cv, &irq_state);
159         return ret;
160 }