4

I wrote a Python program that both reads and writes a L2CAP connection via a bluetooth socket (see example below) to talk to some hardware. However the code may want to do too much IO for the bluetooth adapter too handle, so I equipped it to handle laggy reads and writes by slowing down the bar loop if needed, as this is preferable to delay when responding to requests.

sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
sock.connect((bt_addr, bt_port))
sock.setblocking(False)

stall_loop = asyncio.Event()
stall_loop.set()

async def bar():
    while True:
        await stall_loop.wait()
        await loop.sock_sendall(generate_data(time.time()))
        await asyncio.sleep(maybe_to_small_delay)

async def foo():
    while True:
        await request = loop.sock_recv(sock, 400)
        stall_loop.clear()
        await loop.sock_sendall(generate_response(request))
        stall_loop.set()

asyncio.gather(foo, bar)
        

The problem however is that neither of these loops see any notable slowdown until bar freezes for seconds at a time.

I timed the execution of both loops using time.time() and execution time is mere milliseconds including the IO (except when bar freezes), but anything written by foo may only be sent with up to 15s of delay (as reported by tshark and comparing to the time python considers the write done)

The behavior I want is the write to only return once the data is actually transmitted in order to retain a responsive foo and a bar as close to the wanted speed as possible.

I assume the culprit is some kind of buffering policy that buffers a large amount of data until the buffer is full and then flushes it completely. How can I either disable this bahviour and/or limit the buffer size to e.g. 2 packets? I couldn't find documentaion on ioctl for bluez.

Versions:

  • Raspbian on kernel 5.10.17-vl7+
  • Python 3.7.3
  • bluez 5.50
Poohl
  • 1,932
  • 1
  • 9
  • 22
  • @ukBaz, I thought of that, but sadly they don't support bluetooth sockets. They all are wrappers around the [Opening Network Connection](https://docs.python.org/3/library/asyncio-eventloop.html#id5) API which only supports `AF_INET`, `AF_INET6` and `AF_UNIX` using an additional function. – Poohl Apr 21 '21 at 09:44
  • @ukBaz So you mean I should create the socket as above and then wrap it inside a `Stream` with a small buffer. Wouldn't that just create more lag, since the socket itself already lags and the Stream buffers aswell or do Streams have some knowledge about sockets I don't and configure the socket's buffering? – Poohl Apr 21 '21 at 10:07

1 Answers1

1

The problem was the buffering in Linux, the solution was to disable it:

client_itr.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 0)

Found this option today in the GNU libc docs.

I don't know why it decided to flush the buffer completely once it was filled up (which caused the bar-loop to freeze for a while), but without buffering behaviour is as expected: between foo receiving a request and it sending a reply are a maximum of 2 bar packets, even without the Event-locking.

Note however that without buffering the sendall only blocks until the data was send via bluetooth, not until it was acknowledged by the recipient. For that you would presumably have to lower the window size of L2CAP.

Poohl
  • 1,932
  • 1
  • 9
  • 22