]> git.ozlabs.org Git - petitboot/blob - lib/waiter/waiter.c
e201ad1fbdab2b209cf720d58313f2259d823b97
[petitboot] / lib / waiter / waiter.c
1
2 #include <poll.h>
3 #include <stdbool.h>
4 #include <string.h>
5 #include <assert.h>
6 #include <sys/time.h>
7
8 #include <talloc/talloc.h>
9 #include <list/list.h>
10
11 #include "waiter.h"
12
13 struct waiter {
14         struct waitset  *set;
15         enum {
16                 WAITER_IO,
17                 WAITER_TIME,
18         } type;
19         union {
20                 struct {
21                         int     fd;
22                         int     events;
23                 } io;
24                 struct timeval  timeout;
25         };
26         waiter_cb       callback;
27         void            *arg;
28
29         bool                    active;
30         struct list_item        list;
31 };
32
33 struct waitset {
34         struct waiter   **waiters;
35         int             n_waiters;
36         bool            waiters_changed;
37
38         struct timeval  next_timeout;
39
40         /* These are kept consistent over each call to waiter_poll, as
41          * set->waiters may be updated (by waiters' callbacks calling
42          * waiter_register or waiter_remove) during iteration. */
43         struct pollfd   *pollfds;
44         int             n_pollfds;
45         struct waiter   **io_waiters;
46         int             n_io_waiters;
47         struct waiter   **time_waiters;
48         int             n_time_waiters;
49
50         struct list     free_list;
51 };
52
53 struct waitset *waitset_create(void *ctx)
54 {
55         struct waitset *set = talloc_zero(ctx, struct waitset);
56         list_init(&set->free_list);
57         return set;
58 }
59
60 static struct waiter *waiter_new(struct waitset *set)
61 {
62         struct waiter **waiters, *waiter;
63
64         waiter = talloc(set, struct waiter);
65         if (!waiter)
66                 return NULL;
67
68         waiters = talloc_realloc(set, set->waiters,
69                         struct waiter *, set->n_waiters + 1);
70
71         if (!waiters) {
72                 talloc_free(waiter);
73                 return NULL;
74         }
75
76         set->waiters_changed = true;
77         set->waiters = waiters;
78         set->n_waiters++;
79
80         set->waiters[set->n_waiters - 1] = waiter;
81         waiter->active = true;
82         return waiter;
83 }
84
85 struct waiter *waiter_register_io(struct waitset *set, int fd, int events,
86                 waiter_cb callback, void *arg)
87 {
88         struct waiter *waiter = waiter_new(set);
89
90         waiter->type = WAITER_IO;
91         waiter->set = set;
92         waiter->io.fd = fd;
93         waiter->io.events = events;
94         waiter->callback = callback;
95         waiter->arg = arg;
96
97         return waiter;
98 }
99
100 struct waiter *waiter_register_timeout(struct waitset *set, int delay_ms,
101                 waiter_cb callback, void *arg)
102 {
103         struct waiter *waiter = waiter_new(set);
104         struct timeval now, delay;
105
106         delay.tv_sec = delay_ms / 1000;
107         delay.tv_usec = 1000 * (delay_ms % 1000);
108
109         gettimeofday(&now, NULL);
110
111         timeradd(&now, &delay, &waiter->timeout);
112
113         waiter->type = WAITER_TIME;
114         waiter->set = set;
115         waiter->callback = callback;
116         waiter->arg = arg;
117
118         return waiter;
119 }
120
121 void waiter_remove(struct waiter *waiter)
122 {
123         struct waitset *set = waiter->set;
124         int i;
125
126         for (i = 0; i < set->n_waiters; i++)
127                 if (set->waiters[i] == waiter)
128                         break;
129
130         assert(i < set->n_waiters);
131
132         set->n_waiters--;
133         memmove(&set->waiters[i], &set->waiters[i+1],
134                 (set->n_waiters - i) * sizeof(set->waiters[0]));
135
136         set->waiters = talloc_realloc(set, set->waiters,
137                         struct waiter *, set->n_waiters);
138         set->waiters_changed = true;
139
140         waiter->active = false;
141         list_add(&set->free_list, &waiter->list);
142 }
143
144 static void update_waiters(struct waitset *set)
145 {
146         int n_io, n_time, i_io, i_time, i;
147
148         if (!set->waiters_changed)
149                 return;
150
151         n_io = n_time = 0;
152
153         for (i = 0; i < set->n_waiters; i++) {
154                 if (set->waiters[i]->type == WAITER_IO)
155                         n_io++;
156                 else if (set->waiters[i]->type == WAITER_TIME)
157                         n_time++;
158         }
159
160         /* realloc if counts have changed */
161         if (set->n_io_waiters != n_io) {
162                 set->io_waiters = talloc_realloc(set, set->io_waiters,
163                                 struct waiter *, n_io);
164                 set->pollfds = talloc_realloc(set, set->pollfds,
165                                 struct pollfd, n_io);
166                 set->n_io_waiters = n_io;
167         }
168         if (set->n_time_waiters != n_time) {
169                 set->time_waiters = talloc_realloc(set, set->time_waiters,
170                                 struct waiter *, n_time);
171                 set->n_time_waiters = n_time;
172         }
173
174         i_io = 0;
175         i_time = 0;
176
177         timerclear(&set->next_timeout);
178
179         for (i = 0; i < set->n_waiters; i++) {
180                 struct waiter *waiter = set->waiters[i];
181
182                 /* IO waiters: copy to io_waiters, populate pollfds */
183                 if (waiter->type == WAITER_IO) {
184                         set->pollfds[i_io].fd = waiter->io.fd;
185                         set->pollfds[i_io].events = waiter->io.events;
186                         set->io_waiters[i_io] = waiter;
187                         i_io++;
188                 }
189
190                 /* time waiters: copy to time_waiters, calculate next expiry */
191                 if (waiter->type == WAITER_TIME) {
192                         if (!timerisset(&set->next_timeout) ||
193                                         timercmp(&waiter->timeout,
194                                                 &set->next_timeout, <))
195                                 set->next_timeout = waiter->timeout;
196
197                         set->time_waiters[i_time] = waiter;
198                         i_time++;
199                 }
200         }
201 }
202
203 int waiter_poll(struct waitset *set)
204 {
205         struct timeval now, timeout;
206         struct waiter *waiter, *tmp;
207         int timeout_ms;
208         int i, rc;
209
210         /* If the waiters have been updated, we need to update our
211          * consistent copy */
212         update_waiters(set);
213
214         if (timerisset(&set->next_timeout)) {
215                 gettimeofday(&now, NULL);
216                 timersub(&set->next_timeout, &now, &timeout);
217                 timeout_ms = timeout.tv_sec * 1000 +
218                                 timeout.tv_usec / 1000;
219                 if (timeout_ms < 0)
220                         timeout_ms = 0;
221         } else {
222                 timeout_ms = -1;
223         }
224
225
226         rc = poll(set->pollfds, set->n_io_waiters, timeout_ms);
227
228         if (rc < 0)
229                 goto out;
230
231         for (i = 0; i < set->n_io_waiters; i++) {
232                 struct waiter *waiter = set->io_waiters[i];
233
234                 if (!waiter->active)
235                         continue;
236
237                 if (!set->pollfds[i].revents)
238                         continue;
239                 rc = waiter->callback(waiter->arg);
240
241                 if (rc)
242                         waiter_remove(waiter);
243         }
244
245         if (set->n_time_waiters > 0)
246                 gettimeofday(&now, NULL);
247
248         for (i = 0; i < set->n_time_waiters; i++) {
249                 struct waiter *waiter = set->time_waiters[i];
250
251                 if (!waiter->active)
252                         continue;
253
254                 if (timercmp(&waiter->timeout, &now, >))
255                         continue;
256
257                 waiter->callback(waiter->arg);
258
259                 waiter_remove(waiter);
260         }
261
262         rc = 0;
263
264 out:
265         /* free any waiters that have been removed */
266         list_for_each_entry_safe(&set->free_list, waiter, tmp, list)
267                 talloc_free(waiter);
268         list_init(&set->free_list);
269
270         return rc;
271 }