Fix bug introduced when adding sem_timedwait
[akaros.git] / user / pthread / semaphore.c
1 #include <parlib/uthread.h>
2 #include <semaphore.h>
3 #include <parlib/mcs.h>
4 #include <stdio.h>
5 #include <benchutil/alarm.h>
6 #include <errno.h>
7
8 struct sem_queue_element {
9         TAILQ_ENTRY(sem_queue_element) next;
10         struct sem *sem;
11         pthread_t pthread;
12         uint64_t us_timeout;
13         struct alarm_waiter awaiter;
14         bool timedout;
15 };
16
17 int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
18 {
19         if(__pshared == TRUE) {
20                 printf("__pshared functionality of sem_init is not yet implemented!");
21                 return -1;
22         }
23         __sem->count = __value;
24         TAILQ_INIT(&__sem->queue);
25         spin_pdr_init(&__sem->lock);
26         return 0;
27 }
28
29 int sem_destroy (sem_t *__sem)
30 {
31         return 0;
32 }
33
34 sem_t *sem_open (__const char *__name, int __oflag, ...)
35 {
36         printf("sem_open is not yet implemented!");
37         return NULL;
38 }
39
40 int sem_close (sem_t *__sem)
41 {
42         printf("sem_close is not yet implemented!");
43         return -1;
44 }
45
46 int sem_unlink (__const char *__name)
47 {
48         printf("sem_unlink is not yet implemented!");
49         return -1;
50 }
51
52 static void __sem_timeout(struct alarm_waiter *awaiter)
53 {
54         struct sem_queue_element *e = awaiter->data;
55         struct sem_queue_element *__e = NULL;
56
57         /* Try and yank out the thread. */
58         spin_pdr_lock(&e->sem->lock);
59         TAILQ_FOREACH(__e, &e->sem->queue, next)
60                 if (__e == e) break;
61         if (__e) {
62                 TAILQ_REMOVE(&e->sem->queue, e, next);
63                 e->timedout = true;
64         }
65         spin_pdr_unlock(&e->sem->lock);
66
67         /* If we were able to yank it out, wake it up. */
68         if (__e)
69                 uthread_runnable((struct uthread*)e->pthread);
70
71         /* Set this as the very last thing we do whether we
72          * successfully woke the thread blocked on the futex or not.
73          * Either we set this or post() sets this, not both.  Spin on
74          * this in the bottom-half of the wait() code to ensure there
75          * are no more references to awaiter before freeing the
76          * memory for it. */
77         e->awaiter.data = NULL;
78 }
79
80 static void __sem_block(struct uthread *uthread, void *arg)
81 {
82         struct sem_queue_element *e = (struct sem_queue_element *)arg;
83         pthread_t pthread = (pthread_t)uthread;
84         __pthread_generic_yield(pthread);
85         pthread->state = PTH_BLK_MUTEX;
86         TAILQ_INSERT_TAIL(&e->sem->queue, e, next);
87         spin_pdr_unlock(&e->sem->lock);
88 }
89
90 static void __sem_timedblock(struct uthread *uthread, void *arg)
91 {
92         struct sem_queue_element *e = (struct sem_queue_element *)arg;
93         e->awaiter.data = e;
94         init_awaiter(&e->awaiter, __sem_timeout);
95         set_awaiter_abs(&e->awaiter, e->us_timeout);
96         set_alarm(&e->awaiter);
97         __sem_block(uthread, e->sem);
98 }
99
100 int sem_wait (sem_t *__sem)
101 {
102         pthread_t pthread = (pthread_t)current_uthread;
103         struct sem_queue_element e = {{0}, __sem, pthread, -1, {0}, false};
104
105         spin_pdr_lock(&__sem->lock);
106         if(__sem->count > 0) {
107                 __sem->count--;
108                 spin_pdr_unlock(&__sem->lock);
109         }
110         else {
111                 /* We unlock in the body of __sem_block */
112                 uthread_yield(TRUE, __sem_block, &e);
113         }
114         return 0;
115 }
116
117 int sem_timedwait(sem_t *__sem, const struct timespec *abs_timeout)
118 {
119         int ret = 0;
120         uint64_t us = abs_timeout->tv_nsec/1000 + (abs_timeout->tv_sec)*1000000L;
121         pthread_t pthread = (pthread_t)current_uthread;
122         struct sem_queue_element e = {{0}, __sem, pthread, us, {0}, false};
123
124         spin_pdr_lock(&__sem->lock);
125         if(__sem->count > 0) {
126                 __sem->count--;
127                 spin_pdr_unlock(&__sem->lock);
128         }
129         else {
130                 /* We unlock in the body of __sem_block */
131                 uthread_yield(TRUE, __sem_timedblock, &e);
132
133                 /* Spin briefly to make sure that all references to e are
134                  * gone between the post() and the timeout() code. We use
135                  * e.awaiter.data to do this. */
136                 while (e.awaiter.data != NULL)
137                         cpu_relax();
138
139                 if (e.timedout) {
140                         errno = ETIMEDOUT;
141                         ret = -1;
142                 }
143         }
144         return ret;
145 }
146
147 int sem_trywait (sem_t *__sem)
148 {
149         int ret = -1;
150         spin_pdr_lock(&__sem->lock);
151         if(__sem->count > 0) {
152                 __sem->count--;
153                 ret = 0;
154         }
155         spin_pdr_unlock(&__sem->lock);
156         return ret;
157 }
158
159 int sem_post (sem_t *__sem)
160 {
161         spin_pdr_lock(&__sem->lock);
162         struct sem_queue_element *e = TAILQ_FIRST(&__sem->queue);
163         if (e)
164                 TAILQ_REMOVE(&__sem->queue, e, next);
165         else
166                 __sem->count++; 
167         spin_pdr_unlock(&__sem->lock);
168
169         if (e) {
170                 if(e->us_timeout != (uint64_t)-1) {
171                         /* Try and unset the alarm.  If this fails, then we
172                          * have already started running the alarm callback.  If
173                          * it succeeds, then we can set awaiter->data to NULL
174                          * so that the bottom half of wake can proceed. Either
175                          * we set awaiter->data to NULL or __sem_timeout
176                          * does. The fact that we made it here though, means
177                          * that WE are the one who removed e from the queue, so
178                          * we are basically just deciding who should set
179                          * awaiter->data to NULL to indicate that there are no
180                          * more references to it. */
181                         if(unset_alarm(&e->awaiter))
182                                 e->awaiter.data = NULL;
183                 }
184                 uthread_runnable((struct uthread*)e->pthread);
185         }
186         return 0;
187 }
188
189 int sem_getvalue (sem_t *__restrict __sem, int *__restrict __sval)
190 {
191         spin_pdr_lock(&__sem->lock);
192         *__sval = __sem->count;
193         spin_pdr_unlock(&__sem->lock);
194         return 0;
195 }
196