Skip to content

Commit

Permalink
Merge unhook-threadable-5860
Browse files Browse the repository at this point in the history
Author: exarkun
Review: itamar
Fixes: twisted#5860

Port twisted.python.threadable to Python 3.


git-svn-id: svn://svn.twistedmatrix.com/svn/Twisted/trunk@35498 bbbe8e31-12d6-0310-92fd-ac37d47ddeeb
  • Loading branch information
itamarst committed Sep 3, 2012
1 parent d0edfe3 commit 6117bb1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 48 deletions.
2 changes: 2 additions & 0 deletions admin/_twistedpython3.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# but works well enough to be imported:
"twisted.python.filepath",
"twisted.python._deprecatepy3",
"twisted.python.threadable",
"twisted.python.versions",
"twisted.python.runtime",
"twisted.test",
Expand All @@ -48,4 +49,5 @@
"twisted.test.test_context",
"twisted.test.test_monkey",
"twisted.test.test_paths",
"twisted.test.test_threadable",
]
57 changes: 39 additions & 18 deletions twisted/python/threadable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.


"""
A module that will allow your program to be multi-threaded,
micro-threaded, and single-threaded. Currently microthreads are
unimplemented. The idea is to abstract away some commonly used
functionality so that I don't have to special-case it in all programs.
A module to provide some very basic threading primitives, such as
synchronization.
"""

from __future__ import division, absolute_import


from twisted.python import hook
from functools import wraps

class DummyLock(object):
"""
Expand All @@ -22,36 +19,59 @@ class DummyLock(object):
def __reduce__(self):
return (unpickle_lock, ())



def unpickle_lock():
if threadingmodule is not None:
return XLock()
else:
return DummyLock()
unpickle_lock.__safe_for_unpickling__ = True

def _synchPre(self, *a, **b):


def _synchPre(self):
if '_threadable_lock' not in self.__dict__:
_synchLockCreator.acquire()
if '_threadable_lock' not in self.__dict__:
self.__dict__['_threadable_lock'] = XLock()
_synchLockCreator.release()
self._threadable_lock.acquire()

def _synchPost(self, *a, **b):


def _synchPost(self):
self._threadable_lock.release()



def _sync(klass, function):
@wraps(function)
def sync(self, *args, **kwargs):
_synchPre(self)
try:
return function(self, *args, **kwargs)
finally:
_synchPost(self)
return sync



def synchronize(*klasses):
"""Make all methods listed in each class' synchronized attribute synchronized.
"""
Make all methods listed in each class' synchronized attribute synchronized.
The synchronized attribute should be a list of strings, consisting of the
names of methods that must be synchronized. If we are running in threaded
mode these methods will be wrapped with a lock.
"""
if threadmodule is not None:
if threadingmodule is not None:
for klass in klasses:
for methodName in klass.synchronized:
hook.addPre(klass, methodName, _synchPre)
hook.addPost(klass, methodName, _synchPost)
sync = _sync(klass, klass.__dict__[methodName])
setattr(klass, methodName, sync)



def init(with_threads=1):
"""Initialize threading.
Expand All @@ -62,7 +82,7 @@ def init(with_threads=1):

if with_threads:
if not threaded:
if threadmodule is not None:
if threadingmodule is not None:
threaded = True

class XLock(threadingmodule._RLock, object):
Expand All @@ -78,11 +98,14 @@ def __reduce__(self):
else:
pass



_dummyID = object()
def getThreadID():
if threadmodule is None:
if threadingmodule is None:
return _dummyID
return threadmodule.get_ident()
return threadingmodule.currentThread().ident



def isInIOThread():
Expand All @@ -105,10 +128,8 @@ def registerAsIOThread():


try:
import thread as threadmodule
import threading as threadingmodule
except ImportError:
threadmodule = None
threadingmodule = None
else:
init(True)
Expand Down
99 changes: 69 additions & 30 deletions twisted/test/test_threadable.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Tests for L{twisted.python.threadable}.
"""

from __future__ import division, absolute_import

import sys, pickle

try:
import threading
except ImportError:
threading = None
threadingSkip = "Platform lacks thread support"
else:
threadingSkip = None

from twisted.python.compat import _PY3

if _PY3:
# Switch to SynchronousTestCase when #5885 is resolved.
import unittest
else:
from twisted.trial import unittest

from twisted.trial import unittest
from twisted.python import threadable
from twisted.internet import defer, reactor

