mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
Merge pull request #61 from cjkas/fix/socket-buffer-4k
fix: make the socket buffer big enough for the largest event
This commit is contained in:
commit
e06ee3a060
4 changed files with 19 additions and 12 deletions
|
|
@ -48,25 +48,26 @@ Every read route builds each room, shade, group and linked shade into a 3 KB uni
|
||||||
|
|
||||||
## Large websocket events are dropped
|
## Large websocket events are dropped
|
||||||
|
|
||||||
**Status:** open
|
**Status:** fixed (fix option (c)): the buffer is 4 KB, and the largest event provably fits.
|
||||||
**Location:** [`JsonSockEvent` in src/WResp.cpp](../src/WResp.cpp), buffer `g_response[MAX_SOCK_RESPONSE]` (2048 bytes) in [src/outputs/SocketEmitter.cpp](../src/outputs/SocketEmitter.cpp).
|
**Location:** [`JsonSockEvent` in src/WResp.cpp](../src/WResp.cpp), buffer `g_response[MAX_SOCK_RESPONSE]` (now 4096 bytes, defined in [src/outputs/SocketEmitter.h](../src/outputs/SocketEmitter.h)).
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
Socket events are built into a fixed 2 KB static buffer. When a value doesn't fit, [`JsonSockEvent::_safecat`](../src/WResp.cpp) marks the event as overflowed, and `endEvent` logs an error and doesn't send it. Clients never see malformed JSON. The event is simply lost, and the client isn't told.
|
Socket events are built into one static buffer. When a value doesn't fit, [`JsonSockEvent::_safecat`](../src/WResp.cpp) marks the event as overflowed, and `endEvent` logs an error and doesn't send it. Clients never see malformed JSON. The event is simply lost, and the client isn't told.
|
||||||
|
|
||||||
Unlike the `/controller` HTTP crash, this path does **not** abort — there is no growing cbuf and no `new[]` on the send path. Per-client frame allocations inside `AsyncWebSocket` are bounded by the 2 KB buffer size and have their own overflow guard (queue drop / client disconnect).
|
Unlike the `/controller` HTTP crash, this path does **not** abort: there is no growing cbuf and no `new[]` on the send path.
|
||||||
|
|
||||||
### Concrete failure modes
|
### What can be large
|
||||||
|
|
||||||
1. A single event serializing a fully-populated shade (~1.3–1.5 KB for a shade with all `SOMFY_MAX_LINKED_REMOTES` = 7 populated) gets close to the 2 KB limit. Any additional fields or long names push it over, and that shade's state update never reaches the clients.
|
- **`remoteFrame`** (the frame log) lists every pulse of a received frame: up to `MAX_TIMINGS` = 300. A pulse recorded while waiting for a sync has no upper bound, so in theory each can be 10 digits: 300 × 11 bytes plus about 300 bytes of fields is about 3.8 KB. Real frames come to 1.8–2.1 KB, which was already at the old 2 KB limit. A `static_assert` in `Transceiver.cpp` fails the build if `MAX_TIMINGS` or the buffer change so that it no longer fits.
|
||||||
2. Any event that loops over a collection (e.g. frequency-scan results in `Transceiver.cpp`, batch emits in `SomfyEvents.cpp`) can exceed 2 KB depending on size, with no indication to the client beyond the ESP-side `ESP_LOGE` line.
|
- **The state events are small.** `shadeState` has about 20 fields and no linked remotes, about 400 bytes. (The 1.3–1.5 KB figure this entry used to give was for a shade *with* its linked remotes, which only the read routes send.) `groupState` lists at most 32 shade ids. `frequencyScan`, `memStatus`, `fwStatus`, `ethernet` and `requestResult` have a few fixed fields each.
|
||||||
|
|
||||||
### Fix options (pick one later)
|
The 2 KB more of static RAM is the cost. The log line stays as a backstop.
|
||||||
|
|
||||||
- **(a) Fail loud.** Send a small error event in place of the dropped one so the client knows to reload the state.
|
### Options that weren't taken
|
||||||
- **(b) Split large events across frames.** Use the socket.io ack/chunk pattern to send an event in multiple frames when it wouldn't fit. Requires matching client-side reassembly.
|
|
||||||
- **(c) Raise `MAX_SOCK_RESPONSE`.** Cheapest, but just pushes the limit — doesn't eliminate the failure mode.
|
- **(a) Fail loud:** send a small error event in place of the dropped one so the client reloads the state.
|
||||||
|
- **(b) Split large events across frames:** the socket.io ack/chunk pattern, with matching client-side reassembly.
|
||||||
|
|
||||||
### Related
|
### Related
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,9 @@ bool Transceiver::receive(somfy_rx_t *rx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void Transceiver::emitFrame(somfy_frame_t *frame, somfy_rx_t *rx) {
|
void Transceiver::emitFrame(somfy_frame_t *frame, somfy_rx_t *rx) {
|
||||||
|
// The largest socket event: every pulse as up to 10 digits and a comma,
|
||||||
|
// plus about 300 bytes of fields and framing.
|
||||||
|
static_assert(MAX_TIMINGS * 11 + 512 <= MAX_SOCK_RESPONSE, "remoteFrame can outgrow the socket buffer");
|
||||||
if(sockEmit.activeClients(ROOM_EMIT_FRAME) > 0) {
|
if(sockEmit.activeClients(ROOM_EMIT_FRAME) > 0) {
|
||||||
JsonSockEvent *json = sockEmit.beginEmit("remoteFrame");
|
JsonSockEvent *json = sockEmit.beginEmit("remoteFrame");
|
||||||
json->beginObject();
|
json->beginObject();
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ static void removeClient(uint32_t asyncId) {
|
||||||
if(slots[i].asyncId == asyncId) slots[i].asyncId = 0;
|
if(slots[i].asyncId == asyncId) slots[i].asyncId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_SOCK_RESPONSE 2048
|
|
||||||
static char g_response[MAX_SOCK_RESPONSE];
|
static char g_response[MAX_SOCK_RESPONSE];
|
||||||
|
|
||||||
bool room_t::isJoined(uint8_t num) {
|
bool room_t::isJoined(uint8_t num) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@
|
||||||
|
|
||||||
#define SOCK_MAX_ROOMS 1
|
#define SOCK_MAX_ROOMS 1
|
||||||
#define ROOM_EMIT_FRAME 0
|
#define ROOM_EMIT_FRAME 0
|
||||||
|
// The buffer every socket event is formatted into. An event that doesn't
|
||||||
|
// fit is logged and not sent, so the largest one has to fit: see remoteFrame
|
||||||
|
// in Transceiver.cpp.
|
||||||
|
#define MAX_SOCK_RESPONSE 4096
|
||||||
|
|
||||||
struct room_t {
|
struct room_t {
|
||||||
uint8_t clients[5] = {255, 255, 255, 255, 255};
|
uint8_t clients[5] = {255, 255, 255, 255, 255};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue