#unix-socket #unix #async-io #non-blocking #io #daemon #async

suss

Create collections of single-instance unix socket services, started on-demand by any clients using them

4 releases

0.0.5 Aug 9, 2022
0.0.4 Aug 7, 2022
0.0.3 Aug 7, 2022
0.0.2 Aug 5, 2022

#60 in #unix-socket

AGPL-3.0-or-later

65KB
774 lines

suss

Suss is a library to create collections of unix services that can interact with each other and (optionally) start each other on-demand.

Furthermore, it is pluggable into different async frameworks as long as you provide an implementation of socket_shims::UnixSocketInterface . If you don't care about async at all, either use the standard threadpool implementation (based on blocking ), or write a synchronous API implementation and use a crate like pollster to un-async things.

It is hosted at:

Basic Example

Provide a single interface for a simple echo service, over any compatible unix socket interface - with the ability to start on demand by running a command.

use suss::prelude::*;
use suss::blocking;
use std::io::{Result as IoResult, BufRead};

struct EchoClient<U: UnixSocketInterface>(U::UnixStream);

impl <U: UnixSocketInterface>  EchoClient<U> {
    pub fn new(u: U::UnixStream) -> Self {
        Self(u)
    }

    pub async fn write_and_receive<'b>(&mut self, in_value: &'b [u8]) -> IoResult<Vec<u8>> {
        U::unix_stream_write_all(&mut self.0, &(in_value.len() as u64).to_be_bytes()).await?;
        U::unix_stream_write_all(&mut self.0, in_value).await?;
        let mut out_length_bytes = [0u8;8];
        U::unix_stream_read_exact(&mut self.0, &mut out_length_bytes).await?;
        let mut out_length = u64::from_be_bytes(out_length_bytes);
        let mut out = vec![0u8;out_length.try_into().unwrap()];
        U::unix_stream_read_exact(&mut self.0, &mut out).await?;
        Ok(out)
    }
}

declare_service!{
    /// This is a very nice echo service!
    EchoService <U> = {
        "my-echo-service" "--server" @ "echo-service.socks" as raw |stream| -> Io<EchoClient<U>> { Ok(EchoClient(stream)) } 
    } impl {U: UnixSocketInterface}
}

fn main() { 
    let reified: ReifiedService<_, suss::socket_shims::StdThreadpoolUSocks, _> = EchoService.reify("/var/run/".as_ref());
    
    futures_lite_block_on(async move {
        let mut service_api = reified.connect_to_running().await.expect("No service running");
        let stdin = std::io::stdin();
        let mut stdin = stdin.lock();
        let mut buf = String::new();
        loop {
            stdin.read_line(&mut buf).unwrap();
            if buf.trim() == "die" {
                break
            };
            let received_line = service_api.write_and_receive(buf.as_bytes()).await.unwrap();
            let valid_utf8 = std::str::from_utf8(&received_line).unwrap();
            println!("{valid_utf8}");
        }
    });
}

Uses

This library allows you to:

  • Define a list of services - and unix socket names - in a single location, along with an arbitrary interface wrapping a bare unix stream to act as a client connection, as well as a way to begin the service.
  • Provide implementations of the service in another package, or perhaps the same package in a different module. In theory, this will allow alternate implementations or perhaps test implementations to flourish, as long as the service can keep the clients happy.
  • Automatically attempt to detect running services on initial connection, and then try to start them in case a service was not running, along with an inbuilt mechanism for liveness checking (which is, admittedly, slightly intrusive in that it requires an argument to be passed to a new service in some manner).
  • Uses the asynchronous unix sockets available in your async runtime if present, via optional dependencies (async-std, tokio, and a threadpool-based std fallback are currently available). All of these frameworks can convert to and from standard library unix sockets, so you can use them freely in client and server implementations even as suss remains runtime-agnostic.
  • Provide a runtime directory as a "namespace" for your collection of services - this means that a collection of services can be run systemwide, per user, in some other configuration, all as long as you can provide a different base directory path for each collection of services you want to run.

Dependencies

~1–12MB
~148K SLoC