fwt_software/include/fgc/SingleInstance.h

68 lines
2.7 KiB
C++

#pragma once
#include <string>
namespace fgc {
// Refuses to let two copies of the program run at once. Two instances would
// fight over the motor's serial port, the camera, and the exposure store, so
// the second launch has to fail loudly rather than half-work.
//
// The mechanism is an advisory flock(2) on a lockfile holding the owner's PID.
// The lock lives in the binary (not the launcher script) so every route in -
// scripts/fgc, systemd, or a bare ./fire_gimbal_control over ssh - is guarded.
//
// There is deliberately no stale-lock cleanup: the kernel drops an flock when
// the holding process dies, including a SIGKILL or a power cut, so a lockfile
// left behind with a dead PID in it never blocks the next launch. The PID
// written inside is purely for the "already running as PID N" message.
//
// auto lock = SingleInstance::acquire(SingleInstance::defaultLockPath());
// if (!lock) { std::cerr << lock.error() << "\n"; return 2; }
// ... lock stays in scope for the lifetime of the run ...
class SingleInstance {
public:
// Default lockfile: /run/user/<uid>/fire_gimbal_control.lock when that
// directory exists, else /tmp/fire_gimbal_control-<uid>.lock. Derived from
// the uid rather than $XDG_RUNTIME_DIR so it is identical whether the
// program was launched from an interactive shell, a non-interactive
// `ssh host cmd`, or a tmux pane - see the note in the .cpp.
static std::string defaultLockPath();
// Try to take the lock. Never blocks. On failure the returned object is
// falsey and error() says why (including the holder's PID when we could
// read it). Always check before use.
static SingleInstance acquire(const std::string& path);
// A default-constructed instance holds nothing; useful as a placeholder
// when locking is disabled (--no-lock).
SingleInstance() = default;
~SingleInstance();
SingleInstance(SingleInstance&& other) noexcept;
SingleInstance& operator=(SingleInstance&& other) noexcept;
SingleInstance(const SingleInstance&) = delete;
SingleInstance& operator=(const SingleInstance&) = delete;
bool held() const { return fd_ >= 0; }
explicit operator bool() const { return held(); }
// Human-readable reason the acquire failed; empty when held().
const std::string& error() const { return error_; }
// PID of the process holding the lock, or 0 if unknown. Only meaningful
// after a failed acquire.
int holderPid() const { return holder_pid_; }
const std::string& path() const { return path_; }
// Release early (also done by the destructor). Safe to call twice.
void release();
private:
int fd_ = -1;
int holder_pid_ = 0;
std::string path_;
std::string error_;
};
} // namespace fgc