From b0d9aed0b6f5f37911c9e8b89fd798b927b630e4 Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 13:29:07 +0200 Subject: [PATCH 1/7] Add LOCK_MAX_AGE documentation --- README.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index cfa0917..0d466f8 100644 --- a/README.rst +++ b/README.rst @@ -35,6 +35,8 @@ yourself, you can do that too:: do_something() lock.release() +Note that locks can expire automatically. There is a `LOCK_MAX_AGE` settings where you can specify a default lock release value for locks in your entire Django codebase. This value can be overridden per lock by setting the `max_age` parameter. + Test ----- You can run the tests with @@ -42,13 +44,6 @@ You can run the tests with tox -Wishlist --------- -- Add lock time-out (try to aquire a lock for up to a given TIME_OUT), like - `lockfile's `_ `FileLock.aquire - `_ -- Global locks (for instance on a whole Model not just an object) - Releases -------- v2.0.0: From f01ad93245c348ede32e2205a1f2cf9baba31372 Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 13:34:52 +0200 Subject: [PATCH 2/7] Add /.idea to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 08b5f34..cc6e68a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ coverage.xml # Virtualenvs .venv* .cache/ + +# IDE junk +/.idea From 3b1787efa479ae26605a2576e4abb9f8b41d0f80 Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 13:40:28 +0200 Subject: [PATCH 3/7] Add /venv to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cc6e68a..c2a8d03 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ coverage.xml .mr.developer.cfg # Virtualenvs +/venv .venv* .cache/ From c33d986ba1c26b993d133b6d4d8d40033268b5ab Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 13:56:56 +0200 Subject: [PATCH 4/7] Don't run tests from /venv --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 569fb9c..5bdb8dc 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] DJANGO_SETTINGS_MODULE=test_project.settings python_files = test*.py -norecursedirs = .* *.egg *.egg-info wheel dist build artifacts +norecursedirs = .* *.egg *.egg-info wheel dist build artifacts venv From ba6aa23716aa4f648b4db3391991e641010ec10b Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 14:12:46 +0200 Subject: [PATCH 5/7] Add additional tests for LOCK_MAX_AGE setting --- locking/tests.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/locking/tests.py b/locking/tests.py index 0475967..97c761c 100644 --- a/locking/tests.py +++ b/locking/tests.py @@ -4,10 +4,13 @@ from __future__ import absolute_import import uuid +from datetime import datetime, timedelta +from django.conf import settings + from freezegun import freeze_time from django.contrib.auth.models import User -from django.test import TestCase +from django.test import TestCase, override_settings from .exceptions import AlreadyLocked, RenewalError, NonexistentLock, NotLocked, Expired from .models import NonBlockingLock, _get_lock_name @@ -143,3 +146,36 @@ def test_clean(self): # Only the non-expired lock should remain clean_expired_locks() self.assertEqual(NonBlockingLock.objects.get(), lock_to_be_released) + + def test_implicit_cleaning_disabled(self): + """If no max_age parameter is given and locks aren't configured to autoexpire, don't clean them up.""" + assert hasattr(settings, 'LOCK_MAX_AGE') is False + initial_timestamp = datetime(2017, 1, 1) + + with freeze_time(initial_timestamp): + lock_to_remain = NonBlockingLock.objects.acquire_lock(self.user) + with freeze_time(initial_timestamp + timedelta(days=9600)): + clean_expired_locks() + assert NonBlockingLock.objects.get() == lock_to_remain + + @override_settings(LOCK_MAX_AGE=0) + def test_implicit_cleaning_set_to_zero(self): + """If the LOCK_MAX_AGE setting is set to 0, no locks should autoexpire.""" + initial_timestamp = datetime(2017, 1, 1) + + with freeze_time(initial_timestamp): + lock_to_remain = NonBlockingLock.objects.acquire_lock(self.user) + with freeze_time(initial_timestamp + timedelta(days=9600)): + clean_expired_locks() + assert NonBlockingLock.objects.get() == lock_to_remain + + @override_settings(LOCK_MAX_AGE=1) + def test_implicit_cleaning_set_to_nonzero(self): + """If the LOCK_MAX_AGE setting is bigger than 0, locks should autoexpire.""" + initial_timestamp = datetime(2017, 1, 1) + + with freeze_time(initial_timestamp): + NonBlockingLock.objects.acquire_lock(self.user) + with freeze_time(initial_timestamp + timedelta(seconds=1)): + clean_expired_locks() + assert NonBlockingLock.objects.count() == 0 From 1799a46601c540a94728a2119e61a3faaf90705c Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 15:53:21 +0200 Subject: [PATCH 6/7] Ensure default doesn't rely on Django settings These are dependent on the Django project they're used in. --- locking/migrations/0006_auto_20170609_1342.py | 20 +++++++++++++++++++ locking/models.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 locking/migrations/0006_auto_20170609_1342.py diff --git a/locking/migrations/0006_auto_20170609_1342.py b/locking/migrations/0006_auto_20170609_1342.py new file mode 100644 index 0000000..4ebf4c3 --- /dev/null +++ b/locking/migrations/0006_auto_20170609_1342.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-09 13:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('locking', '0005_merge_20170504_1024'), + ] + + operations = [ + migrations.AlterField( + model_name='nonblockinglock', + name='max_age', + field=models.PositiveIntegerField(default=0, help_text='The age of a lock before it can be overwritten. 0 means indefinitely.', verbose_name='Maximum lock age'), + ), + ] diff --git a/locking/models.py b/locking/models.py index 6699145..ce1789a 100644 --- a/locking/models.py +++ b/locking/models.py @@ -188,7 +188,7 @@ class NonBlockingLock(models.Model): #: The age of a lock before it can be overwritten. If it's ``MAX_AGE_FOREVER``, it will #: never expire. max_age = models.PositiveIntegerField( - default=DEFAULT_MAX_AGE, verbose_name=_('Maximum lock age'), + default=MAX_AGE_FOREVER, verbose_name=_('Maximum lock age'), help_text=_('The age of a lock before it can be overwritten. ' '%s means indefinitely.' % MAX_AGE_FOREVER) ) From 4f8a1b38f8b37d8f50290f9792fffcd22917d5d8 Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Fri, 9 Jun 2017 15:54:11 +0200 Subject: [PATCH 7/7] Ignore SQLite database --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c2a8d03..191d892 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ coverage.xml # IDE junk /.idea + +# SQLite test db +/db.sqlite3