-
-
Notifications
You must be signed in to change notification settings - Fork 357
Description
If a message is dropped because of the Age Limit middleware, then getting the result of that message will be inconsistent depending on how soon after the message is dropped an attempt to get the result is made.
Not sure what the resolution should be here. I think it should either consistently store a None result, or consistently store an exception that is not Exception("unknown").
What OS are you using?
Ubuntu 20.04
What version of Dramatiq are you using?
1.11.0
What did you do?
Use a broker with the AgeLimit and Results middleware. Have a message hit its age limit and get its result in a blocking get_result call. Get the result for the same message again a second or two after the blocking get_result returns a result.
What did you expect would happen?
The result should not change.
What happened?
On the first get_result, the result is None.
On the second get_result, retrieving the result raises a ResultFailure exception with exception Exception("unknown")
This appears to be because:
- When the Age Limit expires, the AgeLimit middleware sets the
failedflag on the MessageProxy before the message is processeddramatiq/dramatiq/middleware/age_limit.py
Lines 41 to 50 in e1f3245
def before_process_message(self, broker, message): actor = broker.get_actor(message.actor_name) max_age = message.options.get("max_age") or actor.options.get("max_age", self.max_age) if not max_age: return if current_millis() - message.message_timestamp >= max_age: self.logger.warning("Message %r has exceeded its age limit.", message.message_id) message.fail() return - In worker.py this causes and emit_after of "process_message" with a result of None
Line 493 in e1f3245
self.broker.emit_after("process_message", message, result=res) - The Results middleware's
after_process_messagewill then store this None result and no exceptiondramatiq/dramatiq/results/middleware.py
Line 95 in e1f3245
self.backend.store_result(message, result, result_ttl) - After this happens, worker.py still has some post-processing of the message to do. Since the message was marked with failed, post_process_message will do an
emit_afterof "nack"Line 347 in e1f3245
self.broker.emit_after("nack", message) - Since there was no exception, the results middleware will then replace the result at the message key in the result store with
dramatiq/dramatiq/results/middleware.py
Lines 105 to 109 in e1f3245
def after_nack(self, broker, message): store_results, result_ttl = self._lookup_options(broker, message) if store_results and message.failed: exception = message._exception or Exception("unknown") self.backend.store_exception(message, exception, result_ttl)
Sample Code
import dramatiq
import time
from dramatiq.brokers.rabbitmq import RabbitmqBroker
from dramatiq.results.backends import RedisBackend
from dramatiq.results import Results
result_backend = RedisBackend()
broker = RabbitmqBroker()
broker.add_middleware(Results(backend=result_backend))
dramatiq.set_broker(broker)
@dramatiq.actor(store_results=True, max_age=1)
def sample_actor():
return 42
if __name__ == "__main__":
message = sample_actor.send()
result_1 = message.get_result(block=True, timeout=2000)
print(result_1)
time.sleep(2)
# An Exception("unknown") will be raised here.
result_2 = message.get_result()
print(result_2)