Skip to content

Commit bc412b1

Browse files
committed
rename WiFiUdpStrem.h to UDPStream.h & SignalHandler corrections
1 parent 9aacbe3 commit bc412b1

8 files changed

Lines changed: 243 additions & 131 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
build/
22
.vscode
3+
.ipynb_checkpoints/
34

ArduinoCore-Linux/cores/arduino/Arduino.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "api/ArduinoAPI.h"
4545
#include "RemoteSerial.h"
4646
#include "HardwareSetup.h"
47+
#include "FileStream.h"
4748

4849
using namespace arduino;
4950

ArduinoCore-Linux/cores/arduino/FileStream.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
namespace arduino {
2525

2626
/**
27-
* @brief We use the FileStream class to be able to provide Serail, Serial1 and
27+
* @brief We use the FileStream class to be able to provide Serial, Serial1 and
2828
* Serial2 outside of the Arduino environment;
2929
*/
3030
class FileStream : public Stream {
@@ -48,6 +48,10 @@ class FileStream : public Stream {
4848
// nothing to be done
4949
}
5050

51+
virtual void end() {
52+
// nothing to be done
53+
}
54+
5155
virtual void print(const char* str) {
5256
out << str;
5357
out.flush();

ArduinoCore-Linux/cores/arduino/HardwareSetupRemote.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#include "RemoteI2C.h"
2727
#include "RemoteSPI.h"
2828
#include "SPIWrapper.h"
29-
#include "WiFiUdpStream.h"
29+
#include "UDPStream.h"
3030

3131
namespace arduino {
3232

@@ -127,7 +127,7 @@ class HardwareSetupRemote : public I2CSource,
127127
HardwareSPI* getSPI() { return &spi; }
128128

129129
protected:
130-
WiFiUDPStream default_stream;
130+
arduino::UDPStream default_stream;
131131
Stream* p_stream = nullptr;
132132
RemoteI2C i2c;
133133
RemoteSPI spi;
@@ -145,15 +145,15 @@ class HardwareSetupRemote : public I2CSource,
145145
int len = s->readBytes(buffer, 18);
146146
buffer[len] = 0;
147147
if (strncmp(buffer, "Arduino-Emulator", 16)==0) {
148-
Logger.info("WiFiUDPStream", "device found!");
148+
Logger.info("UDPStream", "device found!");
149149
break;
150150
} else {
151-
Logger.info("WiFiUDPStream", "unknown command", buffer);
151+
Logger.info("UDPStream", "unknown command", buffer);
152152
}
153153
}
154154
delay(10000);
155155
} catch (const std::exception& ex) {
156-
Logger.error("WiFiUDPStream", ex.what());
156+
Logger.error("UDPStream", ex.what());
157157
}
158158
}
159159
}

ArduinoCore-Linux/cores/arduino/SignalHandler.h

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,43 @@
2424
#include <vector>
2525
#include <map>
2626
#include <algorithm>
27+
#include <mutex>
28+
#include <thread>
29+
#include <unistd.h>
2730

2831
#undef INADDR_NONE
2932

