[pypy-commit] pypy py3k-faulthandler: in-progress

arigo pypy.commits at gmail.com
Fri Sep 23 12:53:12 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: py3k-faulthandler
Changeset: r87348:0c67f2b605a3
Date: 2016-09-23 18:52 +0200
http://bitbucket.org/pypy/pypy/changeset/0c67f2b605a3/

Log:	in-progress

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -422,6 +422,11 @@
         # To be called before using the space
         self.threadlocals.enter_thread(self)
 
+        # Set up faulthandler even if not imported explicitly
+        if self.config.objspace.usemodule.faulthandler:
+            from pypy.module.faulthandler import handler
+            handler.startup(self)
+
         # Initialize already imported builtin modules
         from pypy.interpreter.module import Module
         w_modules = self.sys.get('modules')
@@ -445,6 +450,11 @@
         for w_mod in self.builtin_modules.values():
             if isinstance(w_mod, Module) and w_mod.startup_called:
                 w_mod.shutdown(self)
+        #
+        # Shut down faulthandler
+        if self.config.objspace.usemodule.faulthandler:
+            from pypy.module.faulthandler import handler
+            handler.finish(self)
 
     def wait_for_thread_shutdown(self):
         """Wait until threading._shutdown() completes, provided the threading
diff --git a/pypy/module/faulthandler/__init__.py b/pypy/module/faulthandler/__init__.py
--- a/pypy/module/faulthandler/__init__.py
+++ b/pypy/module/faulthandler/__init__.py
@@ -5,18 +5,18 @@
     }
 
     interpleveldefs = {
-        'enable': 'interp_faulthandler.enable',
-        'disable': 'interp_faulthandler.disable',
-        'is_enabled': 'interp_faulthandler.is_enabled',
-        'register': 'interp_faulthandler.register',
-
-        'dump_traceback': 'interp_faulthandler.dump_traceback',
-
-        '_read_null': 'interp_faulthandler.read_null',
-        '_sigsegv': 'interp_faulthandler.sigsegv',
-        '_sigfpe': 'interp_faulthandler.sigfpe',
-        '_sigabrt': 'interp_faulthandler.sigabrt',
-        #'_sigbus': 'interp_faulthandler.sigbus',
-        #'_sigill': 'interp_faulthandler.sigill',
-        '_fatal_error': 'interp_faulthandler.fatal_error',
+        'enable': 'handler.enable',
+        'disable': 'handler.disable',
+        'is_enabled': 'handler.is_enabled',
+#        'register': 'interp_faulthandler.register',
+#
+#        'dump_traceback': 'interp_faulthandler.dump_traceback',
+#
+#        '_read_null': 'interp_faulthandler.read_null',
+#        '_sigsegv': 'interp_faulthandler.sigsegv',
+#        '_sigfpe': 'interp_faulthandler.sigfpe',
+#        '_sigabrt': 'interp_faulthandler.sigabrt',
+#        #'_sigbus': 'interp_faulthandler.sigbus',
+#        #'_sigill': 'interp_faulthandler.sigill',
+#        '_fatal_error': 'interp_faulthandler.fatal_error',
     }
diff --git a/pypy/module/faulthandler/cintf.py b/pypy/module/faulthandler/cintf.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/cintf.py
@@ -0,0 +1,25 @@
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.translator import cdir
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+
+
+cwd = py.path.local(__file__).dirpath()
+eci = ExternalCompilationInfo(
+    includes=[cwd.join('faulthandler.h')],
+    include_dirs=[str(cwd), cdir],
+    separate_module_files=[cwd.join('faulthandler.c')])
+
+def llexternal(*args, **kwargs):
+    kwargs.setdefault('releasegil', False)
+    kwargs.setdefault('compilation_info', eci)
+    return rffi.llexternal(*args, **kwargs)
+
+pypy_faulthandler_setup = llexternal(
+    'pypy_faulthandler_setup', [], lltype.Void)
+pypy_faulthandler_teardown = llexternal(
+    'pypy_faulthandler_teardown', [], lltype.Void)
+pypy_faulthandler_enable = llexternal(
+    'pypy_faulthandler_enable', [], lltype.Void,
+    save_err=rffi.RFFI_SAVE_ERRNO)
+pypy_faulthandler_disable = llexternal(
+    'pypy_faulthandler_disable', [], lltype.Void)
diff --git a/pypy/module/faulthandler/faulthandler.c b/pypy/module/faulthandler/faulthandler.c
--- a/pypy/module/faulthandler/faulthandler.c
+++ b/pypy/module/faulthandler/faulthandler.c
@@ -1,6 +1,135 @@
+#include "faulthandler.h"
 #include <stdlib.h>
-#include "faulthandler.h"
+#include <signal.h>
+#include <assert.h>
+#include <errno.h>
 
+
+typedef struct sigaction _Py_sighandler_t;
+
+typedef struct {
+    int signum;
+    int enabled;
+    const char* name;
+    _Py_sighandler_t previous;
+    int all_threads;
+} fault_handler_t;
+
+static struct {
+    int enabled;
+    int fd, all_threads;
+} fatal_error;
+
+static stack_t stack;
+
+
+static fault_handler_t faulthandler_handlers[] = {
+#ifdef SIGBUS
+    {SIGBUS, 0, "Bus error", },
+#endif
+#ifdef SIGILL
+    {SIGILL, 0, "Illegal instruction", },
+#endif
+    {SIGFPE, 0, "Floating point exception", },
+    {SIGABRT, 0, "Aborted", },
+    /* define SIGSEGV at the end to make it the default choice if searching the
+       handler fails in faulthandler_fatal_error() */
+    {SIGSEGV, 0, "Segmentation fault", }
+};
+static const int faulthandler_nsignals =
+    sizeof(faulthandler_handlers) / sizeof(fault_handler_t);
+
+
+RPY_EXTERN
+char *pypy_faulthandler_setup(void)
+{
+    assert(!fatal_error.enabled);
+
+    /* Try to allocate an alternate stack for faulthandler() signal handler to
+     * be able to allocate memory on the stack, even on a stack overflow. If it
+     * fails, ignore the error. */
+    stack.ss_flags = 0;
+    stack.ss_size = SIGSTKSZ;
+    stack.ss_sp = malloc(stack.ss_size);
+    if (stack.ss_sp != NULL) {
+        int err = sigaltstack(&stack, NULL);
+        if (err) {
+            free(stack.ss_sp);
+            stack.ss_sp = NULL;
+        }
+    }
+    return NULL;
+}
+
+RPY_EXTERN
+void pypy_faulthandler_teardown(void)
+{
+    pypy_faulthandler_disable();
+    free(stack.ss_sp);
+    stack.ss_sp = NULL;
+}
+
+RPY_EXTERN
+int pypy_faulthandler_enable(int fd, int all_threads)
+{
+    fatal_error.fd = fd;
+    fatal_error.all_threads = all_threads;
+
+    if (!fatal_error.enabled) {
+        int i;
+
+        fatal_error.enabled = 1;
+
+        for (i = 0; i < faulthandler_nsignals; i++) {
+            int err;
+            struct sigaction action;
+            fault_handler_t *handler = &faulthandler_handlers[i];
+
+            action.sa_handler = faulthandler_fatal_error;
+            sigemptyset(&action.sa_mask);
+            /* Do not prevent the signal from being received from within
+               its own signal handler */
+            action.sa_flags = SA_NODEFER;
+            if (stack.ss_sp != NULL) {
+                /* Call the signal handler on an alternate signal stack
+                   provided by sigaltstack() */
+                action.sa_flags |= SA_ONSTACK;
+            }
+            err = sigaction(handler->signum, &action, &handler->previous);
+            if (err) {
+                return -1;
+            }
+            handler->enabled = 1;
+        }
+    }
+    return 0;
+}
+
+RPY_EXTERN
+void pypy_faulthandler_disable(void)
+{
+    if (fatal_error.enabled) {
+        int i;
+
+        fatal_error.enabled = 0;
+        for (i = 0; i < faulthandler_nsignals; i++) {
+            fault_handler_t *handler = &faulthandler_handlers[i];
+            if (!handler->enabled)
+                continue;
+            (void)sigaction(handler->signum, &handler->previous, NULL);
+            handler->enabled = 0;
+        }
+    }
+}
+
+RPY_EXTERN
+int pypy_faulthandler_is_enabled(void)
+{
+    return fatal_error.enabled;
+}
+
+
+#if 0
 int
 pypy_faulthandler_read_null(void)
 {
@@ -75,3 +204,4 @@
     raise(SIGILL);
 }
 #endif
+#endif
diff --git a/pypy/module/faulthandler/faulthandler.h b/pypy/module/faulthandler/faulthandler.h
--- a/pypy/module/faulthandler/faulthandler.h
+++ b/pypy/module/faulthandler/faulthandler.h
@@ -1,9 +1,16 @@
 #ifndef PYPY_FAULTHANDLER_H
 #define PYPY_FAULTHANDLER_H
 
-#include <signal.h>
 #include "src/precommondefs.h"
 
+RPY_EXTERN char *pypy_faulthandler_setup(void);
+RPY_EXTERN void pypy_faulthandler_teardown(void);
+
+RPY_EXTERN int pypy_faulthandler_enable(int fd, int all_threads);
+RPY_EXTERN void pypy_faulthandler_disable(void);
+RPY_EXTERN int pypy_faulthandler_is_enabled(void);
+
+/*
 RPY_EXTERN int pypy_faulthandler_read_null(void);
 RPY_EXTERN void pypy_faulthandler_sigsegv(void);
 RPY_EXTERN int pypy_faulthandler_sigfpe(void);
@@ -15,5 +22,6 @@
 #ifdef SIGILL
 RPY_EXTERN void pypy_faulthandler_sigill(void);
 #endif
+*/
 
 #endif  /* PYPY_FAULTHANDLER_H */
diff --git a/pypy/module/faulthandler/handler.py b/pypy/module/faulthandler/handler.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/handler.py
@@ -0,0 +1,108 @@
+import os
+from rpython.rtyper.lltypesystem import rffi
+from rpython.rlib.rposix import is_valid_fd
+
+from pypy.interpreter.error import oefmt, exception_from_saved_errno
+from pypy.interpreter.gateway import unwrap_spec
+from pypy.module.faulthandler import cintf
+
+
+class Handler(object):
+    def __init__(self, space):
+        self.space = space
+        self._cleanup_()
+
+    def _cleanup_(self):
+        self.is_initialized = False
+        self.fatal_error_w_file = None
+
+    def check_err(self, p_err):
+        if p_err:
+            raise oefmt(self.space.w_RuntimeError, 'faulthandler: %8',
+                        rffi.charp2str(p_err))
+
+    def get_fileno_and_file(self, w_file):
+        space = self.space
+        if space.is_none(w_file):
+            w_file = space.sys.get('stderr')
+            if space.is_none(w_file):
+                raise oefmt(space.w_RuntimeError, "sys.stderr is None")
+        elif space.isinstance_w(w_file, space.w_int):
+            fd = space.int_w(w_file)
+            if fd < 0 or not is_valid_fd(fd):
+                raise oefmt(space.w_ValueError,
+                            "file is not a valid file descriptor")
+            return fd, None
+
+        fd = space.int_w(space.call_method(w_file, 'fileno'))
+        try:
+            space.call_method(w_file, 'flush')
+        except OperationError as e:
+            if e.async(space):
+                raise
+            pass   # ignore flush() error
+        return fd, w_file
+
+    def enable(self, w_file, all_threads):
+        fileno, w_file = self.get_fileno_and_file(w_file)
+        if not self.is_initialized:
+            self.check_err(cintf.pypy_faulthandler_setup())
+            self.is_initialized = True
+
+        self.fatal_error_w_file = w_file
+        err = cintf.pypy_faulthandler_enable(fileno, all_threads)
+        if err:
+            space = self.space
+            raise exception_from_saved_errno(space, space.w_RuntimeError)
+
+    def disable(self):
+        cintf.pypy_faulthandler_disable()
+        self.fatal_error_w_file = None
+
+    def is_enabled(self):
+        return (self.is_initialized and
+                bool(cintf.pypy_faulthandler_is_enabled()))
+
+    def finish(self):
+        if self.is_initialized:
+            cintf.pypy_faulthandler_teardown()
+        self._cleanup_()
+
+
+def startup(space):
+    """Initialize the faulthandler logic when the space is starting
+    (this is called from baseobjspace.py)"""
+    #
+    # Call faulthandler.enable() if the PYTHONFAULTHANDLER environment variable
+    # is defined, or if sys._xoptions has a 'faulthandler' key.
+    if not os.environ.get('PYTHONFAULTHANDLER'):
+        w_options = space.sys.get('_xoptions')
+        if not space.contains(w_options, space.wrap('faulthandler')):
+            return
+    #
+    # Like CPython.  Why not just call enable(space)?  Maybe the goal is
+    # to let the user override the 'faulthandler' module.  Maybe someone
+    # mis-uses ``"faulthandler" in sys.modules'' as a way to check if it
+    # was started by checking if it was imported at all.
+    space.appexec([], """
+        import faulthandler
+        faulthandler.enable()
+    """)
+
+def finish(space):
+    """Finalize the faulthandler logic (called from baseobjspace.py)"""
+    space.fromcache(Handler).finish()
+
+
+ at unwrap_spec(all_threads=int)
+def enable(space, w_file=None, all_threads=0):
+    "enable(file=sys.stderr, all_threads=True): enable the fault handler"
+    space.fromcache(Handler).enable(w_file, all_threads)
+
+def disable(space):
+    "disable(): disable the fault handler"
+    space.fromcache(Handler).disable()
+
+def is_enabled(space):
+    "is_enabled()->bool: check if the handler is enabled"
+    return space.wrap(space.fromcache(Handler).is_enabled())
diff --git a/pypy/module/faulthandler/test/test_faulthander.py b/pypy/module/faulthandler/test/test_faulthander.py
--- a/pypy/module/faulthandler/test/test_faulthander.py
+++ b/pypy/module/faulthandler/test/test_faulthander.py
@@ -6,7 +6,7 @@
 
 class AppTestFaultHandler:
     spaceconfig = {
-        "usemodules": ["faulthandler"]
+        "usemodules": ["faulthandler", "_vmprof"]
     }
 
     def test_enable(self):
diff --git a/pypy/module/sys/__init__.py b/pypy/module/sys/__init__.py
--- a/pypy/module/sys/__init__.py
+++ b/pypy/module/sys/__init__.py
@@ -136,6 +136,7 @@
             space.setitem(self.w_dict, space.wrap('thread_info'), thread_info)
 
     def setup_after_space_initialization(self):
+        "NOT_RPYTHON"
         space = self.space
 
         if not space.config.translating:
diff --git a/rpython/rlib/rvmprof/__init__.py b/rpython/rlib/rvmprof/__init__.py
--- a/rpython/rlib/rvmprof/__init__.py
+++ b/rpython/rlib/rvmprof/__init__.py
@@ -37,3 +37,8 @@
 
 def disable():
     _get_vmprof().disable()
+
+ at specialize.arg(0)
+def enum_all_code_objs(CodeClass, callback, arg):
+    assert _was_registered(CodeClass)
+    CodeClass._vmprof_enum_all_code_objs(callback, arg)
diff --git a/rpython/rlib/rvmprof/rvmprof.py b/rpython/rlib/rvmprof/rvmprof.py
--- a/rpython/rlib/rvmprof/rvmprof.py
+++ b/rpython/rlib/rvmprof/rvmprof.py
@@ -1,6 +1,6 @@
 import sys, os
 from rpython.rlib.objectmodel import specialize, we_are_translated
-from rpython.rlib import jit, rposix
+from rpython.rlib import jit, rposix, rgc
 from rpython.rlib.rvmprof import cintf
 from rpython.rtyper.annlowlevel import cast_instance_to_gcref
 from rpython.rtyper.annlowlevel import cast_base_ptr_to_instance
@@ -116,6 +116,22 @@
         # the types of code objects
         prev = self._gather_all_code_objs
         self._gather_all_code_objs = gather_all_code_objs
+        #
+        # For special usages: the faulthandler pypy module uses this.
+        # It must not allocate anything.
+        @staticmethod
+        @rgc.no_collect
+        def enum_all_code_objs(callback, arg):
+            all_code_wrefs = CodeClass._vmprof_weak_list.get_all_handles()
+            i = len(all_code_wrefs) - 1
+            while i >= 0:
+                code = all_code_wrefs[i]()
+                i -= 1
+                if code is not None:
+                    uid = code._vmprof_unique_id
+                    if uid != 0:
+                        callback(code, uid, arg)
+        CodeClass._vmprof_enum_all_code_objs = enum_all_code_objs
 
     @jit.dont_look_inside
     def enable(self, fileno, interval):


More information about the pypy-commit mailing list