[pypy-commit] pypy py3.7: implement register_at_fork
cfbolz
pypy.commits at gmail.com
Fri Jan 24 06:32:28 EST 2020
Author: Carl Friedrich Bolz-Tereick <cfbolz at gmx.de>
Branch: py3.7
Changeset: r98580:b2176146ccf5
Date: 2020-01-24 11:48 +0100
http://bitbucket.org/pypy/pypy/changeset/b2176146ccf5/
Log: implement register_at_fork
diff --git a/pypy/module/posix/interp_posix.py b/pypy/module/posix/interp_posix.py
--- a/pypy/module/posix/interp_posix.py
+++ b/pypy/module/posix/interp_posix.py
@@ -1393,6 +1393,89 @@
pid, irrelevant = _run_forking_function(space, "F")
return space.newint(pid)
+class ApplevelForkCallbacks(object):
+ def __init__(self, space):
+ self.space = space
+ self.before_w = []
+ self.parent_w = []
+ self.child_w = []
+
+ at unwrap_spec(w_before=WrappedDefault(None), w_after_in_parent=WrappedDefault(None),
+ w_after_in_child=WrappedDefault(None))
+def register_at_fork(space, __kwonly__, w_before, w_after_in_parent, w_after_in_child):
+ """
+ register_at_fork(...)
+ Register callables to be called when forking a new process.
+
+ before
+ A callable to be called in the parent before the fork() syscall.
+ after_in_child
+ A callable to be called in the child after fork().
+ after_in_parent
+ A callable to be called in the parent after fork().
+
+ 'before' callbacks are called in reverse order.
+ 'after_in_child' and 'after_in_parent' callbacks are called in order.
+ """
+ registered = False
+ cbs = space.fromcache(ApplevelForkCallbacks)
+ if not space.is_w(space.w_None, w_before):
+ if not space.callable_w(w_before):
+ raise oefmt(space.w_TypeError,
+ "'before' must be callable, not %T",
+ w_before)
+ cbs.before_w.append(w_before)
+ registered = True
+ if not space.is_w(space.w_None, w_after_in_parent):
+ if not space.callable_w(w_after_in_parent):
+ raise oefmt(space.w_TypeError,
+ "'after_in_parent' must be callable, not %T",
+ w_after_in_parent)
+ cbs.parent_w.append(w_after_in_parent)
+ registered = True
+ if not space.is_w(space.w_None, w_after_in_child):
+ if not space.callable_w(w_after_in_child):
+ raise oefmt(space.w_TypeError,
+ "'after_in_child' must be callable, not %T",
+ w_after_in_child)
+ cbs.child_w.append(w_after_in_child)
+ registered = True
+ if not registered:
+ raise oefmt(space.w_TypeError,
+ "At least one argument is required")
+
+
+def _run_applevel_hook(space, w_callable):
+ try:
+ space.call_function(w_callable)
+ except OperationError as e:
+ e.write_unraisable(space, "fork hook")
+
+def run_applevel_fork_hooks(space, l_w, reverse=False):
+ if not reverse:
+ for i in range(len(l_w)): # callable can append to the list
+ _run_applevel_hook(space, l_w[i])
+ else:
+ for i in range(len(l_w) - 1, -1, -1):
+ _run_applevel_hook(space, l_w[i])
+
+def run_applevel_fork_hooks_before(space):
+ cbs = space.fromcache(ApplevelForkCallbacks)
+ run_applevel_fork_hooks(space, cbs.before_w, reverse=True)
+
+def run_applevel_fork_hooks_parent(space):
+ cbs = space.fromcache(ApplevelForkCallbacks)
+ run_applevel_fork_hooks(space, cbs.parent_w)
+
+def run_applevel_fork_hooks_child(space):
+ cbs = space.fromcache(ApplevelForkCallbacks)
+ run_applevel_fork_hooks(space, cbs.child_w)
+
+add_fork_hook('before', run_applevel_fork_hooks_before)
+add_fork_hook('parent', run_applevel_fork_hooks_parent)
+add_fork_hook('child', run_applevel_fork_hooks_child)
+
+
def openpty(space):
"Open a pseudo-terminal, returning open fd's for both master and slave end."
master_fd = slave_fd = -1
diff --git a/pypy/module/posix/moduledef.py b/pypy/module/posix/moduledef.py
--- a/pypy/module/posix/moduledef.py
+++ b/pypy/module/posix/moduledef.py
@@ -118,6 +118,7 @@
interpleveldefs['readlink'] = 'interp_posix.readlink'
if hasattr(os, 'fork'):
interpleveldefs['fork'] = 'interp_posix.fork'
+ interpleveldefs['register_at_fork'] = 'interp_posix.register_at_fork'
if hasattr(os, 'openpty'):
interpleveldefs['openpty'] = 'interp_posix.openpty'
if hasattr(os, 'forkpty'):
diff --git a/pypy/module/posix/test/apptest_posix.py b/pypy/module/posix/test/apptest_posix.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/posix/test/apptest_posix.py
@@ -0,0 +1,42 @@
+import pytest
+
+try:
+ import posix as os
+except ImportError:
+ pytest.skip("only for posix")
+
+if hasattr(os, "fork"):
+ def test_register_at_fork():
+ with pytest.raises(TypeError): # no args
+ os.register_at_fork()
+ with pytest.raises(TypeError): # positional args not supported
+ os.register_at_fork(lambda : 1)
+ with pytest.raises(TypeError): # not callable
+ os.register_at_fork(before=1)
+
+ # XXX this is unfortunately a small leak! all further tests that fork
+ # will call these callbacks and append four ints to l
+ l = [1]
+ os.register_at_fork(
+ before=lambda: l.append(2),
+ after_in_parent=lambda: l.append(5),
+ after_in_child=lambda: l.append(3))
+ def double_last():
+ l[-1] *= 2
+ os.register_at_fork(
+ before=lambda: l.append(4),
+ after_in_parent=lambda: l.append(-1),
+ after_in_child=double_last)
+ pid = os.fork()
+ if pid == 0: # child
+ # l == [1, 4, 2, 6]
+ os._exit(sum(l))
+
+ assert l == [1, 4, 2, 5, -1]
+
+ pid1, status1 = os.waitpid(pid, 0)
+ assert pid1 == pid
+ assert os.WIFEXITED(status1)
+ res = os.WEXITSTATUS(status1)
+ assert res == 13
+
More information about the pypy-commit
mailing list