Skip to content

Instantly share code, notes, and snippets.

@specialunderwear
Created May 29, 2015 17:08
Show Gist options
  • Save specialunderwear/082cdb49a9aaf2ca4a81 to your computer and use it in GitHub Desktop.
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
"""
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