yiisoft / yiisoft/yii2-queue

TTR Does not work as advertised.

Open
#249 7 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

type:bug
Dominant language
PHP
Stars
1.1k
Forks
285
Avg merge
5d 3h
Merged PRs (30d)
2

Description

What steps will reproduce the problem?

Set up any queue with TTR 30.
Push a job that does this:

for ($i = 0; $i < 1000; $i++) {
    echo $i . "\n";
    sleep 1;
}

Run 2 queue runners.

What's expected?

Based on the docs:

The TTR option sets the time to reserve for job execution. If the execution of a job takes longer than this 
time, execution will be stopped and it will be returned to the queue for later retry. 
What do you get instead?

After the 30 seconds have passed the job is released by the queue (beanstalkd).
The second worker picks it up and starts executing it.
The first worker continues to execute it.

There are 2 solutions:

Proposal 1: Fix docs

Fixing the documentation will make it work as expected.

Proposal 2: Fix functionality

There are 2 methods of executing jobs, in-process and isolated process.
In the case of using an isolated process the solution is simple: kill it just before the TTR expires.
In case of in-process execution I have created a proof of concept to handle the TTR:

<?php
declare(ticks=1);

$end = microtime(true) + 5;
$handler = function() use ($end) {
    $remaining = $end - microtime(true);
    echo ".";
    if ($remaining < 0) {
        throw new \Exception('time limit exceeded');
    }
};
register_tick_function($handler);
try {
$i = 0;

    while ($i < 1000) {
        // This handler suppresses errors.
        try {
            echo "\$i is $i\n";
            sleep(1);
            $i++;
        } catch (\Throwable $t) {
//            echo "E: {$t->getMessage()}\n";
        }
    }
} catch(\Throwable $t) {
    unregister_tick_function($handler);
    echo "Caught: {$t->getMessage()}\n";
} finally {
    unregister_tick_function($handler);
}
..$i is 0
....$i is 1
....$i is 2
....$i is 3
....$i is 4
...Caught: time limit exceeded

The idea is simple:

  • Register a tick function that keeps track of time.
  • As soon as time is over we start throwing exceptions.
  • Job code might be catching exceptions, but as soon as their try clause is called we throw another one (this is repeated for nested try..catch).
  • Finally the exception reaches our own catch clause where immediately unregister the tick function.

This way we can guarantee that no 2 workers are executing the same task at any point in time.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the TTR 30 scenario with beanstalkd and two queue runners, then inspect the in-process and isolated-process execution paths described in the issue. Compare the current behavior with the TTR documentation and determine whether the accepted outcome is a documentation correction or enforcement that prevents both workers from executing the job.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.