Refs #24007 -- Removed an apps.populate() call in model unpickling that can cause deadlocks.

This commit is contained in:
Tim Graham 2016-01-26 07:03:54 -05:00
parent db9f21f0ad
commit 10a162809f
3 changed files with 11 additions and 49 deletions

View File

@ -1694,8 +1694,6 @@ def model_unpickle(model_id, attrs, factory):
Used to unpickle Model subclasses with deferred fields. Used to unpickle Model subclasses with deferred fields.
""" """
if isinstance(model_id, tuple): if isinstance(model_id, tuple):
if not apps.ready:
apps.populate(settings.INSTALLED_APPS)
model = apps.get_model(*model_id) model = apps.get_model(*model_id)
else: else:
# Backwards compat - the model was cached directly in earlier versions. # Backwards compat - the model was cached directly in earlier versions.

View File

@ -461,6 +461,16 @@ Since the introduction of migrations for the contrib apps in Django 1.8, the
tables of these custom user test models were not created anymore making them tables of these custom user test models were not created anymore making them
unusable in a testing context. unusable in a testing context.
Apps registry is no longer auto-populated when unpickling models outside of Django
----------------------------------------------------------------------------------
The apps registry is no longer auto-populated when unpickling models. This was
added in Django 1.7.2 as an attempt to allow unpickling models outside of
Django, such as in an RQ worker, without calling ``django.setup()``, but it
creates the possibility of a deadlock. To adapt your code in the case of RQ,
you can `provide your own worker script <http://python-rq.org/docs/workers/>`_
that calls ``django.setup()``.
Miscellaneous Miscellaneous
------------- -------------

View File

@ -1,17 +1,9 @@
import datetime
import os
import pickle import pickle
import subprocess
import sys
from django.core.files.temp import NamedTemporaryFile
from django.db import DJANGO_VERSION_PICKLE_KEY, models from django.db import DJANGO_VERSION_PICKLE_KEY, models
from django.test import TestCase, mock from django.test import TestCase
from django.utils._os import npath, upath
from django.utils.version import get_version from django.utils.version import get_version
from .models import Article
class ModelPickleTestCase(TestCase): class ModelPickleTestCase(TestCase):
def test_missing_django_version_unpickling(self): def test_missing_django_version_unpickling(self):
@ -51,41 +43,3 @@ class ModelPickleTestCase(TestCase):
msg = "Pickled model instance's Django version 1.0 does not match the current version %s." % get_version() msg = "Pickled model instance's Django version 1.0 does not match the current version %s." % get_version()
with self.assertRaisesMessage(RuntimeWarning, msg): with self.assertRaisesMessage(RuntimeWarning, msg):
pickle.loads(pickle.dumps(p)) pickle.loads(pickle.dumps(p))
def test_unpickling_when_appregistrynotready(self):
"""
#24007 -- Verifies that a pickled model can be unpickled without having
to manually setup the apps registry beforehand.
"""
script_template = """#!/usr/bin/env python
import pickle
from django.conf import settings
data = %r
settings.configure(DEBUG=False, INSTALLED_APPS=['model_regress'], SECRET_KEY = "blah")
article = pickle.loads(data)
print(article.headline)"""
a = Article.objects.create(
headline="Some object",
pub_date=datetime.datetime.now(),
article_text="This is an article",
)
with NamedTemporaryFile(mode='w+', suffix=".py") as script:
script.write(script_template % pickle.dumps(a))
script.flush()
# A path to model_regress must be set in PYTHONPATH
model_regress_dir = os.path.dirname(upath(__file__))
model_regress_path = os.path.abspath(model_regress_dir)
tests_path = os.path.split(model_regress_path)[0]
pythonpath = os.environ.get('PYTHONPATH', '')
pythonpath = npath(os.pathsep.join([tests_path, pythonpath]))
with mock.patch.dict('os.environ', {'PYTHONPATH': pythonpath}):
try:
result = subprocess.check_output([sys.executable, script.name])
except subprocess.CalledProcessError:
self.fail("Unable to reload model pickled data")
self.assertEqual(result.strip().decode(), "Some object")