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, \ runShellCommandsAsSudo, generatedPNDownloadMbps, generatedPNUploadMbps, queuesAvailableOverride, \
OnAStick 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 # 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 # 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")) print("refreshShapersUpdateOnly completed on " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
if __name__ == '__main__': if __name__ == '__main__':
# Check that the host lqosd is running
if is_lqosd_alive(): if is_lqosd_alive():
print("lqosd is running") print("lqosd is running")
else: else:
print("ERROR: lqosd is not running. Aborting") print("ERROR: lqosd is not running. Aborting")
os._exit(-1) 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 = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
'-d', '--debug', '-d', '--debug',
@@ -1305,3 +1315,6 @@ if __name__ == '__main__':
else: else:
# Refresh and/or set up queues # Refresh and/or set up queues
refreshShapers() 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 = [ dependencies = [
"anyhow", "anyhow",
"lqos_bus", "lqos_bus",
"nix",
"pyo3", "pyo3",
"sysinfo",
"tokio", "tokio",
] ]

View File

@@ -12,3 +12,5 @@ pyo3 = "0.17"
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }
tokio = { version = "1.22", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1.22", features = [ "rt", "macros", "net", "io-util", "time" ] }
anyhow = "1" 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 lqos_bus::{BusRequest, BusResponse, TcHandle};
use nix::libc::getpid;
use pyo3::{ use pyo3::{
exceptions::PyOSError, pyclass, pyfunction, pymodule, types::PyModule, exceptions::PyOSError, pyclass, pyfunction, pymodule, types::PyModule,
wrap_pyfunction, PyResult, Python, wrap_pyfunction, PyResult, Python,
@@ -6,6 +8,9 @@ use pyo3::{
mod blocking; mod blocking;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use blocking::run_query; use blocking::run_query;
use sysinfo::{Pid, ProcessExt, System, SystemExt};
const LOCK_FILE: &str = "/run/lqos/libreqos.lock";
/// Defines the Python module exports. /// Defines the Python module exports.
/// All exported functions have to be listed here. /// 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!(delete_ip_mapping))?;
m.add_wrapped(wrap_pyfunction!(add_ip_mapping))?; m.add_wrapped(wrap_pyfunction!(add_ip_mapping))?;
m.add_wrapped(wrap_pyfunction!(validate_shaped_devices))?; 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(()) Ok(())
} }
@@ -147,3 +155,46 @@ fn validate_shaped_devices() -> PyResult<String> {
} }
Ok("".to_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"); let server = UnixSocketServer::new().expect("Unable to spawn server");
server.listen(handle_bus_requests).await server.listen(handle_bus_requests).await
}; };
tokio::spawn(listener); let handle = tokio::spawn(listener);
if lqos_config::LibreQoSConfig::config_exists() && lqos_config::ConfigShapedDevices::exists() { if lqos_config::LibreQoSConfig::config_exists() && lqos_config::ConfigShapedDevices::exists() {
warn!("Since all the files exist, Launching LibreQoS.py to avoid empty queues."); 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"); warn!("ispConfig.py or ShapedDevices.csv hasn't been setup yet. Not automatically running LibreQoS.py");
} }
info!("{:?}", handle.await?);
Ok(()) Ok(())
} }