Skip to content

Catching Exceptions in the Listener #1675

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

Open
guy-radford-sunswap opened this issue Oct 10, 2023 · 2 comments
Open

Catching Exceptions in the Listener #1675

guy-radford-sunswap opened this issue Oct 10, 2023 · 2 comments
Labels

Comments

@guy-radford-sunswap
Copy link

guy-radford-sunswap commented Oct 10, 2023

Describe the bug

This may be similar to #1667

Occasionally when other devices on the CAN Bus are rebooted or turned, we see the TX buffer get full and this OS forces the CAN Bus to go down. This is expected and I am trying to catch this in code allow the code to restart and reconnect.

I can catch the Exception when connecting to the CAN Bus and sending a message, however when the exception is passed to the Listener.on_error this is within a thread, and therefore when I call sys.exit(1) the application keeps running.

I am unable to find an error handler on the Bus object where is exception will be propagated or any other way to handle this Exception.

os._exit() will work, but is not recommended.

To Reproduce

With the following code receiving CAN Messages, execute ifdown can0 in a different terminal.

class NewMessageListener(Listener):

    def __init__(self, fn_new_message_notify: callable):

        Listener.__init__(self)
        self.fn_new_message_notify = fn_new_message_notify

    def on_message_received(self, msg: Message) -> None:
        try:
            self.fn_new_message_notify(msg)

        except RuntimeError as e:
            _logger.error("Error with runtime: %s", e)

        except Exception as e:  # pylint: disable=W0703
            _logger.exception("Exception from handler: %s", e)

    def on_error(self, exc: Exception) -> None:
        if isinstance(exc, (CanOperationError, ValueError, OSError)):
            _logger.critical("CAN Bus Error from receiver: %s", exc)
            sys.exit(1)

        _logger.exception(exc)
        sys.exit(1)

Without the logging obscuring the Exception the error is:

Exception in thread can.notifier for bus "socketcan channel 'can0'":
Traceback (most recent call last):
  File "/root/.local/lib/python3.8/site-packages/can/interfaces/socketcan/socketcan.py", line 531, in capture_message
    cf, ancillary_data, msg_flags, addr = sock.recvmsg(
OSError: [Errno 100] Network is down

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/root/.local/lib/python3.8/site-packages/can/notifier.py", line 122, in _rx_thread
    msg = bus.recv(self.timeout)
  File "/root/.local/lib/python3.8/site-packages/can/bus.py", line 98, in recv
    msg, already_filtered = self._recv_internal(timeout=time_left)
  File "/root/.local/lib/python3.8/site-packages/can/interfaces/socketcan/socketcan.py", line 739, in _recv_internal
    msg = capture_message(self.socket, get_channel)
  File "/root/.local/lib/python3.8/site-packages/can/interfaces/socketcan/socketcan.py", line 539, in capture_message
    raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno)
can.exceptions.CanOperationError: Error receiving: Network is down [Error Code 100]

Expected behavior

Either:

  • The Exception to be passed to the Bus (or other top level object) to be handled
  • A graceful way to close the application.

Additional context

OS and version: Raspberry Pi Running Docker python:3.8-slim-buster
Python version: 3.8
python-can version: ~4.1
python-can interface/s (if applicable):

@Tbruno25
Copy link
Contributor

I'm not certain this is a bug -- rather it's left up to the developer to decide how they'd like to implement.

The most simple and clear solution would be to propagate the error. Consider the following example:

from queue import Queue


class NewMessageListener(Listener):
    exc = Queue()

    def on_message_received(self, msg: Message) -> None:
        pass

    def on_error(self, exc: Exception) -> None:
        self.exc.put(exc)


if __name__ == "__main__":
    bus = can.interface.Bus(channel ='vcan0', bustype ='socketcan')
    listener = NewMessageListener()
    notifier = Notifier(bus, [listener])

    while listener.exc.empty():
        time.sleep(0.01)
    
    exception = listener.exc.get()
    print(f"{exception}\n\nExiting...")

@guy-radford-sunswap
Copy link
Author

Thank you @Tbruno25, that makes sense.

I also found an asyncio loop parameter on the Notifier class, forces the Notifier to use asyncio (which I am for this project). When an exception is thrown in one of the Notifier Listeners, the exception gets propagated to the loop.set_exception_handler(handle_exception) handler for handling ad a global level.

Thank you

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

No branches or pull requests

2 participants