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