diff options
author | tjb900 <ozburgess@gmail.com> | 2019-02-18 15:30:51 (GMT) |
---|---|---|
committer | Antoine Pitrou <pitrou@free.fr> | 2019-02-18 15:30:51 (GMT) |
commit | 4371c0a9c0848f7a0947d43f26f234842b41efdf (patch) | |
tree | 122d9ea80e833f00f856fa58625b7a17f3573ad3 /Lib/test/pickletester.py | |
parent | 4a7f44a2ed49ff1e87db062e7177a56c6e4bbdb0 (diff) | |
download | cpython-4371c0a9c0848f7a0947d43f26f234842b41efdf.zip cpython-4371c0a9c0848f7a0947d43f26f234842b41efdf.tar.gz cpython-4371c0a9c0848f7a0947d43f26f234842b41efdf.tar.bz2 |
bpo-34572: change _pickle unpickling to use import rather than retrieving from sys.modules (GH-9047)
Fix C implementation of pickle.loads to use importlib's locking mechanisms, and thereby avoid using partially-loaded modules.
Diffstat (limited to 'Lib/test/pickletester.py')
-rw-r--r-- | Lib/test/pickletester.py | 67 |
1 files changed, 66 insertions, 1 deletions
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 71c2fea..293393f 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -3,18 +3,22 @@ import copyreg import dbm import io import functools +import os import pickle import pickletools +import shutil import struct import sys +import threading import unittest import weakref +from textwrap import dedent from http.cookies import SimpleCookie from test import support from test.support import ( TestFailed, TESTFN, run_with_locale, no_tracing, - _2G, _4G, bigmemtest, + _2G, _4G, bigmemtest, reap_threads, forget, ) from pickle import bytes_types @@ -1174,6 +1178,67 @@ class AbstractUnpickleTests(unittest.TestCase): for p in badpickles: self.check_unpickling_error(self.truncated_errors, p) + @reap_threads + def test_unpickle_module_race(self): + # https://bugs.python.org/issue34572 + locker_module = dedent(""" + import threading + barrier = threading.Barrier(2) + """) + locking_import_module = dedent(""" + import locker + locker.barrier.wait() + class ToBeUnpickled(object): + pass + """) + + os.mkdir(TESTFN) + self.addCleanup(shutil.rmtree, TESTFN) + sys.path.insert(0, TESTFN) + self.addCleanup(sys.path.remove, TESTFN) + with open(os.path.join(TESTFN, "locker.py"), "wb") as f: + f.write(locker_module.encode('utf-8')) + with open(os.path.join(TESTFN, "locking_import.py"), "wb") as f: + f.write(locking_import_module.encode('utf-8')) + self.addCleanup(forget, "locker") + self.addCleanup(forget, "locking_import") + + import locker + + pickle_bytes = ( + b'\x80\x03clocking_import\nToBeUnpickled\nq\x00)\x81q\x01.') + + # Then try to unpickle two of these simultaneously + # One of them will cause the module import, and we want it to block + # until the other one either: + # - fails (before the patch for this issue) + # - blocks on the import lock for the module, as it should + results = [] + barrier = threading.Barrier(3) + def t(): + # This ensures the threads have all started + # presumably barrier release is faster than thread startup + barrier.wait() + results.append(pickle.loads(pickle_bytes)) + + t1 = threading.Thread(target=t) + t2 = threading.Thread(target=t) + t1.start() + t2.start() + + barrier.wait() + # could have delay here + locker.barrier.wait() + + t1.join() + t2.join() + + from locking_import import ToBeUnpickled + self.assertEqual( + [type(x) for x in results], + [ToBeUnpickled] * 2) + + class AbstractPickleTests(unittest.TestCase): # Subclass must define self.dumps, self.loads. |