68 lines
2.7 KiB
C++
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
|