Created
May 29, 2015 17:08
-
-
Save specialunderwear/082cdb49a9aaf2ca4a81 to your computer and use it in GitHub Desktop.
Enforce exclusive running of uwsgi cron jobs on aws - where uwsgi legion does not work - using a database lock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Ensure that only 1 process can be running a managment command at the same | |
time, even across multiple application servers. | |
Uses a database lock on a table row, in a separate database connection. | |
""" | |
import logging | |
from django.conf import settings | |
from django.db import models | |
from uwsgidecorators import cron | |
from django.core.management import call_command | |
from django import db | |
from django.utils import timezone | |
logger = logging.getLogger(__name__) | |
# have this in your settings file | |
setting.DATABASE['lock'] = setting.DATABASE['default'] | |
# have this somewhere in a models.py | |
class CronLock(models.Model): | |
jobname = models.CharField(max_length=255) | |
last_run = models.DateTimeField(blank=True, null=True) | |
def __unicode__(self): | |
return self.jobname | |
def run_exclusive(name): | |
""" | |
Run a management command inside a database lock. | |
This ensures that only 1 process can be running the command at the same | |
time, even across multiple application servers. | |
Since the management commands can also run database queries, we don't | |
want to run them inside the same transaction. That is why a database | |
alias is created under the name 'lock', with the same connection | |
details as 'default'. Obtaining the lock on this alias ensures that | |
it runs in a separate transaction. | |
""" | |
with db.transaction.commit_manually(using='lock'): | |
try: | |
should_run = CronLock.objects.using('lock').select_for_update( | |
nowait=True).get(jobname=name) | |
logger.debug('Running command %s' % name) | |
call_command(name) | |
should_run.last_run = timezone.now() | |
should_run.save() | |
db.transaction.commit(using='lock') | |
except (db.DatabaseError, CronLock.DoesNotExist): | |
logger.debug("Can not obtain lock on %s, not running" % name) | |
db.transaction.rollback(using='lock') | |
@cron(0, 1, -1, -1, -1) # every day at 1:00 | |
def clearsessions_cron(num): | |
logger.info("cleaning up expired sessions.") | |
run_exclusive('clearsessions') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment