-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug] How to handle exceptions raised in parallelized function #63
Comments
Hey, sorry this response is very late. Pypeln forwards the error object from the workers processes to the main thread / process where the stage is being consumed, in this case to the #!/ust/bin/env python3
import pypeln as pl
def compute(x):
if x == 3:
raise ValueError("Value 3 is not supported")
else:
return x * x
data = [1, 2, 3, 4, 5]
stage = pl.process.map(compute, data, workers=4)
try:
for x in stage:
print(f"Result: {x}")
except ValueError as e:
print(f"Got {e}") |
Hi @cgarciae ,
no worries, and happy new year 👍
I understand the solution your proposed, however it doesn't fit my usecase, because handling the error would break the iteration and the pipeline. How would you implement something like this: try:
for x in stage:
print(f"Result: {x}")
except ValueError as e:
print(f"Got {e}")
continue # sort of Unless there is a hidden mechanic that I'm not seeing, I don't understand how your pipeline iteration can recover from an error and continue iterating on the rest of the values ? Thank you for your time. |
I see, so in looking a bit more at your problem specific code it seems you want to know which elements from the stage failed. You could create a decorator that turns exceptions into return values: #!/ust/bin/env python3
import pypeln as pl
import functools
def return_exceptions(f):
@functools.wraps(f)
def wrapped(x):
if isinstance(x, BaseException):
return x
try:
return f(x)
except BaseException as e:
return e
return wrapped
@return_exceptions
def compute(x):
if x == 3:
raise ValueError("Value 3 is not supported")
else:
return x * x With this you can either handle them immediately in the main thread: data = [1, 2, 3, 4, 5]
stage = pl.process.map(compute, data, workers=4)
for x in stage:
if instance(x, BaseException):
# log error
else:
# do something Or even construct longer pipelines: @return_exceptions
def compute_more(x):
...
data = [1, 2, 3, 4, 5]
stage = pl.process.map(compute, data, workers=4)
stage = pl.thread.map(compute_more, stage, workers=2)
for x in stage:
if instance(x, BaseException):
# log error
else:
# do something Maybe error handling of this type could be incorporated into the library either by providing these decorators or directly having a flag throughout the API. It would be nice to see alternative solutions before commiting to something. |
Hey @cgarciae thank you very much for your proposal ! #!/usr/bin/env python3
import pypeln as pl
import functools
def return_exceptions(f):
@functools.wraps(f)
def wrapped(x):
if isinstance(x, BaseException):
return x
try:
return f(x)
except BaseException as e:
return e
return wrapped
@return_exceptions
def compute(x):
if x == 3:
raise ValueError("Value 3 is not supported")
else:
return x*x
@return_exceptions
def compute_more(x):
if x == 25:
raise ValueError("Value 25 is not supported")
else:
return x+1
data = [1, 2, 3, 4, 5]
stage = pl.process.map(compute, data, workers=4)
stage = pl.process.map(compute_more, stage, workers=4)
for x in stage:
if isinstance(x, BaseException):
print(f"Exception ! {x}")
continue
print(f"Result: {x}")
I agree that it would be a nice additions to
|
As you suggest, an alternative es to create an "applicative" interface e.g. import pypeln as pl
import functools
from abc import ABC, abstractmethod
class Result(ABC):
@abstractmethod
def result(self):
...
@abstractmethod
def apply(self, f):
...
class Ok(Result):
def __init__(self, value):
self.value = value
def result(self):
return self.value
def apply(self, f) -> Result:
try:
y = f(self.value)
except BaseException as e:
return Failed(e)
return Ok(y)
class Failed(Result):
def __init__(self, exception: BaseException):
self.exception = exception
def result(self):
raise self.exception
def apply(self, f) -> Result:
return self
def return_exceptions(f):
@functools.wraps(f)
def wrapped(x):
if not isinstance(x, Result):
x = Ok(x)
return x.apply(f)
return wrapped
@return_exceptions
def compute(x):
if x == 3:
raise ValueError("Value 3 is not supported")
else:
return x * x
@return_exceptions
def compute_more(x):
if x == 25:
raise ValueError("Value 25 is not supported")
else:
return x + 1
data = [1, 2, 3, 4, 5]
stage = pl.process.map(compute, data, workers=4)
stage = pl.process.map(compute_more, stage, workers=4)
for x in stage:
try:
y = x.result()
print(f"Result: {y}")
except BaseException as e:
print(f"Exception ! {e}") The nice thing is that it makes it clear to the user that it has to handle success and failure states, on the other hand I think this kind of functionality should be opt-in for users since its not that common in Python. |
Being a native function I guess it would be implemented in C and should not be a problem. There are already a bunch of |
@cgarciae How to use |
Hey @npuichigo, you can use import pypeln as pl
def map_or_filter(x):
try:
y = ... # do stuff
yield y
except:
pass # not yielding acts like a filter
state = pl.process.flat_map(map_or_filter, data)
... |
Describe the bug
I would like to know how I can handle any exception that would occur in the function that I'm trying to parallelize
Minimal code to reproduce
Results
Expected behavior
I have no expected behavior.
instead, i was looking for a way to use the API and get some error recovery.
In this situation the whole pipeline is broken, and I'm not sure how to recover.
I'm trying to see if I can switch to your library, coming from
concurrent.futures
.This is the operation i would like to do (demo with
concurrent.futures
):⬆️ TLDR I'm using
add_done_callback
in order to chain my futures into the next function and create a pipeline.But as i'm dealing with
Future
objects, their exception is only raised when you try to access theirresult()
(which is not the case withpypeln
)Library Info
0.4.6
Additional context
Add any other context about the problem here.
Thanks for your library, it looks amazing !
The text was updated successfully, but these errors were encountered: