Author: Armin Rigo <ar...@tunes.org> Branch: py3.5 Changeset: r91625:be1dadf96ba4 Date: 2017-06-19 09:59 +0200 http://bitbucket.org/pypy/pypy/changeset/be1dadf96ba4/
Log: hg merge default also attempt to write the Python3 version of cffi's errorbox.py on Windows, but not tested so far diff --git a/lib_pypy/cffi/_cffi_errors.h b/lib_pypy/cffi/_cffi_errors.h new file mode 100644 --- /dev/null +++ b/lib_pypy/cffi/_cffi_errors.h @@ -0,0 +1,141 @@ +#ifndef CFFI_MESSAGEBOX +# ifdef _MSC_VER +# define CFFI_MESSAGEBOX 1 +# else +# define CFFI_MESSAGEBOX 0 +# endif +#endif + + +#if CFFI_MESSAGEBOX +/* Windows only: logic to take the Python-CFFI embedding logic + initialization errors and display them in a background thread + with MessageBox. The idea is that if the whole program closes + as a result of this problem, then likely it is already a console + program and you can read the stderr output in the console too. + If it is not a console program, then it will likely show its own + dialog to complain, or generally not abruptly close, and for this + case the background thread should stay alive. +*/ +static void *volatile _cffi_bootstrap_text; + +static PyObject *_cffi_start_error_capture(void) +{ + PyObject *result = NULL; + PyObject *x, *m, *bi; + + if (InterlockedCompareExchangePointer(&_cffi_bootstrap_text, + (void *)1, NULL) != NULL) + return (PyObject *)1; + + m = PyImport_AddModule("_cffi_error_capture"); + if (m == NULL) + goto error; + + result = PyModule_GetDict(m); + if (result == NULL) + goto error; + + bi = PyImport_ImportModule("__builtin__"); + if (bi == NULL) + goto error; + PyDict_SetItemString(result, "__builtins__", bi); + Py_DECREF(bi); + + x = PyRun_String( + "import sys\n" + "class FileLike:\n" + " def write(self, x):\n" + " of.write(x)\n" + " self.buf += x\n" + "fl = FileLike()\n" + "fl.buf = ''\n" + "of = sys.stderr\n" + "sys.stderr = fl\n" + "def done():\n" + " sys.stderr = of\n" + " return fl.buf\n", /* make sure the returned value stays alive */ + Py_file_input, + result, result); + Py_XDECREF(x); + + error: + if (PyErr_Occurred()) + { + PyErr_WriteUnraisable(Py_None); + PyErr_Clear(); + } + return result; +} + +#pragma comment(lib, "user32.lib") + +static DWORD WINAPI _cffi_bootstrap_dialog(LPVOID ignored) +{ + Sleep(666); /* may be interrupted if the whole process is closing */ +#if PY_MAJOR_VERSION >= 3 + MessageBoxW(NULL, (wchar_t *)_cffi_bootstrap_text, + L"Python-CFFI error", + MB_OK | MB_ICONERROR); +#else + MessageBoxA(NULL, (char *)_cffi_bootstrap_text, + "Python-CFFI error", + MB_OK | MB_ICONERROR); +#endif + _cffi_bootstrap_text = NULL; + return 0; +} + +static void _cffi_stop_error_capture(PyObject *ecap) +{ + PyObject *s; + void *text; + + if (ecap == (PyObject *)1) + return; + + if (ecap == NULL) + goto error; + + s = PyRun_String("done()", Py_eval_input, ecap, ecap); + if (s == NULL) + goto error; + + /* Show a dialog box, but in a background thread, and + never show multiple dialog boxes at once. */ +#if PY_MAJOR_VERSION >= 3 + text = PyUnicode_AsWideCharString(s, NULL); +#else + text = PyString_AsString(s); +#endif + + _cffi_bootstrap_text = text; + + if (text != NULL) + { + HANDLE h; + h = CreateThread(NULL, 0, _cffi_bootstrap_dialog, + NULL, 0, NULL); + if (h != NULL) + CloseHandle(h); + } + /* decref the string, but it should stay alive as 'fl.buf' + in the small module above. It will really be freed only if + we later get another similar error. So it's a leak of at + most one copy of the small module. That's fine for this + situation which is usually a "fatal error" anyway. */ + Py_DECREF(s); + PyErr_Clear(); + return; + + error: + _cffi_bootstrap_text = NULL; + PyErr_Clear(); +} + +#else + +static PyObject *_cffi_start_error_capture(void) { return NULL; } +static void _cffi_stop_error_capture(PyObject *ecap) { } + +#endif diff --git a/lib_pypy/cffi/_embedding.h b/lib_pypy/cffi/_embedding.h --- a/lib_pypy/cffi/_embedding.h +++ b/lib_pypy/cffi/_embedding.h @@ -109,6 +109,8 @@ /********** CPython-specific section **********/ #ifndef PYPY_VERSION +#include "_cffi_errors.h" + #define _cffi_call_python_org _cffi_exports[_CFFI_CPIDX] @@ -220,8 +222,16 @@ /* Print as much information as potentially useful. Debugging load-time failures with embedding is not fun */ + PyObject *ecap; PyObject *exception, *v, *tb, *f, *modules, *mod; PyErr_Fetch(&exception, &v, &tb); + ecap = _cffi_start_error_capture(); + f = PySys_GetObject((char *)"stderr"); + if (f != NULL && f != Py_None) { + PyFile_WriteString( + "Failed to initialize the Python-CFFI embedding logic:\n\n", f); + } + if (exception != NULL) { PyErr_NormalizeException(&exception, &v, &tb); PyErr_Display(exception, v, tb); @@ -230,7 +240,6 @@ Py_XDECREF(v); Py_XDECREF(tb); - f = PySys_GetObject((char *)"stderr"); if (f != NULL && f != Py_None) { PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME "\ncompiled with cffi version: 1.11.0" @@ -249,6 +258,7 @@ PyFile_WriteObject(PySys_GetObject((char *)"path"), f, 0); PyFile_WriteString("\n\n", f); } + _cffi_stop_error_capture(ecap); } result = -1; goto done; diff --git a/lib_pypy/cffi/api.py b/lib_pypy/cffi/api.py --- a/lib_pypy/cffi/api.py +++ b/lib_pypy/cffi/api.py @@ -75,9 +75,10 @@ self._init_once_cache = {} self._cdef_version = None self._embedding = None + self._typecache = model.get_typecache(backend) if hasattr(backend, 'set_ffi'): backend.set_ffi(self) - for name in backend.__dict__: + for name in list(backend.__dict__): if name.startswith('RTLD_'): setattr(self, name, getattr(backend, name)) # diff --git a/lib_pypy/cffi/model.py b/lib_pypy/cffi/model.py --- a/lib_pypy/cffi/model.py +++ b/lib_pypy/cffi/model.py @@ -568,22 +568,26 @@ global_lock = allocate_lock() +_typecache_cffi_backend = weakref.WeakValueDictionary() + +def get_typecache(backend): + # returns _typecache_cffi_backend if backend is the _cffi_backend + # module, or type(backend).__typecache if backend is an instance of + # CTypesBackend (or some FakeBackend class during tests) + if isinstance(backend, types.ModuleType): + return _typecache_cffi_backend + with global_lock: + if not hasattr(type(backend), '__typecache'): + type(backend).__typecache = weakref.WeakValueDictionary() + return type(backend).__typecache def global_cache(srctype, ffi, funcname, *args, **kwds): key = kwds.pop('key', (funcname, args)) assert not kwds try: - return ffi._backend.__typecache[key] + return ffi._typecache[key] except KeyError: pass - except AttributeError: - # initialize the __typecache attribute, either at the module level - # if ffi._backend is a module, or at the class level if ffi._backend - # is some instance. - if isinstance(ffi._backend, types.ModuleType): - ffi._backend.__typecache = weakref.WeakValueDictionary() - else: - type(ffi._backend).__typecache = weakref.WeakValueDictionary() try: res = getattr(ffi._backend, funcname)(*args) except NotImplementedError as e: @@ -591,7 +595,7 @@ # note that setdefault() on WeakValueDictionary is not atomic # and contains a rare bug (http://bugs.python.org/issue19542); # we have to use a lock and do it ourselves - cache = ffi._backend.__typecache + cache = ffi._typecache with global_lock: res1 = cache.get(key) if res1 is None: diff --git a/lib_pypy/cffi/recompiler.py b/lib_pypy/cffi/recompiler.py --- a/lib_pypy/cffi/recompiler.py +++ b/lib_pypy/cffi/recompiler.py @@ -308,6 +308,8 @@ base_module_name,)) prnt('#endif') lines = self._rel_readlines('_embedding.h') + i = lines.index('#include "_cffi_errors.h"\n') + lines[i:i+1] = self._rel_readlines('_cffi_errors.h') prnt(''.join(lines)) self.needs_version(VERSION_EMBEDDED) # diff --git a/pypy/module/_cffi_backend/ccallback.py b/pypy/module/_cffi_backend/ccallback.py --- a/pypy/module/_cffi_backend/ccallback.py +++ b/pypy/module/_cffi_backend/ccallback.py @@ -174,10 +174,13 @@ @jit.dont_look_inside def handle_applevel_exception(self, e, ll_res, extra_line): + from pypy.module._cffi_backend import errorbox space = self.space self.write_error_return_value(ll_res) if self.w_onerror is None: + ecap = errorbox.start_error_capture(space) self.print_error(e, extra_line) + errorbox.stop_error_capture(space, ecap) else: try: e.normalize_exception(space) @@ -189,10 +192,12 @@ self.convert_result(ll_res, w_res) except OperationError as e2: # double exception! print a double-traceback... + ecap = errorbox.start_error_capture(space) self.print_error(e, extra_line) # original traceback e2.write_unraisable(space, '', with_traceback=True, extra_line="\nDuring the call to 'onerror', " "another exception occurred:\n\n") + errorbox.stop_error_capture(space, ecap) class W_CDataCallback(W_ExternPython): diff --git a/pypy/module/_cffi_backend/embedding.py b/pypy/module/_cffi_backend/embedding.py --- a/pypy/module/_cffi_backend/embedding.py +++ b/pypy/module/_cffi_backend/embedding.py @@ -63,6 +63,8 @@ load_embedded_cffi_module(space, version, init_struct) res = 0 except OperationError as operr: + from pypy.module._cffi_backend import errorbox + ecap = errorbox.start_error_capture(space) operr.write_unraisable(space, "initialization of '%s'" % name, with_traceback=True) space.appexec([], r"""(): @@ -71,6 +73,7 @@ sys.pypy_version_info[:3]) sys.stderr.write('sys.path: %r\n' % (sys.path,)) """) + errorbox.stop_error_capture(space, ecap) res = -1 if must_leave: space.threadlocals.leave_thread(space) diff --git a/pypy/module/_cffi_backend/errorbox.py b/pypy/module/_cffi_backend/errorbox.py new file mode 100644 --- /dev/null +++ b/pypy/module/_cffi_backend/errorbox.py @@ -0,0 +1,112 @@ +# for Windows only +import sys +from rpython.rlib import jit +from rpython.rtyper.lltypesystem import lltype, rffi +from rpython.translator.tool.cbuild import ExternalCompilationInfo + + +MESSAGEBOX = sys.platform == "win32" + +MODULE = r""" +#include <Windows.h> +#pragma comment(lib, "user32.lib") + +static void *volatile _cffi_bootstrap_text; + +RPY_EXTERN int _cffi_errorbox1(void) +{ + return InterlockedCompareExchangePointer(&_cffi_bootstrap_text, + (void *)1, NULL) == NULL; +} + +static DWORD WINAPI _cffi_bootstrap_dialog(LPVOID ignored) +{ + Sleep(666); /* may be interrupted if the whole process is closing */ + MessageBoxW(NULL, (wchar_t *)_cffi_bootstrap_text, + L"PyPy: Python-CFFI error", + MB_OK | MB_ICONERROR); + _cffi_bootstrap_text = NULL; + return 0; +} + +RPY_EXTERN void _cffi_errorbox(wchar_t *text) +{ + /* Show a dialog box, but in a background thread, and + never show multiple dialog boxes at once. */ + HANDLE h; + + _cffi_bootstrap_text = text; + h = CreateThread(NULL, 0, _cffi_bootstrap_dialog, + NULL, 0, NULL); + if (h != NULL) + CloseHandle(h); +} +""" + +if MESSAGEBOX: + + eci = ExternalCompilationInfo( + separate_module_sources=[MODULE], + post_include_bits=["RPY_EXTERN int _cffi_errorbox1(void);\n" + "RPY_EXTERN void _cffi_errorbox(wchar_t *);\n"]) + + cffi_errorbox1 = rffi.llexternal("_cffi_errorbox1", [], + rffi.INT, compilation_info=eci) + cffi_errorbox = rffi.llexternal("_cffi_errorbox", [rffi.CWCHARP], + lltype.Void, compilation_info=eci) + + class Message: + def __init__(self, space): + self.space = space + self.text_p = lltype.nullptr(rffi.CWCHARP.TO) + + def start_error_capture(self): + ok = cffi_errorbox1() + if rffi.cast(lltype.Signed, ok) != 1: + return None + + return self.space.appexec([], """(): + import sys + class FileLike: + def write(self, x): + of.write(x) + self.buf += x + fl = FileLike() + fl.buf = '' + of = sys.stderr + sys.stderr = fl + def done(): + sys.stderr = of + return fl.buf + return done + """) + + def stop_error_capture(self, w_done): + if w_done is None: + return + + w_text = self.space.call_function(w_done) + p = rffi.unicode2wcharp(self.space.text_w(w_text), + track_allocation=False) + if self.text_p: + rffi.free_wcharp(self.text_p, track_allocation=False) + self.text_p = p # keepalive + + cffi_errorbox(p) + + + @jit.dont_look_inside + def start_error_capture(space): + msg = space.fromcache(Message) + return msg.start_error_capture() + + @jit.dont_look_inside + def stop_error_capture(space, x): + msg = space.fromcache(Message) + msg.stop_error_capture(x) + +else: + def start_error_capture(space): + return None + def stop_error_capture(space, nothing): + pass diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c --- a/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c @@ -1,6 +1,10 @@ /* Generated by pypy/tool/import_cffi.py */ #include <stdio.h> +#ifdef _MSC_VER +#include <windows.h> +#endif + extern int add1(int, int); @@ -10,5 +14,9 @@ x = add1(40, 2); y = add1(100, -5); printf("got: %d %d\n", x, y); +#ifdef _MSC_VER + if (x == 0 && y == 0) + Sleep(2000); +#endif return 0; } diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py --- a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py @@ -200,3 +200,9 @@ "prepADD2\n" "adding 100 and -5 and -20\n" "got: 42 75\n") + + def test_init_time_error(self): + initerror_cffi = self.prepare_module('initerror') + self.compile('add1-test', [initerror_cffi]) + output = self.execute('add1-test') + assert output == "got: 0 0\n" # plus lots of info to stderr _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit