Add --clean-processes to remove stale session pid files
Scans the same $XDG_STATE_HOME/slocker-lite/run/ directory as --list-processes and removes every pid file that's genuinely stale. The liveness check and the removal happen atomically per file (the same non-blocking flock() used to test it is held across the remove() call itself), rather than reusing a separate earlier scan, closing the race window where a new session could start in between. Only files actually removed are reported, one line each; sessions still running are left untouched and silently skipped. Refactored list_sessions() and the new clean_stale_sessions() to share a private open_session_file() helper for the open/read-pid/ recover-name step they both need. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
@@ -18,12 +18,18 @@ Source layout (all under `src/`):
|
|||||||
- `main.cpp` — CLI entry point, dependency checks, orchestration (`mount_image()`,
|
- `main.cpp` — CLI entry point, dependency checks, orchestration (`mount_image()`,
|
||||||
`run_container()`, `cleanup_image()`, `unmount_image()`, `list_images_command()`,
|
`run_container()`, `cleanup_image()`, `unmount_image()`, `list_images_command()`,
|
||||||
`inspect_image_command()`, `create_volume_command()`, `list_volumes_command()`,
|
`inspect_image_command()`, `create_volume_command()`, `list_volumes_command()`,
|
||||||
`delete_volume_command()`, `list_processes_command()`). `list_processes_command()`
|
`delete_volume_command()`, `list_processes_command()`, `clean_processes_command()`).
|
||||||
implements `--list-processes` (long-option only): calls `list_sessions()`
|
`list_processes_command()` implements `--list-processes` (long-option only):
|
||||||
(`pid_file.{h,cpp}`, see below) and prints one tab-aligned `pid`, `container
|
calls `list_sessions()` (`pid_file.{h,cpp}`, see below) and prints one
|
||||||
name`, `running`/`exited` row per entry (same two-column tab-alignment scheme
|
tab-aligned `pid`, `container name`, `running`/`exited` row per entry (same
|
||||||
as `list_images_command()`/`list_volumes_command()`, extended to a third
|
two-column tab-alignment scheme as `list_images_command()`/
|
||||||
column), no header row, silent success on an empty list.
|
`list_volumes_command()`, extended to a third column), no header row, silent
|
||||||
|
success on an empty list. `clean_processes_command()` implements
|
||||||
|
`--clean-processes` (also long-option only): calls `clean_stale_sessions()`
|
||||||
|
(`pid_file.{h,cpp}`) and prints one `removed stale pid file for '<name>' (pid
|
||||||
|
<pid>)` line per file actually removed — nothing is printed for sessions still
|
||||||
|
running, and an empty result (nothing stale) is silent success, same
|
||||||
|
convention as the rest of this file's list/delete commands.
|
||||||
`inspect_image_command()` implements `-i/--inspect
|
`inspect_image_command()` implements `-i/--inspect
|
||||||
<image.tar>`: prints every `OciImageConfig` field (user/group, exposed ports, env,
|
<image.tar>`: prints every `OciImageConfig` field (user/group, exposed ports, env,
|
||||||
volumes, default command) without mounting or running the image — extend it
|
volumes, default command) without mounting or running the image — extend it
|
||||||
@@ -206,7 +212,16 @@ Source layout (all under `src/`):
|
|||||||
released again immediately either way, never left held by the check itself. A
|
released again immediately either way, never left held by the check itself. A
|
||||||
file that can't be opened or doesn't parse as a pid (e.g. removed mid-scan) is
|
file that can't be opened or doesn't parse as a pid (e.g. removed mid-scan) is
|
||||||
silently skipped, not reported as an error — scanning a live directory is
|
silently skipped, not reported as an error — scanning a live directory is
|
||||||
inherently racy.
|
inherently racy. Both `list_sessions()` and `clean_stale_sessions()` (the
|
||||||
|
latter implements `--clean-processes`) share a private `open_session_file()`
|
||||||
|
helper for the open/read-pid/recover-name step. `clean_stale_sessions()`
|
||||||
|
doesn't just remove whatever a separate `list_sessions()` call reported as not
|
||||||
|
running — it re-takes the same non-blocking `flock()` used to test liveness
|
||||||
|
and holds it across the `remove()` call itself, per file, so the stale check
|
||||||
|
and the removal stay atomic against a new session starting in the gap between
|
||||||
|
a check and a later removal. Only files it actually removes are reported back
|
||||||
|
(as `SessionInfo`s with `running=false`); still-locked (running) files are
|
||||||
|
left untouched and not reported.
|
||||||
- `config_file.{h,cpp}` — `load_config_file()` reads and parses (via libyaml's
|
- `config_file.{h,cpp}` — `load_config_file()` reads and parses (via libyaml's
|
||||||
document API, `<yaml.h>`) the `global` and `volumes` sections of the local YAML
|
document API, `<yaml.h>`) the `global` and `volumes` sections of the local YAML
|
||||||
config file located by `config_file_path()` (`$XDG_CONFIG_HOME/slocker-lite/config.yaml`,
|
config file located by `config_file_path()` (`$XDG_CONFIG_HOME/slocker-lite/config.yaml`,
|
||||||
@@ -285,7 +300,7 @@ Build directory is `buildDir/` (already configured).
|
|||||||
full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`,
|
full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`,
|
||||||
`-l/--list-images`, `-i/--inspect`, `-n/--no-nsenter`, `--user`, `--group`,
|
`-l/--list-images`, `-i/--inspect`, `-n/--no-nsenter`, `--user`, `--group`,
|
||||||
`--hostname`, `-v/--volume`, `--list-volumes`, `--delete-volume`,
|
`--hostname`, `-v/--volume`, `--list-volumes`, `--delete-volume`,
|
||||||
`--delete-volume-full`, `--list-processes`, `-t/--test`, `--log-level`,
|
`--delete-volume-full`, `--list-processes`, `--clean-processes`, `-t/--test`, `--log-level`,
|
||||||
`-h/--help`, `-V/--version`)
|
`-h/--help`, `-V/--version`)
|
||||||
- Run tests: `meson test -C buildDir`
|
- Run tests: `meson test -C buildDir`
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ slocker-lite --list-volumes
|
|||||||
slocker-lite --delete-volume <name>
|
slocker-lite --delete-volume <name>
|
||||||
slocker-lite --delete-volume-full <name>
|
slocker-lite --delete-volume-full <name>
|
||||||
slocker-lite --list-processes
|
slocker-lite --list-processes
|
||||||
|
slocker-lite --clean-processes
|
||||||
slocker-lite -t|--test
|
slocker-lite -t|--test
|
||||||
slocker-lite -h|--help
|
slocker-lite -h|--help
|
||||||
slocker-lite -V|--version
|
slocker-lite -V|--version
|
||||||
@@ -85,6 +86,7 @@ slocker-lite -V|--version
|
|||||||
| `--delete-volume <name>` | Remove a named volume from the config. The host directory is left untouched. |
|
| `--delete-volume <name>` | Remove a named volume from the config. The host directory is left untouched. |
|
||||||
| `--delete-volume-full <name>` | Like `--delete-volume`, but also recursively deletes the volume's host directory. |
|
| `--delete-volume-full <name>` | Like `--delete-volume`, but also recursively deletes the volume's host directory. |
|
||||||
| `--list-processes` | List running `--run` sessions found by their pid files under `$XDG_STATE_HOME/slocker-lite/run/`, with their pid, container name, and status (`running` or `exited`). |
|
| `--list-processes` | List running `--run` sessions found by their pid files under `$XDG_STATE_HOME/slocker-lite/run/`, with their pid, container name, and status (`running` or `exited`). |
|
||||||
|
| `--clean-processes` | Remove stale pid files (see `--list-processes`) left behind by sessions that are no longer running. |
|
||||||
| `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. |
|
| `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. |
|
||||||
| `--log-level <level>` | Set log verbosity (`trace`, `debug`, `info`, `warn`, `error`, `critical`, `off`). |
|
| `--log-level <level>` | Set log verbosity (`trace`, `debug`, `info`, `warn`, `error`, `critical`, `off`). |
|
||||||
| `-h, --help` | Print usage and exit. |
|
| `-h, --help` | Print usage and exit. |
|
||||||
@@ -131,6 +133,9 @@ sudo ./buildDir/slocker-lite -r myimage.tar --user git
|
|||||||
|
|
||||||
# List currently running (and any leftover, exited) --run sessions
|
# List currently running (and any leftover, exited) --run sessions
|
||||||
./buildDir/slocker-lite --list-processes
|
./buildDir/slocker-lite --list-processes
|
||||||
|
|
||||||
|
# Remove any leftover, stale pid files
|
||||||
|
./buildDir/slocker-lite --clean-processes
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -177,7 +182,10 @@ be run concurrently without collisions. The file is removed automatically once t
|
|||||||
run ends; any tool can check whether a session is still alive by attempting the
|
run ends; any tool can check whether a session is still alive by attempting the
|
||||||
same exclusive, non-blocking `flock()` on its file. `--list-processes` does
|
same exclusive, non-blocking `flock()` on its file. `--list-processes` does
|
||||||
exactly that for every pid file it finds, reporting each one's pid, container
|
exactly that for every pid file it finds, reporting each one's pid, container
|
||||||
name, and `running`/`exited` status.
|
name, and `running`/`exited` status. Normally the file is removed automatically
|
||||||
|
when its own session ends, but `--clean-processes` removes any stale ones left
|
||||||
|
behind (e.g. after a crash) using that same check, atomically per file, so it
|
||||||
|
never removes one that's still genuinely running.
|
||||||
|
|
||||||
See `CLAUDE.md` for the full architecture writeup (file-by-file breakdown, the
|
See `CLAUDE.md` for the full architecture writeup (file-by-file breakdown, the
|
||||||
reasoning behind each of the above, and known gaps).
|
reasoning behind each of the above, and known gaps).
|
||||||
|
|||||||
+29
-8
@@ -57,14 +57,15 @@ enum class Mode {
|
|||||||
kDeleteVolume,
|
kDeleteVolume,
|
||||||
kDeleteVolumeFull,
|
kDeleteVolumeFull,
|
||||||
kInspect,
|
kInspect,
|
||||||
kListProcesses
|
kListProcesses,
|
||||||
|
kCleanProcesses
|
||||||
};
|
};
|
||||||
|
|
||||||
// --log-level/--user/--group/--list-volumes/--delete-volume[-full]/--hostname/
|
// --log-level/--user/--group/--list-volumes/--delete-volume[-full]/--hostname/
|
||||||
// --list-processes have no short form (--log-level's was freed up so -l could
|
// --list-processes/--clean-processes have no short form (--log-level's was freed
|
||||||
// become --list-images; -u is already --umount; the rest have no natural free
|
// up so -l could become --list-images; -u is already --umount; the rest have no
|
||||||
// letter left, or don't need one), so they need long-option vals outside the
|
// natural free letter left, or don't need one), so they need long-option vals
|
||||||
// printable-char range short options use.
|
// outside the printable-char range short options use.
|
||||||
constexpr int kLogLevelOpt = 256;
|
constexpr int kLogLevelOpt = 256;
|
||||||
constexpr int kUserOpt = 257;
|
constexpr int kUserOpt = 257;
|
||||||
constexpr int kGroupOpt = 258;
|
constexpr int kGroupOpt = 258;
|
||||||
@@ -73,8 +74,9 @@ constexpr int kDeleteVolumeOpt = 260;
|
|||||||
constexpr int kDeleteVolumeFullOpt = 261;
|
constexpr int kDeleteVolumeFullOpt = 261;
|
||||||
constexpr int kHostnameOpt = 262;
|
constexpr int kHostnameOpt = 262;
|
||||||
constexpr int kListProcessesOpt = 263;
|
constexpr int kListProcessesOpt = 263;
|
||||||
|
constexpr int kCleanProcessesOpt = 264;
|
||||||
|
|
||||||
constexpr std::array<struct option, 20> kLongOptions = {{
|
constexpr std::array<struct option, 21> kLongOptions = {{
|
||||||
{"help", no_argument, nullptr, 'h'},
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"version", no_argument, nullptr, 'V'},
|
{"version", no_argument, nullptr, 'V'},
|
||||||
{"test", no_argument, nullptr, 't'},
|
{"test", no_argument, nullptr, 't'},
|
||||||
@@ -94,6 +96,7 @@ constexpr std::array<struct option, 20> kLongOptions = {{
|
|||||||
{"inspect", required_argument, nullptr, 'i'},
|
{"inspect", required_argument, nullptr, 'i'},
|
||||||
{"hostname", required_argument, nullptr, kHostnameOpt},
|
{"hostname", required_argument, nullptr, kHostnameOpt},
|
||||||
{"list-processes", no_argument, nullptr, kListProcessesOpt},
|
{"list-processes", no_argument, nullptr, kListProcessesOpt},
|
||||||
|
{"clean-processes", no_argument, nullptr, kCleanProcessesOpt},
|
||||||
{nullptr, 0, nullptr, 0},
|
{nullptr, 0, nullptr, 0},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -110,6 +113,7 @@ void print_usage(const char* prog) {
|
|||||||
" {0} --delete-volume <name>\n"
|
" {0} --delete-volume <name>\n"
|
||||||
" {0} --delete-volume-full <name>\n"
|
" {0} --delete-volume-full <name>\n"
|
||||||
" {0} --list-processes\n"
|
" {0} --list-processes\n"
|
||||||
|
" {0} --clean-processes\n"
|
||||||
" {0} -t|--test\n"
|
" {0} -t|--test\n"
|
||||||
" {0} -h|--help\n"
|
" {0} -h|--help\n"
|
||||||
" {0} -V|--version\n"
|
" {0} -V|--version\n"
|
||||||
@@ -170,6 +174,9 @@ void print_usage(const char* prog) {
|
|||||||
" files under $XDG_STATE_HOME/slocker-lite/run/,\n"
|
" files under $XDG_STATE_HOME/slocker-lite/run/,\n"
|
||||||
" with their pid, container name, and status\n"
|
" with their pid, container name, and status\n"
|
||||||
" (running or exited)\n"
|
" (running or exited)\n"
|
||||||
|
" --clean-processes remove stale pid files (see --list-processes)\n"
|
||||||
|
" left behind by sessions that are no longer\n"
|
||||||
|
" running\n"
|
||||||
" -t, --test run the test suite\n"
|
" -t, --test run the test suite\n"
|
||||||
" --log-level <level> set log verbosity (trace, debug, info, warn,\n"
|
" --log-level <level> set log verbosity (trace, debug, info, warn,\n"
|
||||||
" error, critical, off)\n"
|
" error, critical, off)\n"
|
||||||
@@ -469,6 +476,13 @@ int list_processes_command() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int clean_processes_command() {
|
||||||
|
for (const auto& session : clean_stale_sessions()) {
|
||||||
|
fmt::print("removed stale pid file for '{}' (pid {})\n", session.container_name, session.pid);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int delete_volume_command(const std::string& name, const std::filesystem::path& config_path,
|
int delete_volume_command(const std::string& name, const std::filesystem::path& config_path,
|
||||||
AppConfig& config, bool delete_directory) {
|
AppConfig& config, bool delete_directory) {
|
||||||
auto it = std::find_if(config.volumes.begin(), config.volumes.end(),
|
auto it = std::find_if(config.volumes.begin(), config.volumes.end(),
|
||||||
@@ -628,7 +642,8 @@ int main(int argc, char* argv[]) {
|
|||||||
case kListVolumesOpt:
|
case kListVolumesOpt:
|
||||||
case kDeleteVolumeOpt:
|
case kDeleteVolumeOpt:
|
||||||
case kDeleteVolumeFullOpt:
|
case kDeleteVolumeFullOpt:
|
||||||
case kListProcessesOpt: {
|
case kListProcessesOpt:
|
||||||
|
case kCleanProcessesOpt: {
|
||||||
Mode requested;
|
Mode requested;
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 't':
|
case 't':
|
||||||
@@ -661,9 +676,12 @@ int main(int argc, char* argv[]) {
|
|||||||
case kDeleteVolumeFullOpt:
|
case kDeleteVolumeFullOpt:
|
||||||
requested = Mode::kDeleteVolumeFull;
|
requested = Mode::kDeleteVolumeFull;
|
||||||
break;
|
break;
|
||||||
default:
|
case kListProcessesOpt:
|
||||||
requested = Mode::kListProcesses;
|
requested = Mode::kListProcesses;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
requested = Mode::kCleanProcesses;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (mode != Mode::kNone && mode != requested) {
|
if (mode != Mode::kNone && mode != requested) {
|
||||||
spdlog::error("multiple actions specified");
|
spdlog::error("multiple actions specified");
|
||||||
@@ -776,6 +794,9 @@ int main(int argc, char* argv[]) {
|
|||||||
if (mode == Mode::kListProcesses) {
|
if (mode == Mode::kListProcesses) {
|
||||||
return list_processes_command();
|
return list_processes_command();
|
||||||
}
|
}
|
||||||
|
if (mode == Mode::kCleanProcesses) {
|
||||||
|
return clean_processes_command();
|
||||||
|
}
|
||||||
if (mode == Mode::kRun) {
|
if (mode == Mode::kRun) {
|
||||||
std::vector<std::string> command(argv + optind, argv + argc);
|
std::vector<std::string> command(argv + optind, argv + argc);
|
||||||
// As root, containers-storage mount doesn't need to reexec into a private
|
// As root, containers-storage mount doesn't need to reexec into a private
|
||||||
|
|||||||
+84
-25
@@ -56,6 +56,46 @@ std::filesystem::path session_run_dir() {
|
|||||||
return state_home / "slocker-lite" / "run";
|
return state_home / "slocker-lite" / "run";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ParsedSessionFile {
|
||||||
|
pid_t pid;
|
||||||
|
std::string container_name;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Opens `path` and reads back its pid (from the file's own contents, not parsed
|
||||||
|
// from the filename -- ambiguous for names that themselves contain '-') and
|
||||||
|
// recovers container_name by stripping the exact "-<pid>" suffix that pid implies
|
||||||
|
// back off the filename. Returns nullopt (logged at debug level, never an error --
|
||||||
|
// scanning a live directory is inherently racy) if the file can't be opened or its
|
||||||
|
// contents don't parse as a pid. The caller owns the returned fd and is
|
||||||
|
// responsible for closing it.
|
||||||
|
std::optional<ParsedSessionFile> open_session_file(const std::filesystem::path& path) {
|
||||||
|
int fd = open(path.c_str(), O_RDWR);
|
||||||
|
if (fd < 0) {
|
||||||
|
spdlog::debug("failed to open session file {}: {}", path.string(), strerror(errno));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[32] = {};
|
||||||
|
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
pid_t pid = n > 0 ? static_cast<pid_t>(std::atoi(buf)) : 0;
|
||||||
|
if (pid <= 0) {
|
||||||
|
spdlog::debug("skipping malformed session pid file {}", path.string());
|
||||||
|
close(fd);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string filename = path.filename().string();
|
||||||
|
std::string suffix = fmt::format("-{}", pid);
|
||||||
|
std::string container_name =
|
||||||
|
filename.size() > suffix.size() &&
|
||||||
|
filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0
|
||||||
|
? filename.substr(0, filename.size() - suffix.size())
|
||||||
|
: filename;
|
||||||
|
|
||||||
|
return ParsedSessionFile{pid, container_name, fd};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::filesystem::path session_pid_file_path(std::string_view container_name, pid_t pid) {
|
std::filesystem::path session_pid_file_path(std::string_view container_name, pid_t pid) {
|
||||||
@@ -117,39 +157,58 @@ std::vector<SessionInfo> list_sessions() {
|
|||||||
for (auto it = std::filesystem::directory_iterator(dir, it_ec);
|
for (auto it = std::filesystem::directory_iterator(dir, it_ec);
|
||||||
!it_ec && it != std::filesystem::directory_iterator(); it.increment(it_ec)) {
|
!it_ec && it != std::filesystem::directory_iterator(); it.increment(it_ec)) {
|
||||||
const auto& path = it->path();
|
const auto& path = it->path();
|
||||||
|
auto parsed = open_session_file(path);
|
||||||
int fd = open(path.c_str(), O_RDWR);
|
if (!parsed) {
|
||||||
if (fd < 0) {
|
|
||||||
spdlog::debug("failed to open session file {}: {}", path.string(), strerror(errno));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
char buf[32] = {};
|
|
||||||
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
|
||||||
pid_t pid = n > 0 ? static_cast<pid_t>(std::atoi(buf)) : 0;
|
|
||||||
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
|
if (flock(parsed->fd, LOCK_EX | LOCK_NB) == 0) {
|
||||||
running = false;
|
running = false;
|
||||||
flock(fd, LOCK_UN);
|
flock(parsed->fd, LOCK_UN);
|
||||||
}
|
}
|
||||||
close(fd);
|
close(parsed->fd);
|
||||||
|
|
||||||
if (pid <= 0) {
|
sessions.push_back(SessionInfo{parsed->pid, parsed->container_name, running, path});
|
||||||
spdlog::debug("skipping malformed session pid file {}", path.string());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = path.filename().string();
|
|
||||||
std::string suffix = fmt::format("-{}", pid);
|
|
||||||
std::string container_name =
|
|
||||||
filename.size() > suffix.size() &&
|
|
||||||
filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0
|
|
||||||
? filename.substr(0, filename.size() - suffix.size())
|
|
||||||
: filename;
|
|
||||||
|
|
||||||
sessions.push_back(SessionInfo{pid, container_name, running});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<SessionInfo> clean_stale_sessions() {
|
||||||
|
std::vector<SessionInfo> removed;
|
||||||
|
|
||||||
|
auto dir = session_run_dir();
|
||||||
|
std::error_code dir_ec;
|
||||||
|
if (!std::filesystem::is_directory(dir, dir_ec)) {
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code it_ec;
|
||||||
|
for (auto it = std::filesystem::directory_iterator(dir, it_ec);
|
||||||
|
!it_ec && it != std::filesystem::directory_iterator(); it.increment(it_ec)) {
|
||||||
|
const auto& path = it->path();
|
||||||
|
auto parsed = open_session_file(path);
|
||||||
|
if (!parsed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holding the same flock() used to test liveness across the removal
|
||||||
|
// itself (rather than reusing a separate, earlier list_sessions() result)
|
||||||
|
// keeps the check-then-remove atomic against a new session starting in
|
||||||
|
// between.
|
||||||
|
if (flock(parsed->fd, LOCK_EX | LOCK_NB) == 0) {
|
||||||
|
std::error_code rm_ec;
|
||||||
|
std::filesystem::remove(path, rm_ec);
|
||||||
|
if (rm_ec) {
|
||||||
|
spdlog::warn("failed to remove stale pid file {}: {}", path.string(), rm_ec.message());
|
||||||
|
} else {
|
||||||
|
removed.push_back(SessionInfo{parsed->pid, parsed->container_name, false, path});
|
||||||
|
}
|
||||||
|
flock(parsed->fd, LOCK_UN);
|
||||||
|
}
|
||||||
|
close(parsed->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ struct SessionInfo {
|
|||||||
pid_t pid;
|
pid_t pid;
|
||||||
std::string container_name; // as recovered from the pid file's own name (sanitized)
|
std::string container_name; // as recovered from the pid file's own name (sanitized)
|
||||||
bool running;
|
bool running;
|
||||||
|
std::filesystem::path path;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scans $XDG_STATE_HOME/slocker-lite/run/ (see session_pid_file_path()) for pid
|
// Scans $XDG_STATE_HOME/slocker-lite/run/ (see session_pid_file_path()) for pid
|
||||||
@@ -75,3 +76,14 @@ struct SessionInfo {
|
|||||||
// that's inherent to scanning a live directory) is skipped, not reported as an
|
// that's inherent to scanning a live directory) is skipped, not reported as an
|
||||||
// error. An empty or missing run directory yields an empty result, not an error.
|
// error. An empty or missing run directory yields an empty result, not an error.
|
||||||
std::vector<SessionInfo> list_sessions();
|
std::vector<SessionInfo> list_sessions();
|
||||||
|
|
||||||
|
// Scans the same run directory as list_sessions() and removes every pid file
|
||||||
|
// that's genuinely stale, returning the ones actually removed (running=false in
|
||||||
|
// each). Unlike calling list_sessions() and removing what it reports as not
|
||||||
|
// running, the stale check and the removal happen atomically per file (the same
|
||||||
|
// flock() acquired to test liveness is held across the removal itself), closing
|
||||||
|
// the race window where a new session could start between a separate check and a
|
||||||
|
// later removal. A file that can't be opened, doesn't parse as a pid, is still
|
||||||
|
// locked (still running), or fails to remove is left alone/skipped -- logged at
|
||||||
|
// most as a warning, never fatal; this is best-effort cleanup.
|
||||||
|
std::vector<SessionInfo> clean_stale_sessions();
|
||||||
|
|||||||
Reference in New Issue
Block a user