Skip to content

Commit

Permalink
*: add UDS support (mozilla#2206)
Browse files Browse the repository at this point in the history
* *: add UDS support

A new module named `net` is added to unify TCP socket and UDS.
By default, sccache server is still listen on local TCP port,
user can choose to listen on UDS by setting environment variable
`SCCACHE_SERVER_UDS`.

Generic is used in server implementation for best performance,
trait object is used in client implementation for simplicity and
better readability.

Close mozilla#933.

Signed-off-by: Jay Lee <[email protected]>

* fix linux compile errors

Signed-off-by: Jay Lee <[email protected]>

* fix format

Signed-off-by: Jay Lee <[email protected]>

* more fix

Signed-off-by: Jay Lee <[email protected]>

* fix windows

Signed-off-by: Jay Lee <[email protected]>

* address comment

Signed-off-by: Jay Lee <[email protected]>

* fix gcc failure

Signed-off-by: Jay Lee <[email protected]>

* address comment

Signed-off-by: Jay Lee <[email protected]>

* fix clippy

Signed-off-by: Jay Lee <[email protected]>

---------

Signed-off-by: Jay Lee <[email protected]>
  • Loading branch information
BusyJay authored Oct 22, 2024
1 parent 2932b22 commit 877746c
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 101 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ jobs:
env:
LLVM_VERSION: "16"
SCCACHE_GHA_ENABLED: "on"
SCCACHE_SERVER_UDS: "\\x00sccache.socket"

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -651,6 +652,7 @@ jobs:

env:
SCCACHE_GHA_ENABLED: "on"
SCCACHE_SERVER_UDS: "/home/runner/sccache.socket"

steps:
- uses: actions/checkout@v4
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ If you don't [specify otherwise](#storage-options), sccache will use a local dis

sccache works using a client-server model, where the server runs locally on the same machine as the client. The client-server model allows the server to be more efficient by keeping some state in memory. The sccache command will spawn a server process if one is not already running, or you can run `sccache --start-server` to start the background server process without performing any compilation.

By default sccache server will listen on `127.0.0.1:4226`, you can specify environment variable `SCCACHE_SERVER_PORT` to use a different port or `SCCACHE_SERVER_UDS` to listen on unix domain socket. Abstract unix socket is also supported as long as the path is escaped following the [format](https://doc.rust-lang.org/std/ascii/fn.escape_default.html). For example:

```
% env SCCACHE_SERVER_UDS=$HOME/sccache.sock sccache --start-server # unix socket
% env SCCACHE_SERVER_UDS=\\x00sccache.sock sccache --start-server # abstract unix socket
```

You can run `sccache --stop-server` to terminate the server. It will also terminate after (by default) 10 minutes of inactivity.

Running `sccache --show-stats` will print a summary of cache statistics.
Expand Down
32 changes: 16 additions & 16 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,28 @@
// limitations under the License.

use crate::errors::*;
use crate::net::Connection;
use crate::protocol::{Request, Response};
use crate::util;
use byteorder::{BigEndian, ByteOrder};
use retry::{delay::Fixed, retry};
use std::io::{self, BufReader, BufWriter, Read};
use std::net::TcpStream;

/// A connection to an sccache server.
pub struct ServerConnection {
/// A reader for the socket connected to the server.
reader: BufReader<TcpStream>,
reader: BufReader<Box<dyn Connection>>,
/// A writer for the socket connected to the server.
writer: BufWriter<TcpStream>,
writer: BufWriter<Box<dyn Connection>>,
}

impl ServerConnection {
/// Create a new connection using `stream`.
pub fn new(stream: TcpStream) -> io::Result<ServerConnection> {
let writer = stream.try_clone()?;
pub fn new(conn: Box<dyn Connection>) -> io::Result<ServerConnection> {
let write_conn = conn.try_clone()?;
Ok(ServerConnection {
reader: BufReader::new(stream),
writer: BufWriter::new(writer),
reader: BufReader::new(conn),
writer: BufWriter::new(write_conn),
})
}

Expand Down Expand Up @@ -62,24 +62,24 @@ impl ServerConnection {
}
}

/// Establish a TCP connection to an sccache server listening on `port`.
pub fn connect_to_server(port: u16) -> io::Result<ServerConnection> {
trace!("connect_to_server({})", port);
let stream = TcpStream::connect(("127.0.0.1", port))?;
ServerConnection::new(stream)
/// Establish a TCP connection to an sccache server listening on `addr`.
pub fn connect_to_server(addr: &crate::net::SocketAddr) -> io::Result<ServerConnection> {
trace!("connect_to_server({addr})");
let conn = crate::net::connect(addr)?;
ServerConnection::new(conn)
}

/// Attempt to establish a TCP connection to an sccache server listening on `port`.
/// Attempt to establish a TCP connection to an sccache server listening on `addr`.
///
/// If the connection fails, retry a few times.
pub fn connect_with_retry(port: u16) -> io::Result<ServerConnection> {
trace!("connect_with_retry({})", port);
pub fn connect_with_retry(addr: &crate::net::SocketAddr) -> io::Result<ServerConnection> {
trace!("connect_with_retry({addr})");
// TODOs:
// * Pass the server Child in here, so we can stop retrying
// if the process exited.
// * Send a pipe handle to the server process so it can notify
// us once it starts the server instead of us polling.
match retry(Fixed::from_millis(500).take(10), || connect_to_server(port)) {
match retry(Fixed::from_millis(500).take(10), || connect_to_server(addr)) {
Ok(conn) => Ok(conn),
Err(e) => Err(io::Error::new(
io::ErrorKind::TimedOut,
Expand Down
54 changes: 29 additions & 25 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,18 @@ pub const DEFAULT_PORT: u16 = 4226;
const SERVER_STARTUP_TIMEOUT: Duration = Duration::from_millis(10000);

/// Get the port on which the server should listen.
fn get_port() -> u16 {
env::var("SCCACHE_SERVER_PORT")
fn get_addr() -> crate::net::SocketAddr {
#[cfg(unix)]
if let Ok(addr) = env::var("SCCACHE_SERVER_UDS") {
if let Ok(uds) = crate::net::SocketAddr::parse_uds(&addr) {
return uds;
}
}
let port = env::var("SCCACHE_SERVER_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_PORT)
.unwrap_or(DEFAULT_PORT);
crate::net::SocketAddr::with_port(port)
}

/// Check if ignoring all response errors
Expand Down Expand Up @@ -293,28 +300,27 @@ fn run_server_process(startup_timeout: Option<Duration>) -> Result<ServerStartup
})
}

/// Attempt to connect to an sccache server listening on `port`, or start one if no server is running.
/// Attempt to connect to an sccache server listening on `addr`, or start one if no server is running.
fn connect_or_start_server(
port: u16,
addr: &crate::net::SocketAddr,
startup_timeout: Option<Duration>,
) -> Result<ServerConnection> {
trace!("connect_or_start_server({})", port);
match connect_to_server(port) {
trace!("connect_or_start_server({addr})");
match connect_to_server(addr) {
Ok(server) => Ok(server),
Err(ref e)
if e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::TimedOut =>
if (e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::TimedOut)
|| (e.kind() == io::ErrorKind::NotFound && addr.is_unix_path()) =>
{
// If the connection was refused we probably need to start
// the server.
match run_server_process(startup_timeout)? {
ServerStartup::Ok { port: actualport } => {
if port != actualport {
ServerStartup::Ok { addr: actual_addr } => {
if addr.to_string() != actual_addr {
// bail as the next connect_with_retry will fail
bail!(
"sccache: Listening on port {} instead of {}",
actualport,
port
"sccache: Listening on address {actual_addr} instead of {addr}"
);
}
}
Expand All @@ -324,7 +330,7 @@ fn connect_or_start_server(
ServerStartup::TimedOut => bail!("Timed out waiting for server startup. Maybe the remote service is unreachable?\nRun with SCCACHE_LOG=debug SCCACHE_NO_DAEMON=1 to get more information"),
ServerStartup::Err { reason } => bail!("Server startup failed: {}\nRun with SCCACHE_LOG=debug SCCACHE_NO_DAEMON=1 to get more information", reason),
}
let server = connect_with_retry(port)?;
let server = connect_with_retry(addr)?;
Ok(server)
}
Err(e) => Err(e.into()),
Expand Down Expand Up @@ -614,7 +620,7 @@ pub fn run_command(cmd: Command) -> Result<i32> {
match cmd {
Command::ShowStats(fmt, advanced) => {
trace!("Command::ShowStats({:?})", fmt);
let stats = match connect_to_server(get_port()) {
let stats = match connect_to_server(&get_addr()) {
Ok(srv) => request_stats(srv).context("failed to get stats from server")?,
// If there is no server, spawning a new server would start with zero stats
// anyways, so we can just return (mostly) empty stats directly.
Expand Down Expand Up @@ -658,18 +664,16 @@ pub fn run_command(cmd: Command) -> Result<i32> {
// We aren't asking for a log file
daemonize()?;
}
server::start_server(config, get_port())?;
server::start_server(config, &get_addr())?;
}
Command::StartServer => {
trace!("Command::StartServer");
println!("sccache: Starting the server...");
let startup =
run_server_process(startup_timeout).context("failed to start server process")?;
match startup {
ServerStartup::Ok { port } => {
if port != DEFAULT_PORT {
println!("sccache: Listening on port {}", port);
}
ServerStartup::Ok { addr } => {
println!("sccache: Listening on address {addr}");
}
ServerStartup::TimedOut => bail!("Timed out waiting for server startup"),
ServerStartup::AddrInUse => bail!("Server startup failed: Address in use"),
Expand All @@ -679,13 +683,13 @@ pub fn run_command(cmd: Command) -> Result<i32> {
Command::StopServer => {
trace!("Command::StopServer");
println!("Stopping sccache server...");
let server = connect_to_server(get_port()).context("couldn't connect to server")?;
let server = connect_to_server(&get_addr()).context("couldn't connect to server")?;
let stats = request_shutdown(server)?;
stats.print(false);
}
Command::ZeroStats => {
trace!("Command::ZeroStats");
let conn = connect_or_start_server(get_port(), startup_timeout)?;
let conn = connect_or_start_server(&get_addr(), startup_timeout)?;
request_zero_stats(conn).context("couldn't zero stats on server")?;
eprintln!("Statistics zeroed.");
}
Expand Down Expand Up @@ -747,7 +751,7 @@ pub fn run_command(cmd: Command) -> Result<i32> {
),
Command::DistStatus => {
trace!("Command::DistStatus");
let srv = connect_or_start_server(get_port(), startup_timeout)?;
let srv = connect_or_start_server(&get_addr(), startup_timeout)?;
let status =
request_dist_status(srv).context("failed to get dist-status from server")?;
serde_json::to_writer(&mut io::stdout(), &status)?;
Expand Down Expand Up @@ -785,7 +789,7 @@ pub fn run_command(cmd: Command) -> Result<i32> {
} => {
trace!("Command::Compile {{ {:?}, {:?}, {:?} }}", exe, cmdline, cwd);
let jobserver = unsafe { Client::new() };
let conn = connect_or_start_server(get_port(), startup_timeout)?;
let conn = connect_or_start_server(&get_addr(), startup_timeout)?;
let mut runtime = Runtime::new()?;
let res = do_compile(
ProcessCommandCreator::new(&jobserver),
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod dist;
mod jobserver;
pub mod lru_disk_cache;
mod mock_command;
mod net;
mod protocol;
pub mod server;
#[doc(hidden)]
Expand Down
Loading

0 comments on commit 877746c

Please sign in to comment.