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