belyalov / tinyweb

Simple and lightweight HTTP async server for micropython
MIT License
252 stars 39 forks source link

ENOMEM when running sample code from readme.md #19

Closed Kortenbach closed 5 years ago

Kortenbach commented 5 years ago

Make sure you are running the latest version of TinyWeb before reporting an issue.

Device and/or platform: ESP8266

Description of problem:

Expected: Running example code

Traceback (if applicable):

>>> exec(open('./TinyWebTest.py').read(),globals())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 32, in <module>
  File "tinyweb/server.py", line 651, in run
  File "uasyncio/core.py", line 126, in run_forever
  File "uasyncio/core.py", line 87, in run_forever
  File "tinyweb/server.py", line 614, in _tcp_server
OSError: [Errno 12] ENOMEM
>>> scandone

>>> gc.mem_free()
13584

Additional info: The code rus fine after a Hard-reset. Every re-run after that results in ENOMEM. Line 613: sock.bind(addr) Line 614: sock.listen(backlog) It seems the socket is not freed properly or something like that?

belyalov commented 5 years ago

How big is TinyWebTest.py?

Since python is interpreted language - load/compile takes significant amount of memory and 13k of free memory is not enough to compile your code right on microcontroller.

There are 2 possible options:

Anyway - it is strongly recommended to use frozen modules, good reading at:

P.S. It is not clear why you have only 13k of free memory, in my setup using NodeMCU 1.0 it 35k / 30k after loading of tinyweb:

>>>
>>> import gc
>>> gc.mem_free()
36640

>>>
>>> import tinyweb
>>> gc.mem_free()
30400
Kortenbach commented 5 years ago

Hi Konstantin. Thank you for your answer. The source code is straight from the readme.MD on github and so is the firmware. There is no other code involved.

belyalov commented 5 years ago

Can you share it? Which version of esp8266 are you using?

BTW, now examples are included into firmware starting from release 1.3.3, so you can simply run Hello World example right after flash:

>>> import examples.hello_world as hello
>>> hello.run()
belyalov commented 5 years ago

Also, I just tried slightly updated code from README.md:

# Network config done prior to this line, then CTRL+E to enter paste mode
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
import tinyweb
===
===
=== # Create web server application
=== app = tinyweb.webserver()
===
===
=== # Index page
=== @app.route('/')
=== async def index(request, response):
===     # Start HTTP response with content-type text/html
===     await response.start_html()
===     # Send actual HTML page
===     await response.send('<html><body><h1>Hello, world! (<a href="/table">table</a>)</h1></html>\n')
===
===
=== # Another one, more complicated page
=== @app.route('/table')
=== async def table(request, response):
===     # Start HTTP response with content-type text/html
===     await response.start_html()
===     await response.send('<html><body><h1>Simple table</h1>'
===                         '<table border=1 width=400>'
===                         '<tr><td>Name</td><td>Some Value</td></tr>')
===     for i in range(10):
===         await response.send('<tr><td>Name{}</td><td>Value{}</td></tr>'.format(i, i))
===     await response.send('</table>'
===                         '</html>')
===
===
=== def run():
===     app.run(host='0.0.0.0', port=8081)
===
>>>
>>> run()

And from another terminal:

$ curl http://192.168.3.100:8081
<html><body><h1>Hello, world! (<a href="/table">table</a>)</h1></html>

$ curl http://192.168.3.100:8081/table
<html><body><h1>Simple table</h1><table border=1 width=400><tr><td>Name</td><td>Some Value</td></tr><tr><td>Name0</td><td>Value0</td></tr><tr><td>Name1</td><td>Value1</td></tr><tr><td>Name2</td><td>Value2</td></tr><tr><td>Name3</td><td>Value3</td></tr><tr><td>Name4</td><td>Value4</td></tr><tr><td>Name5</td><td>Value5</td></tr><tr><td>Name6</td><td>Value6</td></tr><tr><td>Name7</td><td>Value7</td></tr><tr><td>Name8</td><td>Value8</td></tr><tr><td>Name9</td><td>Value9</td></tr></table></html>

Hope it will help.. :)

Kortenbach commented 5 years ago

Hi, Konstantin. I appreciate your effort, but I don't understand. Is the readme.md code not correct in some way? Why does the code run well after reboot? I am running it on a cheap NodeMCU V1.3 from Aliexpress. I've only flashed your firmware and I ran your code from readme.md. B.t.w. If I just import tinyweb I have about 22k of free memory to spend. After running the sample and ctrl-c, about 10k is lost and I cannot restart, even after garbage collection.

Kortenbach commented 5 years ago

I'm still looking for a nice IDE that let's me browse code by symbol and has code completion. Without it I'm kinda lost...

belyalov commented 5 years ago

I've updated readme code - to make it even simpler. I don't sure that previous version of sample code from readme worked well - I've added it only to show simplicity (it wasn't intended to copy-paste, I though that...)

But now it definitely works :)

Regarding IDE - I don't believe that there is some special IDE, personally I'm using Sublime Text 3, but it is not IDE.. just powerful editor.. )

Kortenbach commented 5 years ago

Thank you Konstantin. I will try the new sample code tomorrow.

Kortenbach commented 5 years ago

Hi Konstantin,

I tried the new firmware V1.3.3 and the build-in example. I'm afraid it didn't go too well either. I'll tell you exactly what I did:

Connect to WiFi

sta_if = network.WLAN(network.STA_IF) sta_if.active(True) sta_if.connect('', '')

Run Hello World! :)

import examples.hello_world as hello gc.mem_free() 25424 hello.run()

- The website runs fine
- Then I press Ctrl-C to interrupt the running process and check free mem:

hello.run() Traceback (most recent call last): File "", line 1, in File "examples/hello_world.py", line 44, in run File "tinyweb/server.py", line 651, in run File "uasyncio/core.py", line 145, in run_forever File "uasyncio/init.py", line 49, in wait KeyboardInterrupt: gc.mem_free() 24752

- Now I try to re-run the sample:

hello.run() Traceback (most recent call last): File "", line 1, in File "examples/hello_world.py", line 44, in run File "tinyweb/server.py", line 651, in run File "uasyncio/core.py", line 126, in run_forever File "uasyncio/core.py", line 87, in run_forever File "tinyweb/server.py", line 614, in _tcp_server OSError: [Errno 12] ENOMEM


- That's when I get the ENOMEM... Why?

A few extra questions that are not directly related to the ENOMEM problem:

belyalov commented 5 years ago

Hi,

Now I got your problem. Actually due to nature of embedded programming "graceful" shutdowns are usually not supported due to:

  1. It increases code complexity
  2. As a result of ^^ flash / RAM usage increases too
  3. In "production" applications you mostly never restart your program (except full reset)

The same for tinyweb - it does not support graceful shutdown / release resources. It is doable, however I don't see any reason to add such support - it will increase bytecode size / RAM usage.

If you want to restart your program from REPL - terminate program by Ctrl + C followed by Ctrl + D "soft reboot" and you'll be able to rerun it :)

belyalov commented 5 years ago

Regarding blinking LED - I have no idea, sorry.. (

Kortenbach commented 5 years ago

Hi Konstantin, Thank you for looking into this. As you might have guessed I'm a PC software builder (Delphi). I'm not used to "not cleaning up", but I totally understand. I want to thank you for your help! Happy holidays!

Kortenbach commented 5 years ago

Do you know if there is a way to free all RAM? If there is I could start my project by doing a "free all RAM". I then would'nt have to manually reset my ESP after each software upload...

belyalov commented 5 years ago

I'm not sure that I understood you right, but for NodeMCU whenever you upload new software through esptool.py it resets your controller automatically...

If you want to reset it programmatically (reset frees all RAM :)) you can do it by:

import machine

machine.reset()
Kortenbach commented 5 years ago

If I start my program with machine.reset() it will be caught in an eternal reset loop. I use upycraft for uploading. It apparently doesn't reset the board on loading. What I need is ram.cleanup() or something similar...

belyalov commented 5 years ago

Try to use esptool.py (which is kinda industry standard for esp devices), it may help

Kortenbach commented 5 years ago

Thanks for the tip, I'll try it tomorrow. One more thing... If I want to do some work other than http, what would be the best way to tie into the main loop when using your server?

belyalov commented 5 years ago

The best way is to start one more uasyncio "task", like that:

import uasyncio

# ....

async def task():
    # do something

# ...

loop = uasyncio.get_event_loop()
loop.create_task(task)

# run even loop through tinyweb http
http.run()

In order to understand this code better - it is good to invest in "async" programming, like this one

Kortenbach commented 5 years ago

Thank you very much! That was what I was looking for. Thanks again for making this great library!

belyalov commented 5 years ago

Cool! :) Thank you and happy new year!

P.S. if you like library - star it :)

Kortenbach commented 5 years ago

There you go! 1* cheers!