You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5f37392
Browse filesBrowse the repository at this point in the historyBrowse files
Greptile found that a retry after the backoff staged a new effect with
a new ID, so a billing API that had already applied the update could
apply it again. Each pending update now carries a key made from the
customer ID and the version.
The accuracy review found claims that went beyond the sources: order
is "not guaranteed" rather than "not kept", a graceful Solid Queue stop
returns in-flight jobs, and Solid Objects does have a dashboard and
repeating reminders. A newer update now also waits for the backoff.
- The runtime stores each message in SQL before `async` returns. Messages for one customer run one at a time, in the order that the runtime stores them. A second message waits in the mailbox. It does not fail and retry.
188
189
-`update_subscription` appends the update to `pending`. It ignores a version that is not newer than the newest one it knows, so a repeated delivery changes nothing.
189
190
-`push_next` stages one effect for the oldest pending update. `pushing` stays true until that effect succeeds or fails. So the billing API never gets two pushes for one customer at the same time, and it gets the versions in order.
190
-
-The effect handler uses `context.id` as the idempotency key. The effect can run more than once after a crash, but the ID stays the same.
191
+
-Each pending update carries its own idempotency key, made from the customer ID and the version. The effect handler sends that key. So the billing API applies an update once, even when a crash runs the effect again or a backoff reminder stages a new effect.
191
192
- The runtime retries a failed effect with a short delay (5 attempts by default). If every attempt fails, `push_failed` counts the failure and schedules `retry_push` with backoff: 2, 4, 8 minutes, up to 60 minutes. The update stays first in `pending`, so it keeps its place. A newer update waits for the retry too.
192
193
- The reminder is a row in the database. It runs after a deploy or a restart.
193
194
@@ -198,6 +199,7 @@ How it works:
198
199
- Three updates for one customer reach the billing API one at a time, as versions 1, 2, and 3.
199
200
- Two customers each have a staged push at the same time. One customer does not wait for the other.
200
201
- A push that runs again after a lost response uses the same idempotency key, and the billing API applies it once.
202
+
- A push that runs again after the backoff also uses the same idempotency key, and the billing API applies it once.
201
203
- A repeated or older update sends no push to the billing API.
202
204
- A newer update that arrives during the backoff waits for the retry.
203
205
- A failed push records the retry count and the next retry time. After the test resets the caller process and moves the clock 3 minutes ahead, the reminder runs, the push succeeds, and the retry count goes back to 0.
@@ -216,7 +218,7 @@ GoodJob is older and has more users. If that matters more than order for each cu
216
218
217
219
**Does it need another process?** Yes. `bundle exec solid_objects start` runs async messages, effects, and reminders. GoodJob in external mode also needs a process: `bundle exec good_job start`. While the Solid Objects process is down, pending work stays in SQL.
218
220
219
-
**Is delivery exactly once?** No. Delivery is at least once. A turn can run again after a crash. Use the effect ID as the idempotency key for each external call, as the example does. See [correctness and delivery semantics](../correctness.md).
221
+
**Is delivery exactly once?** No. Delivery is at least once. A turn can run again after a crash. Give each external call a stable idempotency key. The effect ID (`context.id`) is stable across the retries of one effect. The example stores a key with each update, so the key also survives a backoff retry. See [correctness and delivery semantics](../correctness.md).
220
222
221
223
**Can actor code write my tables?** Not directly. An actor reads application records. To write a row in the same transaction as the actor state, use a commit action. The [ordered jobs guide](../guides/ordered-jobs.md) shows one.
- The runtime stores each message in SQL before `async` returns. Messages for one customer run one at a time, in the order that the runtime stores them.
183
184
-`update_subscription` appends the update to `pending`. It ignores a version that is not newer than the newest one it knows, so a repeated delivery changes nothing.
184
185
-`push_next` stages one effect for the oldest pending update. `pushing` stays true until that effect succeeds or fails. So the billing API never gets two pushes for one customer at the same time, and it gets the versions in order.
185
-
-The effect handler uses `context.id` as the idempotency key. The effect can run more than once after a crash, but the ID stays the same.
186
+
-Each pending update carries its own idempotency key, made from the customer ID and the version. The effect handler sends that key. So the billing API applies an update once, even when a crash runs the effect again or a backoff reminder stages a new effect.
186
187
- The runtime retries a failed effect with a short delay (5 attempts by default). If every attempt fails, `push_failed` counts the failure and schedules `retry_push` with backoff: 2, 4, 8 minutes, up to 60 minutes. The update stays first in `pending`, so it keeps its place. A newer update waits for the retry too.
187
188
- The reminder is a row in the database. It runs after a deploy or a restart.
188
189
@@ -193,6 +194,7 @@ How it works:
193
194
- Three updates for one customer reach the billing API one at a time, as versions 1, 2, and 3.
194
195
- Two customers each have a staged push at the same time. One customer does not wait for the other.
195
196
- A push that runs again after a lost response uses the same idempotency key, and the billing API applies it once.
197
+
- A push that runs again after the backoff also uses the same idempotency key, and the billing API applies it once.
196
198
- A repeated or older update sends no push to the billing API.
197
199
- A newer update that arrives during the backoff waits for the retry.
198
200
- A failed push records the retry count and the next retry time. After the test resets the caller process and moves the clock 3 minutes ahead, the reminder runs, the push succeeds, and the retry count goes back to 0.
@@ -211,7 +213,7 @@ Solid Queue is older and has more users. If that matters more than order for eac
211
213
212
214
**Does it need another process?** Yes. `bundle exec solid_objects start` runs async messages, effects, and reminders. Solid Queue also needs a process: `bin/jobs` or the Puma plugin. While the Solid Objects process is down, pending work stays in SQL.
213
215
214
-
**Is delivery exactly once?** No. Delivery is at least once. A turn can run again after a crash. Use the effect ID as the idempotency key for each external call, as the example does. See [correctness and delivery semantics](../correctness.md).
216
+
**Is delivery exactly once?** No. Delivery is at least once. A turn can run again after a crash. Give each external call a stable idempotency key. The effect ID (`context.id`) is stable across the retries of one effect. The example stores a key with each update, so the key also survives a backoff retry. See [correctness and delivery semantics](../correctness.md).
215
217
216
218
**Can actor code write my tables?** Not directly. An actor reads application records. To write a row in the same transaction as the actor state, use a commit action. The [ordered jobs guide](../guides/ordered-jobs.md) shows one.
0 commit comments