parlib: Add 'timed' functions for sems/mtxs/cvs
[akaros.git] / user / parlib / mutex.c
1 /* Copyright (c) 2016-2017 Google, Inc.
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details. */
4
5 /* Generic Uthread Semaphores, Mutexes, CVs, and other synchronization
6  * functions.  2LSs implement their own sync objects (bottom of the file). */
7
8 #include <parlib/uthread.h>
9 #include <sys/queue.h>
10 #include <parlib/spinlock.h>
11 #include <parlib/alarm.h>
12 #include <malloc.h>
13
14 struct timeout_blob {
15         bool                                            timed_out;
16         struct uthread                          *uth;
17         uth_sync_t                                      *sync_ptr;
18         struct spin_pdr_lock            *lock_ptr;
19 };
20
21 /* When sync primitives want to time out, they can use this alarm handler.  It
22  * needs a timeout_blob, which is independent of any particular sync method. */
23 static void timeout_handler(struct alarm_waiter *waiter)
24 {
25         struct timeout_blob *blob = (struct timeout_blob*)waiter->data;
26
27         spin_pdr_lock(blob->lock_ptr);
28         if (__uth_sync_get_uth(*blob->sync_ptr, blob->uth))
29                 blob->timed_out = TRUE;
30         spin_pdr_unlock(blob->lock_ptr);
31         if (blob->timed_out)
32                 uthread_runnable(blob->uth);
33 }
34
35 /* Minor helper, sets a blob's fields */
36 static void set_timeout_blob(struct timeout_blob *blob, uth_sync_t *sync_ptr,
37                              struct spin_pdr_lock *lock_ptr)
38 {
39         blob->timed_out = FALSE;
40         blob->uth = current_uthread;
41         blob->sync_ptr = sync_ptr;
42         blob->lock_ptr = lock_ptr;
43 }
44
45 /* Minor helper, sets an alarm for blob and a timespec */
46 static void set_timeout_alarm(struct alarm_waiter *waiter,
47                               struct timeout_blob *blob,
48                               const struct timespec *abs_timeout)
49 {
50         init_awaiter(waiter, timeout_handler);
51         waiter->data = blob;
52         set_awaiter_abs_unix(waiter, timespec_to_alarm_time(abs_timeout));
53         set_alarm(waiter);
54 }
55
56 /************** Semaphores and Mutexes **************/
57
58 static void __uth_semaphore_init(void *arg)
59 {
60         struct uth_semaphore *sem = (struct uth_semaphore*)arg;
61
62         spin_pdr_init(&sem->lock);
63         sem->sync_obj = __uth_sync_alloc();
64         /* If we used a static initializer for a semaphore, count is already set.
65          * o/w it will be set by _alloc() or _init() (via uth_semaphore_init()). */
66 }
67
68 /* Initializes a sem acquired from somewhere else.  POSIX's sem_init() needs
69  * this. */
70 void uth_semaphore_init(uth_semaphore_t *sem, unsigned int count)
71 {
72         __uth_semaphore_init(sem);
73         sem->count = count;
74         /* The once is to make sure the object is initialized. */
75         parlib_set_ran_once(&sem->once_ctl);
76 }
77
78 /* Undoes whatever was done in init. */
79 void uth_semaphore_destroy(uth_semaphore_t *sem)
80 {
81         __uth_sync_free(sem->sync_obj);
82 }
83
84 uth_semaphore_t *uth_semaphore_alloc(unsigned int count)
85 {
86         struct uth_semaphore *sem;
87
88         sem = malloc(sizeof(struct uth_semaphore));
89         assert(sem);
90         uth_semaphore_init(sem, count);
91         return sem;
92 }
93
94 void uth_semaphore_free(uth_semaphore_t *sem)
95 {
96         uth_semaphore_destroy(sem);
97         free(sem);
98 }
99
100 static void __semaphore_cb(struct uthread *uth, void *arg)
101 {
102         struct uth_semaphore *sem = (struct uth_semaphore*)arg;
103
104         /* We need to tell the 2LS that its thread blocked.  We need to do this
105          * before unlocking the sem, since as soon as we unlock, the sem could be
106          * released and our thread restarted.
107          *
108          * Also note the lock-ordering rule.  The sem lock is grabbed before any
109          * locks the 2LS might grab. */
110         uthread_has_blocked(uth, sem->sync_obj, UTH_EXT_BLK_MUTEX);
111         spin_pdr_unlock(&sem->lock);
112 }
113
114 bool uth_semaphore_timed_down(uth_semaphore_t *sem,
115                               const struct timespec *abs_timeout)
116 {
117         struct alarm_waiter waiter[1];
118         struct timeout_blob blob[1];
119
120         parlib_run_once(&sem->once_ctl, __uth_semaphore_init, sem);
121         spin_pdr_lock(&sem->lock);
122         if (sem->count > 0) {
123                 /* Only down if we got one.  This means a sem with no more counts is 0,
124                  * not negative (where -count == nr_waiters).  Doing it this way means
125                  * our timeout function works for sems and CVs. */
126                 sem->count--;
127                 spin_pdr_unlock(&sem->lock);
128                 return TRUE;
129         }
130         if (abs_timeout) {
131                 set_timeout_blob(blob, &sem->sync_obj, &sem->lock);
132                 set_timeout_alarm(waiter, blob, abs_timeout);
133         }
134         /* the unlock and sync enqueuing is done in the yield callback.  as always,
135          * we need to do this part in vcore context, since as soon as we unlock the
136          * uthread could restart.  (atomically yield and unlock). */
137         uthread_yield(TRUE, __semaphore_cb, sem);
138         if (abs_timeout) {
139                 /* We're guaranteed the alarm will either be cancelled or the handler
140                  * complete when unset_alarm() returns. */
141                 unset_alarm(waiter);
142                 return blob->timed_out ? FALSE : TRUE;
143         }
144         return TRUE;
145 }
146
147 void uth_semaphore_down(uth_semaphore_t *sem)
148 {
149         uth_semaphore_timed_down(sem, NULL);
150 }
151
152 bool uth_semaphore_trydown(uth_semaphore_t *sem)
153 {
154         bool ret = FALSE;
155
156         parlib_run_once(&sem->once_ctl, __uth_semaphore_init, sem);
157         spin_pdr_lock(&sem->lock);
158         if (sem->count > 0) {
159                 sem->count--;
160                 ret = TRUE;
161         }
162         spin_pdr_unlock(&sem->lock);
163         return ret;
164 }
165
166 void uth_semaphore_up(uth_semaphore_t *sem)
167 {
168         struct uthread *uth;
169
170         /* once-ing the 'up', unlike mtxs 'unlock', since sems can be special. */
171         parlib_run_once(&sem->once_ctl, __uth_semaphore_init, sem);
172         spin_pdr_lock(&sem->lock);
173         uth = __uth_sync_get_next(sem->sync_obj);
174         /* If there was a waiter, we pass our resource/count to them. */
175         if (!uth)
176                 sem->count++;
177         spin_pdr_unlock(&sem->lock);
178         if (uth)
179                 uthread_runnable(uth);
180 }
181
182 /* Takes a void * since it's called by parlib_run_once(), which enables us to
183  * statically initialize the mutex.  This init does everything not done by the
184  * static initializer.  Note we do not allow 'static' destruction.  (No one
185  * calls free). */
186 static void __uth_mutex_init(void *arg)
187 {
188         struct uth_semaphore *mtx = (struct uth_semaphore*)arg;
189
190         __uth_semaphore_init(mtx);
191         mtx->count = 1;
192 }
193
194 void uth_mutex_init(uth_mutex_t *mtx)
195 {
196         __uth_mutex_init(mtx);
197         parlib_set_ran_once(&mtx->once_ctl);
198 }
199
200 void uth_mutex_destroy(uth_mutex_t *mtx)
201 {
202         uth_semaphore_destroy(mtx);
203 }
204
205 uth_mutex_t *uth_mutex_alloc(void)
206 {
207         struct uth_semaphore *mtx;
208
209         mtx = malloc(sizeof(struct uth_semaphore));
210         assert(mtx);
211         uth_mutex_init(mtx);
212         return mtx;
213 }
214
215 void uth_mutex_free(uth_mutex_t *mtx)
216 {
217         uth_semaphore_free(mtx);
218 }
219
220 bool uth_mutex_timed_lock(uth_mutex_t *mtx, const struct timespec *abs_timeout)
221 {
222         parlib_run_once(&mtx->once_ctl, __uth_mutex_init, mtx);
223         return uth_semaphore_timed_down(mtx, abs_timeout);
224 }
225
226 void uth_mutex_lock(uth_mutex_t *mtx)
227 {
228         parlib_run_once(&mtx->once_ctl, __uth_mutex_init, mtx);
229         uth_semaphore_down(mtx);
230 }
231
232 bool uth_mutex_trylock(uth_mutex_t *mtx)
233 {
234         parlib_run_once(&mtx->once_ctl, __uth_mutex_init, mtx);
235         return uth_semaphore_trydown(mtx);
236 }
237
238 void uth_mutex_unlock(uth_mutex_t *mtx)
239 {
240         uth_semaphore_up(mtx);
241 }
242
243 /************** Recursive mutexes **************/
244
245 static void __uth_recurse_mutex_init(void *arg)
246 {
247         struct uth_recurse_mutex *r_mtx = (struct uth_recurse_mutex*)arg;
248
249         __uth_mutex_init(&r_mtx->mtx);
250         /* Since we always manually call __uth_mutex_init(), there's no reason to
251          * mess with the regular mutex's static initializer.  Just say it's been
252          * done. */
253         parlib_set_ran_once(&r_mtx->mtx.once_ctl);
254         r_mtx->lockholder = NULL;
255         r_mtx->count = 0;
256 }
257
258 void uth_recurse_mutex_init(uth_recurse_mutex_t *r_mtx)
259 {
260         __uth_recurse_mutex_init(r_mtx);
261         parlib_set_ran_once(&r_mtx->once_ctl);
262 }
263
264 void uth_recurse_mutex_destroy(uth_recurse_mutex_t *r_mtx)
265 {
266         uth_semaphore_destroy(&r_mtx->mtx);
267 }
268
269 uth_recurse_mutex_t *uth_recurse_mutex_alloc(void)
270 {
271         struct uth_recurse_mutex *r_mtx = malloc(sizeof(struct uth_recurse_mutex));
272
273         assert(r_mtx);
274         uth_recurse_mutex_init(r_mtx);
275         return r_mtx;
276 }
277
278 void uth_recurse_mutex_free(uth_recurse_mutex_t *r_mtx)
279 {
280         uth_recurse_mutex_destroy(r_mtx);
281         free(r_mtx);
282 }
283
284 bool uth_recurse_mutex_timed_lock(uth_recurse_mutex_t *r_mtx,
285                                   const struct timespec *abs_timeout)
286 {
287         parlib_run_once(&r_mtx->once_ctl, __uth_recurse_mutex_init, r_mtx);
288         assert(!in_vcore_context());
289         assert(current_uthread);
290         /* We don't have to worry about races on current_uthread or count.  They are
291          * only written by the initial lockholder, and this check will only be true
292          * for the initial lockholder, which cannot concurrently call this function
293          * twice (a thread is single-threaded).
294          *
295          * A signal handler running for a thread should not attempt to grab a
296          * recursive mutex (that's probably a bug).  If we need to support that,
297          * we'll have to disable notifs temporarily. */
298         if (r_mtx->lockholder == current_uthread) {
299                 r_mtx->count++;
300                 return TRUE;
301         }
302         if (!uth_mutex_timed_lock(&r_mtx->mtx, abs_timeout))
303                 return FALSE;
304         r_mtx->lockholder = current_uthread;
305         r_mtx->count = 1;
306         return TRUE;
307 }
308
309 void uth_recurse_mutex_lock(uth_recurse_mutex_t *r_mtx)
310 {
311         uth_recurse_mutex_timed_lock(r_mtx, NULL);
312 }
313
314 bool uth_recurse_mutex_trylock(uth_recurse_mutex_t *r_mtx)
315 {
316         bool ret;
317
318         parlib_run_once(&r_mtx->once_ctl, __uth_recurse_mutex_init, r_mtx);
319         assert(!in_vcore_context());
320         assert(current_uthread);
321         if (r_mtx->lockholder == current_uthread) {
322                 r_mtx->count++;
323                 return TRUE;
324         }
325         ret = uth_mutex_trylock(&r_mtx->mtx);
326         if (ret) {
327                 r_mtx->lockholder = current_uthread;
328                 r_mtx->count = 1;
329         }
330         return ret;
331 }
332
333 void uth_recurse_mutex_unlock(uth_recurse_mutex_t *r_mtx)
334 {
335         r_mtx->count--;
336         if (!r_mtx->count) {
337                 r_mtx->lockholder = NULL;
338                 uth_mutex_unlock(&r_mtx->mtx);
339         }
340 }
341
342
343 /************** Condition Variables **************/
344
345
346 static void __uth_cond_var_init(void *arg)
347 {
348         struct uth_cond_var *cv = (struct uth_cond_var*)arg;
349
350         spin_pdr_init(&cv->lock);
351         cv->sync_obj = __uth_sync_alloc();
352 }
353
354 void uth_cond_var_init(uth_cond_var_t *cv)
355 {
356         __uth_cond_var_init(cv);
357         parlib_set_ran_once(&cv->once_ctl);
358 }
359
360 void uth_cond_var_destroy(uth_cond_var_t *cv)
361 {
362         __uth_sync_free(cv->sync_obj);
363 }
364
365 uth_cond_var_t *uth_cond_var_alloc(void)
366 {
367         struct uth_cond_var *cv;
368
369         cv = malloc(sizeof(struct uth_cond_var));
370         assert(cv);
371         uth_cond_var_init(cv);
372         return cv;
373 }
374
375 void uth_cond_var_free(uth_cond_var_t *cv)
376 {
377         uth_cond_var_destroy(cv);
378         free(cv);
379 }
380
381 struct uth_cv_link {
382         struct uth_cond_var                     *cv;
383         struct uth_semaphore            *mtx;
384 };
385
386 static void __cv_wait_cb(struct uthread *uth, void *arg)
387 {
388         struct uth_cv_link *link = (struct uth_cv_link*)arg;
389         struct uth_cond_var *cv = link->cv;
390         struct uth_semaphore *mtx = link->mtx;
391
392         /* We need to tell the 2LS that its thread blocked.  We need to do this
393          * before unlocking the cv, since as soon as we unlock, the cv could be
394          * signalled and our thread restarted.
395          *
396          * Also note the lock-ordering rule.  The cv lock is grabbed before any
397          * locks the 2LS might grab. */
398         uthread_has_blocked(uth, cv->sync_obj, UTH_EXT_BLK_MUTEX);
399         spin_pdr_unlock(&cv->lock);
400         /* This looks dangerous, since both the CV and MTX could use the
401          * uth->sync_next TAILQ_ENTRY (or whatever the 2LS uses), but the uthread
402          * never sleeps on both at the same time.  We *hold* the mtx - we aren't
403          * *sleeping* on it.  Sleeping uses the sync_next.  Holding it doesn't.
404          *
405          * Next, consider what happens as soon as we unlock the CV.  Our thread
406          * could get woken up, and then immediately try to grab the mtx and go to
407          * sleep! (see below).  If that happens, the uthread is no longer sleeping
408          * on the CV, and the sync_next is free.  The invariant is that a uthread
409          * can only sleep on one sync_object at a time. */
410         uth_mutex_unlock(mtx);
411 }
412
413 /* Caller holds mtx.  We will 'atomically' release it and wait.  On return,
414  * caller holds mtx again.  Once our uth is on the CV's list, we can release the
415  * mtx without fear of missing a signal.
416  *
417  * POSIX refers to atomicity in this context as "atomically with respect to
418  * access by another thread to the mutex and then the condition variable"
419  *
420  * The idea is that we hold the mutex to protect some invariant; we check it,
421  * and decide to sleep.  Now we get on the list before releasing so that any
422  * changes to that invariant (e.g. a flag is now TRUE) happen after we're on the
423  * list, and so that we don't miss the signal.  To be more clear, the invariant
424  * in a basic wake-up flag scenario is: "whenever a flag is set from FALSE to
425  * TRUE, all waiters that saw FALSE are on the CV's waitqueue."  The mutex is
426  * required for this invariant.
427  *
428  * Note that signal/broadcasters do not *need* to hold the mutex, in general,
429  * but they do in the basic wake-up flag scenario.  If not, the race is this:
430  *
431  * Sleeper:                                                             Waker:
432  * -----------------------------------------------------------------
433  * Hold mutex
434  *   See flag is False
435  *   Decide to sleep
436  *                                                                              Set flag True
437  * PAUSE!                                                               Grab CV lock
438  *                                                                              See list is empty, unlock
439  *
440  *   Grab CV lock
441  *     Get put on list
442  *   Unlock CV lock
443  * Unlock mutex
444  * (Never wake up; we missed the signal)
445  *
446  * For those familiar with the kernel's CVs, we don't couple mutexes with CVs.
447  * cv_lock() actually grabs the spinlock inside the CV and uses *that* to
448  * protect the invariant.  The signallers always grab that lock, so the sleeper
449  * is not in danger of missing the signal.  The tradeoff is that the kernel CVs
450  * use a spinlock instead of a mutex for protecting its invariant; there might
451  * be some case that preferred blocking sync.
452  *
453  * The uthread CVs take a mutex, unlike the kernel CVs, to map more cleanly to
454  * POSIX CVs.  Maybe one approach or the other is a bad idea; we'll see.
455  *
456  * As far as lock ordering goes, once the sleeper holds the mutex and is on the
457  * CV's list, it can unlock in any order it wants.  However, unlocking a mutex
458  * actually requires grabbing its spinlock.  So as to not have a lock ordering
459  * between *spinlocks*, we let go of the CV's spinlock before unlocking the
460  * mutex.  There is an ordering between the mutex and the CV spinlock (mutex->cv
461  * spin), but there is no ordering between the mutex spin and cv spin.  And of
462  * course, we need to unlock the CV spinlock in the yield callback.
463  *
464  * Also note that we use the external API for the mutex operations.  A 2LS could
465  * have their own mutex ops but still use the generic cv ops. */
466 bool uth_cond_var_timed_wait(uth_cond_var_t *cv, uth_mutex_t *mtx,
467                              const struct timespec *abs_timeout)
468 {
469         struct uth_cv_link link;
470         struct alarm_waiter waiter[1];
471         struct timeout_blob blob[1];
472         bool ret = TRUE;
473
474         parlib_run_once(&cv->once_ctl, __uth_cond_var_init, cv);
475         link.cv = cv;
476         link.mtx = mtx;
477         spin_pdr_lock(&cv->lock);
478         if (abs_timeout) {
479                 set_timeout_blob(blob, &cv->sync_obj, &cv->lock);
480                 set_timeout_alarm(waiter, blob, abs_timeout);
481         }
482         uthread_yield(TRUE, __cv_wait_cb, &link);
483         if (abs_timeout) {
484                 unset_alarm(waiter);
485                 ret = blob->timed_out ? FALSE : TRUE;
486         }
487         uth_mutex_lock(mtx);
488         return ret;
489 }
490
491 void uth_cond_var_wait(uth_cond_var_t *cv, uth_mutex_t *mtx)
492 {
493         uth_cond_var_timed_wait(cv, mtx, NULL);
494 }
495
496 /* GCC doesn't list this as one of the C++0x functions, but it's easy to do and
497  * implement uth_cond_var_wait_recurse() with it, just like for all the other
498  * 'timed' functions.
499  *
500  * Note the timeout applies to getting the signal on the CV, not on reacquiring
501  * the mutex. */
502 bool uth_cond_var_timed_wait_recurse(uth_cond_var_t *cv,
503                                      uth_recurse_mutex_t *r_mtx,
504                                      const struct timespec *abs_timeout)
505 {
506         unsigned int old_count = r_mtx->count;
507         bool ret;
508
509         /* In cond_wait, we're going to unlock the internal mutex.  We'll do the
510          * prep-work for that now.  (invariant is that an unlocked r_mtx has no
511          * lockholder and count == 0. */
512         r_mtx->lockholder = NULL;
513         r_mtx->count = 0;
514         ret = uth_cond_var_timed_wait(cv, &r_mtx->mtx, abs_timeout);
515         /* Now we hold the internal mutex again.  Need to restore the tracking. */
516         r_mtx->lockholder = current_uthread;
517         r_mtx->count = old_count;
518         return ret;
519 }
520
521 /* GCC wants this function, though its semantics are a little unclear.  I
522  * imagine you'd want to completely unlock it (say you locked it 3 times), and
523  * when you get it back, that you have your three locks back. */
524 void uth_cond_var_wait_recurse(uth_cond_var_t *cv, uth_recurse_mutex_t *r_mtx)
525 {
526         uth_cond_var_timed_wait_recurse(cv, r_mtx, NULL);
527 }
528
529 void uth_cond_var_signal(uth_cond_var_t *cv)
530 {
531         struct uthread *uth;
532
533         parlib_run_once(&cv->once_ctl, __uth_cond_var_init, cv);
534         spin_pdr_lock(&cv->lock);
535         uth = __uth_sync_get_next(cv->sync_obj);
536         spin_pdr_unlock(&cv->lock);
537         if (uth)
538                 uthread_runnable(uth);
539 }
540
541 void uth_cond_var_broadcast(uth_cond_var_t *cv)
542 {
543         struct uth_tailq restartees = TAILQ_HEAD_INITIALIZER(restartees);
544         struct uthread *i, *safe;
545
546         parlib_run_once(&cv->once_ctl, __uth_cond_var_init, cv);
547         spin_pdr_lock(&cv->lock);
548         /* If this turns out to be slow or painful for 2LSs, we can implement a
549          * get_all or something (default used to use TAILQ_SWAP). */
550         while ((i = __uth_sync_get_next(cv->sync_obj))) {
551                 /* Once the uth is out of the sync obj, we can reuse sync_next. */
552                 TAILQ_INSERT_TAIL(&restartees, i, sync_next);
553         }
554         spin_pdr_unlock(&cv->lock);
555         /* Need the SAFE, since we can't touch the linkage once the uth could run */
556         TAILQ_FOREACH_SAFE(i, &restartees, sync_next, safe)
557                 uthread_runnable(i);
558 }
559
560
561 /************** Default Sync Obj Implementation **************/
562
563 static uth_sync_t uth_default_sync_alloc(void)
564 {
565         struct uth_tailq *tq;
566
567         tq = malloc(sizeof(struct uth_tailq));
568         assert(tq);
569         TAILQ_INIT(tq);
570         return (uth_sync_t)tq;
571 }
572
573 static void uth_default_sync_free(uth_sync_t sync)
574 {
575         struct uth_tailq *tq = (struct uth_tailq*)sync;
576
577         assert(TAILQ_EMPTY(tq));
578         free(tq);
579 }
580
581 static struct uthread *uth_default_sync_get_next(uth_sync_t sync)
582 {
583         struct uth_tailq *tq = (struct uth_tailq*)sync;
584         struct uthread *first;
585
586         first = TAILQ_FIRST(tq);
587         if (first)
588                 TAILQ_REMOVE(tq, first, sync_next);
589         return first;
590 }
591
592 static bool uth_default_sync_get_uth(uth_sync_t sync, struct uthread *uth)
593 {
594         struct uth_tailq *tq = (struct uth_tailq*)sync;
595         struct uthread *i;
596
597         TAILQ_FOREACH(i, tq, sync_next) {
598                 if (i == uth) {
599                         TAILQ_REMOVE(tq, i, sync_next);
600                         return TRUE;
601                 }
602         }
603         return FALSE;
604 }
605
606 /************** External uthread sync interface **************/
607
608 /* Called by the 2LS->has_blocked op, if they are using the default sync.*/
609 void __uth_default_sync_enqueue(struct uthread *uth, uth_sync_t sync)
610 {
611         struct uth_tailq *tq = (struct uth_tailq*)sync;
612
613         TAILQ_INSERT_TAIL(tq, uth, sync_next);
614 }
615
616 /* Called by 2LS-independent sync code when a sync object is created. */
617 uth_sync_t __uth_sync_alloc(void)
618 {
619         if (sched_ops->sync_alloc)
620                 return sched_ops->sync_alloc();
621         return uth_default_sync_alloc();
622 }
623
624 /* Called by 2LS-independent sync code when a sync object is destroyed. */
625 void __uth_sync_free(uth_sync_t sync)
626 {
627         if (sched_ops->sync_free) {
628                 sched_ops->sync_free(sync);
629                 return;
630         }
631         uth_default_sync_free(sync);
632 }
633
634 /* Called by 2LS-independent sync code when a thread needs to be woken. */
635 struct uthread *__uth_sync_get_next(uth_sync_t sync)
636 {
637         if (sched_ops->sync_get_next)
638                 return sched_ops->sync_get_next(sync);
639         return uth_default_sync_get_next(sync);
640 }
641
642 /* Called by 2LS-independent sync code when a specific thread needs to be woken.
643  * Returns TRUE if the uthread was blocked on the object, FALSE o/w. */
644 bool __uth_sync_get_uth(uth_sync_t sync, struct uthread *uth)
645 {
646         if (sched_ops->sync_get_uth)
647                 return sched_ops->sync_get_uth(sync, uth);
648         return uth_default_sync_get_uth(sync, uth);
649 }