Skip to content

fix(cli): prevent late inserts into PendingRequests after clear() in Drop #10408

@coderabbitai

Description

@coderabbitai

Summary

There is a pre-existing race condition in PendingRequests::drop() (in crates/biome_cli/src/service/mod.rs) that was present with DashMap and carries over to the current papaya::HashMap implementation.

Identified during: #10407 (comment: #10407 (comment))
Reported by: @ematipico

Scenario

Two live PendingRequests handles share the same Arc<HashMap>: one in SocketTransport, one in read_task.

  1. read_task terminates (error or shutdown) → its PendingRequests handle is dropped → Drop::drop() calls self.inner.pin().clear(). ✅
  2. A concurrent request() call on the still-alive SocketTransport inserts a new sender after the clear completes.
  3. No one ever clears that entry. The request silently waits the full 15-second timeout instead of failing fast with TransportError::ChannelClosed.

This is not a hard deadlock — it is a slow failure path.

Proposed Fix

Add an AtomicBool shutdown flag to PendingRequests:

use std::sync::atomic::{AtomicBool, Ordering};

#[derive(Clone, Default)]
struct PendingRequests {
    closed: Arc<AtomicBool>,
    inner: Arc<HashMap<u64, PendingChannel>>,
}

impl Drop for PendingRequests {
    fn drop(&mut self) {
        // Signal closure before clearing so any concurrent insert
        // that races with clear() can detect it and bail.
        self.closed.store(true, Ordering::Release);
        self.inner.pin().clear();
    }
}

And in WorkspaceTransport::request(), after inserting, check the flag and cancel immediately if already closed:

self.pending_requests
    .pin()
    .insert(request.id, Mutex::new(Some(send)));

// Guard against the drop having run concurrently.
if self.pending_requests.closed.load(Ordering::Acquire) {
    self.pending_requests.pin().remove(&request.id);
    return Err(TransportError::ChannelClosed);
}

This keeps the fast-cancel guarantee intact without adding heavier synchronisation.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions