Fix a real race: -d checked is_network_in_use() before async cleanup ran
Even after fixing is_network_in_use()'s own exit-code bug, the real device still failed the same way: both managed networks reported "still in use" milliseconds after their only session was confirmed stopped. Root cause: kill_session() only waits for the *sandboxed process itself* (via its cgroup) to die -- it says nothing about the separate, independently scheduled daemonized process that started it, which still has its own post-waitpid() cleanup left to run (stop_tap_relay() among it) before a tap+relay join's host-side device is actually detached from the bridge. Checking is_network_in_use() the instant kill_session() returns can genuinely still see it attached; on the real device, the kill+check pair landed single-digit milliseconds apart in the log. network_becomes_unused() retries is_network_in_use() for up to 10s (nanosleep()-based, matching this project's existing polling style) instead of giving up on the first still-attached answer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
+30
-1
@@ -25,6 +25,7 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
@@ -728,6 +729,34 @@ int compose_up_command(const std::string& images_directory, const std::string& c
|
||||
// state file (no -u/--up ever ran for this file) isn't an error -- just
|
||||
// nothing to stop.
|
||||
//
|
||||
// A killed session's own sandboxed process (what kill_session() actually
|
||||
// waits for, via the cgroup/pid-namespace/tracked-pid strategy it picks)
|
||||
// isn't the same process as the one that started it: run_mounted_container()
|
||||
// runs in the compose service's own daemonized process, blocked in its own
|
||||
// waitpid() on bwrap -- once that returns, *that* process still has to run
|
||||
// its own post-exit cleanup (stop_tap_relay() among it) before a tap+relay
|
||||
// join's own host-side device is actually removed from the bridge. Since
|
||||
// this happens in a completely separate, independently-scheduled process,
|
||||
// checking is_network_in_use() the instant kill_session() itself returns
|
||||
// can genuinely still see it as attached -- confirmed on the real Android
|
||||
// target device, where the two kill+check calls above landed only single-
|
||||
// digit milliseconds apart, well within the gap that process's own
|
||||
// scheduling can take on a loaded device. This retries for a bounded time
|
||||
// (nanosleep()-based, matching this project's own existing polling style)
|
||||
// rather than giving up on the very first still-attached answer.
|
||||
bool network_becomes_unused(const NetworkEntry& network, int timeout_ms, int interval_ms) {
|
||||
for (int waited = 0; waited < timeout_ms; waited += interval_ms) {
|
||||
if (!is_network_in_use(network)) {
|
||||
return true;
|
||||
}
|
||||
struct timespec ts {
|
||||
interval_ms / 1000, (interval_ms % 1000) * 1000000L
|
||||
};
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
return !is_network_in_use(network);
|
||||
}
|
||||
|
||||
// Networks and volumes are then handled asymmetrically, matching real
|
||||
// Compose's own default and the user's own explicit direction: volumes
|
||||
// always persist (never touched here at all -- a compose-managed volume
|
||||
@@ -768,7 +797,7 @@ int compose_down_command(const std::string& compose_file_name, AppConfig& config
|
||||
if (it == config.networks.end()) {
|
||||
continue; // never created, or already removed
|
||||
}
|
||||
if (is_network_in_use(*it)) {
|
||||
if (!network_becomes_unused(*it, 10000, 200)) {
|
||||
fmt::print("network '{}' is still in use, leaving it\n", actual_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user