uasyncio StreamWriter needs asyncio.sleep before running close() otherwise nothing is written to stream #11901
Replies: 6 comments 8 replies
-
I've just had the same issue with the sleep. It's just a wild guess, but maybe drain only flushes the data into the networking device. I.e. it doesn't actually send a TCP package for every drain (which would make sense). Then wait_closed closes the TCP connection before all data has been send to the peer. Waiting a bit would make the networking device send the actual packages??? Did you find anything out about it? |
Beta Was this translation helpful? Give feedback.
-
I have an HTTP web server / web framework I've developed. If you want to look it over for inspiration, it's here: https://github.com/DavesCodeMusings/thimble It's been a while since I've run it on an 8266, but it did work on a NodeMCU 12E last I checked. I haven't found the need to use sleep functions. The connection handler looks like this: async def on_connect(self, reader, writer):
"""
Connection handler for new client requests.
Args:
reader (stream): input received from the client
writer (stream): output to be sent to the client
Returns:
nothing
"""
remote_addr = writer.get_extra_info('peername')[0]
if self.debug:
print(f'Connection from client: {remote_addr}')
try:
req_buffer = await reader.read(self.req_buffer_size)
req = await Thimble.parse_http_request(req_buffer)
except Exception as ex:
await self.send_error(400, writer)
print(f'Unable to parse request: {ex}')
else:
req['remote_addr'] = remote_addr
if self.debug:
print(f'Request: {req}')
route_value = self.resolve_route(req['method'] + req['path'])
if isinstance(route_value, tuple): # a function and URL wildcard value were returned
await self.send_function_results(route_value[0], req, route_value[1], writer)
elif route_value is not None: # just a function was returned
await self.send_function_results(route_value, req, None, writer)
else: # nothing returned, try delivering static content instead
file_path = self.static_folder + req['path']
if file_path.endswith('/'): # '/path/to/' becomes '/path/to/index.html'
file_path = file_path + self.directory_index
await self.send_file_contents(file_path, req, writer)
await writer.drain()
writer.close()
await writer.wait_closed()
reader.close()
await reader.wait_closed()
if self.debug:
print(f'Connection closed for {remote_addr}') The one thing I did find that really ate up memory was serving files from the flash file system. I had to create a function to serve them in 512 byte chunks, otherwise the device would run out of RAM because of buffering. I'm also using pre-compiled bytecode with the thimble.mpy file. That may help ease memory usage as well. Give my module a try if you want. Or keep hacking at your own, it can be a very rewarding experience. Just give me a shout if you have any questions. |
Beta Was this translation helpful? Give feedback.
-
Whoa. Just realized this thread is pretty old. Oh well, if it helps someone... |
Beta Was this translation helpful? Give feedback.
-
The source indicates that |
Beta Was this translation helpful? Give feedback.
-
Ok, I've read somewhere, that you should call drain, after every write. And I've read, that calling write multiple times before calling drain makes new allocations vs calling it 1-1. So I have a lot of small writes in my code. It does not like this. When writing the entire http message with one write, it works just fine, but when writing it byte by byte, we get a connection reset. # ok
async def __on_connect_test(self, r, w):
await r.readline()
w.write(b'HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: text/plain\r\n\r\nhi')
await w.drain()
r.close()
w.close()
await r.wait_closed()
await w.wait_closed()
# connection reset
async def __on_connect_test_short(self, r, w):
await r.readline()
m = b'HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: text/plain\r\n\r\nhi'
for i in range(len(m)):
w.write(m[i:i+1])
await w.drain()
r.close()
w.close()
await r.wait_closed()
await w.wait_closed()
# ok
async def __on_connect_test_short_sleep(self, r, w):
await r.readline()
m = b'HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: text/plain\r\n\r\nhi'
for i in range(len(m)):
w.write(m[i:i+1])
await w.drain()
r.close()
w.close()
await asyncio.sleep(2)
await r.wait_closed()
await w.wait_closed() But why? |
Beta Was this translation helpful? Give feedback.
-
I've just checked, this is not an issue for just asyncio, but a general issue. When writing a synchronous server: def server_sync():
import socket
listening_soc = socket.socket()
listening_soc.bind(('0.0.0.0', 80))
listening_soc.listen(1)
while True:
soc, _ = listening_soc.accept()
r = soc.read(1)
soc.write(b'HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: text/plain\r\n\r\nhi')
soc.close()
It also RESET's the connection. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
So I want to make a really simple and small HTTP server for the esp8266 using the uasyncio TCP server. But now I'm running into a problem where the server replies with nothing every time I make a request, and the solution that I found was to add an
await asyncio.sleep(0.xx)
before closing so it actually replies with the written data. here is the code that does the writing:Here, the longer it sleeps the result becomes more consistent, with smaller values sometimes it would give an empty reply and sometimes it would reply properly. But I feel like this isn't a good solution. I thought the
await self.writer.drain()
line would wait for all the written data to get written to the stream, but apparently not?Are there any better solution for this?
Here is my micropython version:
MicroPython v1.20.0 on 2023-04-26; ESP module with ESP8266
Here is the full code:
Slightly unrelated note: I have tried other HTTP servers for micropython but they keep running out of memory, so I want to try to make my own. Though this one is still not small enough, my non-asynchronous version leaves me with 23kb of memory, and this one leaves me with 8kb of memory. Maybe uasyncio just needs a lot of memory?
Beta Was this translation helpful? Give feedback.
All reactions