python-can
python-can copied to clipboard
Async variant of BusABC?
I am using the asyncio support in Notifier which works well on reception of messages. However there is no async variant of BusABC.send(). It has a timeout which by default is set to None that may block forever according to spec. Not all interfaces seems to be using the timeout parameter and I haven't had any problem using it as-is from async tasks. But it is a potential block which may halt the async event loop if called from an async task. There are other functions in the BusABC API that has similar timeouts, like recv().
I guess the first question would be if an async variant of BusABC.send() is really needed or not. If the timeout is for all practical purposes not used and sends are immediate, then IMHO there is nothing wrong with this blocking call.
What would be a good approach for async? Make a new AsyncBus as wrapper for BusABC in the same manner as ThreadSafeBus is? I believe due to the nature of async tasks, the send() function would need to put the message on a queue and await a signal. A separate (non async) tx thread would be responsible for sending the message using the regular send() including traditional timeouts and then signal back to the sleeping async task.
The default behavior when timeout is None is non-blocking. The interface just adds the message to some transmit queue and returns. If the queue is full an exception is raised. So for most use cases the send method can be used in asynchronous applications without issues.
Some interfaces also support blocking send operations when setting the timeout argument, but in any case I don't think many, if any at all, support truly asynchronous notification of transmit success. So if an application wants to do asynchronous blocking sends it must be done using a thread as you said. asyncio has good support for turning synchronous functions asynchronous so I don't think having python-can managing a threadpool and so on will add that much value. It would rather make it more complex to use.
I would probably use the default non-blocking call and possibly do retries in case the buffer is full.
If the default timeout is None and it corresponds to non-blocking, then I agree that no special async mechanism for send is required. If some async application requires timeout, then it can be left to the user to build the necessary async waits if the delays cause any troubles.
FYI @christiansandberg , that timeout defaults to non-block behavior does not match the spec in BusABC.send(): https://github.com/hardbyte/python-can/blob/develop/can/bus.py#L172
"None blocks indefinitely."
For the sake of the argument, making an awaitable send isn't that hard or complex. Much of the same mechanisms are already in use in the rx thread that python-can use for notifications today. One would need something similar for tx. And its not a prerequisite that the low-level interface driver is non-blocking. The (new) tx thread can be as blocking as it needs to be, only passing signals upon success that can wake the awaiting async task.
TL;DR if delays on send() aren't lengthy and most are immediate, I agree that the efforts of making this into async isn't worth the stretch.
You're right about the documentation being incorrect. Looking through some interface implementations most seem to be non-blocking on None. There does seem to be at least one though that blocks indefinitely on None, but in that case the default is 0 which means non-blocking. So more correct would be that not specifying a timeout at all means non-blocking, and specifying None is interface dependent.
Hi,
I have a project where I will need to read from the CAN bus in an asyncio application and I started reading the documentation. One thing I noticed is that the documentation still says in BusABC.send() that None blocks indefinitely. Only when you go deep into the interface module documentation, you might find something different.
In my case I will also use socketCAN, so I looked up there and the send() documentation says here If [the tiemeout is] not given, the call may fail immediately which could be interpreted as christiansandberg explained. However the may fail part in the documentation and christiansandberg's answer So for most use cases the send method can be used in asynchronous applications without issues are a little bit intriguing to me: which are the cases where send() might block?
Looking at the implementation of SocketcanBus.send(..., timeout=None), then the function does a select.select([], [self.socket], [], time_left)[1] (where time_left=0) which would immediately return when no write operation is possible, right? Or when could this select call block?
So it would be safe to do something like this?
async def worker(bus):
# some initialization
while True:
next_msg = await somequeue.get()
# do something
try:
bus.send(next_msg)
except can.CanError:
# notify my application that the bus is down
# or something like that or log the error, whatever
# or even stop the worker entirely
...
finally:
# do some cleanup
@shaoran I think its safe to do what you propose under async with socketcan. The send() for socketcan will default to timeout=0 if no timeout is given (None) which will be the case in the example.
As @christiansandberg suggests, a the default timeout of None can possibly be interpreted differently by different interface drivers, so I would suggest setting bus.send(next_msg, timeout=0). (Let's hope that a value 0 indeed means no delay in the various interfaces.)