[pypy-commit] pypy default: Implement PyOS_InputHook in cpyext. Call it from pyrepl, once before every

arigo pypy.commits at gmail.com
Thu Oct 25 12:26:04 EDT 2018


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r95243:caaf91a51641
Date: 2018-10-25 18:25 +0200
http://bitbucket.org/pypy/pypy/changeset/caaf91a51641/

Log:	Implement PyOS_InputHook in cpyext. Call it from pyrepl, once before
	every character input. It seems to me that details on when it is
	called are not consistent across multiple platforms of CPython; for
	example with the GNU readline it is called every 0.1 seconds while
	waiting for input. But if you compile CPython without GNU readline
	support, then it is only called once per character, as far as I can
	tell, like pyrepl does now.

diff --git a/lib_pypy/pyrepl/unix_console.py b/lib_pypy/pyrepl/unix_console.py
--- a/lib_pypy/pyrepl/unix_console.py
+++ b/lib_pypy/pyrepl/unix_console.py
@@ -26,6 +26,12 @@
 from pyrepl.fancy_termios import tcgetattr, tcsetattr
 from pyrepl.console import Console, Event
 from pyrepl import unix_eventqueue
+try:
+    from __pypy__ import pyos_inputhook
+except ImportError:
+    def pyos_inputhook():
+        pass
+
 
 class InvalidTerminal(RuntimeError):
     pass
@@ -416,6 +422,7 @@
     def get_event(self, block=1):
         while self.event_queue.empty():
             while 1: # All hail Unix!
+                pyos_inputhook()
                 try:
                     self.push_char(os.read(self.input_fd, 1))
                 except (IOError, OSError), err:
diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py
--- a/pypy/module/__pypy__/__init__.py
+++ b/pypy/module/__pypy__/__init__.py
@@ -109,6 +109,7 @@
         '_promote'                   : 'interp_magic._promote',
         'side_effects_ok'           : 'interp_magic.side_effects_ok',
         'stack_almost_full'         : 'interp_magic.stack_almost_full',
+        'pyos_inputhook'            : 'interp_magic.pyos_inputhook',
     }
     if sys.platform == 'win32':
         interpleveldefs['get_console_cp'] = 'interp_magic.get_console_cp'
diff --git a/pypy/module/__pypy__/interp_magic.py b/pypy/module/__pypy__/interp_magic.py
--- a/pypy/module/__pypy__/interp_magic.py
+++ b/pypy/module/__pypy__/interp_magic.py
@@ -211,3 +211,13 @@
 def revdb_stop(space):
     from pypy.interpreter.reverse_debugging import stop_point
     stop_point()
+
+def pyos_inputhook(space):
+    """Call PyOS_InputHook() from the CPython C API."""
+    if not space.config.objspace.usemodules.cpyext:
+        return
+    w_modules = space.sys.get('modules')
+    if space.finditem_str(w_modules, 'cpyext') is None:
+        return      # cpyext not imported yet, ignore
+    from pypy.module.cpyext.api import invoke_pyos_inputhook
+    invoke_pyos_inputhook(space)
diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -644,6 +644,7 @@
     'Py_FrozenFlag', 'Py_TabcheckFlag', 'Py_UnicodeFlag', 'Py_IgnoreEnvironmentFlag',
     'Py_DivisionWarningFlag', 'Py_DontWriteBytecodeFlag', 'Py_NoUserSiteDirectory',
     '_Py_QnewFlag', 'Py_Py3kWarningFlag', 'Py_HashRandomizationFlag', '_Py_PackageContext',
+    'PyOS_InputHook',
     '_PyTraceMalloc_Track', '_PyTraceMalloc_Untrack', 'PyMem_Malloc',
     'PyObject_Free', 'PyObject_GC_Del', 'PyType_GenericAlloc',
     '_PyObject_New', '_PyObject_NewVar',
@@ -1180,6 +1181,10 @@
     state.C._PyPy_object_dealloc = rffi.llexternal(
         '_PyPy_object_dealloc', [PyObject], lltype.Void,
         compilation_info=eci, _nowrapper=True)
+    FUNCPTR = lltype.Ptr(lltype.FuncType([], rffi.INT))
+    state.C.get_pyos_inputhook = rffi.llexternal(
+        '_PyPy_get_PyOS_InputHook', [], FUNCPTR,
+        compilation_info=eci, _nowrapper=True)
 
 
 def init_function(func):
@@ -1726,6 +1731,12 @@
     w_mod = state.fixup_extension(name, path)
     return w_mod
 
+def invoke_pyos_inputhook(space):
+    state = space.fromcache(State)
+    c_inputhook = state.C.get_pyos_inputhook()
+    if c_inputhook:
+        generic_cpy_call(space, c_inputhook)
+
 @specialize.ll()
 def generic_cpy_call(space, func, *args):
     FT = lltype.typeOf(func).TO
diff --git a/pypy/module/cpyext/include/pythonrun.h b/pypy/module/cpyext/include/pythonrun.h
--- a/pypy/module/cpyext/include/pythonrun.h
+++ b/pypy/module/cpyext/include/pythonrun.h
@@ -47,6 +47,11 @@
 
 #define Py_CompileString(str, filename, start) Py_CompileStringFlags(str, filename, start, NULL)
 
+/* Stuff with no proper home (yet) */
+PyAPI_DATA(int) (*PyOS_InputHook)(void);
+typedef int (*_pypy_pyos_inputhook)(void);
+PyAPI_FUNC(_pypy_pyos_inputhook) _PyPy_get_PyOS_InputHook(void);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/pypy/module/cpyext/src/missing.c b/pypy/module/cpyext/src/missing.c
--- a/pypy/module/cpyext/src/missing.c
+++ b/pypy/module/cpyext/src/missing.c
@@ -31,3 +31,7 @@
 void _Py_setfilesystemdefaultencoding(const char *enc) {
     Py_FileSystemDefaultEncoding = enc;
 }
+int (*PyOS_InputHook)(void) = 0;  /* only ever filled in by C extensions */
+PyAPI_FUNC(_pypy_pyos_inputhook) _PyPy_get_PyOS_InputHook(void) {
+    return PyOS_InputHook;
+}
diff --git a/pypy/module/cpyext/test/test_misc.py b/pypy/module/cpyext/test/test_misc.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/test/test_misc.py
@@ -0,0 +1,35 @@
+from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase
+
+
+class AppTestMisc(AppTestCpythonExtensionBase):
+
+    def test_pyos_inputhook(self):
+        module = self.import_extension('foo', [
+               ("set_pyos_inputhook", "METH_NOARGS",
+                '''
+                    PyOS_InputHook = &my_callback;
+                    Py_RETURN_NONE;
+                '''),
+                ("fetch_value", "METH_NOARGS",
+                '''
+                    return PyInt_FromLong(my_flag);
+                '''),
+            ], prologue='''
+            static long my_flag = 0;
+            static int my_callback(void) { my_flag++; }
+            ''')
+
+        try:
+            import __pypy__
+        except ImportError:
+            skip("only runs on top of pypy")
+        assert module.fetch_value() == 0
+        __pypy__.pyos_inputhook()
+        assert module.fetch_value() == 0
+        module.set_pyos_inputhook()       # <= set
+        assert module.fetch_value() == 0
+        __pypy__.pyos_inputhook()
+        assert module.fetch_value() == 1
+        __pypy__.pyos_inputhook()
+        assert module.fetch_value() == 2
+        assert module.fetch_value() == 2


More information about the pypy-commit mailing list