|
@@ -12,6 +12,53 @@ import random
|
|
|
SERVER_DRIFT = timedelta(seconds=random.vonmisesvariate(1, 4))
|
|
|
|
|
|
|
|
|
+class TableLock(object):
|
|
|
+ """Base class for database table locks. Also works as a NOOP lock."""
|
|
|
+
|
|
|
+ def __init__(self, table, type="read"):
|
|
|
+ self.table = table
|
|
|
+ self.type = type
|
|
|
+ self.cursor = None
|
|
|
+
|
|
|
+ def lock_table(self):
|
|
|
+ """Lock the table."""
|
|
|
+ pass
|
|
|
+
|
|
|
+ def unlock_table(self):
|
|
|
+ """Release previously locked tables."""
|
|
|
+ pass
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def acquire(cls, table, type=None):
|
|
|
+ """Acquire table lock."""
|
|
|
+ lock = cls(table, type)
|
|
|
+ lock.lock_table()
|
|
|
+ return lock
|
|
|
+
|
|
|
+ def release(self):
|
|
|
+ """Release the lock."""
|
|
|
+ self.unlock_table()
|
|
|
+ if self.cursor:
|
|
|
+ self.cursor.close()
|
|
|
+ self.cursor = None
|
|
|
+
|
|
|
+
|
|
|
+class MySQLTableLock(TableLock):
|
|
|
+ """Table lock support for MySQL."""
|
|
|
+
|
|
|
+ def lock_table(self):
|
|
|
+ """Lock MySQL table."""
|
|
|
+ self.cursor = connection.cursor()
|
|
|
+ self.cursor.execute("LOCK TABLES %s %s" % (self.table, self.type.upper()))
|
|
|
+
|
|
|
+ def unlock_table(self):
|
|
|
+ """Unlock MySQL table."""
|
|
|
+ self.cursor.execute("UNLOCK TABLES")
|
|
|
+
|
|
|
+TABLE_LOCK_FOR_ENGINE = {"mysql": MySQLTableLock}
|
|
|
+table_lock = TABLE_LOCK_FOR_ENGINE.get(settings.DATABASE_ENGINE, TableLock)
|
|
|
+
|
|
|
+
|
|
|
class TaskManager(models.Manager):
|
|
|
"""Manager for :class:`celery.models.Task` models."""
|
|
|
|
|
@@ -57,26 +104,6 @@ class TaskManager(models.Manager):
|
|
|
class PeriodicTaskManager(models.Manager):
|
|
|
"""Manager for :class:`celery.models.PeriodicTask` models."""
|
|
|
|
|
|
- def lock(self):
|
|
|
- """Lock the periodic task table for reading."""
|
|
|
- if settings.DATABASE_ENGINE != "mysql":
|
|
|
- return
|
|
|
- cursor = connection.cursor()
|
|
|
- table = self.model._meta.db_table
|
|
|
- cursor.execute("LOCK TABLES %s READ" % table)
|
|
|
- row = cursor.fetchone()
|
|
|
- return row
|
|
|
-
|
|
|
- def unlock(self):
|
|
|
- """Unlock the periodic task table."""
|
|
|
- if settings.DATABASE_ENGINE != "mysql":
|
|
|
- return
|
|
|
- cursor = connection.cursor()
|
|
|
- table = self.model._meta.db_table
|
|
|
- cursor.execute("UNLOCK TABLES")
|
|
|
- row = cursor.fetchone()
|
|
|
- return row
|
|
|
-
|
|
|
def init_entries(self):
|
|
|
"""Add entries for all registered periodic tasks.
|
|
|
|
|
@@ -107,6 +134,7 @@ class PeriodicTaskManager(models.Manager):
|
|
|
:returns: list of :class:`celery.models.PeriodicTaskMeta` objects.
|
|
|
"""
|
|
|
periodic_tasks = tasks.get_all_periodic()
|
|
|
+ db_table = self.model._meta.db_table
|
|
|
|
|
|
# Find all periodic tasks to be run.
|
|
|
waiting = []
|
|
@@ -117,7 +145,7 @@ class PeriodicTaskManager(models.Manager):
|
|
|
if self.is_time(task_meta.last_run_at, run_every):
|
|
|
# Get the object again to be sure noone else
|
|
|
# has already taken care of it.
|
|
|
- self.lock()
|
|
|
+ lock = table_lock.acquire(db_table, "write")
|
|
|
try:
|
|
|
secure = self.get(pk=task_meta.pk)
|
|
|
if self.is_time(secure.last_run_at, run_every):
|
|
@@ -125,5 +153,5 @@ class PeriodicTaskManager(models.Manager):
|
|
|
secure.save()
|
|
|
waiting.append(secure)
|
|
|
finally:
|
|
|
- self.unlock()
|
|
|
+ lock.release()
|
|
|
return waiting
|