Commits

Ronald Oussoren committed 0bfa781

Backport a number of bugfixes and one new feature (objc.object_lock) to the
2.0.x branch.

Passes all unittests.

Comments (0)

Files changed (11)

pyobjc-core/Lib/objc/__init__.py

 from _compat import *
 from _pythonify import *
 from _functions import *
+from _locking import *
 
 ###
 # This can be usefull to hunt down memory problems in the testsuite.

pyobjc-core/Lib/objc/_locking.py

+"""
+Support for @synchronized blocks
+
+The python class object_lock is a contextmanager for with statements that 
+can also be used manually. 
+"""
+import objc as _objc
+
+class object_lock(object):
+    """
+    A context manager that implements the same feature as
+    @synchronized statements in Objective-C. Locking can also
+    be done manually using the ``lock`` and ``unlock`` methods.
+
+    The mutex for object ``anObject`` is represented by 
+    ``objc.object_lock(anObject)``.
+    """
+    def __init__(self, value):
+        self.__value = value
+
+    def __enter__(self):
+        _objc._objc_sync_enter(self.__value)
+
+    def __exit__(self, type, value, tp):
+        _objc._objc_sync_exit(self.__value)
+
+    def lock(self):
+        _objc._objc_sync_enter(self.__value)
+
+    def unlock(self):
+        _objc._objc_sync_exit(self.__value)

pyobjc-core/Lib/objc/test/test_locking.py

+"""
+Test locking objects and interaction with @synchronized() statements
+
+These tests take an annoyingly long time to ensure that we'd hit a race condition when
+locking doesn't actually lock. It should be possible to find a faster mechanism for this.
+"""
+import sys
+import objc.test
+import objc
+import threading
+import time
+
+from objc.test.locking import OC_LockTest
+
+NSAutoreleasePool = objc.lookUpClass("NSAutoreleasePool")
+
+class OtherThread (threading.Thread):
+    def __init__(self, obj):
+        self.obj = obj
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        pool = NSAutoreleasePool.alloc().init()
+
+        lck = objc.object_lock(self.obj)
+
+        for i in range(6):
+            time.sleep(0.05)
+            lck.lock()
+            if self.obj.isLocked():
+                self.obj.appendToList_("LOCK FOUND")
+
+            self.obj.setLocked_(True)
+            self.obj.appendToList_("thread %d a"%(i,))
+            time.sleep(0.5)
+            self.obj.appendToList_("thread %d b"%(i,))
+            self.obj.setLocked_(False)
+            lck.unlock()
+
+        del pool
+
+class ObjCThread (threading.Thread):
+    def __init__(self, obj):
+        self.obj = obj
+
+        threading.Thread.__init__(self)
+
+    def run(self):
+        pool = NSAutoreleasePool.alloc().init()
+
+        native = OC_LockTest.alloc().init()
+        native.threadFunc_(self.obj)
+
+        del native
+        del pool
+
+
+class BaseClass (objc.lookUpClass('NSObject')):
+    def initWithList_(self, list):
+        self = super(BaseClass, self).init()
+        if self is None:
+            return None
+
+        self.list = list
+        self._locked = False
+        return self
+
+    def isLocked(self):
+        return self._locked
+
+    def setLocked_(self, value):
+        self._locked = value
+
+
+    def appendToList_(self, value):
+        self.list.append(value)
+
+class TestLockingBasic (objc.test.TestCase):
+
+    def testBasicLocking(self):
+        lst = []
+
+        obj = BaseClass.alloc().initWithList_(lst)
+        lck = objc.object_lock(obj)
+
+        thr = OtherThread(obj)
+        thr.start()
+        for i in range(5):
+            time.sleep(0.1)
+            lck.lock()
+            self.assert_(not obj.isLocked())
+            obj.setLocked_(True)
+            obj.appendToList_("mainthread")
+            obj.setLocked_(False)
+            lck.unlock()
+
+        thr.join()
+
+        self.assert_("LOCK FOUND" not in lst)
+        for idx in range(len(lst)):
+            if lst[idx].endswith(' a'):
+                self.assert_(lst[idx+1].endswith(' b'))
+
+    def testObjectiveCLocking(self):
+        lst = []
+
+        obj = BaseClass.alloc().initWithList_(lst)
+        lck = objc.object_lock(obj)
+
+        thr = ObjCThread(obj)
+        thr.start()
+        for i in range(5):
+            time.sleep(0.1)
+            lck.lock()
+            self.assert_(not obj.isLocked())
+            obj.setLocked_(True)
+            obj.appendToList_("mainthread")
+            time.sleep(0.5)
+            obj.setLocked_(False)
+            lck.unlock()
+
+        thr.join()
+
+        self.assert_("LOCK FOUND" not in lst)
+        for idx in range(len(lst)):
+            if lst[idx].endswith(' a'):
+                self.assert_(lst[idx+1].endswith(' b'))
+
+
+if sys.version_info[:2] >= (2, 5):
+    # Test using a with-statement, that will only work on 2.5 or later and will cause
+    # a compile error on earlier editions of python, hence and ugly exec statement.
+    exec """
+from __future__ import with_statement
+class TestLockingWithStatement (objc.test.TestCase):
+
+    def testBasicLocking(self):
+        lst = []
+
+        obj = BaseClass.alloc().initWithList_(lst)
+
+        thr = OtherThread(obj)
+        thr.start()
+        for i in range(5):
+            time.sleep(0.1)
+            with objc.object_lock(obj):
+                self.assert_(not obj.isLocked())
+                obj.setLocked_(True)
+                obj.appendToList_("mainthread")
+                time.sleep(0.5)
+                obj.setLocked_(False)
+
+        thr.join()
+
+        self.assert_("LOCK FOUND" not in lst)
+        for idx in range(len(lst)):
+            if lst[idx].endswith(' a'):
+                self.assert_(lst[idx+1].endswith(' b'))
+"""
+
+
+if __name__ == "__main__":
+    objc.test.main()

pyobjc-core/Modules/objc/class-builder.m

 	preclass_addMethod(intermediate_class, 
 		@selector(takeValue:forKey:), (IMP)closure, "v@:@@");
 
+
+	methinfo = PyObjCMethodSignature_FromSignature("c@::");
+	if (methinfo == NULL) goto error_cleanup; 
+	closure = PyObjCFFI_MakeClosure(methinfo, 
+		object_method_respondsToSelector,
+		base_class);
+	Py_DECREF(methinfo); methinfo = NULL;
+	if (closure == NULL) goto error_cleanup;
+	preclass_addMethod(intermediate_class,
+		@selector(respondsToSelector:), 
+		(IMP)closure, "c@::");
+
+	methinfo = PyObjCMethodSignature_FromSignature("@@::");
+	if (methinfo == NULL) goto error_cleanup; 
+	closure = PyObjCFFI_MakeClosure(methinfo, 
+		object_method_methodSignatureForSelector,
+		base_class);
+	Py_DECREF(methinfo); methinfo = NULL;
+	if (closure == NULL) goto error_cleanup;
+	preclass_addMethod(intermediate_class,
+		@selector(methodSignatureForSelector:), 
+		(IMP)closure, "@@::");
+
+	methinfo = PyObjCMethodSignature_FromSignature("v@:@");
+	if (methinfo == NULL) goto error_cleanup; 
+	closure = PyObjCFFI_MakeClosure(methinfo, 
+		object_method_forwardInvocation,
+		base_class);
+	Py_DECREF(methinfo); methinfo = NULL;
+	if (closure == NULL) goto error_cleanup;
+	preclass_addMethod(intermediate_class,
+		@selector(forwardInvocation:), 
+		(IMP)closure, "v@:@");
+
+	
+
 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
 	if (_KVOHackLevel() == BROKEN_KVO) {
 		methinfo = PyObjCMethodSignature_FromSignature("v@:@");
 	} else {
 		need_intermediate = 1;
 	}
+	if (PyDict_GetItemString(class_dict, "respondsToSelector_") == NULL) {
+		PyErr_Clear();
+	} else {
+		need_intermediate = 1;
+	}
+	if (PyDict_GetItemString(class_dict, "instancesRespondToSelector_") == NULL) {
+		PyErr_Clear();
+	} else {
+		need_intermediate = 1;
+	}
+
+	if (PyDict_GetItemString(class_dict, "methodSignatureForSelector_") == NULL) {
+		PyErr_Clear();
+	} else {
+		need_intermediate = 1;
+	}
+	if (PyDict_GetItemString(class_dict, "forwardInvocation_") == NULL) {
+		PyErr_Clear();
+	} else {
+		need_intermediate = 1;
+	}
 
 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
 	if (_KVOHackLevel() == BROKEN_KVO) {
 				"v@:@@",
 				object_method_setValue_forKey_);
 
