ArduPilot / ArduPilot/MAVProxy

Mission Editor window sometimes never appears — child process hangs after fork() before executing any code

Open
#1,711 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
595
Forks
773
Avg merge
2d 6h
Merged PRs (30d)
18

Description

## Summary

While testing #1710 (taskbar icon fix), I found that under MAVProxy's normal
`module load misseditor` / `wp editor` flow, the Mission Editor window
sometimes never appears at all. This reproduces against unmodified `master`
as well — it is unrelated to #1710's changes.

## Steps to reproduce

```bash
mavproxy.py --console --map --master=udp:127.0.0.1:14550
# in the MAVProxy prompt:
MAV> module load misseditor
```

Observed: `Loaded module misseditor` is printed, a new `mavproxy.py` process
appears in `ps`, but no Mission Editor window ever appears on screen (checked
via `xprop -root _NET_CLIENT_LIST` — no new window ID is ever added).

## Diagnosis

`MissionEditorMain.__init__` (`MAVProxy/modules/mavproxy_misseditor/mission_editor.py`)
spawns the GUI in a child process via `multiproc.Process(target=self.child_task, ...)`
(effectively `fork()` on Linux, via the stdlib `multiprocessing` module).

Inspecting the stuck child process:

```
$ cat /proc//status | grep -E "State|Threads"
State: S (sleeping)
Threads: 1
$ cat /proc//wchan
futex_do_wait
```

The child has exactly one thread and is blocked on a futex, despite never
printing anything — I confirmed this by temporarily adding `print(...,
flush=True, file=sys.stderr)` statements at the very start of `child_task()`;
none of them fired.

This matches the well-known "fork() after a multi-threaded process has
already initialized libc/CPython internals" hazard: `fork()` only duplicates
the calling thread, not all threads, so if another thread in the parent
(MAVProxy's main process runs several background threads) held an internal
lock (e.g. the stdio/print lock, or a lock inside a C extension used by wx/
GTK) at the exact instant `fork()` happened, the child inherits that lock
already held — and permanently unheld-by-anyone, since the thread that would
release it doesn't exist in the child. The child then deadlocks the first
time it touches whatever that lock guards.

Console and Map are unaffected because they run directly in the main
process, not in a forked child — Mission Editor is the only wx window that's
deliberately forked into a child process, per this comment in the file:

> The key problem on MacOS is that you can't fork in any process that uses
> threading, which is almost all of processes as so many libraries use
> threads. So instead billiard uses an approach that uses fork+exec and
> re-runs the script in the child. It is horrible, but it seems to be the
> only way to make things work on MacOS

(from `MAVProxy/modules/lib/multiproc.py`) — so this fork-after-threading
hazard is a known, accepted risk on this code path, but on Linux (plain
`multiprocessing.Process`, not billiard's fork+exec workaround) it seems to
occasionally bite.

## Notes

- I did **not** modify `mission_editor.py`, and haven't attempted a fix here
— the multiprocessing/threading interaction is delicate and a proper fix
(e.g. switching Linux to a fork+exec or spawn-based approach similar to
billiard's, or using `os.register_at_fork()` to release/reset problematic
locks before forking) deserves its own focused PR and testing.
- The hang is intermittent-ish in my testing — I did not pin down the exact
trigger (which thread/lock), just confirmed the failure mode via
`/proc//status` and `/proc//wchan`.
- Tested on Ubuntu 24.04, Python 3.12, wxPython 4.2.1 (gtk3, wxWidgets 3.2.4).

## Environment

- OS: Ubuntu 24.04.4 LTS (GNOME, X11 session)
- Python: 3.12
- wxPython: 4.2.1 (gtk3 phoenix, wxWidgets 3.2.4)
- MAVProxy: current `master` (and reproduced independently of #1710)

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with MAVProxy/modules/mavproxy_misseditor/mission_editor.py and MAVProxy/modules/lib/multiproc.py, focusing on MissionEditorMain.__init__, child_task, and the multiprocessing path. Reproduce the hang with the documented MAVProxy command and inspect the child using /proc status and wchan. Done means the Mission Editor window reliably appears without the child remaining blocked after fork().

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, python
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.