Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TCP Connection Timeout Setting #8266

Closed
SanJoseBart opened this issue Aug 7, 2023 · 4 comments · Fixed by #9210
Closed

TCP Connection Timeout Setting #8266

SanJoseBart opened this issue Aug 7, 2023 · 4 comments · Fixed by #9210
Assignees
Labels
enhancement espressif applies to multiple Espressif chips network
Milestone

Comments

@SanJoseBart
Copy link

In standard Python, socket.settimeout() sets a timeout for both TCP connections and data transfer. In CircuitPython, socket.settimeout() sets a timeout for data transfer, but doesn't affect the TCP connection timeout. It sure would be nice to set a TCP connection timeout, either via settimeout() or some other method.

For example, using CircuitPython 8.2.2 on an Adafruit Feather ESP32-S3 (no PSRAM), an attempted TCP connection always times out after 30 seconds, regardless of the settimeout() value. See below.

# Connection timeout demo

import board
import adafruit_sht31d
import wifi
import time
from socketpool import SocketPool

SSID = "[Wi-Fi name]"
PWD = "[Wi-Fi password]"
SERVER = "10.0.0.119"  # any non-existent local address
PORT = 4790  # any arbitrary port

wifi.radio.connect(SSID, PWD, timeout=10)
starttime = time.monotonic()
try:
    pool = SocketPool(wifi.radio)
    with pool.socket(SocketPool.AF_INET, SocketPool.SOCK_STREAM) as s:
        s.settimeout(5)  # Does not affect connection timeout
        s.connect((SERVER, PORT))
        s.sendall(b"Data goes here")
except OSError as ex:
    elapsed = time.monotonic() - starttime
    print(f"Connection timeout in {elapsed} seconds.")

The example code output is "Connection timeout in 30.5 seconds." (give or take a couple of tenths), regardless of the settimeout() value.

@anecdata
Copy link
Member

anecdata commented Aug 7, 2023

Looks like it was intended, but as yet unimplemented:

// TODO: deal with potential failure/add timeout?

@tannewt tannewt added the network label Aug 7, 2023
@tannewt tannewt added this to the Long term milestone Aug 7, 2023
@tannewt tannewt added the espressif applies to multiple Espressif chips label Aug 7, 2023
@birmitt
Copy link

birmitt commented Mar 24, 2024

Be able to set the connection timeout is crucial in asyncio environments.

That's actually a bummer as I'm porting the MiniMQTT lib to asyncio to get a fully non-blocking experience. So at least for the connection phase it won't be that "non-blocking" for now.

In my case I would set the connection timeout very small and build a asyncio based retry mechanism on top. That would give other tasks time to run during connection failures.

@dhalbert dhalbert modified the milestones: Long term, 9.x.x Mar 24, 2024
@dhalbert dhalbert self-assigned this Apr 5, 2024
@dhalbert
Copy link
Collaborator

dhalbert commented Apr 7, 2024

For reference:

https://docs.python.org/3/library/socket.html#socket.socket.settimeout

socket.settimeout(value)
Set a timeout on blocking socket operations. The value argument can be a nonnegative floating point number expressing seconds, or None. If a non-zero value is given, subsequent socket operations will raise a timeout exception if the timeout period value has elapsed before the operation has completed. If zero is given, the socket is put in non-blocking mode. If None is given, the socket is put in blocking mode.

For further information, please consult the notes on socket timeouts.

Changed in version 3.7: The method no longer toggles SOCK_NONBLOCK flag on socket.type.

The link to notes on socket timeouts says:

Notes on socket timeouts

A socket object can be in one of three modes: blocking, non-blocking, or timeout. Sockets are by default always created in blocking mode, but this can be changed by calling setdefaulttimeout().

In blocking mode, operations block until complete or the system returns an error (such as connection timed out).

In non-blocking mode, operations fail (with an error that is unfortunately system-dependent) if they cannot be completed immediately: functions from the select module can be used to know when and whether a socket is available for reading or writing.

In timeout mode, operations fail if they cannot be completed within the timeout specified for the socket (they raise a timeout exception) or if the system returns an error.

Note: At the operating system level, sockets in timeout mode are internally set in non-blocking mode. Also, the blocking and timeout modes are shared between file descriptors and socket objects that refer to the same network endpoint. This implementation detail can have visible consequences if e.g. you decide to use the fileno() of a socket.
Timeouts and the connect method
The connect() operation is also subject to the timeout setting, and in general it is recommended to call settimeout() before calling connect() or pass a timeout parameter to create_connection(). However, the system network stack may also return a connection timeout error of its own regardless of any Python socket timeout setting.

Timeouts and the accept method

If getdefaulttimeout() is not None, sockets returned by the accept() method inherit that timeout. Otherwise, the behaviour depends on settings of the listening socket:

if the listening socket is in blocking mode or in timeout mode, the socket returned by accept() is in blocking mode;

if the listening socket is in non-blocking mode, whether the socket returned by accept() is in blocking or non-blocking mode is operating system-dependent. If you want to ensure cross-platform behaviour, it is recommended you manually override this setting.

@dhalbert
Copy link
Collaborator

dhalbert commented Apr 11, 2024

I did some research on this:

lwip_connect() does not implement SO_CONTIMEO, and thus always uses the default 30-second timeout. This is a well-known issue with lwip. Our code actually sets the socket to be blocking temporarily to do connect(), and thus gets the default, unchangeable, 30-second timeout.

There are some code samples that make the socket be non-blocking and do their own timeout handling:
https://github.com/micropython/micropython/pull/12923/files
espressif/esp-idf@0c7204e (tcp_transport is some kind of under-utilized ESP-IDF thingie)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement espressif applies to multiple Espressif chips network
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants