1. Ronald Oussoren
  2. pyobjc

Commits

Bob Ippolito  committed 62cdbc7

Moved GIL macros to pyobjc-api.h, because they're useful outside of objc core
Added _machsignals and PyObjCTools.MachSignals:
mach port magic to safely and efficiently handle signals from event loops

  • Participants
  • Parent commits 2c9e825
  • Branches pyobjc-ancient

Comments (0)

Files changed (6)

File Lib/PyObjCTools/AppHelper.py

View file
 * endSheetMethod - set correct signature for NSSheet callbacks
 """
 
-__all__ = ( 'runEventLoop', 'endSheetMethod' )
+__all__ = ( 'runEventLoop', 'runConsoleEventLoop', 'endSheetMethod' )
 
 from AppKit import NSApplicationMain, NSApp, NSRunAlertPanel
-from Foundation import NSLog
+from Foundation import NSLog, NSRunLoop
 import sys
 import traceback
 import objc as _objc
             "Continue", "Quit", None)
 
 
-def runEventLoop(argv=None, unexpectedErrorAlert=unexpectedErrorAlert):
+def machInterrupt(signum):
+    app = NSApp()
+    if app:
+        app.terminate_(None)
+    else:
+        import os
+        os._exit(1)
+
+def installMachInterrupt():
+    try:
+        import signal
+        from PyObjCTools import MachSignals
+    except:
+        return
+    MachSignals.signal(signal.SIGINT, machInterrupt)
+
+def runConsoleEventLoop(argv=None, installInterrupt=True):
+    if argv is None:
+        argv = sys.argv
+    if installInterrupt:
+        installMachInterrupt()
+    NSRunLoop.currentRunLoop().run()
+
+def runEventLoop(argv=None, unexpectedErrorAlert=unexpectedErrorAlert, installInterrupt=True):
     """Run the event loop, ask the user if we should continue if an
     exception is caught. Use this function instead of NSApplicationMain().
     """
         try:
             if firstRun:
                 firstRun = 0
+                if installInterrupt:
+                    installMachInterrupt()
                 NSApplicationMain(argv)
             else:
                 NSApp().run()

File Lib/PyObjCTools/MachSignals.py

View file
+import _machsignals
+import signal as signalmodule
+__all__ = ['getsignal', 'signal']
+
+def getsignal(signum):
+    return _machsignals._signalmapping.get(signum)
+
+def signal(signum, handler):
+    rval = getsignal(signum)
+    _machsignals._signalmapping[signum] = handler
+    _machsignals.handleSignal(signum)
+    return rval

File Modules/CoreFoundation/machsignals.m

View file
+#include <mach/mach.h>
+#include <mach/mach_error.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "pyobjc-api.h"
+
+PyDoc_STRVAR(machsignals_doc,
+"_machsignals ...");
+
+static mach_port_t exit_m_port       = MACH_PORT_NULL;
+static PyObject *signalmapping;
+
+
+static void SIGCallback(CFMachPortRef port, void *msg, CFIndex size, void *info)
+{
+	PyObject *tmp;
+	PyObject *callable;
+	int signum;
+	(void)port;     // Unused
+	(void)size;     // Unused
+	(void)info;     // Unused
+	// this is abuse of msgh_id
+	signum = ((mach_msg_header_t*)msg)->msgh_id;
+	if (!signalmapping) {
+		return;
+	}
+	PyObjC_BEGIN_WITH_GIL
+	do {
+		tmp = PyInt_FromLong((long)signum);
+		if (!tmp) break;
+
+		callable = PyDict_GetItem(signalmapping, tmp);
+		Py_DECREF(tmp);
+		if (!callable) break;
+
+		tmp = PyObject_CallFunction(callable, "i", signum);
+		Py_DECREF(callable);
+		Py_XDECREF(tmp);
+	} while (0);
+	if (PyErr_Occurred())
+		PyObjC_GIL_FORWARD_EXC();
+	PyObjC_END_WITH_GIL
+}
+
+static void HandleSIG(int signum)
+{
+	// Send a mach_msg to ourselves (since that is signal safe) telling us to
+    // handle a signal
+	mach_msg_return_t msg_result;
+	mach_msg_header_t header;
+
+	header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
+	header.msgh_remote_port = exit_m_port;
+	header.msgh_local_port = MACH_PORT_NULL;
+	header.msgh_size = sizeof(header);
+	// this is abuse of msgh_id
+	header.msgh_id = signum;
+
+	msg_result = mach_msg_send(&header);
+}
+
+PyDoc_STRVAR(machsignals_handleSignal_doc, 
+	"handleSignal(signum) -> None\n"
+	"\n"
+	"Handle a signal using the registered mach callback\n"
+	"Raises an ObjC exception if the callback fails"
+);
+static PyObject*
+machsignals_handleSignal(PyObject *self __attribute__((__unused__)), PyObject *args, PyObject *kwds)
+{
+	static char* keywords[] = { "signum", 0 };
+	int signum;
+	
+	if (!PyArg_ParseTupleAndKeywords(args, kwds,
+		"i:handleSignal", keywords,
+		&signum)) {
+		return NULL;
+	}
+
+	signal(signum, HandleSIG);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyMethodDef machsignals_methods[] = {
+	{
+		"handleSignal",
+		(PyCFunction)machsignals_handleSignal,
+		METH_VARARGS,
+		machsignals_handleSignal_doc
+	},
+	{ 0, 0, 0, 0}
+};
+
+void init_machsignals(void);
+void init_machsignals(void) {
+	PyObject *m;
+	PyObject *d;
+	CFMachPortRef e_port;
+	CFRunLoopSourceRef e_rls;
+
+	m = Py_InitModule4("_machsignals", machsignals_methods,
+		machsignals_doc, NULL, PYTHON_API_VERSION);
+	if (!m) return;
+
+	d = PyModule_GetDict(m);
+	if (!d) return;
+
+	if (PyObjC_ImportAPI(m) < 0) {
+		printf("Importing objc failed\n");
+		return;
+	}
+
+	signalmapping = PyDict_New();
+	if (!signalmapping) return;
+
+	PyObject_SetAttrString(m, "_signalmapping", signalmapping);
+
+	e_port = CFMachPortCreate(NULL, SIGCallback, NULL, NULL);
+	exit_m_port = CFMachPortGetPort(e_port);
+	e_rls = CFMachPortCreateRunLoopSource(NULL, e_port, 0);
+	CFRunLoopAddSource(CFRunLoopGetCurrent(), e_rls, kCFRunLoopDefaultMode);
+	CFRelease(e_rls);
+}

File Modules/objc/pyobjc-api.h

View file
  * This is the *only* header file that should be used to access 
  * functionality in the core bridge.
  *
- * $Id: pyobjc-api.h,v 1.26 2004/03/19 14:01:44 ronaldoussoren Exp $
+ * $Id: pyobjc-api.h,v 1.27 2004/04/01 03:53:45 etrepum Exp $
  */
 
 #include <Python.h>
 
 #endif
 
+
+/* threading support */
+#define PyObjC_DURING \
+		Py_BEGIN_ALLOW_THREADS \
+		NS_DURING
+
+#define PyObjC_HANDLER NS_HANDLER
+
+#define PyObjC_ENDHANDLER \
+		NS_ENDHANDLER \
+		Py_END_ALLOW_THREADS
+
+#define PyObjC_BEGIN_WITH_GIL \
+	{ \
+		PyGILState_STATE _GILState; \
+		_GILState = PyGILState_Ensure(); 
+
+#define PyObjC_GIL_FORWARD_EXC() \
+		do { \
+            PyObjCErr_ToObjCWithGILState(&_GILState); \
+		} while (0)
+
+
+#define PyObjC_END_WITH_GIL \
+		PyGILState_Release(_GILState); \
+	}
+
+
 #ifndef GNU_RUNTIME
 #include <objc/objc-runtime.h>
 

File Modules/objc/pyobjc.h

View file
 	if (!(expr)) { PyObjCErr_InternalError(); return (retval); }
 
 
-/* threading support */
-#define PyObjC_DURING \
-		Py_BEGIN_ALLOW_THREADS \
-		NS_DURING
-
-#define PyObjC_HANDLER NS_HANDLER
-
-#define PyObjC_ENDHANDLER \
-		NS_ENDHANDLER \
-		Py_END_ALLOW_THREADS
-
-#define PyObjC_BEGIN_WITH_GIL \
-	{ \
-		PyGILState_STATE _GILState; \
-		_GILState = PyGILState_Ensure(); 
-
-#define PyObjC_GIL_FORWARD_EXC() \
-		do { \
-			NSException* exc = PyObjCErr_AsExc(); \
-			PyGILState_Release(_GILState); \
-			[exc raise]; \
-		} while (0)
-
-
-#define PyObjC_END_WITH_GIL \
-		PyGILState_Release(_GILState); \
-	}
-
-
 #endif /* PyObjC_H */

File setup.py

View file
         '-framework', 'Foundation',
         ]
 
+    CF_LDFLAGS=[
+        '-framework', 'CoreFoundation', '-framework', 'Foundation',
+        ]
+
     FND_LDFLAGS=[
-        '-framework CoreFoundation', '-framework', 'Foundation',
+        '-framework', 'CoreFoundation', '-framework', 'Foundation',
         ]
 
     APPKIT_LDFLAGS=[
-        '-framework CoreFoundation', '-framework', 'AppKit',
+        '-framework', 'CoreFoundation', '-framework', 'AppKit',
         ]
 
     ADDRESSBOOK_LDFLAGS=[
-        '-framework CoreFoundation', '-framework', 'AddressBook', '-framework', 'Foundation',
-    ]
+        '-framework', 'CoreFoundation', '-framework', 'AddressBook', '-framework', 'Foundation',
+        ]
 
     SECURITY_INTERFACE_LDFLAGS=[
-        '-framework CoreFoundation', '-framework', 'SecurityInterface', '-framework', 'Foundation',
-    ]
+        '-framework', 'CoreFoundation', '-framework', 'SecurityInterface', '-framework', 'Foundation',
+        ]
 
     PREFPANES_LDFLAGS=[
-        '-framework CoreFoundation', '-framework', 'PreferencePanes', '-framework', 'Foundation',
-    ]
+        '-framework', 'CoreFoundation', '-framework', 'PreferencePanes', '-framework', 'Foundation',
+        ]
 
 else:
     #
     FNDMAP_LDFLAGS=OBJC_LDFLAGS
     APPKIT_LDFLAGS=OBJC_LDFLAGS + ['-lgnustep-gui']
     APPMAP_LDFLAGS=OBJC_LDFLAGS + ['-lgnustep-gui']
+    CF_LDFLAGS=[]
     ADDRESSBOOK_LDFLAGS=[]
     PREFPANES_LDFLAGS=[]
     SECURITY_INTERFACE_LDFLAGS=[]
     AddressBookDepends = {
         'depends': glob.glob('build/codegen/*.inc'),
     }
+    CoreFoundationDepends = {
+    }
     SecurityInterfaceDepends = {
         'depends': glob.glob('build/codegen/*.inc'),
     }
     FoundationDepends = {}
     AppKitDepends = {}
     AddressBookDepends = {}
+    CoreFoundationDepends = {}
     SecurityInterfaceDepends = {}
     PrefPanesDepends = {}
     InterfaceBuilderDepends = {}
                       ),
         ])
 
-
-SecurityInterfacePackages, SecurityInterfaceExtensions = [], []
+CoreFoundationPackages, CoreFoundationExtensions = \
+        IfFrameWork('CoreFoundation.framework', [ 'CoreFoundation' ], [
+            Extension("_machsignals",
+                      [ 'Modules/CoreFoundation/machsignals.m' ],
+                      extra_compile_args=[
+                        '-IModules/objc',
+                      ] + CFLAGS,
+                      extra_link_args=[
+                      ] + CF_LDFLAGS,
+                      **CoreFoundationDepends),
+        ])
+        
 SecurityInterfacePackages, SecurityInterfaceExtensions = \
         IfFrameWork('SecurityInterface.framework', [ 'SecurityInterface' ], [
             Extension('_SecurityInterface',
     raise ValueError, "Version not found"
 
 
+# skipping CoreFoundationPackages, it's fake!
 packages = CorePackages + AppKitPackages + FoundationPackages + AddressBookPackages + PrefPanesPackages + InterfaceBuilderPackages + ScreenSaverPackages + WebKitPackages + SecurityInterfacePackages + [ 'PyObjCTools' ]
+
 # The following line is needed to allow separate flat modules
 # to be installed from a different folder (needed for the
 # bundlebuilder test below).
                            + InterfaceBuilderExtensions
                            + ScreenSaverExtensions
                            + SecurityInterfaceExtensions
+                           + CoreFoundationExtensions
                            + WebKitExtensions
                            ),
              packages = packages,