class TestObject:
synchronized = ['aMethod']
Expand All @@ -19,7 +33,7 @@ class TestObject:
y = 1

def aMethod(self):
for i in xrange(10):
for i in range(10):
self.x, self.y = self.y, self.x
self.z = self.x + self.y
assert self.z == 0, "z == %d, not 0 as expected" % (self.z,)
Expand All @@ -33,26 +47,39 @@ def setUp(self):
more often, hopefully exercising more possible race conditions. Also,
delay actual test startup until the reactor has been started.
"""
if hasattr(sys, 'getcheckinterval'):
self.addCleanup(sys.setcheckinterval, sys.getcheckinterval())
sys.setcheckinterval(7)
# XXX This is a trial hack. We need to make sure the reactor
# actually *starts* for isInIOThread() to have a meaningful result.
# Returning a Deferred here should force that to happen, if it has
# not happened already. In the future, this should not be
# necessary.
d = defer.Deferred()
reactor.callLater(0, d.callback, None)
return d


def testIsInIOThread(self):
if _PY3:
if getattr(sys, 'getswitchinterval', None) is not None:
self.addCleanup(sys.setswitchinterval, sys.getswitchinterval())
sys.setswitchinterval(0.0000001)
else:
if getattr(sys, 'getcheckinterval', None) is not None:
self.addCleanup(sys.setcheckinterval, sys.getcheckinterval())
sys.setcheckinterval(7)


def test_synchronizedName(self):
"""
The name of a synchronized method is inaffected by the synchronization
decorator.
"""
self.assertEqual("aMethod", TestObject.aMethod.__name__)


def test_isInIOThread(self):
"""
L{threadable.isInIOThread} returns C{True} if and only if it is called
in the same thread as L{threadable.registerAsIOThread}.
"""
threadable.registerAsIOThread()
foreignResult = []
t = threading.Thread(target=lambda: foreignResult.append(threadable.isInIOThread()))
t = threading.Thread(
target=lambda: foreignResult.append(threadable.isInIOThread()))
t.start()
t.join()
self.failIf(foreignResult[0], "Non-IO thread reported as IO thread")
self.failUnless(threadable.isInIOThread(), "IO thread reported as not IO thread")
self.assertFalse(
foreignResult[0], "Non-IO thread reported as IO thread")
self.assertTrue(
threadable.isInIOThread(), "IO thread reported as not IO thread")


def testThreadedSynchronization(self):
Expand All @@ -62,9 +89,9 @@ def testThreadedSynchronization(self):

def callMethodLots():
try:
for i in xrange(1000):
for i in range(1000):
o.aMethod()
except AssertionError, e:
except AssertionError as e:
errors.append(str(e))

threads = []
Expand All @@ -79,25 +106,37 @@ def callMethodLots():
if errors:
raise unittest.FailTest(errors)

# stdlib unittest is missing skip support, re-enable these skips after #5885
if threadingSkip is not None:
testThreadedSynchronization.skip = threadingSkip
test_isInIOThread.skip = threadingSkip
del testThreadedSynchronization
del test_isInIOThread


def testUnthreadedSynchronization(self):
o = TestObject()
for i in xrange(1000):
for i in range(1000):
o.aMethod()



class SerializationTestCase(unittest.TestCase):
def testPickling(self):
lock = threadable.XLock()
lockType = type(lock)
lockPickle = pickle.dumps(lock)
newLock = pickle.loads(lockPickle)
self.failUnless(isinstance(newLock, lockType))
self.assertTrue(isinstance(newLock, lockType))

# stdlib unittest is missing skip support, re-enable these skips after #5885
if threadingSkip is not None:
testPickling.skip = threadingSkip
del testPickling


def testUnpickling(self):
lockPickle = 'ctwisted.python.threadable\nunpickle_lock\np0\n(tp1\nRp2\n.'
lockPickle = b'ctwisted.python.threadable\nunpickle_lock\np0\n(tp1\nRp2\n.'
lock = pickle.loads(lockPickle)
newPickle = pickle.dumps(lock, 2)
newLock = pickle.loads(newPickle)

if threading is None:
SynchronizationTestCase.testThreadedSynchronization.skip = "Platform lacks thread support"
SerializationTestCase.testPickling.skip = "Platform lacks thread support"
Empty file added twisted/topfiles/5860.misc
Empty file.

0 comments on commit 6117bb1

Please sign in to comment.