Description
Expected Behaviour
In case config.register_lambda_context(...)
is called correctly before calling an idempotent_function
-decorated function, the record saved to the persistence layer should always contain an in_progress_expiration
field
Current Behaviour
It is an observed behavior of AWS that it kills lambda functions slightly after their timeout has been reached, e.g. after 300.68 seconds, if the lambda timeout is set to 300s.
This creates a time window of 0.68s long within which lambda_context.get_remaining_time_in_millis()
returns 0 (the true value would be negative).
However, this situation is misinterpreted by the DynamoDBPersistenceLayer as this value actually being None, which corresponds with the case of config.register_lambda_context(...)
not having been called correctly. As a result, the in_progress_expiration
field of the idempotency record is not set correctly.
If then the lambda times out before exiting the decorator function, the record will never bet set to status COMPLETED, nor will it ever expire (before the actual TTL expiration, which typically is much larger). This triggers an infinite loop of retries, each time encountering a IdempotencyAlreadyInProgressError
, similar to what is described here: #1038.
Code snippet
config = IdempotencyConfig(expires_after_seconds=24*60*60)
persistence_store = DynamoDBPersistenceLayer(table_name="my-idempotency-table")
@idempotent_function(
data_keyword_argument="task",
persistence_store=persistence_store,
config=config,
)
def process_my_task(task: dict) -> None:
...
if __name__=="__main__":
# create a dummy lambda context that reproduces a situation where we are very close
# (or slightly beyond) the lambda timeout
dummy_lambda_context = LambdaContext(
invoke_id=str(uuid4()),
client_context=dict(),
cognito_identity=dict(),
epoch_deadline_time_in_ms=int(1000 * time.time()) # fakes a lambda timeout = now
)
# call register_lambda_context as if we're in a lambda handler
config.register_lambda_context(dummy_lambda_context)
# call function -> this will trigger creation of an idempotency record without
# a 'in_progress_expiration' field
process_my_task(task=dict(do="some_stuff"))
Possible Solution
The bug seems to boil down to an improper check inside the BasePersistenceLayer
class (base.py, line 304 in my version of the package):
The check if remaining_time_in_millis
results in False both in case of None and 0, while it actually should only result in False when the value is None.
So my suggestion would be to replace this check with if remaining_time_in_millis is not None:
. This seemingly would solve the problem.
Steps to Reproduce
See code snippet.
Powertools for AWS Lambda (Python) version
2.38.1
AWS Lambda function runtime
3.11
Packaging format used
PyPi
Debugging logs
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
Status