From bdf370c5cdc015d26126d2b156159c69845e58da Mon Sep 17 00:00:00 2001 From: Matthias Engelhardt Date: Wed, 9 Sep 2026 08:35:44 +0200 Subject: [PATCH] Fix MAVLink listener socket leak and missing thread-signal reset app/mavlink/src/main/cpp/mavlink.cpp's listen() never closed its UDP socket on any exit path (bind failure, setsockopt failure, recv error, peer shutdown, or the normal loop exit once nativeStop() sets mavlink_thread_signal). The bound fd leaked for the life of the process, silently soaking up every datagram the kernel delivered to port 14550 even after the reading thread had exited -- so once the parser had been started and stopped once, nothing could ever read that port's traffic again for the rest of the process's life, confirmed via `netstat` showing an ever-growing, undrained receive queue. Separately, mavlink_thread_signal was never reset when starting again, so a second nativeStart() call would see the exit flag already set from a previous nativeStop() and exit its loop before reading a single packet. Neither was reachable through stock PixelPilot's own call pattern (nativeStart()/nativeStop() are each called at most once per app lifetime today: once at onCreate, once on the first backgrounding), so both bugs were latent. They matter for a caller that starts/stops the listener repeatedly during the same process -- verified on-device by toggling the listener on and off multiple times and confirming the receive queue no longer grows unbounded and every restart parses new traffic (rather than exiting immediately). Co-Authored-By: Claude Sonnet 5 --- app/mavlink/src/main/cpp/mavlink.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/mavlink/src/main/cpp/mavlink.cpp b/app/mavlink/src/main/cpp/mavlink.cpp index e1d3d310..35cc9063 100644 --- a/app/mavlink/src/main/cpp/mavlink.cpp +++ b/app/mavlink/src/main/cpp/mavlink.cpp @@ -78,6 +78,7 @@ void *listen(int mavlink_port) { if (bind(fd, (struct sockaddr *) (&addr), sizeof(addr)) != 0) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Unable to bind MavLink port %d: %s", mavlink_port, strerror(errno)); + close(fd); return 0; } @@ -88,6 +89,7 @@ void *listen(int mavlink_port) { if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Unable to bind MavLink rx timeout: %s", strerror(errno)); + close(fd); return 0; } @@ -102,11 +104,13 @@ void *listen(int mavlink_port) { continue; } else { __android_log_print(ANDROID_LOG_ERROR, TAG, "Error receiving mavlink: %s", strerror(errno)); + close(fd); return 0; } } else if (ret == 0) { // peer has done an orderly shutdown __android_log_print(ANDROID_LOG_ERROR, TAG, "Shutting down mavlink: ret=0"); + close(fd); return 0; } @@ -315,6 +319,7 @@ void *listen(int mavlink_port) { usleep(1); } + close(fd); __android_log_print(ANDROID_LOG_DEBUG, TAG, "Mavlink thread done."); return 0; } @@ -375,6 +380,11 @@ Java_com_openipc_mavlink_MavlinkNative_nativeCallBack(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT void JNICALL Java_com_openipc_mavlink_MavlinkNative_nativeStart(JNIEnv *env, jclass clazz, jobject context) { + // mavlink_thread_signal is how a previous listen() loop was told to exit; + // it has to be cleared here or a restart (e.g. toggling streaming mode off + // again) would see it already set and exit its loop before ever reading a + // packet. + mavlink_thread_signal = 0; auto threadFunction = []() { listen(14550); };