refactor: move IPC roundtrips to single-owner transport task

This commit is contained in:
Milind Sharma 2026-02-18 23:27:04 +08:00
parent 5f37c40aec
commit 46f5d8b731
3 changed files with 77 additions and 41 deletions

View File

@ -205,13 +205,7 @@ impl KiCadClient {
.clone();
let request_bytes = envelope::encode_request(&token, &self.inner.client_name, command)?;
let transport = self.inner.clone();
let response_bytes = tokio::task::spawn_blocking(move || {
transport.transport.roundtrip(request_bytes.as_slice())
})
.await
.map_err(|err| KiCadError::RuntimeJoin(err.to_string()))??;
let response_bytes = self.inner.transport.roundtrip(request_bytes).await?;
let response = envelope::decode_response(&response_bytes)?;

View File

@ -19,6 +19,9 @@ pub enum KiCadError {
#[error("transport receive failed: {reason}")]
TransportReceive { reason: String },
#[error("transport task is unavailable")]
TransportClosed,
#[error("request timed out after {timeout:?}")]
Timeout { timeout: Duration },

View File

@ -1,19 +1,63 @@
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use nng::options::{Options, RecvTimeout, SendTimeout};
use nng::{Error as NngError, Protocol, Socket};
use tokio::sync::{mpsc, oneshot};
use crate::error::KiCadError;
const TRANSPORT_QUEUE_CAPACITY: usize = 64;
#[derive(Debug)]
pub(crate) struct Transport {
socket: Mutex<Socket>,
timeout: Duration,
request_tx: mpsc::Sender<TransportRequest>,
}
#[derive(Debug)]
struct TransportRequest {
request_bytes: Vec<u8>,
response_tx: oneshot::Sender<Result<Vec<u8>, KiCadError>>,
}
impl Transport {
pub(crate) fn connect(socket_uri: &str, timeout: Duration) -> Result<Self, KiCadError> {
let socket = configured_socket(socket_uri, timeout)?;
let (request_tx, mut request_rx) = mpsc::channel::<TransportRequest>(TRANSPORT_QUEUE_CAPACITY);
let worker_name = format!("kicad-ipc-transport-{}", std::process::id());
thread::Builder::new()
.name(worker_name)
.spawn(move || {
while let Some(request) = request_rx.blocking_recv() {
let response = socket_roundtrip(&socket, request.request_bytes.as_slice(), timeout);
let _ = request.response_tx.send(response);
}
})
.map_err(|err| KiCadError::Connection {
socket_uri: socket_uri.to_string(),
reason: err.to_string(),
})?;
Ok(Self { request_tx })
}
pub(crate) async fn roundtrip(&self, request_bytes: Vec<u8>) -> Result<Vec<u8>, KiCadError> {
let (response_tx, response_rx) = oneshot::channel();
self.request_tx
.send(TransportRequest {
request_bytes,
response_tx,
})
.await
.map_err(|_| KiCadError::TransportClosed)?;
response_rx.await.map_err(|_| KiCadError::TransportClosed)?
}
}
fn configured_socket(socket_uri: &str, timeout: Duration) -> Result<Socket, KiCadError> {
let socket = Socket::new(Protocol::Req0).map_err(|err| KiCadError::Connection {
socket_uri: socket_uri.to_string(),
reason: err.to_string(),
@ -38,29 +82,24 @@ impl Transport {
reason: err.to_string(),
})?;
Ok(Self {
socket: Mutex::new(socket),
timeout,
})
Ok(socket)
}
pub(crate) fn roundtrip(&self, request_bytes: &[u8]) -> Result<Vec<u8>, KiCadError> {
let guard = self
.socket
.lock()
.map_err(|_| KiCadError::InternalPoisoned)?;
guard
fn socket_roundtrip(
socket: &Socket,
request_bytes: &[u8],
timeout: Duration,
) -> Result<Vec<u8>, KiCadError> {
socket
.send(request_bytes)
.map_err(|(_, err)| map_send_error(err, self.timeout))?;
.map_err(|(_, err)| map_send_error(err, timeout))?;
let response = guard
let response = socket
.recv()
.map_err(|err| map_receive_error(err, self.timeout))?;
.map_err(|err| map_receive_error(err, timeout))?;
Ok(response.as_slice().to_vec())
}
}
fn map_send_error(error: NngError, timeout: Duration) -> KiCadError {
if error == NngError::TimedOut {