Add safegaurd against running LibreQoS.py more than once at a time.

ISSUE #52

* Added file locking commands to the Python/Rust library.
* When LibreQoS.py starts, it checks that /var/run/libreqos.lock
  does not exist. If it does, it checks that it contains a PID
  and that PID is not still running with a python process.
* If the lock file exists and is valid, execution aborts.
* If the lock file exists and is invalid, it is cleaned.
* Cleans the lock on termination.
This commit is contained in:
Herbert Wolverson
2023-02-01 17:09:42 +00:00
parent d71f41033c
commit 9ad1de6ef5
5 changed files with 72 additions and 2 deletions

View File

@@ -23,7 +23,8 @@ from ispConfig import sqm, upstreamBandwidthCapacityDownloadMbps, upstreamBandwi
runShellCommandsAsSudo, generatedPNDownloadMbps, generatedPNUploadMbps, queuesAvailableOverride, \
OnAStick
from liblqos_python import is_lqosd_alive, clear_ip_mappings, delete_ip_mapping, validate_shaped_devices
from liblqos_python import is_lqosd_alive, clear_ip_mappings, delete_ip_mapping, validate_shaped_devices, \
is_libre_already_running, create_lock_file, free_lock_file
# Automatically account for TCP overhead of plans. For example a 100Mbps plan needs to be set to 109Mbps for the user to ever see that result on a speed test
# Does not apply to nodes of any sort, just endpoint devices
@@ -1260,12 +1261,21 @@ def refreshShapersUpdateOnly():
print("refreshShapersUpdateOnly completed on " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
if __name__ == '__main__':
# Check that the host lqosd is running
if is_lqosd_alive():
print("lqosd is running")
else:
print("ERROR: lqosd is not running. Aborting")
os._exit(-1)
# Check that we aren't running LibreQoS.py more than once at a time
if is_libre_already_running():
print("LibreQoS.py is already running in another process. Aborting.")
os._exit(-1)
# We've got this far, so create a lock file
create_lock_file()
parser = argparse.ArgumentParser()
parser.add_argument(
'-d', '--debug',
@@ -1305,3 +1315,6 @@ if __name__ == '__main__':
else:
# Refresh and/or set up queues
refreshShapers()
# Free the lock file
free_lock_file()

2
src/rust/Cargo.lock generated
View File

@@ -1362,7 +1362,9 @@ version = "0.1.0"
dependencies = [
"anyhow",
"lqos_bus",
"nix",
"pyo3",
"sysinfo",
"tokio",
]

View File

@@ -12,3 +12,5 @@ pyo3 = "0.17"
lqos_bus = { path = "../lqos_bus" }
tokio = { version = "1.22", features = [ "rt", "macros", "net", "io-util", "time" ] }
anyhow = "1"
sysinfo = "0"
nix = "0"

View File

@@ -1,4 +1,6 @@
use std::{path::Path, fs::{File, remove_file}, io::Write};
use lqos_bus::{BusRequest, BusResponse, TcHandle};
use nix::libc::getpid;
use pyo3::{
exceptions::PyOSError, pyclass, pyfunction, pymodule, types::PyModule,
wrap_pyfunction, PyResult, Python,
@@ -6,6 +8,9 @@ use pyo3::{
mod blocking;
use anyhow::{Error, Result};
use blocking::run_query;
use sysinfo::{Pid, ProcessExt, System, SystemExt};
const LOCK_FILE: &str = "/run/lqos/libreqos.lock";
/// Defines the Python module exports.
/// All exported functions have to be listed here.
@@ -18,6 +23,9 @@ fn liblqos_python(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(delete_ip_mapping))?;
m.add_wrapped(wrap_pyfunction!(add_ip_mapping))?;
m.add_wrapped(wrap_pyfunction!(validate_shaped_devices))?;
m.add_wrapped(wrap_pyfunction!(is_libre_already_running))?;
m.add_wrapped(wrap_pyfunction!(create_lock_file))?;
m.add_wrapped(wrap_pyfunction!(free_lock_file))?;
Ok(())
}
@@ -147,3 +155,46 @@ fn validate_shaped_devices() -> PyResult<String> {
}
Ok("".to_string())
}
#[pyfunction]
fn is_libre_already_running() -> PyResult<bool> {
let lock_path = Path::new(LOCK_FILE);
if lock_path.exists() {
let contents = std::fs::read_to_string(lock_path);
if let Ok(contents) = contents {
if let Ok(pid) = contents.parse::<i32>() {
let sys = System::new_all();
if let Some(process) = sys.processes().get(&Pid::from(pid)) {
if process.name().contains("python") {
return Ok(true);
}
}
} else {
println!("{LOCK_FILE} did not contain a valid PID");
return Ok(false);
}
} else {
println!("Error reading contents of {LOCK_FILE}");
return Ok(false);
}
}
Ok(false)
}
#[pyfunction]
fn create_lock_file() -> PyResult<()> {
let pid = unsafe { getpid() };
let pid_format = format!("{pid}");
{
if let Ok(mut f) = File::create(LOCK_FILE) {
f.write_all(pid_format.as_bytes())?;
}
}
Ok(())
}
#[pyfunction]
fn free_lock_file() -> PyResult<()> {
let _ = remove_file(LOCK_FILE); // Ignore result
Ok(())
}

View File

@@ -109,7 +109,7 @@ async fn main() -> Result<()> {
let server = UnixSocketServer::new().expect("Unable to spawn server");
server.listen(handle_bus_requests).await
};
tokio::spawn(listener);
let handle = tokio::spawn(listener);
if lqos_config::LibreQoSConfig::config_exists() && lqos_config::ConfigShapedDevices::exists() {
warn!("Since all the files exist, Launching LibreQoS.py to avoid empty queues.");
@@ -118,6 +118,8 @@ async fn main() -> Result<()> {
warn!("ispConfig.py or ShapedDevices.csv hasn't been setup yet. Not automatically running LibreQoS.py");
}
info!("{:?}", handle.await?);
Ok(())
}