Skip to content

Commit caeb4f7

Browse files
fix(daemon): bound project open only when a writer is busy
1 parent 0a2c58a commit caeb4f7

2 files changed

Lines changed: 22 additions & 6 deletions

File tree

src/daemon.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ const DAEMON_SHUTDOWN_DEADLINE: Duration = Duration::from_secs(45);
7878
const DAEMON_CLIENT_DRAIN_DEADLINE: Duration = Duration::from_secs(15);
7979
#[cfg(unix)]
8080
const DAEMON_TASK_ABORT_DEADLINE: Duration = Duration::from_secs(2);
81+
/// How long a project open may queue behind an unrelated writer before the
82+
/// client is told to retry. The open itself keeps running in the background.
83+
#[cfg(unix)]
84+
const CONTENDED_PROJECT_OPEN_GRACE: Duration = Duration::from_millis(500);
8185

8286
#[derive(Clone, Default)]
8387
pub(crate) struct DaemonLifecycle {
@@ -2911,13 +2915,18 @@ async fn serve_broker_socket_client(
29112915
}
29122916
}
29132917
let server = if let Some(project_path) = handshake.project_path.as_ref() {
2918+
// Queuing behind an unrelated writer can take that writer's whole
2919+
// operation, so answer with a retry hint rather than holding the
2920+
// client. An uncontended open is this client's own work and must run
2921+
// to completion, otherwise one-shot callers never get a result.
2922+
let contended = engine.store_administration.writer_is_busy();
29142923
let mut project_open = engine.spawn_direct_project_server_open(handshake.clone());
2915-
let server = match tokio::time::timeout(
2916-
std::time::Duration::from_millis(500),
2917-
&mut project_open,
2918-
)
2919-
.await
2920-
{
2924+
let opened = if contended {
2925+
tokio::time::timeout(CONTENDED_PROJECT_OPEN_GRACE, &mut project_open).await
2926+
} else {
2927+
Ok((&mut project_open).await)
2928+
};
2929+
let server = match opened {
29212930
Ok(Ok(Ok(server))) => server,
29222931
Ok(Ok(Err(error))) => {
29232932
write_project_open_error(&mut transport, &first_request_line, &error).await?;

src/daemon/branch_admin.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ impl StoreAdministration {
120120
ensure_no_external_branch_store_holders(database_paths)
121121
}
122122

123+
/// Reports whether writer administration is already held. Work that would
124+
/// queue behind an unrelated writer can be delayed for that writer's whole
125+
/// operation, which callers may want to answer with a retry hint instead.
126+
pub(super) fn writer_is_busy(&self) -> bool {
127+
self.gate.try_lock().is_err()
128+
}
129+
123130
/// Acquires writer administration before constructing the supplied future
124131
/// and holds it until that future completes.
125132
pub(super) async fn with_writer<Operation, OperationFuture, Output>(

0 commit comments

Comments
 (0)