Timeout on Firestore documents requests not working while connected to Network without WAN access #203
-
First of all i want to thank you for maintaining such a great library. I'm currently testing scenarios where the network connection between the AP router and the internet/WAN is unstable while the WiFi connection between the ESP32 and the AP router is stable. I'm using Firestore on my project to read/write documents and it seems to get stuck whenever the internet/WAN connection is down for too long. I tried with both sync and async examples, and i tried the minimal examples on the client i'm using (WifiClientSecure). I'll leave more details below. I decided to post in as a discussion since i'm unsure if this is a bug or if i'm configuring something wrong as to my desires. Board: ESP32 dev kit usb c Router: Samsung Galaxy S23 ap router on 2.4ghz band The behaviour i want is that when internet/WAN is unreachable the task fails with some error code so my code can handle retrys etc... SYNC: the code gets blocked in the .get() function, sometimes it never timeouts, sometimes it erros as expected, depending on the duration of the internet/WAN outage ASYNC: tasks pileup (easily fixable, didn't modify to use exact examples provided), sometimes the first task fails when connection to the internet/WAN is reestablished, the remaining tasks in the queue run sucessfuly, other times the first tasks never errors or timeouts, and the whole queue is locked When either version of the lock occurs the only fixes i found were either to turn off the AP router, thus ending the WiFi connection, or restarting the board I've added an example using only the official example from WifiClientSecure, and the timeouts worked fine with that example. Any help is greatly appreciated, i'm kind of new in embbeded programming. All examples were taken from oficial repository examples with minimal ajustments, mainly in timeout configuration attempts Sync example:Added lines: ssl_client.setTimeout(5000);
ssl_client.setHandshakeTimeout(5);
ssl_client.setConnectionTimeout(5000);
aClient.setSessionTimeout(5);
aClient.setSyncReadTimeout(5);
aClient.setSyncSendTimeout(5); /**
* SYNTAX:
*
* Firestore::Documents::get(<AsyncClient>, <Firestore::Parent>, <documentPath>, <GetDocumentOptions>);
*
* <AsyncClient> - The async client.
* <Firestore::Parent> - The Firestore::Parent object included project Id and database Id in its constructor.
* <documentPath> - The relative path of document to get in the collection.
* <GetDocumentOptions> - The GetDocumentOptions object that provide the functions to set the mask, transaction and readTime options.
*
* The Firebase project Id should be only the name without the firebaseio.com.
* The Firestore database id should be (default) or empty "".
*
* This function returns response payload when task is complete.
*
* The following are the GetDocumentOptions member functions.
*
* The GetDocumentOptions::mask - The fields to return. If not set, returns all fields.
* If the document has a field that is not present in this mask, that field will not be returned in the response. Use comma (,) to separate between the field names.
* GetDocumentOptions::transaction - A base64-encoded string. If set, reads the document in a transaction.
* GetDocumentOptions::readTime - A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.
*
* The complete usage guidelines, please visit https://github.com/mobizt/FirebaseClient
*/
#include <Arduino.h>
#if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA)
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#elif __has_include(<WiFiNINA.h>) || defined(ARDUINO_NANO_RP2040_CONNECT)
#include <WiFiNINA.h>
#elif __has_include(<WiFi101.h>)
#include <WiFi101.h>
#elif __has_include(<WiFiS3.h>) || defined(ARDUINO_UNOWIFIR4)
#include <WiFiS3.h>
#elif __has_include(<WiFiC3.h>) || defined(ARDUINO_PORTENTA_C33)
#include <WiFiC3.h>
#elif __has_include(<WiFi.h>)
#include <WiFi.h>
#endif
#include <FirebaseClient.h>
#define WIFI_SSID "WIFI_AP"
#define WIFI_PASSWORD "WIFI_PASSWORD"
// The API key can be obtained from Firebase console > Project Overview > Project settings.
#define API_KEY "Web_API_KEY"
// User Email and password that already registerd or added in your project.
#define USER_EMAIL "USER_EMAIL"
#define USER_PASSWORD "USER_PASSWORD"
#define DATABASE_URL "URL"
#define FIREBASE_PROJECT_ID "PROJECT_ID"
void authHandler();
void printResult(AsyncResult &aResult);
void printError(int code, const String &msg);
DefaultNetwork network; // initilize with boolean parameter to enable/disable network reconnection
UserAuth user_auth(API_KEY, USER_EMAIL, USER_PASSWORD);
FirebaseApp app;
#if defined(ESP32) || defined(ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W)
#include <WiFiClientSecure.h>
WiFiClientSecure ssl_client;
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_UNOWIFIR4) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) || defined(ARDUINO_PORTENTA_C33) || defined(ARDUINO_NANO_RP2040_CONNECT)
#include <WiFiSSLClient.h>
WiFiSSLClient ssl_client;
#endif
using AsyncClient = AsyncClientClass;
AsyncClient aClient(ssl_client, getNetwork(network));
Firestore::Documents Docs;
AsyncResult aResult_no_callback;
int counter = 0;
unsigned long dataMillis = 0;
bool taskCompleted = false;
void setup()
{
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(300);
}
Serial.println();
Serial.print("Connected with IP: ");
Serial.println(WiFi.localIP());
Serial.println();
Firebase.printf("Firebase Client v%s\n", FIREBASE_CLIENT_VERSION);
Serial.println("Initializing app...");
#if defined(ESP32) || defined(ESP8266) || defined(PICO_RP2040)
ssl_client.setInsecure();
ssl_client.setTimeout(5000);
ssl_client.setHandshakeTimeout(5);
ssl_client.setConnectionTimeout(5000);
#if defined(ESP8266)
ssl_client.setBufferSizes(4096, 1024);
#endif
#endif
aClient.setSessionTimeout(5);
aClient.setSyncReadTimeout(5);
aClient.setSyncSendTimeout(5);
initializeApp(aClient, app, getAuth(user_auth), aResult_no_callback);
authHandler();
// Binding the FirebaseApp for authentication handler.
// To unbind, use Docs.resetApp();
app.getApp<Firestore::Documents>(Docs);
// In case setting the external async result to the sync task (optional)
// To unset, use unsetAsyncResult().
aClient.setAsyncResult(aResult_no_callback);
}
void loop()
{
authHandler();
Docs.loop();
if (app.ready() && (millis() - dataMillis > 10000 || dataMillis == 0))
{
dataMillis = millis();
if (!taskCompleted)
{
taskCompleted = true;
// Map value to append
Values::MapValue jp("time_zone", Values::IntegerValue(9));
jp.add("population", Values::IntegerValue(125570000));
Document<Values::Value> doc("japan", Values::Value(jp));
Values::MapValue bg("time_zone", Values::IntegerValue(1));
bg.add("population", Values::IntegerValue(11492641));
doc.add("Belgium", Values::Value(bg));
Values::MapValue sg("time_zone", Values::IntegerValue(8));
sg.add("population", Values::IntegerValue(5703600));
doc.add("Singapore", Values::Value(sg));
String documentPath = "info/countries";
// The value of Values::xxxValue, Values::Value and Document can be printed on Serial.
Serial.println("Create document... ");
String payload = Docs.createDocument(aClient, Firestore::Parent(FIREBASE_PROJECT_ID), documentPath, DocumentMask(), doc);
if (aClient.lastError().code() == 0)
Serial.println(payload);
else
printError(aClient.lastError().code(), aClient.lastError().message());
}
String documentPath = "info/countries";
// If the document path contains space e.g. "a b c/d e f"
// It should encode the space as %20 then the path will be "a%20b%20c/d%20e%20f"
Serial.println("Get a document... ");
String payload = Docs.get(aClient, Firestore::Parent(FIREBASE_PROJECT_ID), documentPath, GetDocumentOptions(DocumentMask("Singapore")));
if (aClient.lastError().code() == 0)
Serial.println(payload);
else
printError(aClient.lastError().code(), aClient.lastError().message());
}
}
void authHandler()
{
// Blocking authentication handler with timeout
unsigned long ms = millis();
while (app.isInitialized() && !app.ready() && millis() - ms < 120 * 1000)
{
// The JWT token processor required for ServiceAuth and CustomAuth authentications.
// JWT is a static object of JWTClass and it's not thread safe.
// In multi-threaded operations (multi-FirebaseApp), you have to define JWTClass for each FirebaseApp,
// and set it to the FirebaseApp via FirebaseApp::setJWTProcessor(<JWTClass>), before calling initializeApp.
JWT.loop(app.getAuth());
printResult(aResult_no_callback);
}
}
void printResult(AsyncResult &aResult)
{
if (aResult.isEvent())
{
Firebase.printf("Event task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.appEvent().message().c_str(), aResult.appEvent().code());
}
if (aResult.isDebug())
{
Firebase.printf("Debug task: %s, msg: %s\n", aResult.uid().c_str(), aResult.debug().c_str());
}
if (aResult.isError())
{
Firebase.printf("Error task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.error().message().c_str(), aResult.error().code());
}
if (aResult.available())
{
Firebase.printf("task: %s, payload: %s\n", aResult.uid().c_str(), aResult.c_str());
}
}
void printError(int code, const String &msg)
{
Firebase.printf("Error, msg: %s, code: %d\n", msg.c_str(), code);
} Async example:Added lines: ssl_client.setTimeout(5000);
ssl_client.setHandshakeTimeout(5);
ssl_client.setConnectionTimeout(5000);
aClient.setSessionTimeout(5);
aClient.setSyncReadTimeout(5);
aClient.setSyncSendTimeout(5); /**
* SYNTAX:
*
* Firestore::Documents::get(<AsyncClient>, <Firestore::Parent>, <documentPath>, <GetDocumentOptions>, <AsyncResultCallback>, <uid>);
*
* <AsyncClient> - The async client.
* <Firestore::Parent> - The Firestore::Parent object included project Id and database Id in its constructor.
* <documentPath> - The relative path of document to get in the collection.
* <GetDocumentOptions> - The GetDocumentOptions object that provide the functions to set the mask, transaction and readTime options.
* <AsyncResultCallback> - The async result callback (AsyncResultCallback).
* <uid> - The user specified UID of async result (optional).
*
* The Firebase project Id should be only the name without the firebaseio.com.
* The Firestore database id should be (default) or empty "".
*
* The following are the GetDocumentOptions member functions.
*
* The GetDocumentOptions::mask - The fields to return. If not set, returns all fields.
* If the document has a field that is not present in this mask, that field will not be returned in the response. Use comma (,) to separate between the field names.
* GetDocumentOptions::transaction - A base64-encoded string. If set, reads the document in a transaction.
* GetDocumentOptions::readTime - A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.
*
* The complete usage guidelines, please visit https://github.com/mobizt/FirebaseClient
*/
#include <Arduino.h>
#if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA)
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#elif __has_include(<WiFiNINA.h>) || defined(ARDUINO_NANO_RP2040_CONNECT)
#include <WiFiNINA.h>
#elif __has_include(<WiFi101.h>)
#include <WiFi101.h>
#elif __has_include(<WiFiS3.h>) || defined(ARDUINO_UNOWIFIR4)
#include <WiFiS3.h>
#elif __has_include(<WiFiC3.h>) || defined(ARDUINO_PORTENTA_C33)
#include <WiFiC3.h>
#elif __has_include(<WiFi.h>)
#include <WiFi.h>
#endif
#include <FirebaseClient.h>
#define WIFI_SSID "WIFI_AP"
#define WIFI_PASSWORD "WIFI_PASSWORD"
// The API key can be obtained from Firebase console > Project Overview > Project settings.
#define API_KEY "Web_API_KEY"
// User Email and password that already registerd or added in your project.
#define USER_EMAIL "USER_EMAIL"
#define USER_PASSWORD "USER_PASSWORD"
#define DATABASE_URL "URL"
#define FIREBASE_PROJECT_ID "PROJECT_ID"
void asyncCB(AsyncResult &aResult);
void printResult(AsyncResult &aResult);
DefaultNetwork network; // initilize with boolean parameter to enable/disable network reconnection
UserAuth user_auth(API_KEY, USER_EMAIL, USER_PASSWORD);
FirebaseApp app;
#if defined(ESP32) || defined(ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W)
#include <WiFiClientSecure.h>
WiFiClientSecure ssl_client;
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_UNOWIFIR4) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) || defined(ARDUINO_PORTENTA_C33) || defined(ARDUINO_NANO_RP2040_CONNECT)
#include <WiFiSSLClient.h>
WiFiSSLClient ssl_client;
#endif
using AsyncClient = AsyncClientClass;
AsyncClient aClient(ssl_client, getNetwork(network));
Firestore::Documents Docs;
int counter = 0;
unsigned long dataMillis = 0;
bool taskCompleted = false;
void setup()
{
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(300);
}
Serial.println();
Serial.print("Connected with IP: ");
Serial.println(WiFi.localIP());
Serial.println();
Firebase.printf("Firebase Client v%s\n", FIREBASE_CLIENT_VERSION);
Serial.println("Initializing app...");
#if defined(ESP32) || defined(ESP8266) || defined(PICO_RP2040)
ssl_client.setInsecure();
ssl_client.setTimeout(5000);
ssl_client.setHandshakeTimeout(5);
ssl_client.setConnectionTimeout(5000);
#if defined(ESP8266)
ssl_client.setBufferSizes(4096, 1024);
#endif
#endif
aClient.setSessionTimeout(5);
aClient.setSyncReadTimeout(5);
aClient.setSyncSendTimeout(5);
initializeApp(aClient, app, getAuth(user_auth), asyncCB, "authTask");
// Binding the FirebaseApp for authentication handler.
// To unbind, use Docs.resetApp();
app.getApp<Firestore::Documents>(Docs);
}
void loop()
{
// The async task handler should run inside the main loop
// without blocking delay or bypassing with millis code blocks.
app.loop();
Docs.loop();
if (app.ready() && (millis() - dataMillis > 60000 || dataMillis == 0))
{
dataMillis = millis();
if (!taskCompleted)
{
taskCompleted = true;
// Map value to append
Values::MapValue jp("time_zone", Values::IntegerValue(9));
jp.add("population", Values::IntegerValue(125570000));
Document<Values::Value> doc("japan", Values::Value(jp));
Values::MapValue bg("time_zone", Values::IntegerValue(1));
bg.add("population", Values::IntegerValue(11492641));
doc.add("Belgium", Values::Value(bg));
Values::MapValue sg("time_zone", Values::IntegerValue(8));
sg.add("population", Values::IntegerValue(5703600));
doc.add("Singapore", Values::Value(sg));
String documentPath = "info/countries";
// The value of Values::xxxValue, Values::Value and Document can be printed on Serial.
Serial.println("Create document... ");
Docs.createDocument(aClient, Firestore::Parent(FIREBASE_PROJECT_ID), documentPath, DocumentMask(), doc, asyncCB, "createDocumentTask");
}
String documentPath = "info/countries";
// If the document path contains space e.g. "a b c/d e f"
// It should encode the space as %20 then the path will be "a%20b%20c/d%20e%20f"
Serial.println("Get a document... ");
Docs.get(aClient, Firestore::Parent(FIREBASE_PROJECT_ID), documentPath, GetDocumentOptions(DocumentMask("Singapore")), asyncCB, "getTask");
}
}
void asyncCB(AsyncResult &aResult)
{
// WARNING!
// Do not put your codes inside the callback and printResult.
printResult(aResult);
}
void printResult(AsyncResult &aResult)
{
if (aResult.isEvent())
{
Firebase.printf("Event task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.appEvent().message().c_str(), aResult.appEvent().code());
}
if (aResult.isDebug())
{
Firebase.printf("Debug task: %s, msg: %s\n", aResult.uid().c_str(), aResult.debug().c_str());
}
if (aResult.isError())
{
Firebase.printf("Error task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.error().message().c_str(), aResult.error().code());
}
if (aResult.available())
{
Firebase.printf("task: %s, payload: %s\n", aResult.uid().c_str(), aResult.c_str());
}
} WifiClientSecure example:Added lines: client.setTimeout(5000);
client.setHandshakeTimeout(5);
client.setConnectionTimeout(5000); #include <NetworkClientSecure.h>
#include <WiFi.h>
/* This is a very INSECURE approach.
* If for some reason the secure, proper example NetworkClientSecure
* does not work for you; then you may want to check the
* NetworkClientTrustOnFirstUse example first. It is less secure than
* NetworkClientSecure, but a lot better than this totally insecure
* approach shown below.
*/
const char *ssid = "ssid"; // your network SSID (name of wifi network)
const char *password = "password"; // your network password
const char *server = "www.howsmyssl.com"; // Server URL
NetworkClientSecure client;
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(115200);
delay(100);
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// attempt to connect to Wifi network:
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
// wait 1 second for re-trying
delay(1000);
}
Serial.print("Connected to ");
Serial.println(ssid);
Serial.println("\nStarting connection to server...");
client.setInsecure(); //skip verification
client.setTimeout(5000);
client.setHandshakeTimeout(5);
client.setConnectionTimeout(5000);
if (!client.connect(server, 443)) {
Serial.println("Connection failed!");
} else {
Serial.println("Connected to server!");
// Make a HTTP request:
client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0");
client.println("Host: www.howsmyssl.com");
client.println("Connection: close");
client.println();
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") {
Serial.println("headers received");
break;
}
}
// if there are incoming bytes available
// from the server, read them and print them:
while (client.available()) {
char c = client.read();
Serial.write(c);
}
client.stop();
}
}
void loop() {
// do nothing
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 7 replies
-
It depends on how timeout was applied and how the SSL client works. The library timeout is the data timeout or application timeout which applies after the TCP session was opened (server connected). The library's async task timeout was set to 30 seconds by default for sending and receiving the request. Those timeouts cannot change by user unless hard code. The blocking during SSL handshake and server negotiation was out of application control and will not apply for library time out. The If your router or AP does not responding only during this data sending/receiving time, the library's timed out will be displayed. The sync task timeout works in different way which it can set by user as it will not share the cpu cycles for other user code but the AsyncClient or SSL client alone. No It just only exits from writing and reading data process loop and gives the error when timed out occurred. The timeout appliles to the SSL client works in the different layers. It applies to lwIP stack and the SSL client connection/handshak itself. Any network layer blocking process will block the application too. Your test for Anyway, the core SSL client (WiFiClientSecure) is the blocking client, no matter it used in sync or async application it still blocks or waits for the io. To do real async task, it needs async SSL client instead. |
Beta Was this translation helpful? Give feedback.
-
I run this example to run a database query. I change the interval to query to every 60 seconds and print the millis() as a timestamp every 250 ms to check for code blocking. Since I get the query response, I turn off the mobile data in cell phone and leave the mobile AP stay connected with my ESP32 device. Here the serial debug that shows the timed out occurred after 30 seconds after sending the HTTP request. Note that the TCP session was kept alive (reuse) then no new server connection for next requests. Querying a Firestore database...
62214
62465
62716
62967
63218
63469
63720
63971
64222
64473
64724
64975
65226
65477
65728
65979
66230
66481
66732
66983
67234
67485
67736
67987
68238
68489
68740
68991
69242
69493
69744
69995
70246
70497
70748
70999
71250
71501
71752
72003
72254
72505
72756
73007
73258
73509
73760
74011
74262
74513
74764
75015
75266
75517
75768
76019
76270
76521
76772
77023
77274
77525
77776
78027
78278
78529
78780
79031
79282
79533
79784
80035
80286
80537
80788
81039
81290
81541
81792
82043
82294
82545
82796
83047
83298
83549
83800
84051
84302
84553
84804
85055
85306
85557
85808
86059
86310
86561
86812
87063
87314
87565
87816
88067
88318
88569
88820
89071
89322
89573
89824
90075
90326
90577
90828
91079
Debug task: runQueryTask, msg: Terminating the server connection...
Error task: runQueryTask, msg: TCP receive timed out, code: -3
91330
91581
91832
92083
92334 Once the time out occurred, The next iteration of database query, the new server connection will begin followed by SSL handshake, and it blocks (waits) for this process to be done in the timeout period. I just set the connection timeout to 1 seconds and SSL handshake timeout to 5 seconds. ssl_client.setConnectionTimeout(1000);
ssl_client.setHandshakeTimeout(5); This is the result when the server connection fails in 1 second waiting period. There is nothing print out during this blocking period. 123617
123868
124119
Querying a Firestore database...
[125243][E][NetworkClientSecure.cpp:159] connect(): start_ssl_client: connect failed: -1
Debug task: runQueryTask, msg: Connecting to server...
Error task: runQueryTask, msg: TCP connection failed, code: -1
125254
125505
125756
126007 This 1 second blocking happens every 1 min of run query request and the server connection will resume when I turn on the mobile data without freezes or hangs. This is not the first time this scenario testing. I do intensively test in all network types and conditions 24/7 for years to make sure everything works and reliable as I design. Someone does not know the reason why network is unstable when he uses ESP8266 and ESP32 and do some reset the board to solve the problem. The device restart is not needed when working with WiFi network as long as your device works properly with enough free memory used for SSL/normal client and system tasks with good/stable board supply. |
Beta Was this translation helpful? Give feedback.
You are right. Now I get your point.
I've found the timeout bug for specific situation with HTTP GET request when no server response (network/router is not responding).
Now you can update the library to v1.4.18 that solves this issue.
Thanks for reporting this issue.