3033
// Generic signal handler utility
34+
//
35+
// dispatch() used to run the registered handlers (std::map lookup,
36+
// std::function calls) and call exit(0) directly from inside the real OS
37+
// signal handler. None of that is async-signal-safe: exit() in particular
38+
// is explicitly documented as unsafe to call from a signal handler, since
39+
// it runs atexit()/static-destructor cleanup - including destroying the
40+
// very std::map dispatch() itself was reading. In a multi-threaded process
41+
// (any sketch using audio/video/network libraries alongside this) a second
42+
// signal delivered to another thread while the first is mid-exit() re-enters
43+
// dispatch() and reads/frees that map concurrently - a genuine
44+
// heap-use-after-free, reproducible under AddressSanitizer.
45+
//
46+
// Fixed with the standard self-pipe trick: the real signal handler only
47+
// does the one thing that's actually async-signal-safe here - write() one
48+
// byte to a pipe - and a dedicated background thread, running in ordinary
49+
// (non-signal) context, blocks reading that pipe and does the unsafe work
50+
// (map lookup, invoking handlers, exit()) with no signal-safety
51+
// restrictions and no reentrancy risk.
52+
//
53+
// That move alone isn't enough, though: any sketch with other threads
54+
// still running at signal time (e.g. an audio callback thread, a decoder
55+
// thread) still races exit()'s normal C++ shutdown, which runs every
56+
// static/global object's destructor on the reaper thread while those
57+
// other threads may still be mid-call on the very same objects - observed
58+
// as "pure virtual method called" (a virtual call landing on an object
59+
// whose vtable was already torn down mid-destruction). The registered
60+
// HandlerFunc callbacks above are the sketch's actual cleanup and still
61+
// run first, in full; what follows uses _exit(), which ends the process
62+
// immediately without invoking any other static destructor or atexit
63+
// handler, so it can't race with them.
3164
class SignalHandler {
3265
public:
3366
using HandlerFunc = std::function<void(int)>;
@@ -36,21 +69,58 @@ class SignalHandler {
3669
auto& vec = getHandlers()[signum];
3770
vec.push_back(handler);
3871
std::signal(signum, SignalHandler::dispatch);
72+
ensureReaperThread();
3973
}
4074

4175
private:
4276
static std::map<int, std::vector<HandlerFunc>>& getHandlers() {
4377
static std::map<int, std::vector<HandlerFunc>> handlers;
4478
return handlers;
4579
}
80+
81+
// Async-signal-safe: writes one byte and returns. No map access, no
82+
// std::function calls, no exit() - all of that is deferred to the
83+
// reaper thread, well outside signal-handler context.
4684
static void dispatch(int signum) {
47-
auto& handlers = getHandlers();
48-
auto it = handlers.find(signum);
49-
if (it != handlers.end()) {
50-
for (auto& func : it->second) {
51-
func(signum);
52-
}
53-
}
54-
exit(0);
85+
char sig = (char)signum;
86+
ssize_t n = write(pipeWriteFd(), &sig, 1);
87+
(void)n; // nothing safe to do with a failed write() from a handler
88+
}
89+
90+
static int& pipeWriteFd() {
91+
static int fd = -1;
92+
return fd;
93+
}
94+
static int& pipeReadFd() {
95+
static int fd = -1;
96+
return fd;
97+
}
98+
99+
// Starts the reaper thread at most once, the first time any signal is
100+
// registered - safe to call from registerHandler() every time.
101+
static void ensureReaperThread() {
102+
static std::once_flag started;
103+
std::call_once(started, [] {
104+
int fds[2];
105+
pipe(fds);
106+
pipeReadFd() = fds[0];
107+
pipeWriteFd() = fds[1];
108+
std::thread([] {
109+
char sig;
110+
while (read(pipeReadFd(), &sig, 1) == 1) {
111+
int signum = (int)(unsigned char)sig;
112+
auto& handlers = getHandlers();
113+
auto it = handlers.find(signum);
114+
if (it != handlers.end()) {
115+
for (auto& func : it->second) {
116+
func(signum);
117+
}
118+
}
119+
// not exit(): see the class comment - avoids racing other still
120+
// -running threads against this thread's C++ static destructors.
121+
_exit(0);
122+
}
123+
}).detach();
124+
});
55125
}
56126
};
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#pragma once
2+
#include <WiFi.h>
3+
#include <WiFiUdp.h>
4+
#if defined(ESP32)
5+
#include <esp_wifi.h>
6+
#endif
7+
#include "ArduinoLogger.h"
8+
9+
namespace arduino {
10+
11+
/**
12+
* @brief A UDP class which makes sure that we can use UDP as
13+
* simple Stream w/o additional complexity.
14+
* When we did not specify a target IP address we send the data
15+
* to the address we received the data.
16+
* Single character writes will be buffered up to the defined write
17+
* buffer size (default 256 bytes)
18+
* @author Phil Schatzmann
19+
* @copyright GPLv3
20+
*/
21+
22+
class UDPStream : public Stream {
23+
public:
24+
/// Default Constructor
25+
UDPStream() = default;
26+
27+
/// Short hand constructor to start sending data to the indicated address /
28+
/// port
29+
UDPStream(IPAddress a, uint16_t port) { begin(a, port); }
30+
31+
/// @brief Defines an alternative UDP object. By default we use WiFiUDP
32+
/// @param udp
33+
void setUDP(UDP& udp) { p_udp = &udp; };
34+
35+
/// Always return 1492 (MTU 1500 - 8 byte header) as UDP packet available to
36+
/// write
37+
int availableForWrite() override { return 1492; }
38+
39+
/**
40+
* Provides the available size of the current package and if this is used up
41+
* of the next package
42+
*/
43+
int available() override {
44+
int size = p_udp->available();
45+
// if the curren package is used up we prvide the info for the next
46+
if (size == 0) {
47+
size = p_udp->parsePacket();
48+
}
49+
return size;
50+
}
51+
52+
/// Starts to send data to the indicated address / port
53+
bool begin(IPAddress a, uint16_t port) {
54+
remote_address_ext = a;
55+
remote_port_ext = port;
56+
return p_udp->begin(port);
57+
}
58+
59+
/// Starts to receive data from/with the indicated port
60+
bool begin(uint16_t port, uint16_t port_ext = 0) {
61+
remote_address_ext = IPAddress((uint32_t)0);
62+
remote_port_ext = port_ext != 0 ? port_ext : port;
63+
return p_udp->begin(port);
64+
}
65+
66+
/// Starts to receive data in multicast from/with the indicated address / port
67+
bool beginMulticast(IPAddress address, uint16_t port) {
68+
return p_udp->beginMulticast(address, port);
69+
}
70+
71+
/// Alternative to begin(IPAddress a, uint16_t port)
72+
bool setTarget(IPAddress a, uint16_t port) { return begin(a, port); }
73+
74+
/// We use the same remote port as defined in begin for write
75+
uint16_t remotePort() {
76+
uint16_t result = p_udp->remotePort();
77+
return result != 0 ? result : remote_port_ext;
78+
}
79+
80+
/// We use the same remote ip as defined in begin for write
81+
IPAddress remoteIP() {
82+
// Determine address if it has not been specified
83+
if ((uint32_t)remote_address_ext == 0) {
84+
remote_address_ext = p_udp->remoteIP();
85+
}
86+
// EmulatorLogger.log(EmulatorLogger::INFO, "ip: %u", remote_address_ext);
87+
return remote_address_ext;
88+
}
89+
90+
/// Replys will be sent to the initial remote caller
91+
size_t write(const uint8_t* data, size_t len) override {
92+
IPAddress remote = remoteIP();
93+
if ((uint32_t)remote == 0) {
94+
Logger.error("UDPStream", "no remote address defined");
95+
return 0;
96+
}
97+
p_udp->beginPacket(remote, remotePort());
98+
size_t result = p_udp->write(data, len);
99+
p_udp->endPacket();
100+
return result;
101+
}
102+
103+
/// Reads bytes using WiFi::readBytes
104+
size_t readBytes(uint8_t* data, size_t len) {
105+
size_t avail = available();
106+
size_t bytes_read = 0;
107+
if (avail > 0) {
108+
// get the data now
109+
bytes_read = p_udp->readBytes((uint8_t*)data, len);
110+
}
111+
return bytes_read;
112+
}
113+
114+
void stop() { p_udp->stop(); }
115+
116+
int peek() override { return p_udp->peek(); }
117+
118+
int read() override { return p_udp->read(); }
119+
120+
size_t write(uint8_t data) override {
121+
if (write_buffer.capacity() < write_buffer_size) {
122+
write_buffer.reserve(write_buffer_size);
123+
}
124+
write_buffer.push_back(data);
125+
if (write_buffer.size() >= write_buffer_size) {
126+
flush();
127+
}
128+
return 1;
129+
}
130+
131+
void flush() override {
132+
if (write_buffer.empty()) {
133+
return;
134+
}
135+
write(write_buffer.data(), write_buffer.size());
136+
write_buffer.clear();
137+
}
138+
139+
void setWriteBufferSize(size_t size) { write_buffer_size = size; }
140+
141+
protected:
142+
WiFiUDP default_udp;
143+
UDP* p_udp = &default_udp;
144+
uint16_t remote_port_ext = 0;
145+
IPAddress remote_address_ext;
146+
int write_buffer_size = 256;
147+
std::vector<uint8_t> write_buffer;
148+
};
149+
150+
} // namespace arduino
151+
152+
using WiFiUDPStream = arduino::UDPStream;

0 commit comments

Comments
 (0)