Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14329445
D19174.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D19174.diff
View Options
diff --git a/bin/lock b/bin/lock
new file mode 120000
--- /dev/null
+++ b/bin/lock
@@ -0,0 +1 @@
+../scripts/setup/manage_lock.php
\ No newline at end of file
diff --git a/resources/sql/autopatches/20180305.lock.01.locklog.sql b/resources/sql/autopatches/20180305.lock.01.locklog.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20180305.lock.01.locklog.sql
@@ -0,0 +1,9 @@
+CREATE TABLE {$NAMESPACE}_daemon.daemon_locklog (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ lockName VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
+ lockReleased INT UNSIGNED,
+ lockParameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ lockContext LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/scripts/setup/manage_lock.php b/scripts/setup/manage_lock.php
new file mode 100755
--- /dev/null
+++ b/scripts/setup/manage_lock.php
@@ -0,0 +1,21 @@
+#!/usr/bin/env php
+<?php
+
+$root = dirname(dirname(dirname(__FILE__)));
+require_once $root.'/scripts/init/init-script.php';
+
+$args = new PhutilArgumentParser($argv);
+$args->setTagline(pht('manage locks'));
+$args->setSynopsis(<<<EOSYNOPSIS
+**lock** __command__ [__options__]
+ Manage locks.
+
+EOSYNOPSIS
+ );
+$args->parseStandardArguments();
+
+$workflows = id(new PhutilClassMapQuery())
+ ->setAncestorClass('PhabricatorLockManagementWorkflow')
+ ->execute();
+$workflows[] = new PhutilHelpArgumentWorkflow();
+$args->parseWorkflows($workflows);
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2670,6 +2670,8 @@
'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php',
'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php',
'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php',
+ 'PhabricatorDaemonLockLog' => 'applications/daemon/storage/PhabricatorDaemonLockLog.php',
+ 'PhabricatorDaemonLockLogGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLockLogGarbageCollector.php',
'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php',
'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php',
'PhabricatorDaemonLogEventGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php',
@@ -3204,6 +3206,8 @@
'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php',
'PhabricatorLocaleScopeGuardTestCase' => 'infrastructure/internationalization/scope/__tests__/PhabricatorLocaleScopeGuardTestCase.php',
+ 'PhabricatorLockLogManagementWorkflow' => 'applications/daemon/management/PhabricatorLockLogManagementWorkflow.php',
+ 'PhabricatorLockManagementWorkflow' => 'applications/daemon/management/PhabricatorLockManagementWorkflow.php',
'PhabricatorLogTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorLogTriggerAction.php',
'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php',
'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php',
@@ -8194,6 +8198,8 @@
'PhabricatorDaemonController' => 'PhabricatorController',
'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO',
'PhabricatorDaemonEventListener' => 'PhabricatorEventListener',
+ 'PhabricatorDaemonLockLog' => 'PhabricatorDaemonDAO',
+ 'PhabricatorDaemonLockLogGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorDaemonLog' => array(
'PhabricatorDaemonDAO',
'PhabricatorPolicyInterface',
@@ -8794,6 +8800,8 @@
'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase',
'PhabricatorLocaleScopeGuard' => 'Phobject',
'PhabricatorLocaleScopeGuardTestCase' => 'PhabricatorTestCase',
+ 'PhabricatorLockLogManagementWorkflow' => 'PhabricatorLockManagementWorkflow',
+ 'PhabricatorLockManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorLogTriggerAction' => 'PhabricatorTriggerAction',
'PhabricatorLogoutController' => 'PhabricatorAuthController',
'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule',
diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLockLogGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLockLogGarbageCollector.php
new file mode 100644
--- /dev/null
+++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLockLogGarbageCollector.php
@@ -0,0 +1,29 @@
+<?php
+
+final class PhabricatorDaemonLockLogGarbageCollector
+ extends PhabricatorGarbageCollector {
+
+ const COLLECTORCONST = 'daemon.lock-log';
+
+ public function getCollectorName() {
+ return pht('Lock Logs');
+ }
+
+ public function getDefaultRetentionPolicy() {
+ return 0;
+ }
+
+ protected function collectGarbage() {
+ $table = new PhabricatorDaemonLockLog();
+ $conn = $table->establishConnection('w');
+
+ queryfx(
+ $conn,
+ 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100',
+ $table->getTableName(),
+ $this->getGarbageEpoch());
+
+ return ($conn->getAffectedRows() == 100);
+ }
+
+}
diff --git a/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php b/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/daemon/management/PhabricatorLockLogManagementWorkflow.php
@@ -0,0 +1,222 @@
+<?php
+
+final class PhabricatorLockLogManagementWorkflow
+ extends PhabricatorLockManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('log')
+ ->setSynopsis(pht('Enable, disable, or show the lock log.'))
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'enable',
+ 'help' => pht('Enable the lock log.'),
+ ),
+ array(
+ 'name' => 'disable',
+ 'help' => pht('Disable the lock log.'),
+ ),
+ array(
+ 'name' => 'name',
+ 'param' => 'name',
+ 'help' => pht('Review logs for a specific lock.'),
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $is_enable = $args->getArg('enable');
+ $is_disable = $args->getArg('disable');
+
+ if ($is_enable && $is_disable) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not both "--enable" and "--disable" the lock log.'));
+ }
+
+ $with_name = $args->getArg('name');
+
+ if ($is_enable || $is_disable) {
+ if (strlen($with_name)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not both "--enable" or "--disable" with search '.
+ 'parameters like "--name".'));
+ }
+
+ $gc = new PhabricatorDaemonLockLogGarbageCollector();
+ $is_enabled = (bool)$gc->getRetentionPolicy();
+
+ $config_key = 'phd.garbage-collection';
+ $const = $gc->getCollectorConstant();
+ $value = PhabricatorEnv::getEnvConfig($config_key);
+
+ if ($is_disable) {
+ if (!$is_enabled) {
+ echo tsprintf(
+ "%s\n",
+ pht('Lock log is already disabled.'));
+ return 0;
+ }
+ echo tsprintf(
+ "%s\n",
+ pht('Disabling the lock log.'));
+
+ unset($value[$const]);
+ } else {
+ if ($is_enabled) {
+ echo tsprintf(
+ "%s\n",
+ pht('Lock log is already enabled.'));
+ return 0;
+ }
+ echo tsprintf(
+ "%s\n",
+ pht('Enabling the lock log.'));
+
+ $value[$const] = phutil_units('24 hours in seconds');
+ }
+
+ id(new PhabricatorConfigLocalSource())
+ ->setKeys(
+ array(
+ $config_key => $value,
+ ));
+
+ echo tsprintf(
+ "%s\n",
+ pht('Done.'));
+
+ echo tsprintf(
+ "%s\n",
+ pht('Restart daemons to apply changes.'));
+
+ return 0;
+ }
+
+ $table = new PhabricatorDaemonLockLog();
+ $conn = $table->establishConnection('r');
+
+ $parts = array();
+ if (strlen($with_name)) {
+ $parts[] = qsprintf(
+ $conn,
+ 'lockName = %s',
+ $with_name);
+ }
+
+ if (!$parts) {
+ $constraint = '1 = 1';
+ } else {
+ $constraint = '('.implode(') AND (', $parts).')';
+ }
+
+ $logs = $table->loadAllWhere(
+ '%Q ORDER BY id DESC LIMIT 100',
+ $constraint);
+ $logs = array_reverse($logs);
+
+ if (!$logs) {
+ echo tsprintf(
+ "%s\n",
+ pht('No matching lock logs.'));
+ return 0;
+ }
+
+ $table = id(new PhutilConsoleTable())
+ ->setBorders(true)
+ ->addColumn(
+ 'id',
+ array(
+ 'title' => pht('Lock'),
+ ))
+ ->addColumn(
+ 'name',
+ array(
+ 'title' => pht('Name'),
+ ))
+ ->addColumn(
+ 'acquired',
+ array(
+ 'title' => pht('Acquired'),
+ ))
+ ->addColumn(
+ 'released',
+ array(
+ 'title' => pht('Released'),
+ ))
+ ->addColumn(
+ 'held',
+ array(
+ 'title' => pht('Held'),
+ ))
+ ->addColumn(
+ 'parameters',
+ array(
+ 'title' => pht('Parameters'),
+ ))
+ ->addColumn(
+ 'context',
+ array(
+ 'title' => pht('Context'),
+ ));
+
+ $viewer = $this->getViewer();
+
+ foreach ($logs as $log) {
+ $created = $log->getDateCreated();
+ $released = $log->getLockReleased();
+
+ if ($released) {
+ $held = '+'.($released - $created);
+ } else {
+ $held = null;
+ }
+
+ $created = phabricator_datetime($created, $viewer);
+ $released = phabricator_datetime($released, $viewer);
+
+ $parameters = $log->getLockParameters();
+ $context = $log->getLockContext();
+
+ $table->addRow(
+ array(
+ 'id' => $log->getID(),
+ 'name' => $log->getLockName(),
+ 'acquired' => $created,
+ 'released' => $released,
+ 'held' => $held,
+ 'parameters' => $this->flattenParameters($parameters),
+ 'context' => $this->flattenParameters($context),
+ ));
+ }
+
+ $table->draw();
+
+ return 0;
+ }
+
+ private function flattenParameters(array $params, $keys = true) {
+ $flat = array();
+ foreach ($params as $key => $value) {
+ if (is_array($value)) {
+ $value = $this->flattenParameters($value, false);
+ }
+ if ($keys) {
+ $flat[] = "{$key}={$value}";
+ } else {
+ $flat[] = "{$value}";
+ }
+ }
+
+ if ($keys) {
+ $flat = implode(', ', $flat);
+ } else {
+ $flat = implode(' ', $flat);
+ }
+
+ return $flat;
+ }
+
+}
diff --git a/src/applications/daemon/management/PhabricatorLockManagementWorkflow.php b/src/applications/daemon/management/PhabricatorLockManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/daemon/management/PhabricatorLockManagementWorkflow.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class PhabricatorLockManagementWorkflow
+ extends PhabricatorManagementWorkflow {}
diff --git a/src/applications/daemon/storage/PhabricatorDaemonLockLog.php b/src/applications/daemon/storage/PhabricatorDaemonLockLog.php
new file mode 100644
--- /dev/null
+++ b/src/applications/daemon/storage/PhabricatorDaemonLockLog.php
@@ -0,0 +1,32 @@
+<?php
+
+final class PhabricatorDaemonLockLog
+ extends PhabricatorDaemonDAO {
+
+ protected $lockName;
+ protected $lockReleased;
+ protected $lockParameters = array();
+ protected $lockContext = array();
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_SERIALIZATION => array(
+ 'lockParameters' => self::SERIALIZATION_JSON,
+ 'lockContext' => self::SERIALIZATION_JSON,
+ ),
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'lockName' => 'text64',
+ 'lockReleased' => 'epoch?',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_lock' => array(
+ 'columns' => array('lockName'),
+ ),
+ 'key_created' => array(
+ 'columns' => array('dateCreated'),
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
+}
diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php
--- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php
+++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php
@@ -100,8 +100,10 @@
// Hold a lock while performing collection to avoid racing other daemons
// running the same collectors.
- $lock_name = 'gc:'.$this->getCollectorConstant();
- $lock = PhabricatorGlobalLock::newLock($lock_name);
+ $params = array(
+ 'collector' => $this->getCollectorConstant(),
+ );
+ $lock = PhabricatorGlobalLock::newLock('gc', $params);
try {
$lock->lock(5);
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
@@ -1163,12 +1163,16 @@
// Although we're holding this lock on different databases so it could
// have the same name on each as far as the database is concerned, the
// locks would be the same within this process.
- $ref_key = $api->getRef()->getRefKey();
- $ref_hash = PhabricatorHash::digestForIndex($ref_key);
- $lock_name = 'adjust('.$ref_hash.')';
+ $parameters = array(
+ 'refKey' => $api->getRef()->getRefKey(),
+ );
+
+ // We disable logging for this lock because we may not have created the
+ // log table yet, or may need to adjust it.
- return PhabricatorGlobalLock::newLock($lock_name)
+ return PhabricatorGlobalLock::newLock('adjust', $parameters)
->useSpecificConnection($api->getConn(null))
+ ->setDisableLogging(true)
->lock();
}
diff --git a/src/infrastructure/util/PhabricatorGlobalLock.php b/src/infrastructure/util/PhabricatorGlobalLock.php
--- a/src/infrastructure/util/PhabricatorGlobalLock.php
+++ b/src/infrastructure/util/PhabricatorGlobalLock.php
@@ -31,6 +31,8 @@
private $parameters;
private $conn;
private $isExternalConnection = false;
+ private $log;
+ private $disableLogging;
private static $pool = array();
@@ -95,6 +97,11 @@
return $this;
}
+ public function setDisableLogging($disable) {
+ $this->disableLogging = $disable;
+ return $this;
+ }
+
/* -( Implementation )----------------------------------------------------- */
@@ -143,6 +150,24 @@
$conn->rememberLock($lock_name);
$this->conn = $conn;
+
+ if ($this->shouldLogLock()) {
+ global $argv;
+
+ $lock_context = array(
+ 'pid' => getmypid(),
+ 'host' => php_uname('n'),
+ 'argv' => $argv,
+ );
+
+ $log = id(new PhabricatorDaemonLockLog())
+ ->setLockName($lock_name)
+ ->setLockParameters($this->parameters)
+ ->setLockContext($lock_context)
+ ->save();
+
+ $this->log = $log;
+ }
}
protected function doUnlock() {
@@ -175,6 +200,32 @@
$conn->close();
self::$pool[] = $conn;
}
+
+ if ($this->log) {
+ $log = $this->log;
+ $this->log = null;
+
+ $conn = $log->establishConnection('w');
+ queryfx(
+ $conn,
+ 'UPDATE %T SET lockReleased = UNIX_TIMESTAMP() WHERE id = %d',
+ $log->getTableName(),
+ $log->getID());
+ }
+ }
+
+ private function shouldLogLock() {
+ if ($this->disableLogging) {
+ return false;
+ }
+
+ $policy = id(new PhabricatorDaemonLockLogGarbageCollector())
+ ->getRetentionPolicy();
+ if (!$policy) {
+ return false;
+ }
+
+ return true;
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Dec 19, 3:16 PM (21 h, 37 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6907854
Default Alt Text
D19174.diff (16 KB)
Attached To
Mode
D19174: Add a "lock log" for debugging where locks are being held
Attached
Detach File
Event Timeline
Log In to Comment