@alteriom/painlessmesh 1.9.11 → 1.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +1 -1
- package/examples/sendToInternet/README.md +17 -0
- package/examples/sendToInternet/sendToInternet.ino +7 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/AlteriomPainlessMesh.h +2 -2
- package/src/arduino/wifi.hpp +30 -3
- package/src/painlessMesh.h +2 -2
- package/src/painlessmesh/connection.hpp +26 -5
- package/src/painlessmesh/mesh.hpp +55 -5
package/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
- TBD
|
|
21
21
|
|
|
22
|
+
## [1.9.13] - 2025-12-19
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **HTTP 203 "Permanent Response" Issue with sendToInternet()** - Fixed issue where HTTP 203 (Non-Authoritative Information) responses from APIs like Callmebot WhatsApp appeared "permanent" without automatic recovery
|
|
27
|
+
- **Root Cause**: HTTP 203 responses (indicating cached/proxied responses) were correctly identified as failures but treated as terminal - requests were immediately removed from the pending queue without retry
|
|
28
|
+
- **Symptom**: User sees repeated "❌ Failed to send WhatsApp: Ambiguous response - HTTP 203..." messages with no automatic recovery, requiring manual intervention
|
|
29
|
+
- **Solution**: Modified `handleGatewayAck()` to implement intelligent retry logic for retryable failure types:
|
|
30
|
+
- HTTP 203 (Non-Authoritative Information) - cached/proxied responses, often temporary
|
|
31
|
+
- HTTP 5xx (Server Errors) - transient server issues (500, 502, 503, 504, etc.)
|
|
32
|
+
- HTTP 429 (Too Many Requests) - rate limiting with exponential backoff
|
|
33
|
+
- HTTP 0 (Network Errors) - connection failures, timeouts
|
|
34
|
+
- Non-retryable: HTTP 4xx client errors (except 429), HTTP 3xx redirects
|
|
35
|
+
- **Retry Behavior**: Uses exponential backoff (2s, 4s, 8s, 16s...) with configurable max retries (default: 3)
|
|
36
|
+
- **Testing**: Added comprehensive test coverage (50 assertions in 7 test cases) for retry classification and behavior
|
|
37
|
+
- **Documentation**: Added ISSUE_HTTP_203_RETRY_FIX.md with detailed analysis, examples, and HTTP 203 explanation
|
|
38
|
+
- **Impact**: Automatic recovery from temporary API caching issues, eliminating the "permanent" failure problem
|
|
39
|
+
- **API Compatibility**: Fully backward compatible, no breaking changes, existing code works unchanged
|
|
40
|
+
|
|
41
|
+
- **Hard Reset on Node - AsyncClient Deletion Race Condition** - Fixed ESP32/ESP8266 heap corruption crashes caused by race condition in deletion spacing logic during network disruptions
|
|
42
|
+
- **Root Cause**: The `scheduleAsyncClientDeletion()` function was updating `lastScheduledDeletionTime` at BOTH scheduling time (line 111) and execution time (line 130), creating a race condition where scheduler jitter could cause deletions to execute with insufficient spacing
|
|
43
|
+
- **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40831d54. Expected 0xabba1234 got 0x4081fae4" even with 250ms spacing constant, particularly during network disruptions, TCP retries, or WiFi reconnection cycles
|
|
44
|
+
- **Race Condition Scenario**: When Task A scheduled for time T1 executes late at T1+jitter, it updates `lastScheduledDeletionTime` to T1+jitter, potentially AFTER Task B's scheduled time (which was calculated based on T1), causing Task B to execute with less than 250ms spacing
|
|
45
|
+
- **Solution**: Removed the execution-time update of `lastScheduledDeletionTime` in the task callback, relying solely on the scheduling-time update
|
|
46
|
+
- Ensures consistent, predictable spacing based on planned execution times
|
|
47
|
+
- Eliminates race condition where execution-time updates could "rewind" the timestamp
|
|
48
|
+
- Makes spacing calculation immune to scheduler jitter
|
|
49
|
+
- Guarantees minimum 250ms spacing between AsyncClient deletions in all scenarios
|
|
50
|
+
- **Why This Works**: By only updating at scheduling time, subsequent deletions are always spaced from the PREVIOUS deletion's planned time, not its actual execution time, providing conservative spacing guarantees even with scheduler jitter
|
|
51
|
+
- **Testing**: All test suites pass (1000+ assertions), including TCP retry (52), connection (3), and timing tests (7)
|
|
52
|
+
- **Documentation**: Added ISSUE_HARD_RESET_DELETION_RACE_FIX.md with detailed mathematical proof and race condition analysis
|
|
53
|
+
- **Impact**: Eliminates heap corruption crashes during network disruptions, enables stable operation through TCP retries and WiFi reconnection cycles
|
|
54
|
+
|
|
55
|
+
## [1.9.12] - 2025-12-18
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **Hard Reset During Bridge Operations - AsyncClient abort() Timing Issue** - Fixed ESP32/ESP8266 heap corruption crashes during TCP connection failures and bridge operations
|
|
60
|
+
- **Root Cause**: Calling `client->abort()` synchronously before scheduling deferred AsyncClient deletion (1000ms+ later) left the client in an inconsistent state where AsyncTCP's internal cleanup tried to access the aborted client
|
|
61
|
+
- **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40838cdc. Expected 0xabba1234 got 0x4200822e" and "assert failed: multi_heap_free multi_heap_poisoning.c:279" during connection cleanup
|
|
62
|
+
- **Solution**: Removed synchronous `abort()` call from `~BufferedConnection()` destructor
|
|
63
|
+
- The existing `close()` and `close(true)` calls are sufficient for connection termination
|
|
64
|
+
- According to AsyncTCP best practices, `abort()` should only be called immediately before `delete`, not before a deferred deletion
|
|
65
|
+
- Eliminates the 1000ms window where AsyncTCP tries to clean up an aborted but not-yet-deleted client
|
|
66
|
+
- **Testing**: All test suites pass (1000+ assertions), including TCP retry, connection, and mesh connectivity tests
|
|
67
|
+
- **Documentation**: Added ISSUE_ABORT_TIMING_FIX.md with detailed AsyncTCP best practices analysis
|
|
68
|
+
- **Impact**: Completes the AsyncClient lifecycle management improvements, eliminating the last known heap corruption scenario in connection cleanup
|
|
69
|
+
|
|
22
70
|
## [1.9.11] - 2025-12-18
|
|
23
71
|
|
|
24
72
|
### Fixed
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
|
|
7
|
-
**Version 1.9.
|
|
7
|
+
**Version 1.9.13** - Latest release with HTTP 203 retry logic and AsyncClient deletion race condition fixes
|
|
8
8
|
|
|
9
9
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
|
|
10
10
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml)
|
|
@@ -146,6 +146,23 @@ mesh.sendToInternet("https://api.callmebot.com/...", "", callback);
|
|
|
146
146
|
3. Check HTTP status code in callback (200 = success)
|
|
147
147
|
4. URL-encode special characters in the message
|
|
148
148
|
|
|
149
|
+
### Understanding HTTP Status Codes
|
|
150
|
+
|
|
151
|
+
The callback provides `httpStatus` to indicate the result:
|
|
152
|
+
|
|
153
|
+
**SUCCESS (success = true):**
|
|
154
|
+
- `200 OK` - Standard success (most common for WhatsApp API)
|
|
155
|
+
- `201 Created` - Resource successfully created
|
|
156
|
+
- `202 Accepted` - Request accepted for processing
|
|
157
|
+
- `204 No Content` - Successful with no response body
|
|
158
|
+
|
|
159
|
+
**FAILURE (success = false):**
|
|
160
|
+
- `203 Non-Authoritative Information` - **Cached/proxied response, NOT actual delivery**
|
|
161
|
+
- `4xx` - Client error (bad request, unauthorized, not found, etc.)
|
|
162
|
+
- `5xx` - Server error (service unavailable, gateway timeout, etc.)
|
|
163
|
+
|
|
164
|
+
⚠️ **Important:** HTTP 203 is treated as **FAILURE** because it indicates the response came from a cache or proxy, not from the actual WhatsApp API server. If you see `HTTP Status: 203`, the message was **NOT delivered**.
|
|
165
|
+
|
|
149
166
|
## Files
|
|
150
167
|
|
|
151
168
|
- `sendToInternet.ino` - Main example sketch
|
|
@@ -184,6 +184,13 @@ void sendAlertToWhatsApp(String message) {
|
|
|
184
184
|
|
|
185
185
|
// Use sendToInternet() to route the request through a gateway
|
|
186
186
|
// The callback will be invoked when we get a response (or timeout)
|
|
187
|
+
//
|
|
188
|
+
// SUCCESS CODES: Only specific HTTP codes indicate genuine delivery:
|
|
189
|
+
// - 200 OK: Standard success (most common for WhatsApp API)
|
|
190
|
+
// - 201 Created, 202 Accepted, 204 No Content
|
|
191
|
+
//
|
|
192
|
+
// FAILURE: HTTP 203 (Non-Authoritative) is treated as FAILURE because
|
|
193
|
+
// it indicates a cached/proxied response, not actual delivery to WhatsApp.
|
|
187
194
|
uint32_t msgId = mesh.sendToInternet(
|
|
188
195
|
url,
|
|
189
196
|
"", // No payload needed for GET request - params are in URL
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=Alteriom PainlessMesh
|
|
2
|
-
version=1.9.
|
|
2
|
+
version=1.9.13
|
|
3
3
|
author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
|
|
4
4
|
maintainer=Alteriom
|
|
5
5
|
sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alteriom/painlessmesh",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.13",
|
|
4
4
|
"description": "painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices. This Alteriom fork includes additional packages for sensor data (SensorPackage), device commands (CommandPackage), and status monitoring (StatusPackage). It handles routing and network management automatically, so you can focus on your application. The library uses JSON-based messaging and syncs time across all nodes, making it ideal for coordinated behaviour like synchronized light displays or sensor networks reporting to a central node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arduino",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
/**
|
|
30
30
|
* @brief AlteriomPainlessMesh library version information
|
|
31
31
|
*/
|
|
32
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION "1.9.
|
|
32
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION "1.9.13"
|
|
33
33
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MAJOR 1
|
|
34
34
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MINOR 9
|
|
35
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH
|
|
35
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 13
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -2158,10 +2158,37 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
2158
2158
|
}
|
|
2159
2159
|
|
|
2160
2160
|
if (httpCode > 0) {
|
|
2161
|
-
// Only 2xx status codes
|
|
2161
|
+
// Only specific 2xx status codes indicate genuine success
|
|
2162
|
+
// 200 OK: Standard successful response
|
|
2163
|
+
// 201 Created: Resource successfully created
|
|
2164
|
+
// 202 Accepted: Request accepted for processing
|
|
2165
|
+
// 204 No Content: Successful with no response body
|
|
2166
|
+
//
|
|
2167
|
+
// Other 2xx codes like 203 (Non-Authoritative Information) often
|
|
2168
|
+
// indicate cached/proxied responses that may not represent actual
|
|
2169
|
+
// delivery to the destination service (e.g., WhatsApp API).
|
|
2170
|
+
//
|
|
2162
2171
|
// 3xx redirects are not automatically followed
|
|
2163
|
-
success = (httpCode
|
|
2164
|
-
|
|
2172
|
+
success = (httpCode == 200 || httpCode == 201 ||
|
|
2173
|
+
httpCode == 202 || httpCode == 204);
|
|
2174
|
+
|
|
2175
|
+
if (success) {
|
|
2176
|
+
Log(COMMUNICATION, "HTTP request completed: code=%d\n", httpCode);
|
|
2177
|
+
} else if (httpCode >= 200 && httpCode < 300) {
|
|
2178
|
+
// Other 2xx codes - ambiguous success
|
|
2179
|
+
char errorBuf[128];
|
|
2180
|
+
snprintf(errorBuf, sizeof(errorBuf),
|
|
2181
|
+
"Ambiguous response - HTTP %d may indicate cached/proxied response, not actual delivery",
|
|
2182
|
+
httpCode);
|
|
2183
|
+
error = TSTRING(errorBuf);
|
|
2184
|
+
Log(ERROR, "HTTP request ambiguous: code=%d (treated as failure)\n", httpCode);
|
|
2185
|
+
} else {
|
|
2186
|
+
// 1xx, 3xx, 4xx, 5xx
|
|
2187
|
+
char errorBuf[32];
|
|
2188
|
+
snprintf(errorBuf, sizeof(errorBuf), "HTTP %d", httpCode);
|
|
2189
|
+
error = TSTRING(errorBuf);
|
|
2190
|
+
Log(ERROR, "HTTP request failed: code=%d\n", httpCode);
|
|
2191
|
+
}
|
|
2165
2192
|
} else {
|
|
2166
2193
|
error = http.errorToString(httpCode);
|
|
2167
2194
|
Log(ERROR, "HTTP request failed: %s\n", error.c_str());
|
package/src/painlessMesh.h
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* @file painlessMesh.h
|
|
6
6
|
* @brief Main header file for Alteriom painlessMesh library
|
|
7
7
|
*
|
|
8
|
-
* @version 1.9.
|
|
9
|
-
* @date 2025-12-
|
|
8
|
+
* @version 1.9.13
|
|
9
|
+
* @date 2025-12-19
|
|
10
10
|
*
|
|
11
11
|
* painlessMesh is a user-friendly library for creating mesh networks with
|
|
12
12
|
* ESP8266 and ESP32 devices. This Alteriom fork includes additional packages
|
|
@@ -28,11 +28,18 @@ static const uint32_t TCP_CLIENT_CLEANUP_DELAY_MS = 1000; // 1000ms delay before
|
|
|
28
28
|
// This spacing ensures each deletion completes before the next one begins
|
|
29
29
|
static const uint32_t TCP_CLIENT_DELETION_SPACING_MS = 250; // 250ms spacing between deletions
|
|
30
30
|
|
|
31
|
-
// Global state to track AsyncClient deletion scheduling
|
|
31
|
+
// Global state to track AsyncClient deletion scheduling and execution
|
|
32
32
|
// This ensures deletions are spaced out even when multiple deletion requests arrive simultaneously
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
33
|
+
// The timestamp is updated both when a deletion is SCHEDULED and when it EXECUTES, providing
|
|
34
|
+
// double protection against concurrent cleanup operations even with scheduler jitter
|
|
35
|
+
//
|
|
36
|
+
// THREAD SAFETY: No synchronization needed because:
|
|
37
|
+
// - ESP32/ESP8266 Arduino framework is single-threaded by design
|
|
38
|
+
// - TaskScheduler executes callbacks sequentially in the main loop
|
|
39
|
+
// - The scheduler never runs tasks concurrently within the same mesh instance
|
|
40
|
+
// - All mesh operations (including deletion callbacks) execute in the same thread
|
|
41
|
+
// - Even when multiple tasks are ready, they execute one-at-a-time via scheduler->execute()
|
|
42
|
+
static uint32_t lastScheduledDeletionTime = 0; // Timestamp of last deletion scheduled/executed (milliseconds)
|
|
36
43
|
|
|
37
44
|
// Shared buffer for reading/writing to the buffer
|
|
38
45
|
static painlessmesh::buffer::temp_buffer_t shared_buffer;
|
|
@@ -98,6 +105,9 @@ inline void scheduleAsyncClientDeletion(Scheduler* scheduler, AsyncClient* clien
|
|
|
98
105
|
uint32_t actualDelay = targetDeletionTime - currentTime;
|
|
99
106
|
|
|
100
107
|
// Update the last scheduled deletion time
|
|
108
|
+
// IMPORTANT: We update this when scheduling, which provides a minimum guaranteed spacing
|
|
109
|
+
// between scheduled deletions. This ensures even with scheduler jitter, deletions won't
|
|
110
|
+
// be scheduled too close together.
|
|
101
111
|
lastScheduledDeletionTime = targetDeletionTime;
|
|
102
112
|
|
|
103
113
|
Log(CONNECTION, "%s: Scheduling AsyncClient deletion in %u ms (spaced from previous deletions)\n",
|
|
@@ -113,6 +123,12 @@ inline void scheduleAsyncClientDeletion(Scheduler* scheduler, AsyncClient* clien
|
|
|
113
123
|
Task* cleanupTask = new Task(actualDelay * TASK_MILLISECOND, TASK_ONCE, [client, logPrefix]() {
|
|
114
124
|
using namespace logger;
|
|
115
125
|
Log(CONNECTION, "%s: Deferred cleanup of AsyncClient executing now\n", logPrefix);
|
|
126
|
+
|
|
127
|
+
// Note: lastScheduledDeletionTime is updated at scheduling time (before this task runs), not here
|
|
128
|
+
// This ensures consistent spacing based on when deletions were scheduled, preventing
|
|
129
|
+
// the race condition where execution-time updates could "rewind" the timestamp
|
|
130
|
+
// and cause subsequent deletions to be scheduled too close together
|
|
131
|
+
|
|
116
132
|
delete client;
|
|
117
133
|
});
|
|
118
134
|
|
|
@@ -149,7 +165,12 @@ class BufferedConnection
|
|
|
149
165
|
if (!client->freeable()) {
|
|
150
166
|
client->close(true);
|
|
151
167
|
}
|
|
152
|
-
client->abort()
|
|
168
|
+
// Note: client->abort() removed - calling it before deferred deletion
|
|
169
|
+
// can leave the client in an inconsistent state where AsyncTCP is still
|
|
170
|
+
// trying to clean up the aborted connection. The close() and close(true)
|
|
171
|
+
// calls above are sufficient for connection termination.
|
|
172
|
+
// See: AsyncTCP best practices - abort() should only be called immediately
|
|
173
|
+
// before delete, not before a deferred deletion.
|
|
153
174
|
|
|
154
175
|
// Defer deletion of the AsyncClient to prevent heap corruption
|
|
155
176
|
// Use the centralized deletion scheduler to ensure proper spacing between deletions
|
|
@@ -1503,13 +1503,63 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1503
1503
|
|
|
1504
1504
|
PendingInternetRequest& request = it->second;
|
|
1505
1505
|
|
|
1506
|
-
//
|
|
1507
|
-
if (
|
|
1508
|
-
|
|
1506
|
+
// Check if this is a success response
|
|
1507
|
+
if (ack.success) {
|
|
1508
|
+
// Success - call callback and remove request
|
|
1509
|
+
if (request.callback) {
|
|
1510
|
+
request.callback(ack.success, ack.httpStatus, ack.error);
|
|
1511
|
+
}
|
|
1512
|
+
pendingInternetRequests.erase(it);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Failure response - determine if retryable
|
|
1517
|
+
bool isRetryable = false;
|
|
1518
|
+
|
|
1519
|
+
// HTTP 203 (Non-Authoritative Information) indicates cached/proxied response
|
|
1520
|
+
// This is often temporary and retrying may succeed when cache expires
|
|
1521
|
+
if (ack.httpStatus == 203) {
|
|
1522
|
+
isRetryable = true;
|
|
1523
|
+
Log(COMMUNICATION, "handleGatewayAck(): HTTP 203 detected, marking as retryable\n");
|
|
1524
|
+
}
|
|
1525
|
+
// HTTP 5xx server errors are typically transient
|
|
1526
|
+
else if (ack.httpStatus >= 500 && ack.httpStatus < 600) {
|
|
1527
|
+
isRetryable = true;
|
|
1528
|
+
Log(COMMUNICATION, "handleGatewayAck(): HTTP 5xx server error, marking as retryable\n");
|
|
1529
|
+
}
|
|
1530
|
+
// HTTP 429 (Too Many Requests) should be retried with backoff
|
|
1531
|
+
else if (ack.httpStatus == 429) {
|
|
1532
|
+
isRetryable = true;
|
|
1533
|
+
Log(COMMUNICATION, "handleGatewayAck(): HTTP 429 rate limit, marking as retryable\n");
|
|
1509
1534
|
}
|
|
1535
|
+
// Network errors (httpStatus == 0) are retryable
|
|
1536
|
+
else if (ack.httpStatus == 0) {
|
|
1537
|
+
isRetryable = true;
|
|
1538
|
+
Log(COMMUNICATION, "handleGatewayAck(): Network error, marking as retryable\n");
|
|
1539
|
+
}
|
|
1540
|
+
// HTTP 4xx client errors (except 429) are NOT retryable
|
|
1541
|
+
// HTTP 3xx redirects are NOT retryable (should be followed by HTTPClient)
|
|
1542
|
+
// Other status codes are NOT retryable
|
|
1510
1543
|
|
|
1511
|
-
//
|
|
1512
|
-
|
|
1544
|
+
// If retryable and have retries left, schedule retry
|
|
1545
|
+
if (isRetryable && request.retryCount < request.maxRetries) {
|
|
1546
|
+
Log(COMMUNICATION, "handleGatewayAck(): Scheduling retry for msgId=%u (attempt %u/%u)\n",
|
|
1547
|
+
ack.messageId, request.retryCount + 1, request.maxRetries);
|
|
1548
|
+
scheduleInternetRetry(ack.messageId);
|
|
1549
|
+
} else {
|
|
1550
|
+
// Not retryable or max retries reached - call callback and remove
|
|
1551
|
+
if (request.retryCount >= request.maxRetries) {
|
|
1552
|
+
Log(ERROR, "handleGatewayAck(): Max retries reached for msgId=%u\n", ack.messageId);
|
|
1553
|
+
} else {
|
|
1554
|
+
Log(COMMUNICATION, "handleGatewayAck(): Non-retryable failure for msgId=%u (HTTP %u)\n",
|
|
1555
|
+
ack.messageId, ack.httpStatus);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (request.callback) {
|
|
1559
|
+
request.callback(ack.success, ack.httpStatus, ack.error);
|
|
1560
|
+
}
|
|
1561
|
+
pendingInternetRequests.erase(it);
|
|
1562
|
+
}
|
|
1513
1563
|
}
|
|
1514
1564
|
|
|
1515
1565
|
/**
|