Page 1 of 1

socket.settimeout() not working

Posted: Wed Feb 05, 2020 11:27 am
by Romik
PyBoard 1.1 + Ethernet

Code: Select all

>>> dir(socket.socket)
['__class__', '__name__', 'close', 'send', '__bases__', '__del__', 'accept', 'bind', 'connect', 'listen', 'recv', 'recvfrom', 'sendto', 'setblocking',
 'setsockopt', 'settimeout']

>>> dir(socket.socket.settimeout)
['__class__']

>>> host = '10.128.0.148'
>>> NTP_QUERY = bytearray(48)
>>> NTP_QUERY[0] = 0x1b
>>> addr = socket.getaddrinfo(host,123)[0][-1]
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.settimeout(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 107] ENOTCONN

Why is this happening and how can this be fixed.

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 1:43 am
by jimmo
The socket has to be connected before you can set the timeout. Looking at the code, the reason for this is that the timeout is handled by the NIC layer, and until you're connected (or bound), the socket doesn't know which NIC it's using. (This is when you're relying on the NIC to run the TCP stack for you, i.e. using an external wiznet or something). It's a bit different when you're using LWIP (e.g. PYBD with WiFi).

I don't have wiznet to test this with (I assume that's what you're using?) but I think possibly if you bind the socket first to the local address first then it might work?

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 7:36 am
by Romik
All is working fine if host is available..
if the host is not available, msg = s.recvfrom (48) hangs and does not exit.

Code: Select all

nic = WIZNET5K(SPI(1),Pin.board.X12,Pin.board.X11)
ip = ('10.128.0.206','255.255.255.0','10.128.0.1','8.8.8.8')
nic.ifconfig(ip)

host = 'pool.ntp.org'

addr = socket.getaddrinfo(host,123)[0][-1]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)               
# s.settimeout(5)
res = s.sendto(NTP_QUERY, addr)        
msg = s.recvfrom(48)
s.close()

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 9:38 am
by jimmo
Try setting the timeout after the sendto.

The recvfrom will block unless there's a timeout set. To set a timeout you need the socket to be bound to a NIC. To do so you either need to connect(), bind(), or sendto().

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 10:48 am
by Romik
The error has changed: OSError: [Errno 22] EINVAL

Code: Select all

>>> res = s.sendto(NTP_QUERY, addr)
>>> s.settimeout(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] EINVAL

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 10:52 am
by jimmo
Ahh, I just read more of the code. The WizNet driver doesn't implement settimeout:

from ports/stm32/modnwwiznet5k.c

Code: Select all

STATIC int wiznet5k_socket_settimeout(mod_network_socket_obj_t *socket, mp_uint_t timeout_ms, int *_errno) {
    // TODO
    *_errno = MP_EINVAL;
    return -1;

    /*
    if (timeout_ms == 0) {
        // set non-blocking mode
        uint8_t arg = SOCK_IO_NONBLOCK;
        WIZCHIP_EXPORT(ctlsocket)(socket->u_param.fileno, CS_SET_IOMODE, &arg);
    }
    */
}
It wasn't getting to here before because modsocket requires that it has a NIC (hence the original ENOTCONN error), but yeah, the NIC driver doesn't implement this.

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 10:57 am
by Romik
Thank you. :(

Re: socket.settimeout() not working

Posted: Thu Feb 06, 2020 11:40 am
by Romik
I found in documentation. Sorry. All is working.

Code: Select all

# Instead of:
s.settimeout(1.0)  # time in seconds
s.read(10)  # may timeout

# Use:
poller = uselect.poll()
poller.register(s, uselect.POLLIN)
res = poller.poll(1000)  # time in milliseconds
if not res:
    # s is still not ready for input, i.e. operation timed out
My code:

Code: Select all

   addr = socket.getaddrinfo(host,123)[0][-1]
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    res = s.sendto(NTP_QUERY, addr)     
    poller = uselect.poll() 
    poller.register(s, uselect.POLLIN) 
    res = poller.poll(1000)    
    if not res:
        return 0
    else:
        msg = s.recvfrom(48)
    s.close()