Fixing uasyncio webserver bugs.

Author Avatar
xmoiduts 3月 10, 2019
  • 用其他设备扫码打开本文

A dirty hack for uasyncio webserver bugs on micropython:

TypeError: function takes 2 positional arguments but 3 were given

Introduction:

I just used the uasyncio library on microPython (upy) playform for my ESP32 project. As I was implementing a simple web service using the server provided by this library, the example code below didn’t work on my code sketch:

Error appearance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import uasyncio as asyncio
@asyncio.coroutine
def serve(reader, writer):
print(reader, writer)
print("================")
print((yield from reader.read()))
yield from writer.awrite("HTTP/1.0 200 OK\r\n\r\nHello.\r\n")
print("After response write")
yield from writer.aclose()
print("Finished processing request")

loop = asyncio.get_event_loop()
loop.call_soon(asyncio.start_server(serve, "127.0.0.1", 8081))
loop.run_forever()
loop.close()

It said:

File “/lib/uasyncio/__init__.py”, line 60, in remove_writer

TypeError: function takes 2 positional arguments but 3 were given

every time I call yield from writer.aclose() in my code.

#(uasyncio, writer.aclose(), remove_writer)

In order to fix the bug, I dug into the source code to find its defect.

Install uasyncio

——How I installed the uasyncio library on micropython ESP32 build?

1
2
3
import upip
upip.install('micropython-uasyncio')
import uasyncio

With an internet connection, the board will search the web with its builtin (but not loaded by default) package manager upip. It will download the library to /lib/uasyncio/ directory on your ESP32’s file system.

Finding the bug

For a long-time struggle, I finally located where the error occurs. This is the function in __init__.py of uasyncio library:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import uselect as select
class PollEventLoop(EventLoop):
self.poller = select.poll()
...
def remove_writer(self, sock):
if DEBUG and __debug__:
log.debug("remove_writer(%s)", sock)
self.objmap.pop(id(sock), None)
# StreamWriter.awrite() first tries to write to a socket,
# and if that succeeds, yield IOWrite may never be called
# for that socket, and it will never be added to poller. So,
# ignore such error.

self.poller.unregister(sock, False)

As I looked up the documentation, the self.poller.unregister() only receives 2 arguments: (self, fd).

So, as the code tries to pass 3 arguments (self.poller, sock, FALSE) to the poller, the program crashes.

Fix:

My solution, may be a dirty hack:

1
2
3
4
5
6
7
8
9
def remove_writer(self, sock):
if DEBUG and __debug__:
log.debug("remove_writer(%s)", sock)
self.objmap.pop(id(sock), None)
# StreamWriter.awrite() first tries to write to a socket,
# and if that succeeds, yield IOWrite may never be called
# for that socket, and it will never be added to poller. So,
# ignore such error.
self.poller.unregister(sock)
  • By just removing the False flag argument, the code worked like a charm (at least for my small piece of example code).

Another solution from Github:

1
2
3
4
5
6
7
8
9
10
11
12
13
def remove_writer(self, sock):
if DEBUG and __debug__:
log.debug("remove_writer(%s)", sock)
try:
self.poller.unregister(sock)
self.objmap.pop(id(sock), None)
except OSError as e:
# StreamWriter.awrite() first tries to write to a socket,
# and if that succeeds, yield IOWrite may never be called
# for that socket, and it will never be added to poller. So,
# ignore such error.
if e.args[0] != uerrno.ENOENT:
raise
  • Perhaps this one is better a solution.