Skip to content

Commit 86d161e

Browse files
committed
use time.monotonic_ns() to measure timeouts
re-implement timeout similar to Circuitpython (None blocks, 0 doesn't) NOTE: this is a breaking change for any code that uses it bind() saves the bound address and port listen() uses the saved address and port or uses defaults some fixes for pylint/black
1 parent 0f3b271 commit 86d161e

File tree

1 file changed

+47
-28
lines changed

1 file changed

+47
-28
lines changed

adafruit_esp32spi/adafruit_esp32spi_socketpool.py

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from __future__ import annotations
1414

1515
try:
16-
from typing import TYPE_CHECKING, Optional
16+
from typing import TYPE_CHECKING, Optional, Tuple
1717

1818
if TYPE_CHECKING:
1919
from esp32spi.adafruit_esp32spi import ESP_SPIcontrol
@@ -37,7 +37,7 @@ class SocketPool:
3737
SOCK_STREAM = const(1)
3838
SOCK_DGRAM = const(2)
3939
AF_INET = const(2)
40-
SOL_SOCKET = const(0xfff)
40+
SOL_SOCKET = const(0xFFF)
4141
SO_REUSEADDR = const(0x0004)
4242

4343
# implementation specific constants
@@ -94,6 +94,7 @@ def __init__( # pylint: disable=redefined-builtin,too-many-arguments,unused-arg
9494
self._type = type
9595
self._buffer = b""
9696
self._socknum = socknum if socknum is not None else self._interface.get_socket()
97+
self._bound = ()
9798
self.settimeout(0)
9899

99100
def __enter__(self):
@@ -157,12 +158,12 @@ def recv_into(self, buffer, nbytes: int = 0):
157158
if not 0 <= nbytes <= len(buffer):
158159
raise ValueError("nbytes must be 0 to len(buffer)")
159160

160-
last_read_time = time.monotonic()
161+
last_read_time = time.monotonic_ns()
161162
num_to_read = len(buffer) if nbytes == 0 else nbytes
162163
num_read = 0
163164
while num_to_read > 0:
164165
# we might have read socket data into the self._buffer with:
165-
# esp32spi_wsgiserver: socket_readline
166+
# adafruit_wsgi.esp32spi_wsgiserver: socket_readline
166167
if len(self._buffer) > 0:
167168
bytes_to_read = min(num_to_read, len(self._buffer))
168169
buffer[num_read : num_read + bytes_to_read] = self._buffer[
@@ -176,7 +177,7 @@ def recv_into(self, buffer, nbytes: int = 0):
176177

177178
num_avail = self._available()
178179
if num_avail > 0:
179-
last_read_time = time.monotonic()
180+
last_read_time = time.monotonic_ns()
180181
bytes_read = self._interface.socket_read(
181182
self._socknum, min(num_to_read, num_avail)
182183
)
@@ -187,15 +188,27 @@ def recv_into(self, buffer, nbytes: int = 0):
187188
# We got a message, but there are no more bytes to read, so we can stop.
188189
break
189190
# No bytes yet, or more bytes requested.
190-
if self._timeout > 0 and time.monotonic() - last_read_time > self._timeout:
191+
192+
if self._timeout == 0: # if in non-blocking mode, stop now.
193+
break
194+
195+
# Time out if there's a positive timeout set.
196+
delta = (time.monotonic_ns() - last_read_time) // 1_000_000
197+
if self._timeout > 0 and delta > self._timeout:
191198
raise OSError(errno.ETIMEDOUT)
192199
return num_read
193200

194201
def settimeout(self, value):
195-
"""Set the read timeout for sockets.
196-
If value is 0 socket reads will block until a message is available.
202+
"""Set the read timeout for sockets in seconds.
203+
``0`` means non-blocking. ``None`` means block indefinitely.
197204
"""
198-
self._timeout = value
205+
if value is None:
206+
self._timeout = -1
207+
else:
208+
if value < 0:
209+
raise ValueError("Timeout cannot be a negative number")
210+
# internally in milliseconds as an int
211+
self._timeout = int(value * 1000)
199212

200213
def _available(self):
201214
"""Returns how many bytes of data are available to be read (up to the MAX_PACKET length)"""
@@ -237,35 +250,41 @@ def close(self):
237250

238251
def setsockopt(self, *opts, **kwopts):
239252
"""Dummy call for compatibility."""
240-
# FIXME
241-
pass
242253

243-
def listen(self, backlog):
244-
"""Dummy call for compatibility."""
245-
# FIXME
246-
# probably nothing to do actually
247-
# maybe check that we have called bind or something ?
248-
pass
254+
def setblocking(self, flag: bool):
255+
"""Set the blocking behaviour of this socket.
256+
:param bool flag: False means non-blocking, True means block indefinitely.
257+
"""
258+
if flag:
259+
self.settimeout(None)
260+
else:
261+
self.settimeout(0)
249262

250-
def setblocking(self, blocking):
251-
"""Dummy call for compatibility."""
252-
# FIXME
253-
# is this settimeout(0) ? (if True) or something else ?
254-
pass
263+
def bind(self, address: Tuple[str, int]):
264+
"""Bind a socket to an address"""
265+
self._bound = address
255266

256-
def bind(self, host_port):
257-
host, port = host_port
267+
def listen(self, backlog: int): # pylint: disable=unused-argument
268+
"""Set socket to listen for incoming connections.
269+
:param int backlog: length of backlog queue for waiting connections (ignored)
270+
"""
271+
if not self._bound:
272+
self._bound = (self._interface.ip_address, 80)
273+
port = self._bound[1]
258274
self._interface.start_server(port, self._socknum)
259-
print(f"Binding to {self._socknum}")
260275

261276
def accept(self):
277+
"""Accept a connection on a listening socket of type SOCK_STREAM,
278+
creating a new socket of type SOCK_STREAM. Returns a tuple of
279+
(new_socket, remote_address)
280+
"""
262281
client_sock_num = self._interface.socket_available(self._socknum)
263282
if client_sock_num != SocketPool.NO_SOCKET_AVAIL:
264283
sock = Socket(self._socket_pool, socknum=client_sock_num)
265284
# get remote information (addr and port)
266285
remote = self._interface.get_remote_data(client_sock_num)
267-
IP_ADDRESS = "{}.{}.{}.{}".format(*remote['ip_addr'])
268-
PORT = remote['port']
269-
client_address = (IP_ADDRESS, PORT)
286+
ip_address = "{}.{}.{}.{}".format(*remote["ip_addr"])
287+
port = remote["port"]
288+
client_address = (ip_address, port)
270289
return sock, client_address
271290
raise OSError(errno.ECONNRESET)

0 commit comments

Comments
 (0)