esp8266: socket accept() does not always accept
If there is a queue of sockets to accept, accept() does not always clear it, some sockets seem to get "lost", they are still open but accept() will not accept them, it just blocks.
Steps to reproduce
I'm attaching two programs - main.py and client.py. Run client.py on a connected system, under normal python3. Run main.py on an esp8266. Supply the address of the esp8266 as a command line parameter.
Expected results
Despite the sleep(0.25), the sockets should queued and be accepted eventually.
Output should be, for example:
Socks connected: 4
b'Accept 0'
b'Accept 1'
b'Accept 2'
b'Accept 3'
And running the program >1 time should also succeed.
Actual results:
Socks connected: 4
b'Accept 0'
b'Accept 1'
b''
b''
ESP8266 no use without a way to re-open a socket
Basically, if you create a new socket over and over it will leak ram. This actually make sense, as it registers callbacks that keep the memory in the socket allocated. What you need to do is create a single socket and reconnect it, but the code, as is prevents this from being done.
The addition of s->espconn->state != ESPCONN_CLOSE to the allowance to re-open fixes this.
--- a/esp8266/modesp.c
+++ b/esp8266/modesp.c
@@ -275,11 +275,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_socket_accept_obj, esp_socket_accept);
STATIC mp_obj_t esp_socket_connect(mp_obj_t self_in, mp_obj_t addr_in) {
esp_socket_obj_t *s = self_in;
- if (s->espconn == NULL || s->espconn->state != ESPCONN_NONE) {
+ if (s->espconn == NULL || (s->espconn->state != ESPCONN_CLOSE && s->espconn->state != ESPCONN_NONE)) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError,
"transport endpoint is already connected or closed"));
}
The following test runs forever with no leak.
import esp, network, gc
network.connect('iot', 'noodlebrain')
def socket_recv(sock, data):
print(data)
def on_connect(asoc, socky):
print("connected")
asoc.send(socky.what_to_send)
def on_disconnect(asoc):
print("dsconnected")
class Socky:
def __init__(self, host, port):
self.host = host
self.port = port
self.soc = esp.socket()
self.what_to_send = 'GET / HTTP/1.0\r\n\r\n'
self.soc.onconnect(lambda sock: on_connect(sock, self))
self.soc.ondisconnect(on_disconnect)
self.soc.onrecv(socket_recv)
def state(self):
return self.soc.state()
def send(self):
print(self.state())
if self.state() in [esp.ESPCONN_CLOSE, esp.ESPCONN_NONE]:
self.soc.connect((self.host, self.port))
else:
self.soc.send(self.what_to_send)
aa = Socky('131.84.1.191', 8000)
def wrapper(os_timer):
aa.send()
gc.collect()
print(gc.mem_free())
bb = esp.os_timer(wrapper, 1000)