Accept successful completion of StorageRace and kill and reap finite examples that exceed their deadline. Run the native Linux regression with disposable storage as an unprivileged user, since container root cannot necessarily create realtime threads. Use tmpfs so per-round fsync calls do not turn the regression into a disk-latency test.
Exercise same-line updates and concurrent dirtying of another line for
one million rounds, yielding so single-CPU runners make progress. On Linux
hosts, also stall backend IO to verify buffer access and persistence of
concurrent writes, and inject a SITL write failure to check dirty-line retry.
Concurrent writes and flushes can lose dirty bits or race on the buffer. Snapshot and dequeue dirty lines under a semaphore, then write outside that lock so disk latency does not block buffer access; serialize flushes separately to preserve write order.
A writer could update a line before the flusher cleared its dirty bit,
leaving the new value only in RAM. Snapshot and dequeue under the buffer
semaphore, then perform backend IO under a separate flush semaphore so
parameter and mission reads remain accessible. Use a separate flash image
for compaction, and requeue failed writes without losing concurrent updates.
The comment has said since it was added (41744c884d) that gyros are
recalibrated first when on the ground and stationary. No code in the
reset path does this; the filter always resets with the existing gyro
biases. Correct the comment to match the behaviour.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpiNLTCnk5xHmTfPD7Uo8z
test Renode / cubeorangeplus-quadplane (push) Canceled after 0s
test scripts / build (astyle-cleanliness) (push) Canceled after 0s
test scripts / build (check_autotest_options) (push) Canceled after 0s
test scripts / build (logger_metadata) (push) Canceled after 0s
test scripts / build (param-file-validation) (push) Canceled after 0s
test scripts / build (param_parse) (push) Canceled after 0s
test scripts / build (python-cleanliness) (push) Canceled after 0s
test scripts / build (shellcheck) (push) Canceled after 0s
test scripts / build (validate_board_list) (push) Canceled after 0s
NET_Pn set to UDP_CLIENT connect()'d its socket to the configured
destination, which makes the kernel filter incoming packets to match
that exact source address *and port*. That's fine for the common case
of a device that replies from the same socket it was queried on, but
some devices (confirmed against a real Topotek KHP415 gimbal) reply
from a different, fixed source port instead - every reply was silently
dropped before ArduPilot's own code ever saw it, regardless of NET_Pn
config being otherwise correct.
For a unicast destination, stop calling connect() and use sendto()/an
unconnected recv() instead, checking the source IP ourselves (but not
the port) before accepting a packet. Broadcast and multicast
destinations keep the original connect()-based path unchanged, since
connect() also does necessary setup for them (joining the multicast
group via IP_ADD_MEMBERSHIP) unrelated to this fix, and neither is a
point-to-point relationship that could hit this problem in the first
place.
Verified against the real KHP415 (which replies from a fixed but
different port than it's queried on) by hand-crafting its wire
protocol and sending it from an unconnected socket. Regression-tested
against the existing TestLogDownloadMAVProxyNetwork suite (unicast/
multicast/broadcast UDP client, UDP server, TCP client/server) and the
full AP_Mount network autotest suite - no regressions.
Exercise the QuadPlane-specific parts of ArduPlane/GCS_MAVLink_Plane.cpp
which no existing test reached:
- AVAILABLE_MODES including the VTOL modes
- ATTITUDE_TARGET from the VTOL attitude controller
- PID_TUNING from the VTOL rate and vertical acceleration controllers
- HIGH_LATENCY2 target heading and altitude in VTOL modes
- NAV_TAKEOFF and DO_VTOL_TRANSITION rejection paths, and NAV_TAKEOFF
as COMMAND_INT
- precision landing using LANDING_TARGET
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Coverage analysis of the Plane and QuadPlane autotest suites showed
large parts of ArduPlane/GCS_MAVLink_Plane.cpp were never executed.
These tests exercise that code:
- AVAILABLE_MODES, including FLTMODE_GCSBLOCK and single-index requests
- rejection paths of DO_REPOSITION, DO_CHANGE_ALTITUDE, DO_CHANGE_SPEED,
GUIDED_CHANGE_SPEED/ALTITUDE/HEADING, MISSION_START, DO_LAND_START
and the VTOL commands on a non-VTOL Plane
- flying headings and course-over-ground with GUIDED_CHANGE_HEADING
- SET_POSITION_TARGET_LOCAL_NED altitude offsets
- SET_ATTITUDE_TARGET and SET_POSITION_TARGET_GLOBAL_INT being ignored
outside GUIDED, and an invalid frame being reported
- DO_RETURN_PATH_START as a command
- SET_HAGL triggering the landing flare
- DO_PARACHUTE enable, disable, unknown action and repeated release
- DO_SET_MISSION_CURRENT as a command
- DO_REPOSITION loiter radius and direction
- DO_SET_HOME while in RTL
- HEARTBEAT system_status through standby, active, failsafe and crash
- EXTENDED_SYS_STATE landed_state through a fixed-wing flight
- PID_TUNING axes selected by GCS_PID_MASK, including the landing PID
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MAVProxy can do some funky things with SIGTERM, accomodate it
also cut out a 0.1s pause during pexpect shutdown by nuking some of its attributes when we know the process is dead
WIND_VEL_VARIANCE_MAX is already a variance: the airspeed-present
path constrains tasDataDelayed.tasVariance with it, and
ConstrainVariances() clamps P[22][22] and P[23][23] to it directly.
Three seeding paths nevertheless applied sq() to it, asking for a
160,000 m^2/s^2 (400m/s 1-sigma) covariance on the wind states:
- the no-usable-airspeed branch of setWindMagStateLearningMode()
- the no-valid-heading branch below it
- the "allow EKF to relearn wind states rapidly" path, taken when
the wind states stop being treated as truth
This changes no estimator behaviour. All three seeds are clamped
back to WIND_VEL_VARIANCE_MAX by ConstrainVariances() before any
fusion step sees them: within one UpdateFilter() cycle
controlFilterModes() seeds, CovariancePrediction() (which contains
both the third site and the ConstrainVariances() call) runs next,
and the fusion selectors run after it. A four-run alternating A/B
on Plane.DeadreckoningNoAirSpeed shows the two arms indistinguishable
in wind estimate, convergence time (283-287 simulated seconds in all
four runs) and dead-reckoning divergence. The only observable
difference is in logging: a seed landing on a frame where
runUpdates is false is not clamped until the next prediction, so
XKV2 can record 160,000 for that one sample where it now records 400.
The value of the change is that it removes a units error that is
invisible only because a later clamp masks it, and that would bite
the moment the clamp moved, was relaxed, or a fourth site copied the
pattern.
The "use 2-sigma for faster initial convergence" comment on the
first site is replaced: the seed is the constant itself (20m/s
1-sigma), identical to the bound the airspeed-present branch clamps
to, and ConstrainVariances() would flatten any inflation regardless.
The first two sites arrived together in 59d31cc7d5 ("Rework
non-airspeed wind estimation"), which introduced the constant and
substituted it for a "typical wind speed" of 5.0f without dropping
that speed's sq(); the third repeated the pattern in ffde7f815c.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Vagrant: compile wxpython in home for resolute and future releases
switch to excluding older versions to avoid needing to change this when making new releases
dir_list() accepted a name matching dirname exactly, so opendir() of a
file's path succeeded, and readdir() then skipped past the end of that
file's name, taking the name from whatever followed it in memory. Over
MAVFTP, listing a path such as @ROMFS/locations.txt was enough.
Match only names with a separator after dirname.
opendir() took one of the four directory records, and kept it when the
directory turned out not to exist. Four such attempts left every later
ROMFS opendir() failing with ENFILE until a reboot; over MAVFTP, four
listings of a missing @ROMFS path were enough. It also returned without
setting errno.
Free the record and set ENOENT.
Reading SITL's 300 character ROMFS names must give names cut short and
terminated, and ending the directory's name at its separator must not
write into another open listing's entry. Both fail against the readdir()
these follow.
readdir() ends a directory's name by writing a terminator at the
directory separator's index, which is past the end of d_name for a
directory name that long. Only shorten the name to the directory where
that falls within the name as copied.
readdir() copied the name with strncpy(), which leaves it unterminated
when it fills d_name, so anything reading it as a string - GCS_FTP's
listing among them - could run off the end of it.
Truncate to leave room for the terminator, as the FATFS backend does.
Co-authored-by: David Buzz <davidbuzz@gmail.com>
readdir() copied at most sizeof(d_name) bytes including the terminator,
so a name as long as d_name was left without one. On ChibiOS d_name is
MAX_NAME_LEN (255) bytes and LittleFS allows 255 character names, so
such an entry's name ran on into d_type and, for a directory, beyond it,
for anything reading it as a string - GCS_FTP's listing among them.
Truncate to leave room for the terminator, as the FATFS backend does.
Neither of SITL's 300 character ROMFS names fits in a listing packet, so
listing their directory gives nothing, but finding them must not read or
write outside the names around them. Run under --asan, this caught
AP_ROMFS::dir_list() reading past the previous name.
SITL's ROMFS gains files which sort just before a directory of the same
name, at the top level and below it, so MAVFTPListROMFS checks such a
directory is still listed.
dir_list() lists a directory once, for its first file, by skipping any
file whose leading directory matches the previous file's. It compared
the directory's name without the separator after it, so "sub.txt",
which sorts just before "sub/", was taken for it and the directory was
never listed. At the top level the name was also taken to start one
character in.
Compare through the separator, from the start of the name.
dir_list() compares an entry's leading directory with the previous
entry's using memcmp() over the directory's length, which reads past the
end of the previous name when that is shorter. strncmp() stops at its
end.
Nothing listed @ROMFS. Walk it as a GCS browsing it would, and check every
file named in the build's embedded header is found, at its decompressed
size, with nothing extra and no directory listed twice. Then read
vehicleinfo.json out of it, which is stored compressed and takes many
reads, and check it matches the file it was built from.
SITL's ROMFS gains a file and a directory, each with a 300 character name,
so the tests can check listing them is safe: a directory entry's d_name
is 255 bytes on ChibiOS and 256 with glibc, so neither name fits. They go
under autotest_fixtures, where autotest's fixtures are kept apart from
anything a user embeds.
test Renode / cubeorangeplus-quadplane (push) Canceled after 0s
test scripts / build (astyle-cleanliness) (push) Canceled after 0s
test scripts / build (check_autotest_options) (push) Canceled after 0s
test scripts / build (logger_metadata) (push) Canceled after 0s
test scripts / build (param-file-validation) (push) Canceled after 0s
test scripts / build (param_parse) (push) Canceled after 0s
test scripts / build (python-cleanliness) (push) Canceled after 0s
test scripts / build (shellcheck) (push) Canceled after 0s
test scripts / build (validate_board_list) (push) Canceled after 0s
In AP_Baro_MS5837::_calculate_5837_02ba, the multiplication (dT * _cal_reg.c6) overflows a signed 32-bit integer when dT is positive (~250,000 at ~25-30 degC) and c6 is ~28,000, exceeding INT32_MAX (~7e9 vs 2.14e9). This resulted in truncated/invalid temperature readings (e.g. ~19 degC).
Cast dT to int64_t before multiplication to prevent overflow.
fetch_file_via_ftp() polled 'ftp status' for 'No transfer in progress'
and took that as completion, within a timeout measured in simulated
time. Both are wrong:
- the loop is paced by MAVProxy and pexpect, not the simulation. At
full speedup a single one-second expect consumes the whole
twenty-second sim-time budget, so a status poll which lands
mid-transfer fails the fetch about a second after it began, even
though the file arrived:
Wrote 7403 bytes to /tmp/tmptcsw8tm2 in 0.02s 391.1kByte/s
Timed out looking for No transfer in progress
Exception caught: expected complete transfer
- 'No transfer in progress' is also true before the transfer starts,
and after one is aborted, so it can yield an empty or truncated
file. PerfInfo has failed both ways:
Expected TasksV2 as first line first not ()
Expected EFI last not (AP_Generator::update ...)
Expect MAVProxy's 'Wrote N bytes to' message instead, which is printed
only once the whole file has been written, with a wall-clock timeout,
and retry the fetch a couple of times if it does not arrive.
install_script() honours install_name when choosing the destination, but
install_applet_script_context() recorded the source name for removal, so
a script installed under a different name was never cleaned up when the
context went away.
AerobaticsScripting installs Aerobatics/FixedWing/Schedules/AirShow.txt
as trick72.txt: context pop then tried to unlink scripts/AirShow.txt,
which had never existed, and left scripts/trick72.txt behind after every
run. The scripting engine itself never loads that leftover - it takes
only names ending in .lua - but plane_aerobatics.lua searches scripts/
for trick<n>.txt, so a later run reads the stale copy and would still
pass even if the install had broken.
install_script_module_context() and install_driver_script_context()
already resolve install_name this way; do the same here, and spell the
parameter out as they do rather than taking it through **kwargs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fails without the fix (EKF height 2.5 m high above 5 m), with only the
baro offset held (3.0 m drop at liftoff) and with only the reset skipped
(2.5 m high). Takeoff only; the touchdown half of the fix is not covered.
With EK3_RNG_USE_HGT, Copter uses a range finder that reads on the ground
for height while taking off, so the baro offset filter runs while the
motors spool up and learns the prop-wash baro error as offset. ALT_HOLD's
takeoff ends as the vehicle leaves the ground, the source goes back to baro
there, and the offset carries the error into the flight: in SITL with
SIM_BARO_GEFF_M 3 the EKF height read 2.5 m high from then until landing.
While takeoff or touchdown is expected, do not update the offset, and do
not reset the height to the baro on switching to it. Either alone is not
enough: with only the offset held, the reset drops the height by the whole
error at liftoff (3.0 m); with only the reset skipped, the offset still
carries it (2.5 m). With both, -0.17 m worst in the takeoff and 0.03 m mean
above 5 m.
Baro drift is not learned while the flags are set, which on Copter
includes the whole time it sits armed on the ground. Learning only the
upward part of the error was tried: in SITL it did no better, and baro
noise ratchets it upwards (0.07 m after a 10 s armed wait with
SIM_BARO_RND 0.2). Fixed wing sets the flags for a launch rather than for
rotor wash, so it is left as it was.
The check is about aiding in flight. With the flow focus floor from #34292
the flow on the ground after touchdown is not fused, so relative aiding
stops before the disarm and would count against this test. Count only
between NOT_LANDED and LAND_COMPLETE.
Fail every compass in flight, remove optical flow until aiding stops,
then restore it. Aiding has to restart, which needs the gyro bias check
to ignore the Z axis while no yaw is being fused.
Covers no yaw source, a compass that stops delivering data and GPS yaw
lost. The simulated gyro has no bias, so any Z bias the EKF learns is
phantom. The yaw reference is removed at arming and the flow scale error
doubled, so the bias grows within 40 s of flight rather than 240 s: yaw
fused during the climb otherwise holds it down.
Flow has to have been fused for a zero bias to mean anything. XKF5 flow
innovations do not show that, as they are written before the innovation
gate and never cleared, so the test requires that flow fusion started
and that aiding never stopped while armed, which with flow as the only
aiding source means an update passed the gate at least every 5 s.
Debug build, max Z gyro bias in deg/s against a 0.1 limit. Merge-base:
no yaw source 0.26 to 0.27 (4 runs), compass lost 0.95 to 1.10 (4
runs), GPS yaw lost 1.09 to 1.26 (3 runs). With the fix: 0.00, 0.01 and
0.01 to 0.02 (3 runs). The test takes about 23 s.
Adds an additional check that fuseEulerYaw actually fused the yaw rather than just attempting to fuse it. The most likely reason they can be different is the yaw value's innovations are higher than the gate
On the ground with EK3_MAG_CAL 7 the compass yaw is fused as an anchor
before 3-axis fusion. If the 3-axis fusion then fails its innovation
check, last_mag_yaw_fuse_ms was not refreshed although a yaw update
was applied.
checkGyroCalStatus() only drops the Z axis from the delAngBiasLearned
test when no yaw source is configured. Optical flow no longer learns
the Z gyro bias while no yaw is being fused, so with a yaw source that
is configured but not fusing, such as a failed compass, P[12][12] stays
above the threshold. delAngBiasLearned then stays false, and once a
flow dropout has stopped aiding, readyToUseOptFlow() never lets it
restart.
Use the same fusion test as the flow mask, so a configured source that
is not being fused is treated like no yaw source.
SITL, flow-only Copter, all compasses failed in flight, flow removed
until aiding stopped and then restored: before, aiding did not restart
within 30 s; after, it restarted 1.0 s after flow returned. Boot on a
default compass and GPS Copter and on Plane is unchanged: compass yaw
is first fused 0.2 s and 2.0 s after covariance init, long before the
bias variances converge.
With optical flow as the horizontal aiding source and no yaw reference
being fused, heading and the Z gyro bias are only poorly observable.
Flow fusion can absorb a flow-velocity error as a phantom Z gyro bias,
which then drifts yaw and walks the dead-reckoned position.
Mask state 12 out of the flow Kalman update unless GPS, compass or
external nav yaw has been fused within the last 5 s. The test reads
fusion timestamps, never the configured source: a source that is
configured but lost in flight, or a compass that stops delivering
data, is the case the mask is for. GPS yaw already records
last_gps_yaw_fuse_ms; external nav and the compass now record theirs
where a fusion is applied (last_extnav_yaw_fusion_ms is refreshed by
rejected samples too), and a yaw fusion only counts if FinishFusion()
applied it. Anything else inhibits, which is safe because any real yaw
fusion learns the bias through its own gains.
X/Y gyro bias remain observable via gravity and are unaffected. This is
the same K-only mask FuseVelPosNED already applies to poorly observable
gyro bias axes in AID_NONE.
SITL, flow-only Loiter for 240 s with a 20 percent flow scale error and
no real gyro bias, maximum learned Z gyro bias with no yaw source 0.30
-> 0.00 deg/s, with all compasses failed 0.30 -> 0.03, with GPS yaw
lost 0.23 -> 0.01.
publish_sens_para() declared baro_ok and mag_ok unconditionally but only
used them inside the AP_BARO_EXTERNALAHRS_ENABLED and
AP_COMPASS_EXTERNALAHRS_ENABLED blocks. With either feature disabled the
variable is unused and -Werror=unused-variable fails the build:
./waf configure --board sitl --disable-EXTERNALAHRS_BARO && ./waf copter
AP_ExternalAHRS_Aeron_plx.cpp:432:16: error: unused variable 'baro_ok'
Move each declaration inside the block that uses it. No behaviour change
with the default feature set.