[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