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