1
1
#include "alarm.h"
2
2
#include <assert.h>
3
- #include <limits.h>
4
3
#include <stdlib.h>
5
4
6
5
#define MAX_TICKS UINT32_MAX
7
6
7
+ /** \brief Checks if `now` is between `reference` and `dt`, meaning
8
+ * the alarm has not yet expired.
9
+ *
10
+ * Invariants:
11
+ * 1. `now` hasn't wrapped `reference`, i.e., in an infinite space,
12
+ * `now` - `reference` <= MAX_TICKS
13
+ * 2. `dt` is at most MAX_TICKS, such that `reference + dt` at most
14
+ * wraps back to `reference`.
15
+ * 3. `now` happens after `reference`, so if `now` is not "within"
16
+ * `reference` and `dt`, it is after, not before.
17
+ * 4. `now` may be larger or smaller than `reference` since ticks
18
+ * wrap.
19
+ *
20
+ * Corrolaries:
21
+ * - if `now` is larger than `reference`, `now - reference` is
22
+ * directly comparable to `dt` (1 & 2)
23
+ * - if `now` is less than reference, `now - reference` wraps, and is
24
+ * - comparable to `dt` because `now` is guaranteed to happened after
25
+ * `reference` (3)
26
+ *
27
+ * The result is that the check is fairly simple, but its simplicitly
28
+ * relies specifically on `now` never happening before `reference`,
29
+ * and thus tied to the specific logic in `alarm_upcall` (see inline
30
+ * comment on delaying executing callbacks until after checking all
31
+ * outstanding alarms).
32
+ */
33
+ static bool is_within (uint32_t now , uint32_t reference , uint32_t dt ) {
34
+ return now - reference < dt ;
35
+ }
36
+
8
37
/** \brief Convert milliseconds to clock ticks
9
38
*
10
39
* WARNING: This function will assert if the output
@@ -101,14 +130,20 @@ static void root_insert(libtock_alarm_ticks_t* alarm) {
101
130
bool cur_overflows = (* cur )-> reference > (UINT32_MAX - (* cur )-> dt );
102
131
103
132
// This alarm (`cur`) happens after the new alarm (`alarm`) if:
104
- // - both overflow or neither overflow, and cur expiration is
133
+ //
134
+ // - both overflow or neither overflow and cur expiration is
105
135
// larger than the new expiration
106
- // - or, only cur overflows
136
+ // - only cur overflows, cur's reference is before new reference
137
+ // (both started this epoch, cur must expire next epoch)
138
+ // - only cur overflows, cur's reference is after new reference
139
+ // (cur started in previous epoch, cur also must expire this
140
+ // epoch) and cur's expiration is larger than the new
141
+ // expiration.
107
142
//
108
143
// If the new alarm overflows and this alarm doesn't, this alarm
109
144
// happens _before_ the new alarm.
110
145
if (((cur_overflows == new_overflows ) && (cur_expiration > new_expiration )) ||
111
- cur_overflows ) {
146
+ ( cur_overflows && (( * cur ) -> reference < alarm -> reference || cur_expiration > new_expiration )) ) {
112
147
// insert before
113
148
libtock_alarm_ticks_t * tmp = * cur ;
114
149
* cur = alarm ;
@@ -145,27 +180,118 @@ static libtock_alarm_ticks_t* root_peek(void) {
145
180
return root ;
146
181
}
147
182
183
+ /** \brief Upcall for internal virtual alarms
184
+ *
185
+ * This upcall checks the ordered list of outstanding alarms for
186
+ * expired alarms, removes them from the list, and invokes their
187
+ * callbacks.
188
+ *
189
+ * Invariants:
190
+ * 1. The list of outstanding alarms is ordered by absolute expiration time.
191
+ * 2. The alarm event that invoked this upcall is for the current `head` of the list.
192
+ * 3. No alarms are added (or re-added) to the list between while
193
+ * iterating through alarms
194
+ * 4. For each alarm, `alarm->dt < MAX_TICKS + 1`
195
+ *
196
+ * Corrollaries:
197
+ * - If the head of the list hasn't expired, no alarms in the tail of
198
+ * the list have expired (1)
199
+ * - `scheduled` happens after `head->reference` (2)
200
+ * - `scheduled` happens before `head->reference + MAX_TICKS + 1` (4)
201
+ * - For each alarm in the list, `now` happens after
202
+ * `alarm->reference` (2, 3)
203
+ * - For each alarm, `scheduled` cannot have cannot have wrapped
204
+ * `alarm->reference` (1, 4)
205
+ * - For each alarm, if `scheduled` and `now` are not on the same side of
206
+ * `alarm->reference`, the alarm must have expired (1, 4)
207
+ *
208
+ * Critically, this upcall cannot allow any alarms to be added to the
209
+ * ordered list while iterating by invoking upcalls, as that could
210
+ * violate invariant (3) and result in `now` happening before some
211
+ * `alarm->reference`.
212
+ *
213
+ * Some alarms that expire between `now` and the end of the upcall may
214
+ * be "missed", which may mean they are delivered later. They should
215
+ * still show up first in the list at the end, so will fire next.
216
+ */
148
217
static void alarm_upcall (__attribute__ ((unused )) int kernel_now ,
149
- __attribute__ (( unused )) int scheduled ,
218
+ int scheduled0 ,
150
219
__attribute__ ((unused )) int unused2 ,
151
220
__attribute__ ((unused )) void * opaque ) {
221
+ // `tocall` is a temporary list to keep track of expired alarms to call later.
222
+ libtock_alarm_ticks_t * tocall = NULL ;
223
+ libtock_alarm_ticks_t * tocall_last = NULL ;
224
+
225
+ // We want `scheduled` as unsigned so wrapping math works out correctly.
226
+ uint32_t scheduled = scheduled0 ;
227
+
228
+ // Take the current tick value. We could use `kernel_now`, but would
229
+ // potentially unnecessarily delay some alarms.
230
+ uint32_t now ;
231
+ libtock_alarm_command_read (& now );
232
+
233
+ // We know this upcall is associated with the head, so it's easier
234
+ // to deal with.
235
+ libtock_alarm_ticks_t * head = root_pop ();
236
+
237
+ // Formally, we should be able to just add head to
238
+ // `tocall`, but let's just be defensive just in case there is an
239
+ // errant alarm.
240
+ assert (head != NULL && !is_within (scheduled , head -> reference , head -> dt ));
241
+ head -> next = NULL ;
242
+ head -> prev = NULL ;
243
+ tocall = head ;
244
+ tocall_last = head ;
245
+
246
+ // Now iterate through the remaining alarms in case any of them have
247
+ // also expired.
152
248
for (libtock_alarm_ticks_t * alarm = root_peek (); alarm != NULL ; alarm = root_peek ()) {
153
- uint32_t now ;
154
- libtock_alarm_command_read (& now );
155
- // has the alarm not expired yet? (distance from `now` has to be larger or
156
- // equal to distance from current clock value.
157
- if (alarm -> dt > now - alarm -> reference ) {
158
- libtock_alarm_command_set_absolute (alarm -> reference , alarm -> dt );
249
+ // has the alarm not expired yet?
250
+ // Three cases:
251
+ // 1. scheduled - reference is larger than now - reference:
252
+ // ticks have wrapped reference, alarm must have expired.
253
+ // 2. scheduled is no longer within reference + dt (alarm
254
+ // expired)
255
+ // 3. now is no longer within reference + dt (alarm expired)
256
+ //
257
+ // Simpler to check the non-expiring case first.
258
+ if ((now - alarm -> reference >= scheduled - alarm -> reference ) &&
259
+ is_within (scheduled , alarm -> reference , alarm -> dt ) &&
260
+ is_within (now , alarm -> reference , alarm -> dt )) {
261
+ // Nope, has not expired, nothing "after" this alarm has
262
+ // expired either, since we add alarms in expiration order.
159
263
break ;
160
264
} else {
265
+ // Expired, add to `tocall` list.
161
266
root_pop ();
267
+ alarm -> next = NULL ;
268
+ alarm -> prev = tocall_last ;
269
+ // If this expired, head must have also expired, so `tocall` and
270
+ // `tocall_last` are non-null, so just add to the end.
271
+ tocall_last -> next = alarm ;
272
+ }
273
+ }
162
274
163
- if (alarm -> callback ) {
164
- uint32_t expiration = alarm -> reference + alarm -> dt ;
165
- alarm -> callback (now , expiration , alarm -> ud );
166
- }
275
+ for (libtock_alarm_ticks_t * alarm = tocall ; alarm != NULL ; alarm = tocall ) {
276
+ alarm -> prev = NULL ;
277
+ tocall = alarm -> next ;
278
+ alarm -> next = NULL ;
279
+ if (alarm -> callback ) {
280
+ uint32_t expiration = alarm -> reference + alarm -> dt ;
281
+ alarm -> callback (now , expiration , alarm -> ud );
167
282
}
168
283
}
284
+
285
+ head = root_peek ();
286
+ if (head != NULL ) {
287
+ // TODO(alevy): At this point, is it possible we've wrapped so far
288
+ // past `reference` that we might end up delaying a technically
289
+ // expired alarm by another timer wrap? I think technically yes,
290
+ // though techincally the interface only guarantees alarms will
291
+ // delay *at least* `dt`, so more is fine, and we could be delayed
292
+ // arbitrarily long by the kernel anyway.
293
+ libtock_alarm_command_set_absolute (head -> reference , head -> dt );
294
+ }
169
295
}
170
296
171
297
static int libtock_alarm_at_internal (uint32_t reference , uint32_t dt , libtock_alarm_callback cb , void * ud ,
@@ -178,10 +304,6 @@ static int libtock_alarm_at_internal(uint32_t reference, uint32_t dt, libtock_al
178
304
alarm -> next = NULL ;
179
305
180
306
root_insert (alarm );
181
- int i = 0 ;
182
- for (libtock_alarm_ticks_t * cur = root_peek (); cur != NULL ; cur = cur -> next ) {
183
- i ++ ;
184
- }
185
307
186
308
if (root_peek () == alarm ) {
187
309
libtock_alarm_set_upcall ((subscribe_upcall * )alarm_upcall , NULL );
@@ -235,8 +357,10 @@ static void overflow_callback(__attribute__ ((unused)) uint32_t now,
235
357
// schedule next intermediate alarm that will overflow
236
358
tock_timer -> overflows_left -- ;
237
359
360
+ const uint32_t max_ticks_in_ms = libtock_alarm_ticks_to_ms (MAX_TICKS );
361
+ const uint32_t max_ms_in_ticks = ms_to_ticks (max_ticks_in_ms );
238
362
libtock_alarm_at (last_timer_fire_time ,
239
- MAX_TICKS ,
363
+ max_ms_in_ticks ,
240
364
(libtock_alarm_callback ) overflow_callback ,
241
365
(void * ) tock_timer ,
242
366
& (tock_timer -> alarm ));
@@ -253,6 +377,7 @@ int libtock_alarm_in_ms(uint32_t ms, libtock_alarm_callback cb, void* opaque, li
253
377
// and the remainder ticks to reach the target length of time. The overflows use the
254
378
// `overflow_callback` for each intermediate overflow.
255
379
const uint32_t max_ticks_in_ms = libtock_alarm_ticks_to_ms (MAX_TICKS );
380
+ const uint32_t max_ms_in_ticks = ms_to_ticks (max_ticks_in_ms );
256
381
if (ms > max_ticks_in_ms ) {
257
382
// overflows_left is the number of intermediate alarms that need to be scheduled to reach the target
258
383
// dt_ms. After the alarm in this block is scheduled, we have this many overflows left (hence the reason
@@ -263,7 +388,7 @@ int libtock_alarm_in_ms(uint32_t ms, libtock_alarm_callback cb, void* opaque, li
263
388
alarm -> user_data = opaque ;
264
389
alarm -> callback = cb ;
265
390
266
- return libtock_alarm_at (now , MAX_TICKS , (libtock_alarm_callback )overflow_callback , (void * )(alarm ),
391
+ return libtock_alarm_at (now , max_ms_in_ticks , (libtock_alarm_callback )overflow_callback , (void * )(alarm ),
267
392
& (alarm -> alarm ));
268
393
} else {
269
394
// No overflows needed
0 commit comments