pebble/io/outbox_queue.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file outbox_queue.h | ||
| 3 | * @brief The outbox's work queue, its failed set, and the slot holding whatever is in flight. | ||
| 4 | * | ||
| 5 | * One AppMessage goes out at a time, so everything the watch wants to send queues here first. A | ||
| 6 | * job the phone nacks is held in the failed set and moved back for a later pass, because its pkjs | ||
| 7 | * was usually asleep or busy and that first send is often what wakes it. Holding it means a whole | ||
| 8 | * poll, or the one settings reply a launch gets, is not lost to one nack. | ||
| 9 | * | ||
| 10 | * None of that needs the SDK. The transport owns one of these, does the sending and the timers, and | ||
| 11 | * asks here what to send next, which keeps the deciding testable on the host. | ||
| 12 | * | ||
| 13 | * @ingroup lib_io | ||
| 14 | */ | ||
| 15 | #pragma once | ||
| 16 | #include <stdbool.h> | ||
| 17 | |||
| 18 | /** | ||
| 19 | * @addtogroup lib_io | ||
| 20 | * @{ | ||
| 21 | */ | ||
| 22 | |||
| 23 | /// Most jobs the work queue or the failed set holds at once | ||
| 24 | #define OUTBOX_QUEUE_MAX 6 | ||
| 25 | |||
| 26 | /** | ||
| 27 | * @brief What an outbox job sends. | ||
| 28 | */ | ||
| 29 | typedef enum | ||
| 30 | { | ||
| 31 | OUTBOX_NONE, ///< The zero value, so a default-initialized job slot reads as nothing rather than a real request | ||
| 32 | OUTBOX_WEATHER, ///< A weather request | ||
| 33 | OUTBOX_SETTINGS, ///< The settings reply | ||
| 34 | OUTBOX_FRESH, ///< The short settings reply, only whether the watch booted with nothing saved | ||
| 35 | OUTBOX_STOCK, ///< A stock request | ||
| 36 | OUTBOX_CALENDAR ///< A calendar request | ||
| 37 | } OutboxKind; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * @brief One job waiting to go out, or already in flight. | ||
| 41 | */ | ||
| 42 | typedef struct | ||
| 43 | { | ||
| 44 | OutboxKind kind; ///< What this job sends | ||
| 45 | int retries_left; ///< Retry passes left after a failed send | ||
| 46 | } OutboxJob; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * @brief Everything the outbox is holding: what is waiting, what nacked, and what is out. | ||
| 50 | * | ||
| 51 | * Zero-initialized is the empty state, since `OUTBOX_NONE` is the enum's zero. | ||
| 52 | */ | ||
| 53 | typedef struct | ||
| 54 | { | ||
| 55 | OutboxJob queue[OUTBOX_QUEUE_MAX]; ///< This pass's work queue, oldest first | ||
| 56 | int queue_len; ///< How many jobs are in the queue | ||
| 57 | OutboxJob failed[OUTBOX_QUEUE_MAX]; ///< Requests that nacked this pass, held for the next | ||
| 58 | int failed_len; ///< How many jobs are in the failed set | ||
| 59 | OutboxJob inflight; ///< The job in flight, OUTBOX_NONE when nothing is out | ||
| 60 | } OutboxQueue; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * @brief Whether anything is in flight, which is what says the outbox is busy. | ||
| 64 | * | ||
| 65 | * @param outbox The outbox to read. | ||
| 66 | * @return True while a send is waiting on its sent or failed callback. | ||
| 67 | */ | ||
| 68 | 6 | static inline bool outbox_busy(const OutboxQueue *outbox) | |
| 69 | { | ||
| 70 | 6 | return outbox->inflight.kind != OUTBOX_NONE; | |
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * @brief Whether this kind is already somewhere in the outbox. | ||
| 75 | * | ||
| 76 | * Covers all three places a job can be, so a poll that repeats while an earlier one is still | ||
| 77 | * working its way out does not queue a second copy of the same request. | ||
| 78 | * | ||
| 79 | * @param outbox The outbox to read. | ||
| 80 | * @param kind The job kind to look for. | ||
| 81 | * @return True when that kind is in flight, queued, or held after a nack. | ||
| 82 | */ | ||
| 83 | 17 | static inline bool outbox_pending(const OutboxQueue *outbox, OutboxKind kind) | |
| 84 | { | ||
| 85 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 16 times.
|
17 | if (kind == OUTBOX_NONE) |
| 86 | { | ||
| 87 | // the empty flight slot and every unused array entry read as OUTBOX_NONE, so without this | ||
| 88 | // an idle outbox would answer that nothing is already pending | ||
| 89 | 1 | return false; | |
| 90 | } | ||
| 91 | |||
| 92 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 15 times.
|
16 | if (outbox->inflight.kind == kind) |
| 93 | { | ||
| 94 | 1 | return true; | |
| 95 | } | ||
| 96 | |||
| 97 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 14 times.
|
25 | for (int i = 0; i < outbox->queue_len; i++) |
| 98 | { | ||
| 99 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 10 times.
|
11 | if (outbox->queue[i].kind == kind) |
| 100 | { | ||
| 101 | 1 | return true; | |
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 13 times.
|
14 | for (int i = 0; i < outbox->failed_len; i++) |
| 106 | { | ||
| 107 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | if (outbox->failed[i].kind == kind) |
| 108 | { | ||
| 109 | 1 | return true; | |
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | 13 | return false; | |
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 117 | * @brief Add a job to the work queue unless its kind is already pending. | ||
| 118 | * | ||
| 119 | * @param outbox The outbox to add to. | ||
| 120 | * @param kind The job kind to send. | ||
| 121 | * @param retries Retry passes left if the first send nacks. | ||
| 122 | * @return True when the job was added, false when it was already pending or the queue is full. | ||
| 123 | */ | ||
| 124 | 16 | static inline bool outbox_push(OutboxQueue *outbox, OutboxKind kind, int retries) | |
| 125 | { | ||
| 126 |
4/4✓ Branch 1 taken 13 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 12 times.
|
16 | if (outbox_pending(outbox, kind) || outbox->queue_len >= OUTBOX_QUEUE_MAX) |
| 127 | { | ||
| 128 | 4 | return false; | |
| 129 | } | ||
| 130 | |||
| 131 | 12 | outbox->queue[outbox->queue_len].kind = kind; | |
| 132 | 12 | outbox->queue[outbox->queue_len].retries_left = retries; | |
| 133 | 12 | outbox->queue_len++; | |
| 134 | 12 | return true; | |
| 135 | } | ||
| 136 | |||
| 137 | /** | ||
| 138 | * @brief Drop the front job off the work queue, shifting the rest forward. | ||
| 139 | * | ||
| 140 | * @param outbox The outbox to drop from. | ||
| 141 | */ | ||
| 142 | 6 | static inline void outbox_pop_front(OutboxQueue *outbox) | |
| 143 | { | ||
| 144 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | for (int i = 1; i < outbox->queue_len; i++) |
| 145 | { | ||
| 146 | 2 | outbox->queue[i - 1] = outbox->queue[i]; | |
| 147 | } | ||
| 148 | |||
| 149 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
|
6 | if (outbox->queue_len > 0) |
| 150 | { | ||
| 151 | 5 | outbox->queue_len--; | |
| 152 | } | ||
| 153 | 6 | } | |
| 154 | |||
| 155 | /** | ||
| 156 | * @brief Take the head of the queue as the job now in flight. | ||
| 157 | * | ||
| 158 | * The caller sends it first and only takes it once the send was accepted, so a refused send leaves | ||
| 159 | * the head where it was. | ||
| 160 | * | ||
| 161 | * @param outbox The outbox to take from. | ||
| 162 | */ | ||
| 163 | 5 | static inline void outbox_take_head(OutboxQueue *outbox) | |
| 164 | { | ||
| 165 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
|
5 | if (outbox->queue_len == 0) |
| 166 | { | ||
| 167 | 1 | return; // outbox_clear leaves the array behind, so an empty queue still has one to read | |
| 168 | } | ||
| 169 | |||
| 170 | 4 | outbox->inflight = outbox->queue[0]; | |
| 171 | 4 | outbox_pop_front(outbox); | |
| 172 | } | ||
| 173 | |||
| 174 | /** | ||
| 175 | * @brief Mark whatever was in flight as settled, whichever way it went. | ||
| 176 | * | ||
| 177 | * @param outbox The outbox to clear. | ||
| 178 | * @return The job that was in flight, so a nacked one can be handed to outbox_hold_failed. | ||
| 179 | */ | ||
| 180 | 1 | static inline OutboxJob outbox_release(OutboxQueue *outbox) | |
| 181 | { | ||
| 182 | 1 | OutboxJob settled = outbox->inflight; | |
| 183 | 1 | outbox->inflight.kind = OUTBOX_NONE; | |
| 184 | 1 | return settled; | |
| 185 | } | ||
| 186 | |||
| 187 | /** | ||
| 188 | * @brief Hold a nacked job for the next pass, if it still has retries. | ||
| 189 | * | ||
| 190 | * @param outbox The outbox to hold it in. | ||
| 191 | * @param job The job that just nacked. | ||
| 192 | */ | ||
| 193 | 13 | static inline void outbox_hold_failed(OutboxQueue *outbox, OutboxJob job) | |
| 194 | { | ||
| 195 | // a failure reported once the slot is already free comes with no job, and holding it would | ||
| 196 | // queue a send for nothing ahead of the real requests | ||
| 197 |
4/4✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
|
13 | if (job.kind == OUTBOX_NONE || job.retries_left <= 0) |
| 198 | { | ||
| 199 | 3 | return; | |
| 200 | } | ||
| 201 | |||
| 202 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (outbox->failed_len < OUTBOX_QUEUE_MAX) |
| 203 | { | ||
| 204 | 10 | outbox->failed[outbox->failed_len].kind = job.kind; | |
| 205 | 10 | outbox->failed[outbox->failed_len].retries_left = job.retries_left - 1; | |
| 206 | 10 | outbox->failed_len++; | |
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | /** | ||
| 211 | * @brief Move the whole failed set back into the work queue for another pass. | ||
| 212 | * | ||
| 213 | * @param outbox The outbox to move within. | ||
| 214 | * @return How many jobs were moved back. | ||
| 215 | */ | ||
| 216 | 1 | static inline int outbox_retry_pass(OutboxQueue *outbox) | |
| 217 | { | ||
| 218 | 1 | int moved = 0; | |
| 219 | |||
| 220 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
3 | while (outbox->failed_len > 0 && outbox->queue_len < OUTBOX_QUEUE_MAX) |
| 221 | { | ||
| 222 | 2 | outbox->queue[outbox->queue_len++] = outbox->failed[0]; | |
| 223 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
|
3 | for (int i = 1; i < outbox->failed_len; i++) |
| 224 | { | ||
| 225 | 1 | outbox->failed[i - 1] = outbox->failed[i]; | |
| 226 | } | ||
| 227 | 2 | outbox->failed_len--; | |
| 228 | 2 | moved++; | |
| 229 | } | ||
| 230 | |||
| 231 | 1 | return moved; | |
| 232 | } | ||
| 233 | |||
| 234 | /** | ||
| 235 | * @brief Forget everything waiting, for a phone that is no longer there. | ||
| 236 | * | ||
| 237 | * Nothing reaches a phone that is not connected, so a store's next poll asks again once it is back | ||
| 238 | * rather than a stale queue draining into it. | ||
| 239 | * | ||
| 240 | * @param outbox The outbox to empty. | ||
| 241 | */ | ||
| 242 | 1 | static inline void outbox_clear(OutboxQueue *outbox) | |
| 243 | { | ||
| 244 | 1 | outbox->queue_len = 0; | |
| 245 | 1 | outbox->failed_len = 0; | |
| 246 | 1 | } | |
| 247 | |||
| 248 | /** @} */ | ||
| 249 |