Delayed execution
Since 2.0 a message can carry executeAt, a microtime(true) timestamp. The fourth constructor argument is created. The fifth is executeAt.
$executeAt = microtime(true) + 60;
$message = new Message('send-email', [
'to' => 'test@test.com',
'subject' => 'Later',
'message' => 'Hello from hermes!',
], executeAt: $executeAt);
$emitter->emit($message);Supported drivers are RedisSetDriver and PredisSetDriver. LazyRabbitMqDriver::send() and AmazonSqsDriver::send() do not read getExecuteAt().
Redis extension
send() delays when getExecuteAt() is not null and is greater than microtime(true):
$this->redis->zAdd($this->scheduleKey, $message->getExecuteAt(), $serialized);The default schedule key is hermes_schedule. Change it with the fourth constructor argument if the default key is already used. The emitter and the worker must use the same key.
Each wait() iteration calls zRangeByScore($scheduleKey, '-inf', $now, ['limit' => [0, 1]]). For each returned member it calls zRem(). Only when ZREM reports that it removed the member does the driver re-send it, so two workers never promote the same entry. Up to 5.x this step used SPOP, which does not work on a sorted set, so due messages stayed in the schedule (issue #95).
After a successful ZREM the driver unserializes it and calls send() with no priority. send() sees the same executeAt. When that timestamp is still in the future the payload goes back to the sorted set. When it is due, send() writes the default priority set (hermes unless you changed the key). The original priority is not stored on the message, so a delayed message does not return to hermes_high.
Predis
send() delays when getExecuteAt() is truthy and greater than now. The write is zadd($scheduleKey, [$serialized => $executeAt]).
wait() reads with zrangebyscore and LIMIT OFFSET 0 COUNT 1, then zrems that exact member, then send()s it. That zrem is the 4.0.1 fix for issue #48. Promotion still uses send() without a priority, so the due message joins the default set.
Retry uses the same field
Dispatcher sets executeAt from nextRetry() and calls send(). On Redis and Predis the retry waits. On RabbitMQ and SQS it does not. The formula is on the Retry page.
FAQ
How do I delay a message?
Pass a Unix timestamp as the fifth Message argument, executeAt, for example microtime(true) + 3600. The delayed reminder recipe shows a complete example.
Which drivers support delays?
RedisSetDriver and PredisSetDriver. Both promote due messages with ZREM. In 5.x RedisSetDriver never promoted them (issue #95), so use Predis there. RabbitMQ and SQS deliver at once.
Where are delayed messages stored?
In the sorted set hermes_schedule by default, scored by executeAt. The emitter and the worker must use the same schedule key.
Does a delayed message keep its priority?
No. When it is due, the driver calls send() without a priority, so it lands on the default priority 100.
What if no worker is running when the message is due?
Nothing is lost. Due messages are moved only inside wait(), so the message runs as soon as a worker starts.