1use crate::broker::backend_sdk::{FrameClient, FrameClientError};
31use crate::broker::client::{
32 broker_disabled_by_env, connect_to_backend, BackendConnection, BackendConnectionRoute,
33 BrokerClientError, BrokerDisableEnvError, ConnectBackendRequest,
34};
35use crate::broker::protocol::{Frame, Negotiated};
36
37pub struct BrokerSession {
46 client: FrameClient,
47 route: BackendConnectionRoute,
48 endpoint: String,
49 negotiated: Option<Negotiated>,
50}
51
52impl BrokerSession {
53 pub fn adopt(request: ConnectBackendRequest<'_>) -> Result<Self, AdoptError> {
61 if broker_disabled_by_env()? {
62 return Err(AdoptError::BrokerDisabled);
63 }
64 Ok(Self::from_connection(connect_to_backend(request)?))
65 }
66
67 fn from_connection(connection: BackendConnection) -> Self {
68 Self {
69 client: FrameClient::from_stream(connection.stream),
70 route: connection.route,
71 endpoint: connection.endpoint,
72 negotiated: connection.negotiated,
73 }
74 }
75
76 pub fn route(&self) -> BackendConnectionRoute {
78 self.route
79 }
80
81 pub fn endpoint(&self) -> &str {
83 &self.endpoint
84 }
85
86 pub fn negotiated(&self) -> Option<&Negotiated> {
88 self.negotiated.as_ref()
89 }
90
91 pub fn request(
93 &mut self,
94 payload_protocol: u32,
95 payload: Vec<u8>,
96 ) -> Result<Frame, FrameClientError> {
97 self.client.request(payload_protocol, payload)
98 }
99
100 pub fn client_mut(&mut self) -> &mut FrameClient {
102 &mut self.client
103 }
104
105 pub fn into_client(self) -> FrameClient {
107 self.client
108 }
109
110 pub fn into_backend_io(self) -> Result<OwnedBackendIo, IntoBackendIoError> {
125 let buffered = self.client.buffered_len();
126 if buffered != 0 {
127 return Err(IntoBackendIoError::BufferedResidual { buffered });
128 }
129 OwnedBackendIo::from_local_socket_stream(self.client.into_stream())
130 }
131}
132
133#[derive(Debug)]
141pub struct OwnedBackendIo {
142 #[cfg(unix)]
146 fd: std::os::fd::OwnedFd,
147}
148
149impl OwnedBackendIo {
150 #[cfg(unix)]
151 pub(crate) fn from_local_socket_stream(
152 stream: crate::platform::ipc::Stream,
153 ) -> Result<Self, IntoBackendIoError> {
154 Ok(Self {
155 fd: stream.into_owned_fd(),
156 })
157 }
158
159 #[cfg(windows)]
160 pub(crate) fn from_local_socket_stream(
161 _stream: crate::platform::ipc::Stream,
162 ) -> Result<Self, IntoBackendIoError> {
163 Err(IntoBackendIoError::WindowsUnsupported)
164 }
165
166 #[cfg(unix)]
168 pub fn into_owned_fd(self) -> std::os::fd::OwnedFd {
169 self.fd
170 }
171}
172
173#[cfg(unix)]
174impl std::os::fd::AsFd for OwnedBackendIo {
175 fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
176 self.fd.as_fd()
177 }
178}
179
180#[derive(Debug, thiserror::Error)]
183pub enum IntoBackendIoError {
184 #[error(
188 "frame client has {buffered} buffered response byte(s); cannot hand off the raw socket without losing them"
189 )]
190 BufferedResidual {
191 buffered: usize,
193 },
194 #[cfg(feature = "client-async")]
197 #[error("async frame client was poisoned by a prior request panic")]
198 Poisoned,
199 #[cfg(windows)]
202 #[error("into_backend_io() is not yet supported on Windows; the OwnedHandle path is deferred (#720)")]
203 WindowsUnsupported,
204}
205
206#[derive(Debug, thiserror::Error)]
208pub enum AdoptError {
209 #[error("broker disabled via RUNNING_PROCESS_DISABLE=1; use the direct path")]
212 BrokerDisabled,
213 #[error(transparent)]
215 DisableEnv(#[from] BrokerDisableEnvError),
216 #[error(transparent)]
219 Connect(#[from] BrokerClientError),
220 #[cfg(feature = "client-async")]
223 #[error("async adopt worker failed to join: {0}")]
224 AsyncJoin(String),
225}
226
227#[cfg(feature = "client-async")]
234#[derive(Clone, Debug)]
235pub struct OwnedConnectRequest {
236 pub broker_endpoint: String,
238 pub service_name: String,
240 pub wanted_version: String,
242 pub self_version: String,
244 pub cached_backend_endpoint: Option<String>,
246 pub client_version: String,
248 pub client_lib_name: String,
250 pub client_lib_version: String,
252 pub client_keepalive_secs: u64,
254 pub adopt_handed_off_connection: bool,
256 pub handoff_ready_timeout: std::time::Duration,
258}
259
260#[cfg(feature = "client-async")]
261impl OwnedConnectRequest {
262 pub fn new(
264 broker_endpoint: impl Into<String>,
265 service_name: impl Into<String>,
266 wanted_version: impl Into<String>,
267 self_version: impl Into<String>,
268 ) -> Self {
269 Self {
270 broker_endpoint: broker_endpoint.into(),
271 service_name: service_name.into(),
272 wanted_version: wanted_version.into(),
273 self_version: self_version.into(),
274 cached_backend_endpoint: None,
275 client_version: String::new(),
276 client_lib_name: "running-process".to_string(),
277 client_lib_version: env!("CARGO_PKG_VERSION").to_string(),
278 client_keepalive_secs: 0,
279 adopt_handed_off_connection: false,
280 handoff_ready_timeout: crate::broker::client::DEFAULT_HANDOFF_READY_TIMEOUT,
281 }
282 }
283
284 fn as_request(&self) -> ConnectBackendRequest<'_> {
285 ConnectBackendRequest {
286 broker_endpoint: &self.broker_endpoint,
287 service_name: &self.service_name,
288 wanted_version: &self.wanted_version,
289 self_version: &self.self_version,
290 cached_backend_endpoint: self.cached_backend_endpoint.as_deref(),
291 client_version: &self.client_version,
292 client_lib_name: &self.client_lib_name,
293 client_lib_version: &self.client_lib_version,
294 client_keepalive_secs: self.client_keepalive_secs,
295 adopt_handed_off_connection: self.adopt_handed_off_connection,
296 handoff_ready_timeout: self.handoff_ready_timeout,
297 }
298 }
299}
300
301#[cfg(feature = "client-async")]
310pub struct AsyncBrokerSession {
311 client: crate::broker::backend_sdk::AsyncFrameClient,
312 route: BackendConnectionRoute,
313 endpoint: String,
314 negotiated: Option<Negotiated>,
315}
316
317#[cfg(feature = "client-async")]
318impl AsyncBrokerSession {
319 pub async fn adopt(request: OwnedConnectRequest) -> Result<Self, AdoptError> {
322 let joined = tokio::task::spawn_blocking(move || adopt_async_blocking(request))
323 .await
324 .map_err(|err| AdoptError::AsyncJoin(err.to_string()))?;
325 let (route, endpoint, negotiated, client) = joined?;
326 Ok(Self {
327 client: crate::broker::backend_sdk::AsyncFrameClient::from_blocking(client),
328 route,
329 endpoint,
330 negotiated,
331 })
332 }
333
334 pub fn route(&self) -> BackendConnectionRoute {
336 self.route
337 }
338
339 pub fn endpoint(&self) -> &str {
341 &self.endpoint
342 }
343
344 pub fn negotiated(&self) -> Option<&Negotiated> {
346 self.negotiated.as_ref()
347 }
348
349 pub async fn request(
351 &mut self,
352 payload_protocol: u32,
353 payload: Vec<u8>,
354 ) -> Result<Frame, FrameClientError> {
355 self.client.request(payload_protocol, payload).await
356 }
357
358 pub fn into_client(self) -> crate::broker::backend_sdk::AsyncFrameClient {
360 self.client
361 }
362
363 pub fn into_backend_io(self) -> Result<OwnedBackendIo, IntoBackendIoError> {
372 let client = self
373 .client
374 .into_blocking()
375 .ok_or(IntoBackendIoError::Poisoned)?;
376 let buffered = client.buffered_len();
377 if buffered != 0 {
378 return Err(IntoBackendIoError::BufferedResidual { buffered });
379 }
380 OwnedBackendIo::from_local_socket_stream(client.into_stream())
381 }
382}
383
384#[cfg(feature = "client-async")]
385type AdoptedAsync = (
386 BackendConnectionRoute,
387 String,
388 Option<Negotiated>,
389 FrameClient,
390);
391
392#[cfg(feature = "client-async")]
399fn adopt_async_blocking(request: OwnedConnectRequest) -> Result<AdoptedAsync, AdoptError> {
400 if broker_disabled_by_env()? {
401 return Err(AdoptError::BrokerDisabled);
402 }
403
404 #[cfg(feature = "test-seams")]
405 if std::env::var_os(crate::broker::client::RUNNING_PROCESS_FAKE_BACKEND_ENV)
406 .is_some_and(|value| !value.is_empty())
407 {
408 return BrokerSession::adopt(request.as_request()).map(|session| {
409 (
410 session.route,
411 session.endpoint,
412 session.negotiated,
413 session.client,
414 )
415 });
416 }
417
418 if request.adopt_handed_off_connection {
419 return BrokerSession::adopt(request.as_request()).map(|session| {
420 (
421 session.route,
422 session.endpoint,
423 session.negotiated,
424 session.client,
425 )
426 });
427 }
428
429 if request.wanted_version == request.self_version {
430 if let Some(endpoint) = request.cached_backend_endpoint.as_deref() {
431 if let Ok(stream) = crate::broker::client::connect_local_socket(endpoint) {
432 return Ok((
433 BackendConnectionRoute::HelloSkip,
434 endpoint.to_owned(),
435 None,
436 FrameClient::from_stream(stream),
437 ));
438 }
439 }
440 }
441
442 let mut hello = request.as_request().hello();
443 hello.request_id = format!("client_v2-{}-{}", request.service_name, std::process::id());
444 let session = crate::broker::client_v2::connect_hello_at_endpoint_with_deadline(
445 request.broker_endpoint,
446 hello,
447 crate::broker::client::broker_client_deadline(),
448 )
449 .map_err(map_explicit_hello_error)?;
450 let negotiated = session.negotiated().clone();
451 let endpoint = negotiated.backend_pipe.clone();
452 let stream = session
453 .connect_backend_ipc()
454 .map_err(map_v2_backend_error)?;
455 Ok((
456 BackendConnectionRoute::BrokerNegotiated,
457 endpoint,
458 Some(negotiated),
459 FrameClient::from_stream(stream),
460 ))
461}
462
463#[cfg(feature = "client-async")]
464fn map_v2_broker_error(error: crate::broker::client_v2::BrokerV2Error) -> AdoptError {
465 use crate::broker::client_v2::BrokerV2Error;
466 let mapped = match error {
467 BrokerV2Error::Dial { source, .. } | BrokerV2Error::Io(source) => {
468 BrokerClientError::BrokerConnect(source)
469 }
470 BrokerV2Error::Framing(source) => BrokerClientError::Framing(source),
471 BrokerV2Error::Decode(source) => BrokerClientError::DecodeHelloReply(source),
472 BrokerV2Error::MissingResult => BrokerClientError::MissingHelloReplyResult,
473 BrokerV2Error::Refused {
474 reason,
475 retry_after_ms,
476 details,
477 } => BrokerClientError::Refused {
478 code: details.code(),
479 reason,
480 retry_after_ms,
481 },
482 other => BrokerClientError::BrokerConnect(std::io::Error::other(other.to_string())),
483 };
484 AdoptError::Connect(mapped)
485}
486
487#[cfg(feature = "client-async")]
488fn map_explicit_hello_error(error: crate::broker::client_v2::ExplicitHelloError) -> AdoptError {
489 use crate::broker::client_v2::ExplicitHelloError;
490 match error {
491 ExplicitHelloError::Broker(error) => map_v2_broker_error(error),
492 ExplicitHelloError::DecodeFrame(source) => {
493 AdoptError::Connect(BrokerClientError::DecodeFrame(source))
494 }
495 ExplicitHelloError::UnexpectedResponseFrame(reason) => {
496 AdoptError::Connect(BrokerClientError::UnexpectedResponseFrame(reason))
497 }
498 }
499}
500
501#[cfg(feature = "client-async")]
502fn map_v2_backend_error(error: crate::broker::client_v2::BackendDialError) -> AdoptError {
503 use crate::broker::client_v2::BackendDialError;
504 let mapped = match error {
505 BackendDialError::EmptyBackendPipe => BrokerClientError::EmptyBackendPipe,
506 BackendDialError::Connect(source) => BrokerClientError::BackendConnect(source),
507 BackendDialError::IntoBackendIo(source) => {
508 BrokerClientError::BackendConnect(std::io::Error::other(source.to_string()))
509 }
510 };
511 AdoptError::Connect(mapped)
512}