This program
-
Detects application uniqueness, if the application is a unique/primary instance, it launches a server, otherwise a client over a Unix domain socket.
-
Client will send a message that will trigger the server to invoke a time-heavy task.
-
When the task is running, the server will ignore all the incoming messages. This is currently worked-around by making the client send the current time to the server. Server keeps track of the heavy task and when it finishes. I'd like a way to actually drop the socket before the task starts but I can't find a way to re-activate the socket.
///// Begin imports
use {
nix::{
errno::Errno::EADDRINUSE,
sys::socket::{
bind, connect, recv, send, socket, AddressFamily, MsgFlags, SockAddr, SockFlag,
SockType, UnixAddr,
},
unistd::close,
Error::Sys,
}, // "0.15.0"
std::{error::Error, os::unix::io::RawFd},
};
///// End imports
///// Begin constsnts
static SOCK_ADDR: &'static str = "com.localserver.myapp.sock";
//// End constants
//// Begin macros
macro_rules! Expected {
() => {
Result<(), Box<dyn Error>>
};
($t:ty) => {
Result<$t, Box<dyn Error>>
}
}
//// End Macros
//// Begin helper functions
fn heavy_task() -> Expected!() {
println!("Performing heavy task");
std::thread::sleep(std::time::Duration::from_secs(5));
println!("Task finished.");
Ok(())
}
/// Return the current timestamp in nanosecond precision
fn get_time() -> Expected!(u128) {
Ok(std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_nanos())
}
//// End helper functions
fn server(msgsock: RawFd) -> Expected!() {
// listen(sock, MESSAGE_QUEUE_SIZE)?;
let mut earliest_non_busy_time: u128 = get_time()?;
loop {
println!("Listening...");
loop {
let mut buf = [0u8; std::mem::size_of::<u128>()];
let rval = recv(msgsock, &mut buf[..], MsgFlags::empty())?;
if rval != std::mem::size_of::<u128>() {
break;
} else {
let incoming_time = u128::from_ne_bytes(buf);
if incoming_time > earliest_non_busy_time {
heavy_task()?;
earliest_non_busy_time = get_time()?;
} else {
println!("Task request rejected since the task had already been running");
}
}
}
}
// Ok(()) // unreachable
}
fn client(sock: RawFd, addr: &SockAddr) -> Expected!() {
match connect(sock, addr) {
Ok(_) => {
let current_time: u128 = get_time()?;
let msg = current_time.to_ne_bytes();
if send(sock, &msg, MsgFlags::empty())? != std::mem::size_of::<u128>() {
close(sock)?;
panic!("Message could not be sent");
}
close(sock)?;
Ok(())
}
Err(e) => {
close(sock)?;
panic!("Error connecting to socket: {}", e);
}
}
}
fn main() -> Expected!() {
let sock: RawFd = socket(
AddressFamily::Unix,
SockType::Datagram,
SockFlag::empty(),
None, // Protocol
)?;
let addr = SockAddr::Unix(UnixAddr::new_abstract(SOCK_ADDR.as_bytes())?);
match bind(sock, &addr) {
Err(e) => match e {
Sys(EADDRINUSE) => {
println!("Secondary instance detected, launching client.");
match client(sock, &addr) {
Ok(_) => println!("Message sent."),
Err(_) => println!("Message sending failed."),
}
println!("Exiting...");
}
_ => {
panic!("Socket binding failed due to: {:?}", e);
}
},
Ok(_) => {
println!("Primary instance detected, launching server.");
server(sock)?;
}
}
Ok(())
}
See IPC on POSIX ($1893594) · Snippets · Snippets · GitLab if you want a continuous view
Further explained here: Detecting Application Uniqueness and interprocess communication using Unix Domain Socket ($1903637) · Snippets · Snippets · GitLab