+			METH(
+				"forwardInvocation_",
+				@selector(forwardInvocation:),
+				"v@:@",
+				object_method_forwardInvocation);
+			METH(
+				"methodSignatureForSelector_",
+				@selector(methodSignatureForSelector:),
+				"@@::",
+				object_method_methodSignatureForSelector);
+			METH(
+				"respondsToSelector",
+				@selector(respondsToSelector:),
+				"c@::",
+				object_method_respondsToSelector);
+
 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
 			if (_KVOHackLevel() == BROKEN_KVO) {
 				METH(
 			}
 #endif
 		}
-		/* FIXME: 
-		 * all these should be in the intermediate class as well,
-		 * define the intermediate class when any of them are 
-		 * overridden
-		 */
 
-		/* These methods are necessary to deal with mixin classes (multiple inheritance),
-		 * the default implementation only looks at methods in the ObjC class.
-		 */
-		METH(
-			"respondsToSelector_", 
-			@selector(respondsToSelector:), 
-			"c@::", 
-			object_method_respondsToSelector);
-		METH(
-			"methodSignatureForSelector_", 
-			@selector(methodSignatureForSelector:), 
-			"@@::", 
-			object_method_methodSignatureForSelector);
-		METH(
-			"forwardInvocation_", 
-			@selector(forwardInvocation:), 
-			"v@:@", 
-			object_method_forwardInvocation);
 
 		if (!have_intermediate && [super_class instancesRespondToSelector:@selector(copyWithZone:)]) {
 			if (copyWithZone_signature[0] == '\0') {

pyobjc-core/Modules/objc/corefoundation.h

 #ifndef PyObjC_COREFOUNDATION_H
 #define PyObjC_COREFOUNDATION_H
 
+extern PyObject* PyObjC_NSCFTypeClass;
+
 extern int PyObjCCFType_Setup(void);
 extern PyObject* PyObjCCFType_New(char* name, char* encoding, CFTypeID typeID);
 extern PyObject* PyObjCCF_NewSpecial(char* encoding, void* datum);

pyobjc-core/Modules/objc/corefoundation.m

 #include <CoreFoundation/CoreFoundation.h>
 
 static PyObject* gTypeid2class = NULL;
-static PyObject* gBaseClass = NULL;
+PyObject* PyObjC_NSCFTypeClass = NULL;
 
 static PyObject*
 cf_repr(PyObject* self)
 
 	bases = PyTuple_New(1);
 
-	PyTuple_SET_ITEM(bases, 0, gBaseClass);
-	Py_INCREF(gBaseClass);
+	PyTuple_SET_ITEM(bases, 0, PyObjC_NSCFTypeClass);
+	Py_INCREF(PyObjC_NSCFTypeClass);
 
 	args = PyTuple_New(3);
 	PyTuple_SetItem(args, 0, PyString_FromString(name));
 	((PyTypeObject*)result)->tp_str = cf_repr;
 
 	info = (PyObjCClassObject*)result;
-	info->class = PyObjCClass_GetClass(gBaseClass);
+	info->class = PyObjCClass_GetClass(PyObjC_NSCFTypeClass);
 	info->sel_to_py = NULL;
 	info->method_magic = 0;
 	info->dictoffset = 0;
 			"Cannot locate NSCFType");
 		return -1;
 	}
-	gBaseClass = PyObjCClass_New(cls);
-	if (gBaseClass == NULL) {
+	PyObjC_NSCFTypeClass = PyObjCClass_New(cls);
+	if (PyObjC_NSCFTypeClass == NULL) {
 		return -1;
 	}
 

pyobjc-core/Modules/objc/module.m

 #include "pyobjc.h"
 #include "OC_NSBundleHack.h"
 #include <objc/Protocol.h>
+#include <objc/objc-sync.h>
 
 #include <stddef.h>
 #include <ctype.h>
 	return Py_None;
 }
 
