Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
75 changes: 40 additions & 35 deletions can/thread_safe_bus.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from contextlib import nullcontext
from threading import RLock
from typing import Any

from can import typechecking
from can.bus import BusABC, BusState, CanProtocol
from can.message import Message
from typing import TYPE_CHECKING, Any, cast

from . import typechecking
from .bus import BusState, CanProtocol
from .interface import Bus
from .message import Message

if TYPE_CHECKING:
from .bus import BusABC

try:
# Only raise an exception on instantiation but allow module
Expand All @@ -15,11 +17,13 @@

import_exc = None
except ImportError as exc:
ObjectProxy = object
ObjectProxy = None # type: ignore[misc,assignment]
import_exc = exc


class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method
class ThreadSafeBus(
ObjectProxy
): # pylint: disable=abstract-method # type: ignore[assignment]
"""
Contains a thread safe :class:`can.BusABC` implementation that
wraps around an existing interface instance. All public methods
Expand All @@ -36,8 +40,6 @@ class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method
instead of :meth:`~can.BusABC.recv` directly.
"""

__wrapped__: BusABC

def __init__(
self,
channel: typechecking.Channel | None = None,
Expand All @@ -59,58 +61,61 @@ def __init__(
)
)

# store wrapped bus as a proxy-local attribute. Name it with the
# `_self_` prefix so wrapt won't forward it onto the wrapped object.
self._self_wrapped = cast(
"BusABC", object.__getattribute__(self, "__wrapped__")
)

# now, BusABC.send_periodic() does not need a lock anymore, but the
# implementation still requires a context manager
self.__wrapped__._lock_send_periodic = nullcontext() # type: ignore[assignment]
self._self_wrapped._lock_send_periodic = nullcontext() # type: ignore[assignment]

# init locks for sending and receiving separately
self._lock_send = RLock()
self._lock_recv = RLock()
self._self_lock_send = RLock()
self._self_lock_recv = RLock()

def recv(self, timeout: float | None = None) -> Message | None:
with self._lock_recv:
return self.__wrapped__.recv(timeout=timeout)
with self._self_lock_recv:
return self._self_wrapped.recv(timeout=timeout)

def send(self, msg: Message, timeout: float | None = None) -> None:
with self._lock_send:
return self.__wrapped__.send(msg=msg, timeout=timeout)

# send_periodic does not need a lock, since the underlying
# `send` method is already synchronized
with self._self_lock_send:
return self._self_wrapped.send(msg=msg, timeout=timeout)

@property
def filters(self) -> typechecking.CanFilters | None:
with self._lock_recv:
return self.__wrapped__.filters
with self._self_lock_recv:
return self._self_wrapped.filters

@filters.setter
def filters(self, filters: typechecking.CanFilters | None) -> None:
with self._lock_recv:
self.__wrapped__.filters = filters
with self._self_lock_recv:
self._self_wrapped.filters = filters

def set_filters(self, filters: typechecking.CanFilters | None = None) -> None:
with self._lock_recv:
return self.__wrapped__.set_filters(filters=filters)
with self._self_lock_recv:
return self._self_wrapped.set_filters(filters=filters)

def flush_tx_buffer(self) -> None:
with self._lock_send:
return self.__wrapped__.flush_tx_buffer()
with self._self_lock_send:
return self._self_wrapped.flush_tx_buffer()

def shutdown(self) -> None:
with self._lock_send, self._lock_recv:
return self.__wrapped__.shutdown()
with self._self_lock_send, self._self_lock_recv:
return self._self_wrapped.shutdown()

@property
def state(self) -> BusState:
with self._lock_send, self._lock_recv:
return self.__wrapped__.state
with self._self_lock_send, self._self_lock_recv:
return self._self_wrapped.state

@state.setter
def state(self, new_state: BusState) -> None:
with self._lock_send, self._lock_recv:
self.__wrapped__.state = new_state
with self._self_lock_send, self._self_lock_recv:
self._self_wrapped.state = new_state

@property
def protocol(self) -> CanProtocol:
with self._lock_send, self._lock_recv:
return self.__wrapped__.protocol
with self._self_lock_send, self._self_lock_recv:
return self._self_wrapped.protocol
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ docs = [
"furo",
]
lint = [
"pylint==3.3.*",
"pylint==4.0.*",
"ruff==0.14.*",
"black==25.9.*",
"mypy==1.18.*",
Expand Down
Loading