Skip to content

Commit d2d7ac3

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 d50d5d1 commit d2d7ac3

File tree

1 file changed

+50
-29
lines changed

1 file changed

+50
-29
lines changed

adafruit_esp32spi/adafruit_esp32spi_socketpool.py

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from __future__ import annotations
1515

1616
try:
17-
from typing import TYPE_CHECKING, Optional
17+
from typing import TYPE_CHECKING, Optional, Tuple
1818

1919
if TYPE_CHECKING:
2020
from esp32spi.adafruit_esp32spi import ESP_SPIcontrol # noqa: UP007
@@ -40,7 +40,7 @@ class SocketPool:
4040
SOCK_STREAM = const(1)
4141
SOCK_DGRAM = const(2)
4242
AF_INET = const(2)
43-
SOL_SOCKET = const(0xfff)
43+
SOL_SOCKET = const(0xFFF)
4444
SO_REUSEADDR = const(0x0004)
4545

4646
# implementation specific constants
@@ -95,6 +95,7 @@ def __init__(
9595
self._type = type
9696
self._buffer = b""
9797
self._socknum = socknum if socknum is not None else self._interface.get_socket()
98+
self._bound = ()
9899
self.settimeout(0)
99100

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

159-
last_read_time = time.monotonic()
160+
last_read_time = time.monotonic_ns()
160161
num_to_read = len(buffer) if nbytes == 0 else nbytes
161162
num_read = 0
162163
while num_to_read > 0:
163164
# we might have read socket data into the self._buffer with:
164-
# esp32spi_wsgiserver: socket_readline
165+
# adafruit_wsgi.esp32spi_wsgiserver: socket_readline
165166
if len(self._buffer) > 0:
166167
bytes_to_read = min(num_to_read, len(self._buffer))
167168
buffer[num_read : num_read + bytes_to_read] = self._buffer[:bytes_to_read]
@@ -173,24 +174,38 @@ def recv_into(self, buffer, nbytes: int = 0):
173174

174175
num_avail = self._available()
175176
if num_avail > 0:
176-
last_read_time = time.monotonic()
177-
bytes_read = self._interface.socket_read(self._socknum, min(num_to_read, num_avail))
177+
last_read_time = time.monotonic_ns()
178+
bytes_read = self._interface.socket_read(
179+
self._socknum, min(num_to_read, num_avail)
180+
)
178181
buffer[num_read : num_read + len(bytes_read)] = bytes_read
179182
num_read += len(bytes_read)
180183
num_to_read -= len(bytes_read)
181184
elif num_read > 0:
182185
# We got a message, but there are no more bytes to read, so we can stop.
183186
break
184187
# No bytes yet, or more bytes requested.
185-
if self._timeout > 0 and time.monotonic() - last_read_time > self._timeout:
188+
189+
if self._timeout == 0: # if in non-blocking mode, stop now.
190+
break
191+
192+
# Time out if there's a positive timeout set.
193+
delta = (time.monotonic_ns() - last_read_time) // 1_000_000
194+
if self._timeout > 0 and delta > self._timeout:
186195
raise OSError(errno.ETIMEDOUT)
187196
return num_read
188197

189198
def settimeout(self, value):
190-
"""Set the read timeout for sockets.
191-
If value is 0 socket reads will block until a message is available.
199+
"""Set the read timeout for sockets in seconds.
200+
``0`` means non-blocking. ``None`` means block indefinitely.
192201
"""
193-
self._timeout = value
202+
if value is None:
203+
self._timeout = -1
204+
else:
205+
if value < 0:
206+
raise ValueError("Timeout cannot be a negative number")
207+
# internally in milliseconds as an int
208+
self._timeout = int(value * 1000)
194209

195210
def _available(self):
196211
"""Returns how many bytes of data are available to be read (up to the MAX_PACKET length)"""
@@ -230,35 +245,41 @@ def close(self):
230245

231246
def setsockopt(self, *opts, **kwopts):
232247
"""Dummy call for compatibility."""
233-
# FIXME
234-
pass
235248

236-
def listen(self, backlog):
237-
"""Dummy call for compatibility."""
238-
# FIXME
239-
# probably nothing to do actually
240-
# maybe check that we have called bind or something ?
241-
pass
249+
def setblocking(self, flag: bool):
250+
"""Set the blocking behaviour of this socket.
251+
:param bool flag: False means non-blocking, True means block indefinitely.
252+
"""
253+
if flag:
254+
self.settimeout(None)
255+
else:
256+
self.settimeout(0)
242257

243-
def setblocking(self, blocking):
244-
"""Dummy call for compatibility."""
245-
# FIXME
246-
# is this settimeout(0) ? (if True) or something else ?
247-
pass
258+
def bind(self, address: Tuple[str, int]):
259+
"""Bind a socket to an address"""
260+
self._bound = address
248261

249-
def bind(self, host_port):
250-
host, port = host_port
262+
def listen(self, backlog: int): # pylint: disable=unused-argument
263+
"""Set socket to listen for incoming connections.
264+
:param int backlog: length of backlog queue for waiting connections (ignored)
265+
"""
266+
if not self._bound:
267+
self._bound = (self._interface.ip_address, 80)
268+
port = self._bound[1]
251269
self._interface.start_server(port, self._socknum)
252-
print(f"Binding to {self._socknum}")
253270

254271
def accept(self):
272+
"""Accept a connection on a listening socket of type SOCK_STREAM,
273+
creating a new socket of type SOCK_STREAM. Returns a tuple of
274+
(new_socket, remote_address)
275+
"""
255276
client_sock_num = self._interface.socket_available(self._socknum)
256277
if client_sock_num != SocketPool.NO_SOCKET_AVAIL:
257278
sock = Socket(self._socket_pool, socknum=client_sock_num)
258279
# get remote information (addr and port)
259280
remote = self._interface.get_remote_data(client_sock_num)
260-
IP_ADDRESS = "{}.{}.{}.{}".format(*remote['ip_addr'])
261-
PORT = remote['port']
262-
client_address = (IP_ADDRESS, PORT)
281+
ip_address = "{}.{}.{}.{}".format(*remote["ip_addr"])
282+
port = remote["port"]
283+
client_address = (ip_address, port)
263284
return sock, client_address
264285
raise OSError(errno.ECONNRESET)

0 commit comments

Comments
 (0)