+/* Support for locking */
+static PyObject*
+PyObjC_objc_sync_enter(PyObject* self __attribute__((__unused__)), PyObject* args)
+{
+	NSObject* object;
+	int rv;
+
+	if (!PyArg_ParseTuple(args, "O&", 
+			PyObjCObject_Convert, &object)) {
+		return NULL;
+	}
+
+	Py_BEGIN_ALLOW_THREADS
+		rv = objc_sync_enter(object);
+	
+	Py_END_ALLOW_THREADS
+
+	if (rv == OBJC_SYNC_SUCCESS) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	PyErr_Format(PyObjCExc_LockError, "objc_sync_enter failed: %d", rv);
+	return NULL;
+}
+
+static PyObject*
+PyObjC_objc_sync_exit(PyObject* self __attribute__((__unused__)), PyObject* args)
+{
+	NSObject* object;
+	int rv;
+
+	if (!PyArg_ParseTuple(args, "O&", 
+			PyObjCObject_Convert, &object)) {
+		return NULL;
+	}
+
+	Py_BEGIN_ALLOW_THREADS
+		rv = objc_sync_exit(object);
+	Py_END_ALLOW_THREADS
+	if (rv == OBJC_SYNC_SUCCESS) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	PyErr_Format(PyObjCExc_LockError, "objc_sync_exit failed: %d", rv);
+	return NULL;
+}
+
+static PyObject*
+PyObjC_objc_sync_notify(PyObject* self __attribute__((__unused__)), PyObject* args)
+{
+	NSObject* object;
+	int rv;
+
+	if (!PyArg_ParseTuple(args, "O&", 
+			PyObjCObject_Convert, &object)) {
+		return NULL;
+	}
+
+	Py_BEGIN_ALLOW_THREADS
+		rv = objc_sync_notify(object);
+
+	Py_END_ALLOW_THREADS
+
+	if (rv == OBJC_SYNC_SUCCESS) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	PyErr_Format(PyObjCExc_LockError, "objc_sync_notify failed: %d", rv);
+	return NULL;
+}
+
+static PyObject*
+PyObjC_objc_sync_notifyAll(PyObject* self __attribute__((__unused__)), PyObject* args)
+{
+	NSObject* object;
+	int rv;
+
+	if (!PyArg_ParseTuple(args, "O&", 
+			PyObjCObject_Convert, &object)) {
+		return NULL;
+	}
+
+	Py_BEGIN_ALLOW_THREADS
+		rv = objc_sync_notifyAll(object);
+		
+	Py_END_ALLOW_THREADS
+
+	if (rv == OBJC_SYNC_SUCCESS) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	PyErr_Format(PyObjCExc_LockError, "objc_sync_notifyAll failed: %d", rv);
+	return NULL;
+}
+
+
+static PyObject*
+PyObjC_objc_sync_wait(PyObject* self __attribute__((__unused__)), PyObject* args)
+{
+	NSObject* object;
+	long long timeout;
+	int rv;
+
+	if (!PyArg_ParseTuple(args, "O&L", 
+			PyObjCObject_Convert, &object, &timeout)) {
+		return NULL;
+	}
+
+	Py_BEGIN_ALLOW_THREADS
+		rv = objc_sync_wait(object, timeout);
+	
+	Py_END_ALLOW_THREADS
+
+	if (rv == OBJC_SYNC_SUCCESS) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	PyErr_Format(PyObjCExc_LockError, "objc_sync_wait failed: %d", rv);
+	return NULL;
+}
+
+
 PyDoc_STRVAR(parseBridgeSupport_doc,
  "parseBridgeSupport(xmldata, globals, framework [, dylib_path] [, inlineTab]) -> None\n"
  "\n"
 		METH_O, "private function" },
 	{ "_ivar_dict", (PyCFunction)ivar_dict, METH_NOARGS, "private functions" },
 
+	{ "_objc_sync_enter", (PyCFunction)PyObjC_objc_sync_enter,
+		METH_VARARGS, "acquire mutex for an object" },
+	{ "_objc_sync_exit", (PyCFunction)PyObjC_objc_sync_exit,
+		METH_VARARGS, "release mutex for an object" },
+	{ "_objc_sync_wait", (PyCFunction)PyObjC_objc_sync_wait,
+		METH_VARARGS, "wait for mutex for an object" },
+	{ "_objc_sync_notify", (PyCFunction)PyObjC_objc_sync_notify,
+		METH_VARARGS, 
+		"notify a thread waiting for mutex for an object" },
+	{ "_objc_sync_notifyAll", (PyCFunction)PyObjC_objc_sync_notifyAll,
+		METH_VARARGS, 
+		"notify a all threads waiting for mutex for an object" },
+
 
 	{ 0, 0, 0, 0 } /* sentinel */
 };

pyobjc-core/Modules/objc/objc_util.h

 extern PyObject* PyObjCExc_InternalError;
 extern PyObject* PyObjCExc_UnInitDeallocWarning;
 extern PyObject* PyObjCExc_ObjCRevivalWarning;
+extern PyObject* PyObjCExc_LockError;
 
 int PyObjCUtil_Init(PyObject* module);
 

pyobjc-core/Modules/objc/objc_util.m

 PyObject* PyObjCExc_InternalError;
 PyObject* PyObjCExc_UnInitDeallocWarning;
 PyObject* PyObjCExc_ObjCRevivalWarning;
