Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions can/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ class BusABC(metaclass=ABCMeta):
#: Log level for received messages
RECV_LOGGING_LEVEL = 9

_is_shutdown: bool = False

@abstractmethod
def __init__(
self,
channel: Any,
can_filters: Optional[can.typechecking.CanFilters] = None,
**kwargs: object
**kwargs: object,
):
"""Construct and open a CAN bus instance of the specified type.

Expand Down Expand Up @@ -417,17 +419,34 @@ def flush_tx_buffer(self) -> None:

def shutdown(self) -> None:
"""
Called to carry out any interface specific cleanup required
in shutting down a bus.
Called to carry out any interface specific cleanup required in shutting down a bus.

This method can be safely called multiple times.
"""
if self._is_shutdown:
LOG.debug("%s is already shut down", self.__class__)
return

self.stop_all_periodic_tasks()
self._is_shutdown = True

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.shutdown()

def __del__(self) -> None:
try:
if not self._is_shutdown:
# We do some best-effort cleanup if the user
# forgot to properly close the bus instance
self.shutdown()
LOG.warning("%s was not properly shut down", self.__class__)
except Exception as e: # pylint: disable=W0703
# Prevent unwanted output from being printed to stdout/stderr
LOG.debug(e)

@property
def state(self) -> BusState:
"""
Expand Down
1 change: 1 addition & 0 deletions can/interfaces/ixxat/canlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def _send_periodic_internal(self, msgs, period, duration=None):
return self.bus._send_periodic_internal(msgs, period, duration)

def shutdown(self):
super().shutdown()
return self.bus.shutdown()

@property
Expand Down
2 changes: 1 addition & 1 deletion can/interfaces/socketcand/socketcand.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,5 @@ def send(self, msg, timeout=None):
self._tcp_send(ascii_msg)

def shutdown(self):
self.stop_all_periodic_tasks()
super().shutdown()
self.__socket.close()
9 changes: 9 additions & 0 deletions test/test_bus.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import gc
from unittest.mock import patch

import can
Expand All @@ -12,3 +13,11 @@ def test_bus_ignore_config():

_ = can.Bus(interface="virtual")
assert can.util.load_config.called


@patch.object(can.bus.BusABC, "shutdown")
def test_bus_attempts_self_cleanup(mock_shutdown):
bus = can.Bus(interface="virtual")
del bus
gc.collect()
mock_shutdown.assert_called()
36 changes: 36 additions & 0 deletions test/test_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import importlib
from unittest.mock import patch

import pytest

import can
from can.interfaces import BACKENDS


@pytest.fixture(params=(BACKENDS.keys()))
def constructor(request):
mod, cls = BACKENDS[request.param]

try:
module = importlib.import_module(mod)
constructor = getattr(module, cls)
except:
pytest.skip("Unable to load interface")

return constructor


@pytest.fixture
def interface(constructor):
with patch.object(constructor, "__init__", return_value=None):
return constructor()


@patch.object(can.bus.BusABC, "shutdown")
def test_all_interfaces_call_parent_shutdown(mock_shutdown, interface):
try:
interface.shutdown()
except:
pass
finally:
mock_shutdown.assert_called()