22Contains the ABC bus implementation and its documentation.
33"""
44
5- from typing import cast , Any , Iterator , List , Optional , Sequence , Tuple , Union
6-
7- import can .typechecking
8-
9- from abc import ABC , ABCMeta , abstractmethod
10- import can
5+ import contextlib
116import logging
127import threading
13- from time import time
8+ from abc import ABC , ABCMeta , abstractmethod
149from enum import Enum , auto
10+ from time import time
11+ from typing import Any , Iterator , List , Optional , Sequence , Tuple , Union , cast
1512
16- from can .broadcastmanager import ThreadBasedCyclicSendTask , CyclicSendTaskABC
13+ import can
14+ import can .typechecking
15+ from can .broadcastmanager import CyclicSendTaskABC , ThreadBasedCyclicSendTask
1716from can .message import Message
1817
1918LOG = logging .getLogger (__name__ )
@@ -52,13 +51,15 @@ class BusABC(metaclass=ABCMeta):
5251 #: Log level for received messages
5352 RECV_LOGGING_LEVEL = 9
5453
54+ _is_shutdown : bool = False
55+
5556 @abstractmethod
5657 def __init__ (
5758 self ,
5859 channel : Any ,
5960 protocol : CanProtocol = CanProtocol .CAN_20 ,
6061 can_filters : Optional [can .typechecking .CanFilters ] = None ,
61- ** kwargs : object
62+ ** kwargs : object ,
6263 ):
6364 """Construct and open a CAN bus instance of the specified type.
6465
@@ -110,7 +111,6 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
110111 time_left = timeout
111112
112113 while True :
113-
114114 # try to get a message
115115 msg , already_filtered = self ._recv_internal (timeout = time_left )
116116
@@ -126,7 +126,6 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
126126 # try next one only if there still is time, and with
127127 # reduced timeout
128128 else :
129-
130129 time_left = timeout - (time () - start )
131130
132131 if time_left > 0 :
@@ -320,6 +319,10 @@ def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None:
320319 :param remove_tasks:
321320 Stop tracking the stopped tasks.
322321 """
322+ if not hasattr (self , "_periodic_tasks" ):
323+ # avoid AttributeError for partially initialized BusABC instance
324+ return
325+
323326 for task in self ._periodic_tasks :
324327 # we cannot let `task.stop()` modify `self._periodic_tasks` while we are
325328 # iterating over it (#634)
@@ -434,9 +437,15 @@ def flush_tx_buffer(self) -> None:
434437
435438 def shutdown (self ) -> None :
436439 """
437- Called to carry out any interface specific cleanup required
438- in shutting down a bus.
440+ Called to carry out any interface specific cleanup required in shutting down a bus.
441+
442+ This method can be safely called multiple times.
439443 """
444+ if self ._is_shutdown :
445+ LOG .debug ("%s is already shut down" , self .__class__ )
446+ return
447+
448+ self ._is_shutdown = True
440449 self .stop_all_periodic_tasks ()
441450
442451 def __enter__ (self ):
@@ -445,6 +454,14 @@ def __enter__(self):
445454 def __exit__ (self , exc_type , exc_val , exc_tb ):
446455 self .shutdown ()
447456
457+ def __del__ (self ) -> None :
458+ if not self ._is_shutdown :
459+ LOG .warning ("%s was not properly shut down" , self .__class__ )
460+ # We do some best-effort cleanup if the user
461+ # forgot to properly close the bus instance
462+ with contextlib .suppress (AttributeError ):
463+ self .shutdown ()
464+
448465 @property
449466 def state (self ) -> BusState :
450467 """
0 commit comments