[Zope3-checkins] SVN: Zope3/branches/ZopeX3-3.0/s Merged from trunk
26023:
Jim Fulton
jim at zope.com
Fri Jul 2 18:34:31 EDT 2004
Log message for revision 26084:
Merged from trunk 26023:
Implemented a thread-local data proposal
Proposed for Python 2.4 on python-dev:
http://mail.python.org/pipermail/python-dev/2004-June/045785.html
This mechanism replaces the previous "thread globals"
mechanism.
-=-
Modified: Zope3/branches/ZopeX3-3.0/setup.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/setup.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/setup.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -243,6 +243,9 @@
Extension("zope.hookable._zope_hookable",
["src/zope/hookable/_zope_hookable.c"]),
+ Extension("zope.thread._zope_thread",
+ ["src/zope/thread/_zope_thread.c"]),
+
Extension("zope.app.container._zope_app_container_contained",
["src/zope/app/container/_zope_app_container_contained.c"],
include_dirs = ["src/persistent",
Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/component/hooks.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/component/hooks.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/component/hooks.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -16,7 +16,6 @@
$Id$
"""
-import warnings
from zope.component import getService, getAdapter
from zope.component.interfaces import IServiceService
from zope.app.site.interfaces import ISite
@@ -29,20 +28,26 @@
from zope.app.location import locate
from zope.component.servicenames import Presentation
from zope.interface import Interface
-from zope.thread import thread_globals
+import warnings
+import zope.thread
+siteinfo = zope.thread.local()
+
def setSite(site=None):
if site is None:
- services = None
+ siteinfo.services = None
else:
- services = trustedRemoveSecurityProxy(site.getSiteManager())
+ siteinfo.services = trustedRemoveSecurityProxy(site.getSiteManager())
- thread_globals().services = services
-
def getSite():
- services = thread_globals().services
+ try:
+ services = siteinfo.services
+ except AttributeError:
+ services = siteinfo.services = None
+
if services is None:
return None
+
return services.__parent__
@@ -50,15 +55,15 @@
if context is None:
try:
- services = thread_globals().services
+ services = siteinfo.services
except AttributeError:
- thread_globals().services = services = None
-
+ services = siteinfo.services = None
+
if services is None:
return serviceManager
- else:
- return services
+ return services
+
try:
# This try-except is just backward compatibility really
return trustedRemoveSecurityProxy(getAdapter(context, IServiceService))
Modified: Zope3/branches/ZopeX3-3.0/src/zope/security/management.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/security/management.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/security/management.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -25,8 +25,10 @@
from zope.security.interfaces import ISecurityManagement
from zope.security.interfaces import IInteractionManagement
from zope.testing.cleanup import addCleanUp
-from zope.thread import thread_globals
+import zope.thread
+thread_local = zope.thread.local()
+
moduleProvides(ISecurityManagement, IInteractionManagement)
@@ -63,32 +65,28 @@
# IInteractionManagement implementation
#
-def queryInteraction(_thread=None):
+def queryInteraction():
"""Get the current interaction."""
- return thread_globals(_thread).interaction
+ return getattr(thread_local, 'interaction', None)
-def newInteraction(participation=None, _thread=None, _policy=None):
+def newInteraction(participation=None, _policy=None):
"""Start a new interaction."""
- if queryInteraction(_thread) is not None:
- stack = queryInteraction(_thread)._newInteraction_called_from
+ if queryInteraction() is not None:
+ stack = queryInteraction()._newInteraction_called_from
raise AssertionError("newInteraction called"
" while another interaction is active:\n%s"
% "".join(traceback.format_list(stack)))
interaction = getSecurityPolicy().createInteraction(participation)
interaction._newInteraction_called_from = traceback.extract_stack()
- thread_globals(_thread).interaction = interaction
+ thread_local.interaction = interaction
-def endInteraction(_thread=None):
+def endInteraction():
"""End the current interaction."""
- thread_globals(_thread).interaction = None
+ thread_local.interaction = None
+addCleanUp(endInteraction)
-def _cleanUp():
- thread_globals().interaction = None
-addCleanUp(_cleanUp)
-
-
# circular imports are not fun
from zope.security.simplepolicies import ParanoidSecurityPolicy
Modified: Zope3/branches/ZopeX3-3.0/src/zope/security/tests/test_management.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/security/tests/test_management.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/security/tests/test_management.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -41,52 +41,25 @@
setSecurityPolicy(policy)
self.assert_(getSecurityPolicy() is policy)
- def test_queryInteraction(self):
- # XXX this test is a bit obfuscated
+ def test_query_new_end_Interaction(self):
from zope.security.management import queryInteraction
+ self.assertEquals(queryInteraction(), None)
- marker = object()
- class ThreadVars:
- interaction = marker
- class ThreadStub:
- __zope3_thread_globals__ = ThreadVars()
-
- self.assert_(queryInteraction(_thread=ThreadStub()) is marker)
-
- def test_newInteraction(self):
- # XXX this test is a bit obfuscated
from zope.security.management import newInteraction
- class ThreadVars:
- interaction = None
- class ThreadStub:
- __zope3_thread_globals__ = ThreadVars()
-
rq = None
- thread = ThreadStub()
- newInteraction(rq, _thread=thread)
- self.assert_(thread.__zope3_thread_globals__.interaction is not None)
+ newInteraction(rq)
- self.assertRaises(AssertionError, newInteraction, rq, _thread=thread)
+ self.assert_(queryInteraction() is not None)
+ self.assertRaises(AssertionError, newInteraction, rq)
- def test_endInteraction(self):
- # XXX this test is a bit obfuscated
from zope.security.management import endInteraction
- marker = object()
- class ThreadVars:
- interaction = marker
- class ThreadStub:
- __zope3_thread_globals__ = ThreadVars()
+ endInteraction()
+ self.assertEquals(queryInteraction(), None)
+ endInteraction()
+ self.assertEquals(queryInteraction(), None)
- thread = ThreadStub()
- endInteraction(_thread=thread)
- self.assert_(thread.__zope3_thread_globals__.interaction is None)
-
- # again
- endInteraction(_thread=thread)
- self.assert_(thread.__zope3_thread_globals__.interaction is None)
-
def test_checkPermission(self):
from zope.security import checkPermission
from zope.security.management import setSecurityPolicy
Modified: Zope3/branches/ZopeX3-3.0/src/zope/thread/DEPENDENCIES.cfg
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/thread/DEPENDENCIES.cfg 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/thread/DEPENDENCIES.cfg 2004-07-02 22:34:30 UTC (rev 26084)
@@ -1 +0,0 @@
-zope.interface
Modified: Zope3/branches/ZopeX3-3.0/src/zope/thread/__init__.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/thread/__init__.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/thread/__init__.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -11,33 +11,216 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""zope.thread
+"""Thread-local objects
-Implements thread global variables.
+Thread-local objects support the management of thread-local data.
+If you have data that you want to be local to a thread, simply create
+a thread-local object and use it's attributes:
+
+ >>> import zope.thread
+ >>> mydata = zope.thread.local()
+ >>> mydata.__class__.__name__
+ 'local'
+ >>> mydata.number = 42
+ >>> mydata.number
+ 42
+
+You can also access the local-object's dictionary:
+
+ >>> mydata.__dict__
+ {'number': 42}
+ >>> mydata.__dict__.setdefault('widgets', [])
+ []
+ >>> mydata.widgets
+ []
+
+What's important about thread-local objects is that their data are
+local to a thread. If we access the data in a different thread:
+
+ >>> log = []
+ >>> def f():
+ ... items = mydata.__dict__.items()
+ ... items.sort()
+ ... log.append(items)
+ ... mydata.number = 11
+ ... log.append(mydata.number)
+
+ >>> import threading
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+ >>> log
+ [[], 11]
+
+we get different data. Furthermore, changes made in the other thread
+don't affect data seen in this thread:
+
+ >>> mydata.number
+ 42
+
+Of course, values you get from a local object, including a __dict__
+attribute, are for whatever thread was current at the time the
+attribute was read. For that reason, you generally don't want to save
+these values across threads, as they apply only to the thread they
+came from.
+
+You can create custom local objects by subclassing the local class:
+
+ >>> class MyLocal(zope.thread.local):
+ ... number = 2
+ ... initialized = False
+ ... def __init__(self, **kw):
+ ... if self.initialized:
+ ... raise SystemError('__init__ called too many times')
+ ... self.initialized = True
+ ... self.__dict__.update(kw)
+ ... def squared(self):
+ ... return self.number ** 2
+
+This can be useful to support default values, methods and
+initialization. Note that if you define an __init__ method, it will be
+called each time the local object is used in a separate thread. This
+is necessary to initialize each thread's dictionary.
+
+Now if we create a local object:
+
+ >>> mydata = MyLocal(color='red')
+
+Now we have a default number:
+
+ >>> mydata.number
+ 2
+
+an initial color:
+
+ >>> mydata.color
+ 'red'
+ >>> del mydata.color
+
+And a method that operates on the data:
+
+ >>> mydata.squared()
+ 4
+
+As before, we can access the data in a separate thread:
+
+ >>> log = []
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+ >>> log
+ [[('color', 'red'), ('initialized', True)], 11]
+
+without effecting this threads data:
+
+ >>> mydata.number
+ 2
+ >>> mydata.color
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'MyLocal' object has no attribute 'color'
+
+Note that subclasses can define slots, but they are not thread
+local. They are shared across threads:
+
+ >>> class MyLocal(zope.thread.local):
+ ... __slots__ = 'number'
+
+ >>> mydata = MyLocal()
+ >>> mydata.number = 42
+ >>> mydata.color = 'red'
+
+So, the separate thread:
+
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+
+affects what we see:
+
+ >>> mydata.number
+ 11
"""
-import threading
-from zope.interface import moduleProvides, implements
-from zope.thread.interfaces import IZopeThreadAPI
-from zope.thread.interfaces import IInteractionThreadGlobal, ISiteThreadGlobal
+try:
+ import _zope_thread
+except ImportError:
+ from threading import currentThread, enumerate, RLock
-__metaclass__ = type
+ class _localbase(object):
+ __slots__ = '_local__key', '_local__args', '_local__lock'
-moduleProvides(IZopeThreadAPI)
+ def __new__(cls, *args, **kw):
+ self = object.__new__(cls)
+ key = '_local__key', 'thread.local.' + str(id(self))
+ object.__setattr__(self, '_local__key', key)
+ object.__setattr__(self, '_local__args', (args, kw))
+ object.__setattr__(self, '_local__lock', RLock())
+ if args or kw and (cls.__init__ is object.__init__):
+ raise TypeError("Initialization arguments are not supported")
-def thread_globals(thread=None):
- """See IZopeThreadAPI."""
- if thread is None:
- thread = threading.currentThread()
- if not hasattr(thread, '__zope3_thread_globals__'):
- thread.__zope3_thread_globals__ = ThreadGlobals()
- return thread.__zope3_thread_globals__
+ # We need to create the thread dict in anticipation of
+ # __init__ being called, to make sire we don't cal it
+ # again ourselves.
+ dict = object.__getattribute__(self, '__dict__')
+ currentThread().__dict__[key] = dict
+ return self
-class ThreadGlobals:
- implements(IInteractionThreadGlobal, ISiteThreadGlobal)
+ def _patch(self):
+ key = object.__getattribute__(self, '_local__key')
+ d = currentThread().__dict__.get(key)
+ if d is None:
+ d = {}
+ currentThread().__dict__[key] = d
+ object.__setattr__(self, '__dict__', d)
- interaction = None
- site = None
+ # we have a new instance dict, so call out __init__ if we have
+ # one
+ cls = type(self)
+ if cls.__init__ is not object.__init__:
+ args, kw = object.__getattribute__(self, '_local__args')
+ cls.__init__(self, *args, **kw)
+ else:
+ object.__setattr__(self, '__dict__', d)
+
+ class local(_localbase):
+
+ def __getattribute__(self, name):
+ lock = object.__getattribute__(self, '_local__lock')
+ lock.acquire()
+ try:
+ _patch(self)
+ return object.__getattribute__(self, name)
+ finally:
+ lock.release()
+ def __setattr__(self, name, value):
+ lock = object.__getattribute__(self, '_local__lock')
+ lock.acquire()
+ try:
+ _patch(self)
+ return object.__setattr__(self, name, value)
+ finally:
+ lock.release()
+
+ def __delattr__(self, name):
+ lock = object.__getattribute__(self, '_local__lock')
+ lock.acquire()
+ try:
+ _patch(self)
+ return object.__delattr__(self, name)
+ finally:
+ lock.release()
+
+
+ def __del__(self, enumerate=enumerate):
+ key = object.__getattribute__(self, '_local__key')
+ for thread in enumerate():
+ if key in thread.__dict__:
+ del thread.__dict__[key]
+
+else:
+ local = _zope_thread.local
+ del _zope_thread
Copied: Zope3/branches/ZopeX3-3.0/src/zope/thread/_zope_thread.c (from rev 26023, Zope3/trunk/src/zope/thread/_zope_thread.c)
Property changes on: Zope3/branches/ZopeX3-3.0/src/zope/thread/_zope_thread.c
___________________________________________________________________
Name: svn:eol-style
+ native
Deleted: Zope3/branches/ZopeX3-3.0/src/zope/thread/interfaces.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/thread/interfaces.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/thread/interfaces.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -1,38 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Interfaces for zope.thread.
-
-$Id$
-"""
-
-from zope.interface import Interface, Attribute
-
-
-class IZopeThreadAPI(Interface):
-
- def thread_globals(thread=None):
- """Return the thread globals instance for the given thread.
-
- If thread is None, returns the globals for the current thread.
- """
-
-
-class IInteractionThreadGlobal(Interface):
-
- interaction = Attribute("""IInteraction for the current thread.""")
-
-
-class ISiteThreadGlobal(Interface):
-
- site = Attribute("""Site for the current thread.""")
Deleted: Zope3/branches/ZopeX3-3.0/src/zope/thread/tests/__init__.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/thread/tests/__init__.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/thread/tests/__init__.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -1,2 +0,0 @@
-#
-# This file is necessary to make this directory a package.
Deleted: Zope3/branches/ZopeX3-3.0/src/zope/thread/tests/test_thread.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/thread/tests/test_thread.py 2004-07-02 22:31:12 UTC (rev 26083)
+++ Zope3/branches/ZopeX3-3.0/src/zope/thread/tests/test_thread.py 2004-07-02 22:34:30 UTC (rev 26084)
@@ -1,56 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Unit tests for zope.thread.
-
-$Id$
-"""
-
-import unittest
-from zope.interface.verify import verifyObject
-
-
-class ThreadStub:
- pass
-
-
-class TestThread(unittest.TestCase):
-
- def test_ThreadGlobals(self):
- from zope.thread import ThreadGlobals
- from zope.thread.interfaces import IInteractionThreadGlobal
- from zope.thread.interfaces import ISiteThreadGlobal
- globals = ThreadGlobals()
- verifyObject(IInteractionThreadGlobal, globals)
- verifyObject(ISiteThreadGlobal, globals)
-
- def test_thread_globals(self):
- from zope.thread import thread_globals
- from zope.thread.interfaces import IInteractionThreadGlobal
- fake_thread = ThreadStub()
- another_thread = ThreadStub()
- globals = thread_globals(fake_thread)
- verifyObject(IInteractionThreadGlobal, globals)
- self.assert_(thread_globals(fake_thread) is globals)
- self.assert_(thread_globals(another_thread) is not globals)
-
-
-def test_suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(TestThread))
- return suite
-
-
-if __name__ == '__main__':
- unittest.main()
-
Copied: Zope3/branches/ZopeX3-3.0/src/zope/thread/tests.py (from rev 26023, Zope3/trunk/src/zope/thread/tests.py)
Property changes on: Zope3/branches/ZopeX3-3.0/src/zope/thread/tests.py
___________________________________________________________________
Name: cvs2svn:cvs-rev
+ 1.2
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
More information about the Zope3-Checkins
mailing list