1414
1515
1616import sys
17+ import traceback
1718from shutil import rmtree
1819
1920if sys .platform != 'win32' :
@@ -157,6 +158,58 @@ def __call__(self, f, inputs):
157158 sage: del unpickle_override[('sage.rings.polynomial.polynomial_rational_flint', 'Polynomial_rational_flint')]
158159 sage: list(Polygen([QQ,QQ]))
159160 [(((Rational Field,), {}), x), (((Rational Field,), {}), x)]
161+
162+ When the function raises an exception, the exception is re-raised in the parent process with the same type.
163+ The traceback is gone, but we print a textual version::
164+
165+ sage: def fff(x): return ggg(x)
166+ sage: def ggg(x): return hhh(x)
167+ sage: def hhh(x): return 1/x
168+ sage: try: list(F(fff, [([0],{}), ([1],{})]))
169+ ....: except: import traceback; traceback.print_exc()
170+ RuntimeError: forked subprocess raised:
171+ Traceback (most recent call last):
172+ ...in ggg...
173+ ZeroDivisionError: rational division by zero
174+ The above exception was the direct cause of the following exception:
175+ ...
176+ ZeroDivisionError: rational division by zero
177+
178+ When the erroneous invocation is not the first one, because of parallelism,
179+ the exception might be raised on the first or second ``next()`` function call.
180+ Both of the following behaviors are possible::
181+
182+ sage: # not tested
183+ sage: it = F(fff, [([1],{}), ([0],{})])
184+ sage: next(it)
185+ (([1], {}), 1)
186+ sage: next(it)
187+ Traceback (most recent call last):
188+ ...
189+ ZeroDivisionError: rational division by zero
190+
191+ sage: # not tested
192+ sage: it = F(fff, [([1],{}), ([0],{})])
193+ sage: next(it)
194+ Traceback (most recent call last):
195+ ...
196+ ZeroDivisionError: rational division by zero
197+
198+ Other corner cases::
199+
200+ sage: class UnpicklableException(Exception): __reduce__ = None
201+ sage: def fff(x): raise UnpicklableException()
202+ sage: list(F(fff, [([0],{}), ([1],{})]))
203+ Traceback (most recent call last):
204+ ...
205+ RuntimeError: cannot pickle exception object
206+
207+ sage: class UnpicklableValue: __reduce__ = None
208+ sage: def fff(x): return UnpicklableValue()
209+ sage: list(F(fff, [([0],{}), ([1],{})]))
210+ Traceback (most recent call last):
211+ ...
212+ RuntimeError: cannot pickle return value
160213 """
161214 import os
162215 import sys
@@ -222,13 +275,13 @@ def __call__(self, f, inputs):
222275 with open (sobj , "rb" ) as file :
223276 data = file .read ()
224277 except OSError :
225- answer = "NO DATA" + W .failure
278+ should_raise , answer = False , "NO DATA" + W .failure
226279 else :
227280 os .unlink (sobj )
228281 try :
229- answer = loads (data , compress = False )
282+ should_raise , answer = loads (data , compress = False )
230283 except Exception as E :
231- answer = "INVALID DATA {}" .format (E )
284+ should_raise , answer = False , "INVALID DATA {}" .format (E )
232285
233286 out = os .path .join (dir , '%s.out' % pid )
234287 try :
@@ -238,6 +291,10 @@ def __call__(self, f, inputs):
238291 except OSError :
239292 pass
240293
294+ if should_raise :
295+ exc , tb_str = answer
296+ raise exc from RuntimeError (f"forked subprocess raised:\n { tb_str } " )
297+
241298 yield (W .input , answer )
242299 finally :
243300 # Send SIGKILL signal to workers that are left.
@@ -325,8 +382,17 @@ def _subprocess(self, f, dir, args, kwds={}):
325382 set_random_seed (self .worker_seed )
326383
327384 # Now evaluate the function f.
328- value = f (* args , ** kwds )
385+ try :
386+ value = False , f (* args , ** kwds )
387+ except BaseException as e :
388+ value = True , (e , traceback .format_exc ())
329389
330390 # And save the result to disk.
331391 sobj = os .path .join (dir , '%s.sobj' % os .getpid ())
332- save (value , sobj , compress = False )
392+ try :
393+ save (value , sobj , compress = False )
394+ except BaseException as e :
395+ if value [0 ]:
396+ save ((True , (RuntimeError ('cannot pickle exception object' ), traceback .format_exc ())), sobj , compress = False )
397+ else :
398+ save ((True , (RuntimeError ('cannot pickle return value' ), traceback .format_exc ())), sobj , compress = False )
0 commit comments