+PyObject* PyObjCExc_LockError;
 
 
 int 
 	NEW_EXC(PyObjCExc_InternalError, "internal_error", PyObjCExc_Error);
 	NEW_EXC(PyObjCExc_UnInitDeallocWarning, "UninitializedDeallocWarning", PyExc_Warning);
 	NEW_EXC(PyObjCExc_ObjCRevivalWarning, "RevivedObjectiveCObjectWarning", PyExc_Warning);
+	NEW_EXC(PyObjCExc_LockError, "LockError", PyObjCExc_Error);
 
 	return 0;
 }

pyobjc-core/Modules/objc/test/locking.m

+/*
+ * Helper methods opaque-pointer tests (objc.test.test_opaque)
+ */
+#include "Python.h"
+#include "pyobjc-api.h"
+#include <stdarg.h>
+
+#import <Foundation/Foundation.h>
+
+typedef struct _Foo* FooHandle;
+typedef struct _Bar* BarHandle;
+
+@interface NSObject (OC_LockingTest)
+-(void)setLocked:(NSObject*)value;
+-(NSObject*)isLocked;
+-(void)appendToList:(NSObject*)value;
+@end
+
+@interface OC_LockTest : NSObject
+-(void)threadFunc:(NSObject*)object;
+@end
+
+@implementation OC_LockTest
+-(void)threadFunc:(NSObject*)object
+{
+	int i;
+	for (i = 0; i < 6; i++) {
+		usleep(500000);
+		@synchronized(object) {
+			NSNumber* isLocked = (NSNumber*)[object isLocked];
+			if ([isLocked boolValue]) {
+				[object appendToList:@"LOCK FOUND"];
+			}
+			[object setLocked:[NSNumber numberWithBool:YES]];
+			[object appendToList:@"threading a"];
+			usleep(5000000);
+			[object appendToList:@"threading b"];
+			[object setLocked:[NSNumber numberWithBool:NO]];
+		}
+	}
+}
+@end
+
+
+static PyMethodDef mod_methods[] = {
+	        { 0, 0, 0, 0 }
+};
+
+void initlocking(void);
+void initlocking(void)
+{
+	PyObject* m;
+
+	m = Py_InitModule4("locking", mod_methods, NULL, NULL, PYTHON_API_VERSION);
+
+	PyObjC_ImportAPI(m);
+
+	PyModule_AddObject(m, "OC_LockTest", 
+		PyObjCClass_New([OC_LockTest class]));
+}

pyobjc-core/NEWS.txt

 Version 2.0.1 (...)
 -------------------
 
-- Fix crash when printing CF objects that are magic cookies.
+- BUGFIX: don't crash when printing CF objects that are magic cookies.
 
+- BUGFIX: It is now possible to override ``respondsToSelector:`` in Python.
 
-Version 2.0 (LEOPARD, post WWDC07)
-----------------------------------
+- BUGFIX: The wrappers for CoreFoundation types no longer create a new type 
+  in the Objective-C runtime, that type wasn't used anywhere and was an 
+  unwanted side-effect of how CoreFoundation types are wrapped.
+
+- Add support for interacting with '@synchronized' blocks in Objective-C.
+
+  The function ``object_lock(object)`` is a contextmanager that acquires and
+  releases the '@synchronized' mutex for an object, and can also be used
+  manually.
+
+  That is (as context manager)::
+  	from __future__ import with_statement
+
+	obj = NSObject.new()
+
+	with objc.object_lock(obj):
+	    # Perform work while owning the @synchronize lock
+	    pass
+
+   or (manually)::
+
+   	obj = NSObject.new()
+	mutex = objc.object_lock(obj)
+	mutex.lock()
+	try:
+	    # Perform work while owning the @synchronized lock
+	    pass
+	finally:
+	    mutex.unlock()
+
+   Note that the first version is slightly saver (see the documentation
+   for with-statements for the details).
+
+Version 2.0 (MacOS X 10.5.0)
+----------------------------
 
 - The basic infrastructure for playing nice in a GC host was added.
 
 	     self.description = 42
 
 
-
-Version 2.0 (LEOPARD)
----------------------
-
-- NOTE: most of pyobjc should be 64-bit safe, but I haven't tested this yet
-  and the included libffi probably doesn't work with 64-bit darwin.
-
 - PyObjC has been split into several smaller packages: ``pyobjc-core`` contains
   the core bridge and frameworks are wrapped as seperate setuptools packages.