Skip to content
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

Exception in first-class function called through typed list swallows exceptions #8246

Open
nelson2005 opened this issue Jul 15, 2022 · 5 comments

Comments

@nelson2005
Copy link

As discussed in gitter

numba=0.55.1
python=3.7.1
windows10

Calling the first-class function from a jitted function causes the exception to be swallowed.

The exception terminates the program as expected when called from plain python.

import numba
import sys

@numba.njit('int64(int64, int64)')
def raise_err(a, b):
    raise RuntimeError('yo')

def make_typed_list(pylist):
    lst_type = pylist[0].nopython_signatures[0].as_type()
    lst = numba.typed.List.empty_list(lst_type)
    [lst.append(x) for x in pylist]
    return lst

py_list = [raise_err]
lst = make_typed_list(py_list)

@numba.njit
def docall(fns, a, b):
    fns[0](a, b)

print('calling from jit func', file=sys.stderr)
docall(lst, 3, 7)

print('\ncalling from plain python', file=sys.stderr)
lst[0](3, 7)
@sklam
Copy link
Member

sklam commented Jul 18, 2022

Current implementation of first-class function is going through the cfunc wrapper and it is intentionally ignoring exception in this part of the code:

numba/numba/core/cpu.py

Lines 201 to 211 in 88fef5b

with builder.if_then(status.is_error, likely=False):
# If (and only if) an error occurred, acquire the GIL
# and use the interpreter to write out the exception.
pyapi = self.get_python_api(builder)
gil_state = pyapi.gil_ensure()
self.call_conv.raise_error(builder, pyapi, status)
cstr = self.insert_const_string(builder.module, repr(self))
strobj = pyapi.string_from_string(cstr)
pyapi.err_write_unraisable(strobj)
pyapi.decref(strobj)
pyapi.gil_release(gil_state)

This is probably not what user is expecting. We might need a new call wrapper just for first-class function.

@sklam sklam added the discussion An issue requiring discussion label Jul 18, 2022
@nelson2005
Copy link
Author

nelson2005 commented Jul 18, 2022

Edit: when I first wrote this, I didn't remember where but later found the link.

Also, I don't quite remember where, but at one point not too long ago there was a discussion about a swallowed error when inline='always'.
Maybe not related but superficially seems to be in my mind.

@sklam sklam added feature_request and removed needtriage discussion An issue requiring discussion labels Jul 19, 2022
@nelson2005
Copy link
Author

Is there any chance this could be labeled as a bug fix rather than a feature request? It's pretty surprising, diverges from plain-python behavior, and seems like it would be supported based on my interpretation of the documentation

If not, that's fine. I was just hoping for this to be fixed sooner rather than later. :)

@nelson2005
Copy link
Author

Okay, this bug is officially vexing me... I'll offer a USD100 bounty, open through the end of 2022. :)

@DannyWeitekamp
Copy link
Contributor

DannyWeitekamp commented Jan 17, 2023

I'm also interested in this getting fixed soon. Using pyapi.err_write_unraisable(strobj) seems like an odd choice, since it just prints the error text without properly raising an error. This is better than nothing, but definitely not the expected behavior, and
it makes it impossible to catch errors.

This feels closer to correct for the lines @sklam pointed to:
https://github.com/DannyWeitekamp/numba/blob/f4fe69da88568207ffa8faa7fa9d2f6ea6c4b2b0/numba/core/cpu.py#L206-L209

            pyapi = self.get_python_api(builder)
            gil_state = pyapi.gil_ensure()
            self.call_conv.raise_error(builder, pyapi, status)
            pyapi.gil_release(gil_state)
            builder.ret(ir.Constant(ll_return_type, None))

With these changes made to the source, now consider this reproducer:

from numba import njit, f8, types
from numba.typed import List
import pytest

@njit(f8(f8,f8))
def Divide(a, b):
    if(a == 0):
        raise ValueError("Bad a")
    else:
        return a / b

fn_typ = types.FunctionType(f8(f8,f8))
lst = List.empty_list(fn_typ)
lst.append(Divide)

@njit(cache=True)
def apply_it(lst,a,b):
    for f in lst:
        f(a,b)

apply_it(lst, 0,2)
print("SHOULD NOT GET RUN, PREVIOUS LINE SHOULD RAISE ERROR.")

And the output:

Traceback (most recent call last):
  File "/home/danny/Projects/Cognitive-Rule-Engine/cre/garbage25.py", line 8, in Divide
    raise ValueError("Bad a")
ValueError: Bad a

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/danny/Projects/Cognitive-Rule-Engine/cre/garbage25.py", line 23, in <module>
    apply_it(lst, 0,2)
SystemError: CPUDispatcher(<function apply_it at 0x7efd077ba550>) returned a result with an error set

So this still seem to not be quite right because there is perhaps something slightly off about the error... perhaps some return value isn't getting set to Null (that last line doesn't seem to be doing the trick)... and so python catches the improper error raise and then reraises it as a SystemError. Which is at least an error being raised, but also means you can't directly catch the error by type.
(Stack overflow reference about these kinds of SystemErrors: https://stackoverflow.com/a/53796516)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants