summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/private/AppFramework/Db/Db.php17
-rw-r--r--lib/private/BackgroundJob/JobList.php86
-rw-r--r--lib/private/DB/Adapter.php20
-rw-r--r--lib/private/DB/AdapterMySQL.php12
-rw-r--r--lib/private/DB/AdapterSqlite.php12
-rw-r--r--lib/private/DB/Connection.php32
-rw-r--r--lib/private/Server.php6
-rw-r--r--lib/public/BackgroundJob/IJobList.php11
-rw-r--r--lib/public/IDBConnection.php19
9 files changed, 179 insertions, 36 deletions
diff --git a/lib/private/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php
index 0d17d7bc225..ab06d56cfd1 100644
--- a/lib/private/AppFramework/Db/Db.php
+++ b/lib/private/AppFramework/Db/Db.php
@@ -29,6 +29,7 @@ namespace OC\AppFramework\Db;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDb;
use OCP\IDBConnection;
+use OCP\PreConditionNotMetException;
/**
* @deprecated use IDBConnection directly, will be removed in ownCloud 10
@@ -157,13 +158,27 @@ class Db implements IDb {
* @param array $updatePreconditionValues ensure values match preconditions (column name => value)
* @return int number of new rows
* @throws \Doctrine\DBAL\DBALException
- * @throws PreconditionNotMetException
+ * @throws PreConditionNotMetException
*/
public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
return $this->connection->setValues($table, $keys, $values, $updatePreconditionValues);
}
/**
+ * @inheritdoc
+ */
+ public function lockTable($tableName) {
+ $this->connection->lockTable($tableName);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function unlockTable() {
+ $this->connection->unlockTable();
+ }
+
+ /**
* Start a transaction
*/
public function beginTransaction() {
diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php
index 2429b830446..c84969776c2 100644
--- a/lib/private/BackgroundJob/JobList.php
+++ b/lib/private/BackgroundJob/JobList.php
@@ -25,27 +25,34 @@
namespace OC\BackgroundJob;
use OCP\AppFramework\QueryException;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\IJobList;
use OCP\AutoloadNotAllowedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
class JobList implements IJobList {
- /** @var \OCP\IDBConnection */
+
+ /** @var IDBConnection */
protected $connection;
- /**
- * @var \OCP\IConfig $config
- */
+ /**@var IConfig */
protected $config;
+ /**@var ITimeFactory */
+ protected $timeFactory;
+
/**
- * @param \OCP\IDBConnection $connection
- * @param \OCP\IConfig $config
+ * @param IDBConnection $connection
+ * @param IConfig $config
+ * @param ITimeFactory $timeFactory
*/
- public function __construct($connection, $config) {
+ public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
$this->connection = $connection;
$this->config = $config;
+ $this->timeFactory = $timeFactory;
}
/**
@@ -71,6 +78,7 @@ class JobList implements IJobList {
'class' => $query->createNamedParameter($class),
'argument' => $query->createNamedParameter($argument),
'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
+ 'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
]);
$query->execute();
}
@@ -167,45 +175,40 @@ class JobList implements IJobList {
* @return IJob|null
*/
public function getNext() {
- $lastId = $this->getLastJob();
-
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('jobs')
- ->where($query->expr()->lt('id', $query->createNamedParameter($lastId, IQueryBuilder::PARAM_INT)))
- ->orderBy('id', 'DESC')
+ ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
+ ->orderBy('last_checked', 'ASC')
->setMaxResults(1);
+
+ $update = $this->connection->getQueryBuilder();
+ $update->update('jobs')
+ ->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
+ ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
+ ->where($update->expr()->eq('id', $update->createParameter('jobid')));
+
+ $this->connection->lockTable('jobs');
$result = $query->execute();
$row = $result->fetch();
$result->closeCursor();
if ($row) {
- $jobId = $row['id'];
+ $update->setParameter('jobid', $row['id']);
+ $update->execute();
+ $this->connection->unlockTable();
+
$job = $this->buildJob($row);
- } else {
- //begin at the start of the queue
- $query = $this->connection->getQueryBuilder();
- $query->select('*')
- ->from('jobs')
- ->orderBy('id', 'DESC')
- ->setMaxResults(1);
- $result = $query->execute();
- $row = $result->fetch();
- $result->closeCursor();
-
- if ($row) {
- $jobId = $row['id'];
- $job = $this->buildJob($row);
- } else {
- return null; //empty job list
+
+ if ($job === null) {
+ // Background job from disabled app, try again.
+ return $this->getNext();
}
- }
- if (is_null($job)) {
- $this->removeById($jobId);
- return $this->getNext();
- } else {
return $job;
+ } else {
+ $this->connection->unlockTable();
+ return null;
}
}
@@ -267,13 +270,30 @@ class JobList implements IJobList {
* @param IJob $job
*/
public function setLastJob($job) {
+ $this->unlockJob($job);
$this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
}
/**
+ * Remove the reservation for a job
+ *
+ * @param IJob $job
+ */
+ public function unlockJob($job) {
+ $query = $this->connection->getQueryBuilder();
+ $query->update('jobs')
+ ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
+ $query->execute();
+ }
+
+ /**
* get the id of the last ran job
*
* @return int
+ * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
+ * only tells you which job finished last, but since we now allow multiple
+ * executors to run in parallel, it's not used to calculate the next job.
*/
public function getLastJob() {
return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
diff --git a/lib/private/DB/Adapter.php b/lib/private/DB/Adapter.php
index 9522f768c88..bcced395cb7 100644
--- a/lib/private/DB/Adapter.php
+++ b/lib/private/DB/Adapter.php
@@ -58,6 +58,26 @@ class Adapter {
}
/**
+ * Create an exclusive read+write lock on a table
+ *
+ * @param string $tableName
+ * @since 9.1.0
+ */
+ public function lockTable($tableName) {
+ $this->conn->beginTransaction();
+ $this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE');
+ }
+
+ /**
+ * Release a previous acquired lock again
+ *
+ * @since 9.1.0
+ */
+ public function unlockTable() {
+ $this->conn->commit();
+ }
+
+ /**
* Insert a row if the matching row does not exists.
*
* @param string $table The table name (will replace *PREFIX* with the actual prefix)
diff --git a/lib/private/DB/AdapterMySQL.php b/lib/private/DB/AdapterMySQL.php
index ab87c589747..8504fc74e0f 100644
--- a/lib/private/DB/AdapterMySQL.php
+++ b/lib/private/DB/AdapterMySQL.php
@@ -24,6 +24,18 @@
namespace OC\DB;
class AdapterMySQL extends Adapter {
+
+ /**
+ * @param string $tableName
+ */
+ public function lockTable($tableName) {
+ $this->conn->executeUpdate('LOCK TABLES `' .$tableName . '` WRITE');
+ }
+
+ public function unlockTable() {
+ $this->conn->executeUpdate('UNLOCK TABLES');
+ }
+
public function fixupStatement($statement) {
$statement = str_replace(' ILIKE ', ' COLLATE utf8_general_ci LIKE ', $statement);
return $statement;
diff --git a/lib/private/DB/AdapterSqlite.php b/lib/private/DB/AdapterSqlite.php
index 3466e0e1aac..cefb06ffac6 100644
--- a/lib/private/DB/AdapterSqlite.php
+++ b/lib/private/DB/AdapterSqlite.php
@@ -27,6 +27,18 @@
namespace OC\DB;
class AdapterSqlite extends Adapter {
+
+ /**
+ * @param string $tableName
+ */
+ public function lockTable($tableName) {
+ $this->conn->executeUpdate('BEGIN EXCLUSIVE TRANSACTION');
+ }
+
+ public function unlockTable() {
+ $this->conn->executeUpdate('COMMIT TRANSACTION');
+ }
+
public function fixupStatement($statement) {
$statement = preg_replace('( I?LIKE \?)', '$0 ESCAPE \'\\\'', $statement);
$statement = preg_replace('/`(\w+)` ILIKE \?/', 'LOWER($1) LIKE LOWER(?)', $statement);
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index 7cdc13a7c6d..5b7026db2f3 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -25,6 +25,7 @@
*/
namespace OC\DB;
+
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Configuration;
@@ -46,6 +47,8 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection {
*/
protected $adapter;
+ protected $lockedTable = null;
+
public function connect() {
try {
return parent::connect();
@@ -281,7 +284,7 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection {
foreach ($values as $name => $value) {
$updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
}
- $where = $updateQb->expr()->andx();
+ $where = $updateQb->expr()->andX();
$whereValues = array_merge($keys, $updatePreconditionValues);
foreach ($whereValues as $name => $value) {
$where->add($updateQb->expr()->eq(
@@ -302,6 +305,33 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection {
}
/**
+ * Create an exclusive read+write lock on a table
+ *
+ * @param string $tableName
+ * @throws \BadMethodCallException When trying to acquire a second lock
+ * @since 9.1.0
+ */
+ public function lockTable($tableName) {
+ if ($this->lockedTable !== null) {
+ throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
+ }
+
+ $tableName = $this->tablePrefix . $tableName;
+ $this->lockedTable = $tableName;
+ $this->adapter->lockTable($tableName);
+ }
+
+ /**
+ * Release a previous acquired lock again
+ *
+ * @since 9.1.0
+ */
+ public function unlockTable() {
+ $this->adapter->unlockTable();
+ $this->lockedTable = null;
+ }
+
+ /**
* returns the error code and message as a string for logging
* works with DoctrineException
* @return string
diff --git a/lib/private/Server.php b/lib/private/Server.php
index a4294ee2c88..0b7b8f9e403 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -362,7 +362,11 @@ class Server extends ServerContainer implements IServerContainer {
});
$this->registerService('JobList', function (Server $c) {
$config = $c->getConfig();
- return new \OC\BackgroundJob\JobList($c->getDatabaseConnection(), $config);
+ return new \OC\BackgroundJob\JobList(
+ $c->getDatabaseConnection(),
+ $config,
+ new TimeFactory()
+ );
});
$this->registerService('Router', function (Server $c) {
$cacheFactory = $c->getMemCacheFactory();
diff --git a/lib/public/BackgroundJob/IJobList.php b/lib/public/BackgroundJob/IJobList.php
index 5a76ce1ba26..9e401e68419 100644
--- a/lib/public/BackgroundJob/IJobList.php
+++ b/lib/public/BackgroundJob/IJobList.php
@@ -93,10 +93,21 @@ interface IJobList {
public function setLastJob($job);
/**
+ * Remove the reservation for a job
+ *
+ * @param IJob $job
+ * @since 9.1.0
+ */
+ public function unlockJob($job);
+
+ /**
* get the id of the last ran job
*
* @return int
* @since 7.0.0
+ * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
+ * only tells you which job finished last, but since we now allow multiple
+ * executors to run in parallel, it's not used to calculate the next job.
*/
public function getLastJob();
diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php
index 780fcd26364..4ecf01ca27e 100644
--- a/lib/public/IDBConnection.php
+++ b/lib/public/IDBConnection.php
@@ -125,6 +125,25 @@ interface IDBConnection {
public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []);
/**
+ * Create an exclusive read+write lock on a table
+ *
+ * Important Note: Due to the nature how locks work on different DBs, it is
+ * only possible to lock one table at a time. You should also NOT start a
+ * transaction while holding a lock.
+ *
+ * @param string $tableName
+ * @since 9.1.0
+ */
+ public function lockTable($tableName);
+
+ /**
+ * Release a previous acquired lock again
+ *
+ * @since 9.1.0
+ */
+ public function unlockTable();
+
+ /**
* Start a transaction
* @since 6.0.0
*/