Commit 8ee7639
committed
fix(idempotency): Redis persistence layer reclaims live in-progress records as orphans, allowing concurrent double-execution
RedisCachePersistenceLayer._put_in_progress_record() only guarded against a
competing invocation when the existing record's in_progress_expiry_timestamp
was set AND still in the future. When that field is None -- which is the
normal case for idempotent_function, since it never calls
config.register_lambda_context(), unlike the idempotent() handler decorator --
the guard was skipped entirely and a genuinely still-running invocation's
record fell through to the "orphan record" branch, which unconditionally
overwrites the record with no NX guard. A second concurrent invocation with
the same idempotency key would then proceed to execute the underlying
function too, defeating idempotency (e.g. double-charging a customer).
This is exactly what persistence/base.py's own warning at that call site
flags ("Couldn't determine the remaining time left. Did you call
register_lambda_context on IdempotencyConfig?") -- it warns, but nothing
downstream actually failed closed on it.
The DynamoDB persistence layer does not have this gap: its conditional
expression requires attribute_exists(#in_progress_expiry) before allowing an
expired-in-progress reclaim, so a missing attribute correctly blocks the
competing writer instead of granting a reclaim.
Fix: when status is INPROGRESS and in_progress_expiry_timestamp is None,
treat the record as still in progress (fail closed) instead of falling
through to the orphan-reclaim path, mirroring the DynamoDB layer's behavior.1 parent 8db13c7 commit 8ee7639
2 files changed
Lines changed: 45 additions & 4 deletions
File tree
- aws_lambda_powertools/utilities/idempotency/persistence
- tests/functional/idempotency/_redis
Lines changed: 11 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
425 | 425 | | |
426 | 426 | | |
427 | 427 | | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
432 | 439 | | |
433 | 440 | | |
434 | 441 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
201 | 213 | | |
202 | 214 | | |
203 | 215 | | |
| |||
303 | 315 | | |
304 | 316 | | |
305 | 317 | | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
306 | 340 | | |
307 | 341 | | |
308 | 342 | | |
| |||
0 commit comments