Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: add UDS support #2206

Merged
merged 9 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think rather than add another configuration know, SCCACHE_SERVER_PORT should be parsed as an int, and if that doesn't work, try as a UDS, and if that doesn't work, use the default TCP port.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, actually, if parsing doesn't work, it should be an error, rather than fallback. Only fallback when nothing is explicitly given.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying not to break compatibility here. And it seems confusing to me to parse something named "port" as a path. If we want to use one single environment variable, we should choose some name like "SCCACHE_SERVER_ADDR" instead.

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
Loading