[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