Describe the bug
MavlinkFTP closes an open session when no request has been received for a fixed idle time, even while the server itself is actively streaming a BurstReadFile to the client. Outgoing stream packets do not count as activity.
_last_work_buffer_access is only updated in _ensure_buffers_exist(), which is called from _process_request() — i.e. only when an FTP request arrives. send() then does:
} else if (_session_info.fd != -1) {
// close session without activity
if (hrt_elapsed_time(&_last_work_buffer_access) > 10_s) { // 30_s on main
::close(_session_info.fd);
_session_info.fd = -1;
_session_info.stream_download = false;
_last_reply_valid = false;
PX4_WARN("Session was closed without activity");
}
}
During a burst the GCS is not supposed to send anything — it just receives the stream. So on a slow link the server can spend most of that window busily transmitting a 35 KB chunk to the client and then declare the session idle.
Sequence observed with QGC over a SiK radio (57600 baud, PX4 v1.17):
OpenFileRO + BurstReadFile at t=0. Last request the server will see for a while.
- Server streams the 35 KB chunk. On this link that takes ~7 s. The final packet carrying
burst_complete=1 is lost.
- GCS waits its silence timeout (3 s) and re-requests
BurstReadFile from the last good offset at t=10.6 s.
- Server has already closed the session at t=10 s →
Nak InvalidSession. Download fails.
With a slower or rate-limited link the chunk alone can exceed the window (35 KB at 1200 B/s = 29 s), so even the 30 s value on main is reachable without any packet loss.
To Reproduce
- PX4 v1.17 (or
main, with a link slow enough that a 35 KB burst chunk plus the client's retry delay exceeds 30 s).
- Telemetry link ~1–4 KB/s (SiK radio at 57600 is enough).
- Download a file ≥ 35 KB via MAVLink FTP burst (e.g.
/etc/extras/parameters.json.xz).
- Have the client wait ≥ 3 s before re-requesting after the burst goes quiet.
Expected behavior
Streaming burst packets to the client should count as session activity. The idle timer should be reset (or not run) while _session_info.stream_download is true / while send() is emitting stream packets, so a session can only expire when the server is genuinely doing nothing for it.
Additional context
- The MAVLink FTP spec allows the server to expire idle sessions, so the client will also handle
InvalidSession by re-opening. This issue is about the server mis-measuring "idle".
- Suggested fix: update
_last_work_buffer_access in send() whenever a stream packet is emitted (or skip the idle check while stream_download is set). Two lines.
- Relevant:
_workBurst sets stream_download = true; send() streams in 35 KB chunks (stream_chunk_transmitted > 35000).
Log excerpt from the GCS side (QGC Vehicle.FTPManager), times in seconds:
28.808 Open File RO Ack, openFileLength 70256
28.809 Burst Read File request, offset 0 <- last request server sees
29.180 .. 36.291 burst data arriving (lossy link)
36.291 last accepted burst packet, offset 32982 <- chunk ends ~35000; burst_complete packet lost
39.386 Burst Read File retry, offset 33221 <- 10.58 s after last request
39.495 Nak "Invalid Session"
Describe the bug
MavlinkFTPcloses an open session when no request has been received for a fixed idle time, even while the server itself is actively streaming aBurstReadFileto the client. Outgoing stream packets do not count as activity._last_work_buffer_accessis only updated in_ensure_buffers_exist(), which is called from_process_request()— i.e. only when an FTP request arrives.send()then does:During a burst the GCS is not supposed to send anything — it just receives the stream. So on a slow link the server can spend most of that window busily transmitting a 35 KB chunk to the client and then declare the session idle.
Sequence observed with QGC over a SiK radio (57600 baud, PX4 v1.17):
OpenFileRO+BurstReadFileat t=0. Last request the server will see for a while.burst_complete=1is lost.BurstReadFilefrom the last good offset at t=10.6 s.Nak InvalidSession. Download fails.With a slower or rate-limited link the chunk alone can exceed the window (35 KB at 1200 B/s = 29 s), so even the 30 s value on
mainis reachable without any packet loss.To Reproduce
main, with a link slow enough that a 35 KB burst chunk plus the client's retry delay exceeds 30 s)./etc/extras/parameters.json.xz).Expected behavior
Streaming burst packets to the client should count as session activity. The idle timer should be reset (or not run) while
_session_info.stream_downloadis true / whilesend()is emitting stream packets, so a session can only expire when the server is genuinely doing nothing for it.Additional context
InvalidSessionby re-opening. This issue is about the server mis-measuring "idle"._last_work_buffer_accessinsend()whenever a stream packet is emitted (or skip the idle check whilestream_downloadis set). Two lines._workBurstsetsstream_download = true;send()streams in 35 KB chunks (stream_chunk_transmitted > 35000).Log excerpt from the GCS side (QGC
Vehicle.FTPManager), times in seconds: