from pypy.module._stackless.interp_coroutine import AbstractThunk, Coroutine from pypy.rlib.rgc import gc_swap_pool, gc_clone from pypy.rlib.objectmodel import we_are_translated from pypy.interpreter.error import OperationError class InterpClonableMixin: local_pool = None _mixin_ = True def hello_local_pool(self): if we_are_translated(): self.saved_pool = gc_swap_pool(self.local_pool) def goodbye_local_pool(self): if we_are_translated(): self.local_pool = gc_swap_pool(self.saved_pool) self.saved_pool = None def clone_into(self, copy, extradata=None): if not we_are_translated(): raise NotImplementedError # cannot gc_clone() directly self, because it is not in its own # local_pool. Moreover, it has a __del__, which cloning doesn't # support properly at the moment. copy.parent = self.parent # the hello/goodbye pair has two purposes: it forces # self.local_pool to be computed even if it was None up to now, # and it puts the 'data' tuple in the correct pool to be cloned. self.hello_local_pool() data = (self.frame, extradata) self.goodbye_local_pool() # clone! data, copy.local_pool = gc_clone(data, self.local_pool) copy.frame, extradata = data copy.thunk = self.thunk # in case we haven't switched to self yet return extradata class InterpClonableCoroutine(Coroutine, InterpClonableMixin): def hello(self): self.hello_local_pool() def goodbye(self): self.goodbye_local_pool() def clone(self): # hack, this is overridden in AppClonableCoroutine if self.getcurrent() is self: raise RuntimeError("clone() cannot clone the current coroutine; " "use fork() instead") copy = InterpClonableCoroutine(self.costate) self.clone_into(copy) return copy class ForkThunk(AbstractThunk): def __init__(self, coroutine): self.coroutine = coroutine self.newcoroutine = None def call(self): oldcoro = self.coroutine self.coroutine = None newcoro = oldcoro.clone() newcoro.parent = oldcoro self.newcoroutine = newcoro def fork(): """Fork, as in the Unix fork(): the call returns twice, and the return value of the call is either the new 'child' coroutine object (if returning into the parent), or None (if returning into the child). This returns into the parent first, which can switch to the child later. """ current = InterpClonableCoroutine.getcurrent() if not isinstance(current, InterpClonableCoroutine): raise RuntimeError("fork() in a non-clonable coroutine") thunk = ForkThunk(current) coro_fork = InterpClonableCoroutine() coro_fork.bind(thunk) coro_fork.switch() # we resume here twice. The following would need explanations about # why it returns the correct thing in both the parent and the child... return thunk.newcoroutine ## from pypy.rpython.lltypesystem import lltype, lloperation ## lloperation.llop.debug_view(lltype.Void, current, thunk, ## lloperation.llop.gc_x_size_header(lltype.Signed))