mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Fix an issue where PhabricatorWorkerLeaseQuery may lease different tasks than it selects
Summary: We lock tasks by setting `leaseOwner` to a unique value, but the value is currently unique-to-the-process rather than unique-to-the-query. This means that if a process leases a task, then leases another task, both tasks will have the same `leaseOwner`. This can cause an issue where we go to select the task we just leased and get the other task instead, if we aren't careful about the select construction. We can avoid this by being clever and making sure the select is constructed correctly, but making the `leaseOwner` unique to the query is much simpler and more foolproof. This guarantees we always select only the rows we just leased. Also remove `PhabricatorGoodForNothingWorker` since `PhabricatorTestWorker` fills its role of allowing things to be tested, and simplify the unit tests since we don't need to be clever about avoiding this issue any more. Test Plan: Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T2015 Differential Revision: https://secure.phabricator.com/D3862
This commit is contained in:
parent
f0fdcf1a51
commit
0364ccdd5f
4 changed files with 16 additions and 45 deletions
|
@ -775,7 +775,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/PhabricatorGarbageCollectorDaemon.php',
|
'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/PhabricatorGarbageCollectorDaemon.php',
|
||||||
'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
|
'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
|
||||||
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
|
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
|
||||||
'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/PhabricatorGoodForNothingWorker.php',
|
|
||||||
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
|
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
|
||||||
'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php',
|
'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php',
|
||||||
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
|
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
|
||||||
|
@ -1982,7 +1981,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFormExample' => 'PhabricatorUIExample',
|
'PhabricatorFormExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
|
'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
|
||||||
'PhabricatorGlobalLock' => 'PhutilLock',
|
'PhabricatorGlobalLock' => 'PhutilLock',
|
||||||
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
|
|
||||||
'PhabricatorHeaderView' => 'AphrontView',
|
'PhabricatorHeaderView' => 'AphrontView',
|
||||||
'PhabricatorHelpController' => 'PhabricatorController',
|
'PhabricatorHelpController' => 'PhabricatorController',
|
||||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||||
|
|
|
@ -47,7 +47,7 @@ final class PhabricatorWorkerTestCase extends PhabricatorTestCase {
|
||||||
$task1 = $this->scheduleTask();
|
$task1 = $this->scheduleTask();
|
||||||
$task2 = $this->scheduleTask();
|
$task2 = $this->scheduleTask();
|
||||||
|
|
||||||
$this->expectNextLease($task1)->executeTask();
|
$this->expectNextLease($task1);
|
||||||
$this->expectNextLease($task2);
|
$this->expectNextLease($task2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ final class PhabricatorWorkerTestCase extends PhabricatorTestCase {
|
||||||
$task1->setLeaseExpires(time() - 100000);
|
$task1->setLeaseExpires(time() - 100000);
|
||||||
$task1->forceSaveWithoutLease();
|
$task1->forceSaveWithoutLease();
|
||||||
|
|
||||||
$this->expectNextLease($task2)->executeTask();
|
$this->expectNextLease($task2);
|
||||||
$this->expectNextLease($task1);
|
$this->expectNextLease($task1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
|
||||||
|
|
||||||
const PHASE_UNLEASED = 'unleased';
|
const PHASE_UNLEASED = 'unleased';
|
||||||
const PHASE_EXPIRED = 'expired';
|
const PHASE_EXPIRED = 'expired';
|
||||||
const PHASE_SELECT = 'select';
|
|
||||||
|
|
||||||
const DEFAULT_LEASE_DURATION = 60; // Seconds
|
const DEFAULT_LEASE_DURATION = 60; // Seconds
|
||||||
|
|
||||||
|
@ -92,10 +91,11 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
|
||||||
'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime
|
'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime
|
||||||
FROM %T task LEFT JOIN %T taskdata
|
FROM %T task LEFT JOIN %T taskdata
|
||||||
ON taskdata.id = task.dataID
|
ON taskdata.id = task.dataID
|
||||||
%Q %Q %Q',
|
WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP()
|
||||||
|
%Q %Q',
|
||||||
$task_table->getTableName(),
|
$task_table->getTableName(),
|
||||||
$taskdata_table->getTableName(),
|
$taskdata_table->getTableName(),
|
||||||
$this->buildWhereClause($conn_w, self::PHASE_SELECT),
|
$lease_ownership_name,
|
||||||
$this->buildOrderClause($conn_w),
|
$this->buildOrderClause($conn_w),
|
||||||
$this->buildLimitClause($conn_w, $limit));
|
$this->buildLimitClause($conn_w, $limit));
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildWhereClause(AphrontDatabaseConnection $conn_w, $phase) {
|
private function buildWhereClause(AphrontDatabaseConnection $conn_w, $phase) {
|
||||||
|
|
||||||
$where = array();
|
$where = array();
|
||||||
|
|
||||||
switch ($phase) {
|
switch ($phase) {
|
||||||
|
@ -125,13 +126,6 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
|
||||||
case self::PHASE_EXPIRED:
|
case self::PHASE_EXPIRED:
|
||||||
$where[] = 'leaseExpires < UNIX_TIMESTAMP()';
|
$where[] = 'leaseExpires < UNIX_TIMESTAMP()';
|
||||||
break;
|
break;
|
||||||
case self::PHASE_SELECT:
|
|
||||||
$where[] = qsprintf(
|
|
||||||
$conn_w,
|
|
||||||
'leaseOwner = %s',
|
|
||||||
$this->getLeaseOwnershipName());
|
|
||||||
$where[] = 'leaseExpires > UNIX_TIMESTAMP()';
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new Exception("Unknown phase '{$phase}'!");
|
throw new Exception("Unknown phase '{$phase}'!");
|
||||||
}
|
}
|
||||||
|
@ -155,11 +149,16 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getLeaseOwnershipName() {
|
private function getLeaseOwnershipName() {
|
||||||
static $name = null;
|
static $sequence = 0;
|
||||||
if ($name === null) {
|
|
||||||
$name = getmypid().':'.time().':'.php_uname('n');
|
$parts = array(
|
||||||
}
|
getmypid(),
|
||||||
return $name;
|
time(),
|
||||||
|
php_uname('n'),
|
||||||
|
++$sequence,
|
||||||
|
);
|
||||||
|
|
||||||
|
return implode(':', $parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2012 Facebook, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trivial example worker; processes tasks which require no work very slowly.
|
|
||||||
*/
|
|
||||||
final class PhabricatorGoodForNothingWorker extends PhabricatorWorker {
|
|
||||||
protected function doWork() {
|
|
||||||
sleep(10);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue