earlephilhower / arduino-pico

Raspberry Pi Pico Arduino core, for all RP2040 and RP2350 boards
GNU Lesser General Public License v2.1
2.03k stars 422 forks source link

Pico W, Wifi HTTP request delays. #1522

Closed hreintke closed 1 year ago

hreintke commented 1 year ago

Hi,

Running non FreeRTOS, basic webserver example I see some late responses (5 seconds delay, but not always) on HTTP requests. This only happens when I use a webbroswer, using curl all works as expected (keep tcp connection open ?)

Would like to do some IP traffic analyses for that, using the Netdump library I made for esp8266.

I am not sure, but think there needs to be a patch/addition in lwip netif (phy_capture) for that. Was not (yet?) be able to track that down in pico-w and/or esp8266.

Do you know where/how to add that ? If not, was @d-a-v involved in the wifi/lwip implementation of pico-w ? I can ask him for some help too.

earlephilhower commented 1 year ago

@d-a-v was not involved here, sadly. I'm not sure he'd be able to help w/the PicoW side of things.

My guess would be that if you're using Chrome it's trying to send a bunch of connections in parallel, and since the processing is very much serial in nature on the Pico, things get stuck waiting. FWIW under Firefox I haven't noticed any issue like this on the AdvancedWebServer.

We are using the unpatched LWIP, straight from the Pico-SDK, but I think there are a couple things low-effort to try.

First, you can grab/dumb all received Ethernet level packets by hacking in to https://github.com/earlephilhower/arduino-pico/blob/cc800713bd7b30b19ca4b5f02e88f3e915802527/cores/rp2040/cyw43_wrappers.cpp#L48. You can get all Ethernet sent packets here: https://github.com/earlephilhower/arduino-pico/blob/cc800713bd7b30b19ca4b5f02e88f3e915802527/libraries/lwIP_CYW43/src/utility/CYW43shim.cpp#L108

It is also possible to enable very verbose LWIP debugging by editing https://github.com/earlephilhower/arduino-pico/blob/master/tools/libpico/lwipopts.h (and enabling Tools->Debug Port->Serial to get ::printf output to USB).

earlephilhower commented 1 year ago

Another possibility could be a bug in the webserver code, which just happens to have been noted a couple days ago in the upstream sources... https://github.com/esp8266/Arduino/issues/8941

I have not looked at the code, yet, so can't really comment if it's a real issue or not, but if it is then it's one heck of a coincidence. :four_leaf_clover:

hreintke commented 1 year ago

Checked into it.

You are using another webserver implementation (webserver vs esp8266webserver) so the issue cannot come from there. But it is definitely possible the the issue is caused by the webserver/webclient code.

Did a quick try to use the esp8266webserver for the pico but got into trouble because esp8266 has a custom implementation of String and you are using the Arduino api one. Not sure if is possible to solve that with namespaces.

Edit : Found a similar construct in your webserver implementation. Will look into that.

Edit 2 : In WebserverTemplate.h change code to :

template <typename ServerType, int DefaultPort>
void WebServerTemplate<ServerType, DefaultPort>::handleClient() {
    if (_currentStatus == HC_NONE) {
        if (_currentClient) {
            delete _currentClient;
            _currentClient = nullptr;
        }

//        ClientType client = _server.available();
        _currentClient = new ClientType(_server.available());
        if (!_currentClient) {
            if (_nullDelay) {
                delay(1);
            }
            return;
        }

//        _currentClient = new ClientType(client);
        _currentStatus = HC_WAIT_READ;
        _statusChange = millis();
    }
    httpHandleClient();
}

That had no effect on the issue.

Edit 3 : I think it is related to the fact that the browser already opens a new TCP connection when the first one is finished, without immediately sending data over the connection. Just to be prepared for the next request.

earlephilhower commented 1 year ago

Edit 3 : I think it is related to the fact that the browser already opens a new TCP connection when the first one is finished, without immediately sending data over the connection. Just to be prepared for the next request.

That's a tough nut to crack here. The TCP connections are processed by LWIP in order of reception and we need to finish the lifecycle for the current one before starting processing on the next one. You'd need to use the AsyncWebServer to handle something like that.

(The ESP8266 WebServer evolved to fit its niche so well that it's nigh on unusable elsewhere. In addition to the String there are Stream changes as well. Bringing those in requires bringing in lots of other 8266-specific oddities, as far as I could tell when I tried it before going to the ESP32 one. It's really well tuned for low memory usage, but not really portable anymore.)

hreintke commented 1 year ago

it before going to the ESP32 one

Tried the same on esp32, that suffers the same issue.

Running it on esp8266, it runs OK As far my testing goes.

Do you want to close this as a "known limitation" ?

earlephilhower commented 1 year ago

I think that's the best we can do for now. I wonder if me-no-dev's async web server can work here...

hreintke commented 1 year ago

I have seen this https://github.com/khoih-prog/AsyncWebServer_RP2040W but did not do any tests with it.

supersjellie commented 1 year ago

I see my issue is linked and read your story. Maybe a longshot but sharing my experiences. When using chrome on my ESP devices chrome asks for the page and the favicon.ico. It the latter request was first (since the default webserver does requests one by one) delays of a few seconds occurred (you have to wait for a timeout on the icon). Usually the first time, than a lot of requests ok, and than chrome "rechecked" the icon. And that's why it's ok with curl or in my case openHAB calls, they don't care about the favicon.

I solved this issue by adding an empty icon in the arduino code.

(somewhere in the setup method)
server.on("/favicon.ico", handleIcon);

//send no favicon (else blocks webserver for 3-5 seconds)
void handleIcon() {
  sendResponse(404,F("NA"));
}

Maybe it helps, you can give it a try.

hreintke commented 1 year ago

@supersjellie No, it is not the favicon.ico request. I've seen that before and this different. See also the remark below.

@earlephilhower : Was wondering why esp8266 does not suffer from issue so took a dive into the code. It is in the 'httpHandleClient()' part where there is always a wait when no data is (yet) available.

httpServer.cpp line 282

            } else { // !_currentClient->available()
                if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
                    keepCurrentClient = true;
                }

esp8266webserver-impl.h line 367

      } else {
        // !_currentClient.available(): waiting for more data
        unsigned long timeSinceChange = millis() - _statusChange;
        // Use faster connection drop timeout if any other client has data
        // or the buffer of pending clients is full
        if ((_server.hasClientData() || _server.hasMaxPendingClients())
          && timeSinceChange > HTTP_MAX_DATA_AVAILABLE_WAIT)
            DBGWS("webserver: closing since there's another connection to read from\n");
        else {
          if (timeSinceChange > HTTP_MAX_DATA_WAIT)
            DBGWS("webserver: closing after read timeout\n");
          else
            keepCurrentClient = true;
        }
        callYield = true;
      }

When I found the differences, I also found the issue/PR on which it was solved. https://github.com/esp8266/Arduino/pull/8216

It is not a one to one port to pico, as it depends on other changes in their code But I can give it a try if you want.

earlephilhower commented 1 year ago

That would be great if you could, since you seem to also have a good test case w/your web browser setup. I'm kind of surprised the same change wasn't already done to the ESP32 webserver (where this one was taken from)...

hreintke commented 1 year ago

From @d-a-v in the issue

edit: esp32/arduino uses lwIP's socket API while here the raw API is used. Changes will not be the same there.