Commits

Ronald Oussoren committed e442ed7

- Add unittests for objc.object_lock
- Make sure that objc.object_lock actually works

Comments (0)

Files changed (4)

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/libffi_support.m

 	} else if (PyObjCPythonSelector_Check(callable)) {
 		return _argcount(((PyObjCPythonSelector*)callable)->callable);
 
+
+	} else if (PyObjCNativeSelector_Check(callable)) {
+		PyObjCMethodSignature* sig = PyObjCSelector_GetMetadata(callable);
+		 int result = sig->ob_size - 1;
+		 
+		 Py_DECREF(sig);
+		 return result;
+
+
 	} else {
 		PyErr_Format(PyExc_TypeError,
 			"Sorry, cannot create IMP for instances of type %s",

pyobjc-core/Modules/objc/module.m

 		return NULL;
 	}
 
-	rv = objc_sync_enter(object);
+	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;
 		return NULL;
 	}
 
-	rv = objc_sync_exit(object);
+	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;
 		return NULL;
 	}
 
-	rv = objc_sync_notify(object);
+	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;
 		return NULL;
 	}
 
-	rv = objc_sync_notifyAll(object);
+	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;
 		return NULL;
 	}
 
-	rv = objc_sync_wait(object, timeout);
+	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;

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]));
+}