[pypy-commit] pypy default: Add support for TCL libraries compiled with --disable-threads.

amauryfa noreply at buildbot.pypy.org
Sun Sep 8 21:49:40 CEST 2013


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: 
Changeset: r66851:a2cfb94ccff5
Date: 2013-09-08 21:47 +0200
http://bitbucket.org/pypy/pypy/changeset/a2cfb94ccff5/

Log:	Add support for TCL libraries compiled with --disable-threads.

	(manually tested with a custom build of TCL)

diff --git a/lib_pypy/_tkinter/__init__.py b/lib_pypy/_tkinter/__init__.py
--- a/lib_pypy/_tkinter/__init__.py
+++ b/lib_pypy/_tkinter/__init__.py
@@ -22,6 +22,7 @@
 READABLE = tklib.TCL_READABLE
 WRITABLE = tklib.TCL_WRITABLE
 EXCEPTION = tklib.TCL_EXCEPTION
+DONT_WAIT = tklib.TCL_DONT_WAIT
 
 def create(screenName=None, baseName=None, className=None,
            interactive=False, wantobjects=False, wantTk=True,
diff --git a/lib_pypy/_tkinter/app.py b/lib_pypy/_tkinter/app.py
--- a/lib_pypy/_tkinter/app.py
+++ b/lib_pypy/_tkinter/app.py
@@ -4,7 +4,23 @@
 from . import TclError
 from .tclobj import TclObject, FromObj, AsObj, TypeCache
 
+import contextlib
 import sys
+import threading
+import time
+
+
+class _DummyLock(object):
+    "A lock-like object that does not do anything"
+    def acquire(self):
+        pass
+    def release(self):
+        pass
+    def __enter__(self):
+        pass
+    def __exit__(self, *exc):
+        pass
+
 
 def varname_converter(input):
     if isinstance(input, TclObject):
@@ -37,17 +53,18 @@
     def PythonCmd(clientData, interp, argc, argv):
         self = tkffi.from_handle(clientData)
         assert self.app.interp == interp
-        try:
-            args = [tkffi.string(arg) for arg in argv[1:argc]]
-            result = self.func(*args)
-            obj = AsObj(result)
-            tklib.Tcl_SetObjResult(interp, obj)
-        except:
-            self.app.errorInCmd = True
-            self.app.exc_info = sys.exc_info()
-            return tklib.TCL_ERROR
-        else:
-            return tklib.TCL_OK
+        with self.app._tcl_lock_released():
+            try:
+                args = [tkffi.string(arg) for arg in argv[1:argc]]
+                result = self.func(*args)
+                obj = AsObj(result)
+                tklib.Tcl_SetObjResult(interp, obj)
+            except:
+                self.app.errorInCmd = True
+                self.app.exc_info = sys.exc_info()
+                return tklib.TCL_ERROR
+            else:
+                return tklib.TCL_OK
 
     @tkffi.callback("Tcl_CmdDeleteProc")
     def PythonCmdDelete(clientData):
@@ -58,6 +75,8 @@
 
 
 class TkApp(object):
+    _busywaitinterval = 0.02  # 20ms.
+
     def __new__(cls, screenName, baseName, className,
                 interactive, wantobjects, wantTk, sync, use):
         if not wantobjects:
@@ -73,6 +92,12 @@
         self.quitMainLoop = False
         self.errorInCmd = False
 
+        if not self.threaded:
+            # TCL is not thread-safe, calls needs to be serialized.
+            self._tcl_lock = threading.Lock()
+        else:
+            self._tcl_lock = _DummyLock()
+
         self._typeCache = TypeCache()
         self._commands = {}
 
@@ -133,6 +158,13 @@
         if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread():
             raise RuntimeError("Calling Tcl from different appartment")
 
+    @contextlib.contextmanager
+    def _tcl_lock_released(self):
+        "Context manager to temporarily release the tcl lock."
+        self._tcl_lock.release()
+        yield
+        self._tcl_lock.acquire()
+
     def loadtk(self):
         # We want to guard against calling Tk_Init() multiple times
         err = tklib.Tcl_Eval(self.interp, "info exists     tk_version")
@@ -159,22 +191,25 @@
         flags=tklib.TCL_LEAVE_ERR_MSG
         if global_only:
             flags |= tklib.TCL_GLOBAL_ONLY
-        res = tklib.Tcl_GetVar2Ex(self.interp, name1, name2, flags)
-        if not res:
-            self.raiseTclError()
-        assert self._wantobjects
-        return FromObj(self, res)
+        with self._tcl_lock:
+            res = tklib.Tcl_GetVar2Ex(self.interp, name1, name2, flags)
+            if not res:
+                self.raiseTclError()
+            assert self._wantobjects
+            return FromObj(self, res)
 
     def _setvar(self, name1, value, global_only=False):
         name1 = varname_converter(name1)
+        # XXX Acquire tcl lock???
         newval = AsObj(value)
         flags=tklib.TCL_LEAVE_ERR_MSG
         if global_only:
             flags |= tklib.TCL_GLOBAL_ONLY
-        res = tklib.Tcl_SetVar2Ex(self.interp, name1, tkffi.NULL,
-                                  newval, flags)
-        if not res:
-            self.raiseTclError()
+        with self._tcl_lock:
+            res = tklib.Tcl_SetVar2Ex(self.interp, name1, tkffi.NULL,
+                                      newval, flags)
+            if not res:
+                self.raiseTclError()
 
     def _unsetvar(self, name1, name2=None, global_only=False):
         name1 = varname_converter(name1)
@@ -183,9 +218,10 @@
         flags=tklib.TCL_LEAVE_ERR_MSG
         if global_only:
             flags |= tklib.TCL_GLOBAL_ONLY
-        res = tklib.Tcl_UnsetVar2(self.interp, name1, name2, flags)
-        if res == tklib.TCL_ERROR:
-            self.raiseTclError()
+        with self._tcl_lock:
+            res = tklib.Tcl_UnsetVar2(self.interp, name1, name2, flags)
+            if res == tklib.TCL_ERROR:
+                self.raiseTclError()
 
     def getvar(self, name1, name2=None):
         return self._var_invoke(self._getvar, name1, name2)
@@ -219,9 +255,10 @@
         if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread():
             raise NotImplementedError("Call from another thread")
 
-        res = tklib.Tcl_CreateCommand(
-            self.interp, cmdName, _CommandData.PythonCmd,
-            clientData, _CommandData.PythonCmdDelete)
+        with self._tcl_lock:
+            res = tklib.Tcl_CreateCommand(
+                self.interp, cmdName, _CommandData.PythonCmd,
+                clientData, _CommandData.PythonCmdDelete)
         if not res:
             raise TclError("can't create Tcl command")
 
@@ -229,7 +266,8 @@
         if self.threaded and self.thread_id != tklib.Tcl_GetCurrentThread():
             raise NotImplementedError("Call from another thread")
 
-        res = tklib.Tcl_DeleteCommand(self.interp, cmdName)
+        with self._tcl_lock:
+            res = tklib.Tcl_DeleteCommand(self.interp, cmdName)
         if res == -1:
             raise TclError("can't delete Tcl command")
 
@@ -256,11 +294,12 @@
                 tklib.Tcl_IncrRefCount(obj)
                 objects[i] = obj
 
-            res = tklib.Tcl_EvalObjv(self.interp, argc, objects, flags)
-            if res == tklib.TCL_ERROR:
-                self.raiseTclError()
-            else:
-                result = self._callResult()
+            with self._tcl_lock:
+                res = tklib.Tcl_EvalObjv(self.interp, argc, objects, flags)
+                if res == tklib.TCL_ERROR:
+                    self.raiseTclError()
+                else:
+                    result = self._callResult()
         finally:
             for obj in objects:
                 if obj:
@@ -280,17 +319,19 @@
 
     def eval(self, script):
         self._check_tcl_appartment()
-        res = tklib.Tcl_Eval(self.interp, script)
-        if res == tklib.TCL_ERROR:
-            self.raiseTclError()
-        return tkffi.string(tklib.Tcl_GetStringResult(self.interp))
+        with self._tcl_lock:
+            res = tklib.Tcl_Eval(self.interp, script)
+            if res == tklib.TCL_ERROR:
+                self.raiseTclError()
+            return tkffi.string(tklib.Tcl_GetStringResult(self.interp))
 
     def evalfile(self, filename):
         self._check_tcl_appartment()
-        res = tklib.Tcl_EvalFile(self.interp, filename)
-        if res == tklib.TCL_ERROR:
-            self.raiseTclError()
-        return tkffi.string(tklib.Tcl_GetStringResult(self.interp))
+        with self._tcl_lock:
+            res = tklib.Tcl_EvalFile(self.interp, filename)
+            if res == tklib.TCL_ERROR:
+                self.raiseTclError()
+            return tkffi.string(tklib.Tcl_GetStringResult(self.interp))
 
     def split(self, arg):
         if isinstance(arg, tuple):
@@ -375,7 +416,10 @@
             if self.threaded:
                 result = tklib.Tcl_DoOneEvent(0)
             else:
-                raise NotImplementedError("TCL configured without threads")
+                with self._tcl_lock:
+                    result = tklib.Tcl_DoOneEvent(tklib.TCL_DONT_WAIT)
+                if result == 0:
+                    time.sleep(self._busywaitinterval)
 
             if result < 0:
                 break
diff --git a/lib_pypy/_tkinter/tklib.py b/lib_pypy/_tkinter/tklib.py
--- a/lib_pypy/_tkinter/tklib.py
+++ b/lib_pypy/_tkinter/tklib.py
@@ -19,6 +19,8 @@
 #define TCL_EVAL_DIRECT ...
 #define TCL_EVAL_GLOBAL ...
 
+#define TCL_DONT_WAIT ...
+
 typedef unsigned short Tcl_UniChar;
 typedef ... Tcl_Interp;
 typedef ...* Tcl_ThreadId;


More information about the pypy-commit mailing list