Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:31:25.263Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

peer.rs

1375 lines · 52.9 KB · rust
1use super::{
2    Connection,
3    message_stream::{Message, MessageStream},
4    proto::{
5        self, AnyTypedEnvelope, EnvelopedMessage, PeerId, Receipt, RequestMessage, TypedEnvelope,
6    },
7};
8use anyhow::{Context as _, Result, anyhow};
9use collections::HashMap;
10use futures::{
11    FutureExt, SinkExt, StreamExt, TryFutureExt,
12    channel::{mpsc, oneshot},
13    stream::BoxStream,
14};
15use parking_lot::{Mutex, RwLock};
16use proto::{ErrorCode, ErrorCodeExt, ErrorExt, RpcError};
17use serde::{Serialize, ser::SerializeStruct};
18use std::{
19    fmt, future,
20    future::Future,
21    sync::atomic::Ordering::SeqCst,
22    sync::{
23        Arc,
24        atomic::{self, AtomicU32},
25    },
26    time::Duration,
27    time::Instant,
28};
29
30#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)]
31pub struct ConnectionId {
32    pub owner_id: u32,
33    pub id: u32,
34}
35
36impl From<ConnectionId> for PeerId {
37    fn from(id: ConnectionId) -> Self {
38        PeerId {
39            owner_id: id.owner_id,
40            id: id.id,
41        }
42    }
43}
44
45impl From<PeerId> for ConnectionId {
46    fn from(peer_id: PeerId) -> Self {
47        Self {
48            owner_id: peer_id.owner_id,
49            id: peer_id.id,
50        }
51    }
52}
53
54impl fmt::Display for ConnectionId {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "{}/{}", self.owner_id, self.id)
57    }
58}
59
60pub struct Peer {
61    epoch: AtomicU32,
62    pub connections: RwLock<HashMap<ConnectionId, ConnectionState>>,
63    next_connection_id: AtomicU32,
64}
65
66#[derive(Clone, Serialize)]
67pub struct ConnectionState {
68    #[serde(skip)]
69    outgoing_tx: mpsc::UnboundedSender<Message>,
70    next_message_id: Arc<AtomicU32>,
71    #[allow(clippy::type_complexity)]
72    #[serde(skip)]
73    response_channels: Arc<
74        Mutex<
75            Option<
76                HashMap<
77                    u32,
78                    oneshot::Sender<(proto::Envelope, std::time::Instant, oneshot::Sender<()>)>,
79                >,
80            >,
81        >,
82    >,
83    #[allow(clippy::type_complexity)]
84    #[serde(skip)]
85    stream_response_channels: Arc<
86        Mutex<
87            Option<
88                HashMap<u32, mpsc::UnboundedSender<(Result<proto::Envelope>, oneshot::Sender<()>)>>,
89            >,
90        >,
91    >,
92}
93
94const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
95const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
96pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(10);
97
98impl Peer {
99    pub fn new(epoch: u32) -> Arc<Self> {
100        Arc::new(Self {
101            epoch: AtomicU32::new(epoch),
102            connections: Default::default(),
103            next_connection_id: Default::default(),
104        })
105    }
106
107    pub fn epoch(&self) -> u32 {
108        self.epoch.load(SeqCst)
109    }
110
111    pub fn add_connection<F, Fut, Out>(
112        self: &Arc<Self>,
113        connection: Connection,
114        create_timer: F,
115    ) -> (
116        ConnectionId,
117        impl Future<Output = anyhow::Result<()>> + Send + use<F, Fut, Out>,
118        BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
119    )
120    where
121        F: Send + Fn(Duration) -> Fut,
122        Fut: Send + Future<Output = Out>,
123        Out: Send,
124    {
125        // For outgoing messages, use an unbounded channel so that application code
126        // can always send messages without yielding. For incoming messages, use a
127        // bounded channel so that other peers will receive backpressure if they send
128        // messages faster than this peer can process them.
129        #[cfg(any(test, feature = "test-support"))]
130        const INCOMING_BUFFER_SIZE: usize = 1;
131        #[cfg(not(any(test, feature = "test-support")))]
132        const INCOMING_BUFFER_SIZE: usize = 256;
133        let (mut incoming_tx, incoming_rx) = mpsc::channel(INCOMING_BUFFER_SIZE);
134        let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded();
135
136        let connection_id = ConnectionId {
137            owner_id: self.epoch.load(SeqCst),
138            id: self.next_connection_id.fetch_add(1, SeqCst),
139        };
140        let connection_state = ConnectionState {
141            outgoing_tx,
142            next_message_id: Default::default(),
143            response_channels: Arc::new(Mutex::new(Some(Default::default()))),
144            stream_response_channels: Arc::new(Mutex::new(Some(Default::default()))),
145        };
146        let mut writer = MessageStream::new(connection.tx);
147        let mut reader = MessageStream::new(connection.rx);
148
149        let this = self.clone();
150        let response_channels = connection_state.response_channels.clone();
151        let stream_response_channels = connection_state.stream_response_channels.clone();
152
153        let handle_io = async move {
154            tracing::trace!(%connection_id, "handle io future: start");
155
156            let _end_connection = util::defer(|| {
157                response_channels.lock().take();
158                if let Some(channels) = stream_response_channels.lock().take() {
159                    for channel in channels.values() {
160                        let _ = channel.unbounded_send((
161                            Err(anyhow!("connection closed")),
162                            oneshot::channel().0,
163                        ));
164                    }
165                }
166                this.connections.write().remove(&connection_id);
167                tracing::trace!(%connection_id, "handle io future: end");
168            });
169
170            // Send messages on this frequency so the connection isn't closed.
171            let keepalive_timer = create_timer(KEEPALIVE_INTERVAL).fuse();
172            futures::pin_mut!(keepalive_timer);
173
174            // Disconnect if we don't receive messages at least this frequently.
175            let receive_timeout = create_timer(RECEIVE_TIMEOUT).fuse();
176            futures::pin_mut!(receive_timeout);
177
178            loop {
179                tracing::trace!(%connection_id, "outer loop iteration start");
180                let read_message = reader.read().fuse();
181                futures::pin_mut!(read_message);
182
183                loop {
184                    tracing::trace!(%connection_id, "inner loop iteration start");
185                    futures::select_biased! {
186                        outgoing = outgoing_rx.next().fuse() => match outgoing {
187                            Some(outgoing) => {
188                                tracing::trace!(%connection_id, "outgoing rpc message: writing");
189                                futures::select_biased! {
190                                    result = writer.write(outgoing).fuse() => {
191                                        tracing::trace!(%connection_id, "outgoing rpc message: done writing");
192                                        result.context("failed to write RPC message")?;
193                                        tracing::trace!(%connection_id, "keepalive interval: resetting after sending message");
194                                        keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
195                                    }
196                                    _ = create_timer(WRITE_TIMEOUT).fuse() => {
197                                        tracing::trace!(%connection_id, "outgoing rpc message: writing timed out");
198                                        anyhow::bail!("timed out writing message");
199                                    }
200                                }
201                            }
202                            None => {
203                                tracing::trace!(%connection_id, "outgoing rpc message: channel closed");
204                                return Ok(())
205                            },
206                        },
207                        _ = keepalive_timer => {
208                            tracing::trace!(%connection_id, "keepalive interval: pinging");
209                            futures::select_biased! {
210                                result = writer.write(Message::Ping).fuse() => {
211                                    tracing::trace!(%connection_id, "keepalive interval: done pinging");
212                                    result.context("failed to send keepalive")?;
213                                    tracing::trace!(%connection_id, "keepalive interval: resetting after pinging");
214                                    keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
215                                }
216                                _ = create_timer(WRITE_TIMEOUT).fuse() => {
217                                    tracing::trace!(%connection_id, "keepalive interval: pinging timed out");
218                                    anyhow::bail!("timed out sending keepalive");
219                                }
220                            }
221                        }
222                        incoming = read_message => {
223                            let incoming = incoming.context("error reading rpc message from socket")?;
224                            tracing::trace!(%connection_id, "incoming rpc message: received");
225                            tracing::trace!(%connection_id, "receive timeout: resetting");
226                            receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse());
227                            if let (Message::Envelope(incoming), received_at) = incoming {
228                                tracing::trace!(%connection_id, "incoming rpc message: processing");
229                                futures::select_biased! {
230                                    result = incoming_tx.send((incoming, received_at)).fuse() => match result {
231                                        Ok(_) => {
232                                            tracing::trace!(%connection_id, "incoming rpc message: processed");
233                                        }
234                                        Err(_) => {
235                                            tracing::trace!(%connection_id, "incoming rpc message: channel closed");
236                                            return Ok(())
237                                        }
238                                    },
239                                    _ = create_timer(WRITE_TIMEOUT).fuse() => {
240                                        tracing::trace!(%connection_id, "incoming rpc message: processing timed out");
241                                        anyhow::bail!("timed out processing incoming message");
242                                    }
243                                }
244                            }
245                            break;
246                        },
247                        _ = receive_timeout => {
248                            tracing::trace!(%connection_id, "receive timeout: delay between messages too long");
249                            anyhow::bail!("delay between messages too long");
250                        }
251                    }
252                }
253            }
254        };
255
256        let response_channels = connection_state.response_channels.clone();
257        let stream_response_channels = connection_state.stream_response_channels.clone();
258        self.connections
259            .write()
260            .insert(connection_id, connection_state);
261
262        let incoming_rx = incoming_rx.filter_map(move |(incoming, received_at)| {
263            let response_channels = response_channels.clone();
264            let stream_response_channels = stream_response_channels.clone();
265            async move {
266                let message_id = incoming.id;
267                tracing::trace!(?incoming, "incoming message future: start");
268                let _end = util::defer(move || {
269                    tracing::trace!(%connection_id, message_id, "incoming message future: end");
270                });
271
272                if let Some(responding_to) = incoming.responding_to {
273                    tracing::trace!(
274                        %connection_id,
275                        message_id,
276                        responding_to,
277                        "incoming response: received"
278                    );
279                    let response_channel =
280                        response_channels.lock().as_mut()?.remove(&responding_to);
281                    let terminal_stream_response = matches!(
282                        &incoming.payload,
283                        Some(proto::envelope::Payload::Error(_))
284                            | Some(proto::envelope::Payload::EndStream(_))
285                    );
286                    let stream_response_channel = if terminal_stream_response {
287                        stream_response_channels
288                            .lock()
289                            .as_mut()?
290                            .remove(&responding_to)
291                    } else {
292                        stream_response_channels
293                            .lock()
294                            .as_ref()?
295                            .get(&responding_to)
296                            .cloned()
297                    };
298
299                    if let Some(tx) = response_channel {
300                        let requester_resumed = oneshot::channel();
301                        if let Err(error) = tx.send((incoming, received_at, requester_resumed.0)) {
302                            tracing::trace!(
303                                %connection_id,
304                                message_id,
305                                responding_to = responding_to,
306                                ?error,
307                                "incoming response: request future dropped",
308                            );
309                        }
310
311                        tracing::trace!(
312                            %connection_id,
313                            message_id,
314                            responding_to,
315                            "incoming response: waiting to resume requester"
316                        );
317                        let _ = requester_resumed.1.await;
318                        tracing::trace!(
319                            %connection_id,
320                            message_id,
321                            responding_to,
322                            "incoming response: requester resumed"
323                        );
324                    } else if let Some(tx) = stream_response_channel {
325                        let requester_resumed = oneshot::channel();
326                        if let Err(error) = tx.unbounded_send((Ok(incoming), requester_resumed.0)) {
327                            tracing::debug!(
328                                %connection_id,
329                                message_id,
330                                responding_to = responding_to,
331                                ?error,
332                                "incoming stream response: request future dropped",
333                            );
334                            // The consumer has gone away, so drop the bookkeeping
335                            // for this stream rather than letting it accumulate
336                            // every subsequent message until a terminal frame.
337                            if let Some(channels) = stream_response_channels.lock().as_mut() {
338                                channels.remove(&responding_to);
339                            }
340                        } else {
341                            let _ = requester_resumed.1.await;
342                        }
343                    } else {
344                        let message_type = proto::build_typed_envelope(
345                            connection_id.into(),
346                            received_at,
347                            incoming,
348                        )
349                        .map(|p| p.payload_type_name());
350                        tracing::warn!(
351                            %connection_id,
352                            message_id,
353                            responding_to,
354                            message_type,
355                            "incoming response: unknown request"
356                        );
357                    }
358
359                    None
360                } else {
361                    tracing::trace!(%connection_id, message_id, "incoming message: received");
362                    proto::build_typed_envelope(connection_id.into(), received_at, incoming)
363                        .or_else(|| {
364                            tracing::error!(
365                                %connection_id,
366                                message_id,
367                                "unable to construct a typed envelope"
368                            );
369                            None
370                        })
371                }
372            }
373        });
374        (connection_id, handle_io, incoming_rx.boxed())
375    }
376
377    #[cfg(any(test, feature = "test-support"))]
378    pub fn add_test_connection(
379        self: &Arc<Self>,
380        connection: Connection,
381        executor: gpui::BackgroundExecutor,
382    ) -> (
383        ConnectionId,
384        impl Future<Output = anyhow::Result<()>> + Send + use<>,
385        BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
386    ) {
387        self.add_connection(connection, move |duration| executor.timer(duration))
388    }
389
390    pub fn disconnect(&self, connection_id: ConnectionId) {
391        self.connections.write().remove(&connection_id);
392    }
393
394    #[cfg(any(test, feature = "test-support"))]
395    pub fn reset(&self, epoch: u32) {
396        self.next_connection_id.store(0, SeqCst);
397        self.epoch.store(epoch, SeqCst);
398    }
399
400    pub fn teardown(&self) {
401        self.connections.write().clear();
402    }
403
404    /// Make a request and wait for a response.
405    pub fn request<T: RequestMessage>(
406        &self,
407        receiver_id: ConnectionId,
408        request: T,
409    ) -> impl Future<Output = Result<T::Response>> + use<T> {
410        self.request_internal(None, receiver_id, request)
411            .map_ok(|envelope| envelope.payload)
412    }
413
414    pub fn request_envelope<T: RequestMessage>(
415        &self,
416        receiver_id: ConnectionId,
417        request: T,
418    ) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> + use<T> {
419        self.request_internal(None, receiver_id, request)
420    }
421
422    pub fn forward_request<T: RequestMessage>(
423        &self,
424        sender_id: ConnectionId,
425        receiver_id: ConnectionId,
426        request: T,
427    ) -> impl Future<Output = Result<T::Response>> {
428        self.request_internal(Some(sender_id), receiver_id, request)
429            .map_ok(|envelope| envelope.payload)
430    }
431
432    fn request_internal<T: RequestMessage>(
433        &self,
434        original_sender_id: Option<ConnectionId>,
435        receiver_id: ConnectionId,
436        request: T,
437    ) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> + use<T> {
438        let envelope = request.into_envelope(0, None, original_sender_id.map(Into::into));
439        let response = self.request_dynamic(receiver_id, envelope, T::NAME);
440        async move {
441            let (response, received_at) = response.await?;
442            Ok(TypedEnvelope {
443                message_id: response.id,
444                sender_id: receiver_id.into(),
445                original_sender_id: response.original_sender_id,
446                payload: T::Response::from_envelope(response)
447                    .context("received response of the wrong type")?,
448                received_at,
449            })
450        }
451    }
452
453    /// Make a request and wait for a response.
454    ///
455    /// The caller must make sure to deserialize the response into the request's
456    /// response type. This interface is only useful in trait objects, where
457    /// generics can't be used. If you have a concrete type, use `request`.
458    pub fn request_dynamic(
459        &self,
460        receiver_id: ConnectionId,
461        mut envelope: proto::Envelope,
462        type_name: &'static str,
463    ) -> impl Future<Output = Result<(proto::Envelope, Instant)>> + use<> {
464        let (tx, rx) = oneshot::channel();
465        let send = self.connection_state(receiver_id).and_then(|connection| {
466            envelope.id = connection.next_message_id.fetch_add(1, SeqCst);
467            connection
468                .response_channels
469                .lock()
470                .as_mut()
471                .context("connection was closed")?
472                // requesting to forward the response on the oneshot tx
473                // when it's envelope.id matches the one for the request we are about to send
474                .insert(envelope.id, tx);
475            connection
476                .outgoing_tx
477                // request that the message is send at some point in the future
478                .unbounded_send(Message::Envelope(envelope))
479                .context("connection was closed")?;
480            Ok(())
481        });
482        async move {
483            send?; // wait for reception
484            let (response, received_at, _barrier) = rx.await.context("connection was closed")?;
485            if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
486                return Err(RpcError::from_proto(error, type_name));
487            }
488            Ok((response, received_at))
489        }
490    }
491
492    pub fn request_stream<T: RequestMessage>(
493        &self,
494        receiver_id: ConnectionId,
495        request: T,
496    ) -> impl Future<Output = Result<BoxStream<'static, Result<T::Response>>>> {
497        let stream =
498            self.request_stream_dynamic(receiver_id, request.into_envelope(0, None, None), T::NAME);
499
500        async move {
501            Ok(stream
502                .await?
503                .map(|response| {
504                    T::Response::from_envelope(response?)
505                        .context("received response of the wrong type")
506                })
507                .boxed())
508        }
509    }
510
511    pub fn request_stream_dynamic(
512        &self,
513        receiver_id: ConnectionId,
514        mut envelope: proto::Envelope,
515        request_type: &'static str,
516    ) -> impl Future<Output = Result<BoxStream<'static, Result<proto::Envelope>>>> + use<> {
517        let (tx, rx) = mpsc::unbounded();
518        let send = self.connection_state(receiver_id).and_then(|connection| {
519            let message_id = connection.next_message_id.fetch_add(1, SeqCst);
520            envelope.id = message_id;
521            let stream_response_channels = connection.stream_response_channels.clone();
522            stream_response_channels
523                .lock()
524                .as_mut()
525                .context("connection was closed")?
526                .insert(message_id, tx);
527            if let Err(error) = connection
528                .outgoing_tx
529                .unbounded_send(Message::Envelope(envelope))
530            {
531                if let Some(channels) = stream_response_channels.lock().as_mut() {
532                    channels.remove(&message_id);
533                }
534                return Err(error).context("connection was closed");
535            }
536            Ok((message_id, stream_response_channels))
537        });
538
539        async move {
540            let (message_id, stream_response_channels) = send?;
541            let stream_response_channels = Arc::downgrade(&stream_response_channels);
542            let cleanup_stream_response_channel = util::defer({
543                let stream_response_channels = stream_response_channels.clone();
544                move || {
545                    if let Some(channels) = stream_response_channels.upgrade()
546                        && let Some(channels) = channels.lock().as_mut()
547                    {
548                        channels.remove(&message_id);
549                    }
550                }
551            });
552
553            Ok(rx
554                .filter_map(move |(response, _barrier)| {
555                    let _keep_cleanup_guard_alive = &cleanup_stream_response_channel;
556                    let stream_response_channels = stream_response_channels.clone();
557                    future::ready(match response {
558                        Ok(response) => {
559                            if let Some(proto::envelope::Payload::Error(error)) = &response.payload
560                            {
561                                // Remove the transmitting end of the response channel to end the stream.
562                                if let Some(channels) = stream_response_channels.upgrade()
563                                    && let Some(channels) = channels.lock().as_mut()
564                                {
565                                    channels.remove(&message_id);
566                                }
567                                Some(Err(RpcError::from_proto(error, request_type)))
568                            } else if let Some(proto::envelope::Payload::EndStream(_)) =
569                                &response.payload
570                            {
571                                // Remove the transmitting end of the response channel to end the stream.
572                                if let Some(channels) = stream_response_channels.upgrade()
573                                    && let Some(channels) = channels.lock().as_mut()
574                                {
575                                    channels.remove(&message_id);
576                                }
577                                None
578                            } else {
579                                Some(Ok(response))
580                            }
581                        }
582                        Err(error) => Some(Err(error)),
583                    })
584                })
585                .boxed())
586        }
587    }
588
589    pub fn send<T: EnvelopedMessage>(&self, receiver_id: ConnectionId, message: T) -> Result<()> {
590        let connection = self.connection_state(receiver_id)?;
591        let message_id = connection
592            .next_message_id
593            .fetch_add(1, atomic::Ordering::SeqCst);
594        connection.outgoing_tx.unbounded_send(Message::Envelope(
595            message.into_envelope(message_id, None, None),
596        ))?;
597        Ok(())
598    }
599
600    pub fn send_dynamic(&self, receiver_id: ConnectionId, message: proto::Envelope) -> Result<()> {
601        let connection = self.connection_state(receiver_id)?;
602        connection
603            .outgoing_tx
604            .unbounded_send(Message::Envelope(message))?;
605        Ok(())
606    }
607
608    pub fn forward_send<T: EnvelopedMessage>(
609        &self,
610        sender_id: ConnectionId,
611        receiver_id: ConnectionId,
612        message: T,
613    ) -> Result<()> {
614        let connection = self.connection_state(receiver_id)?;
615        let message_id = connection
616            .next_message_id
617            .fetch_add(1, atomic::Ordering::SeqCst);
618        connection
619            .outgoing_tx
620            .unbounded_send(Message::Envelope(message.into_envelope(
621                message_id,
622                None,
623                Some(sender_id.into()),
624            )))?;
625        Ok(())
626    }
627
628    pub fn respond<T: RequestMessage>(
629        &self,
630        receipt: Receipt<T>,
631        response: T::Response,
632    ) -> Result<()> {
633        let connection = self.connection_state(receipt.sender_id.into())?;
634        let message_id = connection
635            .next_message_id
636            .fetch_add(1, atomic::Ordering::SeqCst);
637        connection
638            .outgoing_tx
639            .unbounded_send(Message::Envelope(response.into_envelope(
640                message_id,
641                Some(receipt.message_id),
642                None,
643            )))?;
644        Ok(())
645    }
646
647    pub fn end_stream<T: RequestMessage>(&self, receipt: Receipt<T>) -> Result<()> {
648        let connection = self.connection_state(receipt.sender_id.into())?;
649        let message_id = connection
650            .next_message_id
651            .fetch_add(1, atomic::Ordering::SeqCst);
652
653        let message = proto::EndStream {};
654
655        connection
656            .outgoing_tx
657            .unbounded_send(Message::Envelope(message.into_envelope(
658                message_id,
659                Some(receipt.message_id),
660                None,
661            )))?;
662        Ok(())
663    }
664
665    pub fn respond_with_error<T: RequestMessage>(
666        &self,
667        receipt: Receipt<T>,
668        response: proto::Error,
669    ) -> Result<()> {
670        let connection = self.connection_state(receipt.sender_id.into())?;
671        let message_id = connection
672            .next_message_id
673            .fetch_add(1, atomic::Ordering::SeqCst);
674        connection
675            .outgoing_tx
676            .unbounded_send(Message::Envelope(response.into_envelope(
677                message_id,
678                Some(receipt.message_id),
679                None,
680            )))?;
681        Ok(())
682    }
683
684    pub fn respond_with_unhandled_message(
685        &self,
686        sender_id: ConnectionId,
687        request_message_id: u32,
688        message_type_name: &'static str,
689    ) -> Result<()> {
690        let connection = self.connection_state(sender_id)?;
691        let response = ErrorCode::Internal
692            .message(format!("message {} was not handled", message_type_name))
693            .to_proto();
694        let message_id = connection
695            .next_message_id
696            .fetch_add(1, atomic::Ordering::SeqCst);
697        connection
698            .outgoing_tx
699            .unbounded_send(Message::Envelope(response.into_envelope(
700                message_id,
701                Some(request_message_id),
702                None,
703            )))?;
704        Ok(())
705    }
706
707    fn connection_state(&self, connection_id: ConnectionId) -> Result<ConnectionState> {
708        let connections = self.connections.read();
709        let connection = connections
710            .get(&connection_id)
711            .with_context(|| format!("no such connection: {connection_id}"))?;
712        Ok(connection.clone())
713    }
714
715    #[cfg(any(test, feature = "test-support"))]
716    pub fn pending_stream_request_count(&self, connection_id: ConnectionId) -> Option<usize> {
717        let connection = self.connection_state(connection_id).ok()?;
718        let channels = connection.stream_response_channels.lock();
719        Some(channels.as_ref()?.len())
720    }
721}
722
723impl Serialize for Peer {
724    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
725    where
726        S: serde::Serializer,
727    {
728        let mut state = serializer.serialize_struct("Peer", 2)?;
729        state.serialize_field("connections", &*self.connections.read())?;
730        state.end()
731    }
732}
733
734#[cfg(test)]
735mod tests {
736    use super::*;
737    use async_tungstenite::tungstenite::Message as WebSocketMessage;
738    use gpui::TestAppContext;
739
740    fn init_logger() {
741        zlog::init_test();
742    }
743
744    #[gpui::test(iterations = 50)]
745    async fn test_request_response(cx: &mut TestAppContext) {
746        init_logger();
747
748        let executor = cx.executor();
749
750        // create 2 clients connected to 1 server
751        let server = Peer::new(0);
752        let client1 = Peer::new(0);
753        let client2 = Peer::new(0);
754
755        let (client1_to_server_conn, server_to_client_1_conn, _kill) =
756            Connection::in_memory(cx.executor());
757        let (client1_conn_id, io_task1, client1_incoming) =
758            client1.add_test_connection(client1_to_server_conn, cx.executor());
759        let (_, io_task2, server_incoming1) =
760            server.add_test_connection(server_to_client_1_conn, cx.executor());
761
762        let (client2_to_server_conn, server_to_client_2_conn, _kill) =
763            Connection::in_memory(cx.executor());
764        let (client2_conn_id, io_task3, client2_incoming) =
765            client2.add_test_connection(client2_to_server_conn, cx.executor());
766        let (_, io_task4, server_incoming2) =
767            server.add_test_connection(server_to_client_2_conn, cx.executor());
768
769        executor.spawn(io_task1).detach();
770        executor.spawn(io_task2).detach();
771        executor.spawn(io_task3).detach();
772        executor.spawn(io_task4).detach();
773        executor
774            .spawn(handle_messages(server_incoming1, server.clone()))
775            .detach();
776        executor
777            .spawn(handle_messages(client1_incoming, client1.clone()))
778            .detach();
779        executor
780            .spawn(handle_messages(server_incoming2, server.clone()))
781            .detach();
782        executor
783            .spawn(handle_messages(client2_incoming, client2.clone()))
784            .detach();
785
786        assert_eq!(
787            client1
788                .request(client1_conn_id, proto::Ping {},)
789                .await
790                .unwrap(),
791            proto::Ack {}
792        );
793
794        assert_eq!(
795            client2
796                .request(client2_conn_id, proto::Ping {},)
797                .await
798                .unwrap(),
799            proto::Ack {}
800        );
801
802        assert_eq!(
803            client1
804                .request(client1_conn_id, proto::Test { id: 1 },)
805                .await
806                .unwrap(),
807            proto::Test { id: 1 }
808        );
809
810        assert_eq!(
811            client2
812                .request(client2_conn_id, proto::Test { id: 2 })
813                .await
814                .unwrap(),
815            proto::Test { id: 2 }
816        );
817
818        client1.disconnect(client1_conn_id);
819        client2.disconnect(client1_conn_id);
820
821        async fn handle_messages(
822            mut messages: BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
823            peer: Arc<Peer>,
824        ) -> Result<()> {
825            while let Some(envelope) = messages.next().await {
826                let envelope = envelope.into_any();
827                if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Ping>>() {
828                    let receipt = envelope.receipt();
829                    peer.respond(receipt, proto::Ack {})?
830                } else if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Test>>()
831                {
832                    peer.respond(envelope.receipt(), envelope.payload.clone())?
833                } else {
834                    panic!("unknown message type");
835                }
836            }
837
838            Ok(())
839        }
840    }
841
842    #[gpui::test(iterations = 50)]
843    async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
844        let executor = cx.executor();
845        let server = Peer::new(0);
846        let client = Peer::new(0);
847
848        let (client_to_server_conn, server_to_client_conn, _kill) =
849            Connection::in_memory(executor.clone());
850        let (client_to_server_conn_id, io_task1, mut client_incoming) =
851            client.add_test_connection(client_to_server_conn, executor.clone());
852
853        let (server_to_client_conn_id, io_task2, mut server_incoming) =
854            server.add_test_connection(server_to_client_conn, executor.clone());
855
856        executor.spawn(io_task1).detach();
857        executor.spawn(io_task2).detach();
858
859        executor
860            .spawn(async move {
861                let future = server_incoming.next().await;
862                let request = future
863                    .unwrap()
864                    .into_any()
865                    .downcast::<TypedEnvelope<proto::Ping>>()
866                    .unwrap();
867
868                server
869                    .send(
870                        server_to_client_conn_id,
871                        ErrorCode::Internal
872                            .message("message 1".to_string())
873                            .to_proto(),
874                    )
875                    .unwrap();
876                server
877                    .send(
878                        server_to_client_conn_id,
879                        ErrorCode::Internal
880                            .message("message 2".to_string())
881                            .to_proto(),
882                    )
883                    .unwrap();
884                server.respond(request.receipt(), proto::Ack {}).unwrap();
885
886                // Prevent the connection from being dropped
887                server_incoming.next().await;
888            })
889            .detach();
890
891        let events = Arc::new(Mutex::new(Vec::new()));
892
893        let response = client.request(client_to_server_conn_id, proto::Ping {});
894        let response_task = executor.spawn({
895            let events = events.clone();
896            async move {
897                response.await.unwrap();
898                events.lock().push("response".to_string());
899            }
900        });
901
902        executor
903            .spawn({
904                let events = events.clone();
905                async move {
906                    let incoming1 = client_incoming
907                        .next()
908                        .await
909                        .unwrap()
910                        .into_any()
911                        .downcast::<TypedEnvelope<proto::Error>>()
912                        .unwrap();
913                    events.lock().push(incoming1.payload.message);
914                    let incoming2 = client_incoming
915                        .next()
916                        .await
917                        .unwrap()
918                        .into_any()
919                        .downcast::<TypedEnvelope<proto::Error>>()
920                        .unwrap();
921                    events.lock().push(incoming2.payload.message);
922
923                    // Prevent the connection from being dropped
924                    client_incoming.next().await;
925                }
926            })
927            .detach();
928
929        response_task.await;
930        assert_eq!(
931            &*events.lock(),
932            &[
933                "message 1".to_string(),
934                "message 2".to_string(),
935                "response".to_string()
936            ]
937        );
938    }
939
940    #[gpui::test(iterations = 50)]
941    async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
942        let executor = cx.executor();
943        let server = Peer::new(0);
944        let client = Peer::new(0);
945
946        let (client_to_server_conn, server_to_client_conn, _kill) =
947            Connection::in_memory(cx.executor());
948        let (client_to_server_conn_id, io_task1, mut client_incoming) =
949            client.add_test_connection(client_to_server_conn, cx.executor());
950        let (server_to_client_conn_id, io_task2, mut server_incoming) =
951            server.add_test_connection(server_to_client_conn, cx.executor());
952
953        executor.spawn(io_task1).detach();
954        executor.spawn(io_task2).detach();
955
956        executor
957            .spawn(async move {
958                let request1 = server_incoming
959                    .next()
960                    .await
961                    .unwrap()
962                    .into_any()
963                    .downcast::<TypedEnvelope<proto::Ping>>()
964                    .unwrap();
965                let request2 = server_incoming
966                    .next()
967                    .await
968                    .unwrap()
969                    .into_any()
970                    .downcast::<TypedEnvelope<proto::Ping>>()
971                    .unwrap();
972
973                server
974                    .send(
975                        server_to_client_conn_id,
976                        ErrorCode::Internal
977                            .message("message 1".to_string())
978                            .to_proto(),
979                    )
980                    .unwrap();
981                server
982                    .send(
983                        server_to_client_conn_id,
984                        ErrorCode::Internal
985                            .message("message 2".to_string())
986                            .to_proto(),
987                    )
988                    .unwrap();
989                server.respond(request1.receipt(), proto::Ack {}).unwrap();
990                server.respond(request2.receipt(), proto::Ack {}).unwrap();
991
992                // Prevent the connection from being dropped
993                server_incoming.next().await;
994            })
995            .detach();
996
997        let events = Arc::new(Mutex::new(Vec::new()));
998
999        let request1 = client.request(client_to_server_conn_id, proto::Ping {});
1000        let request1_task = executor.spawn(request1);
1001        let request2 = client.request(client_to_server_conn_id, proto::Ping {});
1002        let request2_task = executor.spawn({
1003            let events = events.clone();
1004            async move {
1005                request2.await.unwrap();
1006                events.lock().push("response 2".to_string());
1007            }
1008        });
1009
1010        executor
1011            .spawn({
1012                let events = events.clone();
1013                async move {
1014                    let incoming1 = client_incoming
1015                        .next()
1016                        .await
1017                        .unwrap()
1018                        .into_any()
1019                        .downcast::<TypedEnvelope<proto::Error>>()
1020                        .unwrap();
1021                    events.lock().push(incoming1.payload.message);
1022                    let incoming2 = client_incoming
1023                        .next()
1024                        .await
1025                        .unwrap()
1026                        .into_any()
1027                        .downcast::<TypedEnvelope<proto::Error>>()
1028                        .unwrap();
1029                    events.lock().push(incoming2.payload.message);
1030
1031                    // Prevent the connection from being dropped
1032                    client_incoming.next().await;
1033                }
1034            })
1035            .detach();
1036
1037        // Allow the request to make some progress before dropping it.
1038        cx.executor().simulate_random_delay().await;
1039        drop(request1_task);
1040
1041        request2_task.await;
1042        assert_eq!(
1043            &*events.lock(),
1044            &[
1045                "message 1".to_string(),
1046                "message 2".to_string(),
1047                "response 2".to_string()
1048            ]
1049        );
1050    }
1051
1052    #[gpui::test(iterations = 50)]
1053    async fn test_request_stream(cx: &mut TestAppContext) {
1054        init_logger();
1055
1056        let executor = cx.executor();
1057        let server = Peer::new(0);
1058        let client = Peer::new(0);
1059
1060        let (client_to_server_conn, server_to_client_conn, _kill) =
1061            Connection::in_memory(executor.clone());
1062        let (client_to_server_conn_id, io_task1, mut client_incoming) =
1063            client.add_test_connection(client_to_server_conn, executor.clone());
1064        let (_, io_task2, mut server_incoming) =
1065            server.add_test_connection(server_to_client_conn, executor.clone());
1066
1067        executor.spawn(io_task1).detach();
1068        executor.spawn(io_task2).detach();
1069        executor
1070            .spawn(async move { while client_incoming.next().await.is_some() {} })
1071            .detach();
1072
1073        executor
1074            .spawn({
1075                let server = server.clone();
1076                async move {
1077                    let request = server_incoming
1078                        .next()
1079                        .await
1080                        .unwrap()
1081                        .into_any()
1082                        .downcast::<TypedEnvelope<proto::Test>>()
1083                        .unwrap();
1084                    let receipt = request.receipt();
1085                    server.respond(receipt, proto::Test { id: 1 }).unwrap();
1086                    server.respond(receipt, proto::Test { id: 2 }).unwrap();
1087                    server.respond(receipt, proto::Test { id: 3 }).unwrap();
1088                    server.end_stream(receipt).unwrap();
1089
1090                    // Prevent the connection from being dropped.
1091                    server_incoming.next().await;
1092                }
1093            })
1094            .detach();
1095
1096        let mut stream = client
1097            .request_stream(client_to_server_conn_id, proto::Test { id: 0 })
1098            .await
1099            .unwrap();
1100
1101        let mut received = Vec::new();
1102        while let Some(item) = stream.next().await {
1103            received.push(item.unwrap());
1104        }
1105
1106        assert_eq!(
1107            received,
1108            vec![
1109                proto::Test { id: 1 },
1110                proto::Test { id: 2 },
1111                proto::Test { id: 3 },
1112            ]
1113        );
1114        assert_eq!(
1115            client.pending_stream_request_count(client_to_server_conn_id),
1116            Some(0)
1117        );
1118    }
1119
1120    #[gpui::test]
1121    async fn test_request_stream_send_failure_cleans_up_response_channel(cx: &mut TestAppContext) {
1122        init_logger();
1123
1124        let executor = cx.executor();
1125        let client = Peer::new(0);
1126
1127        let (client_to_server_conn, _server_to_client_conn, _kill) =
1128            Connection::in_memory(executor.clone());
1129        let (client_to_server_conn_id, io_task, _client_incoming) =
1130            client.add_test_connection(client_to_server_conn, executor.clone());
1131
1132        drop(io_task);
1133
1134        let result = client
1135            .request_stream(client_to_server_conn_id, proto::Test { id: 0 })
1136            .await;
1137
1138        assert!(
1139            result.is_err(),
1140            "stream request should fail when the connection write task has gone away"
1141        );
1142        assert_eq!(
1143            client.pending_stream_request_count(client_to_server_conn_id),
1144            Some(0),
1145            "failed stream request should not leave response channel bookkeeping behind"
1146        );
1147    }
1148
1149    #[gpui::test(iterations = 50)]
1150    async fn test_request_stream_terminates_on_error(cx: &mut TestAppContext) {
1151        init_logger();
1152
1153        let executor = cx.executor();
1154        let server = Peer::new(0);
1155        let client = Peer::new(0);
1156
1157        let (client_to_server_conn, server_to_client_conn, _kill) =
1158            Connection::in_memory(executor.clone());
1159        let (client_to_server_conn_id, io_task1, mut client_incoming) =
1160            client.add_test_connection(client_to_server_conn, executor.clone());
1161        let (_, io_task2, mut server_incoming) =
1162            server.add_test_connection(server_to_client_conn, executor.clone());
1163
1164        executor.spawn(io_task1).detach();
1165        executor.spawn(io_task2).detach();
1166        executor
1167            .spawn(async move { while client_incoming.next().await.is_some() {} })
1168            .detach();
1169
1170        executor
1171            .spawn({
1172                let server = server.clone();
1173                async move {
1174                    let request = server_incoming
1175                        .next()
1176                        .await
1177                        .unwrap()
1178                        .into_any()
1179                        .downcast::<TypedEnvelope<proto::Test>>()
1180                        .unwrap();
1181                    let receipt = request.receipt();
1182                    server.respond(receipt, proto::Test { id: 1 }).unwrap();
1183                    // Send an Error without a trailing EndStream. The Error alone
1184                    // should be treated as a terminal stream response.
1185                    server
1186                        .respond_with_error(
1187                            receipt,
1188                            ErrorCode::Internal.message("boom".to_string()).to_proto(),
1189                        )
1190                        .unwrap();
1191
1192                    // Prevent the connection from being dropped.
1193                    server_incoming.next().await;
1194                }
1195            })
1196            .detach();
1197
1198        let mut stream = client
1199            .request_stream(client_to_server_conn_id, proto::Test { id: 0 })
1200            .await
1201            .unwrap();
1202
1203        assert_eq!(stream.next().await.unwrap().unwrap(), proto::Test { id: 1 });
1204
1205        let error = stream.next().await.unwrap().unwrap_err();
1206        assert!(
1207            format!("{error}").contains("boom"),
1208            "expected error to surface server message, got: {error}"
1209        );
1210
1211        // The error alone (without an EndStream) should terminate the stream.
1212        assert!(stream.next().await.is_none());
1213        assert_eq!(
1214            client.pending_stream_request_count(client_to_server_conn_id),
1215            Some(0)
1216        );
1217    }
1218
1219    #[gpui::test(iterations = 50)]
1220    async fn test_dropping_stream_request_before_completion(cx: &mut TestAppContext) {
1221        init_logger();
1222
1223        let executor = cx.executor();
1224        let server = Peer::new(0);
1225        let client = Peer::new(0);
1226
1227        let (client_to_server_conn, server_to_client_conn, _kill) =
1228            Connection::in_memory(executor.clone());
1229        let (client_to_server_conn_id, io_task1, mut client_incoming) =
1230            client.add_test_connection(client_to_server_conn, executor.clone());
1231        let (_, io_task2, mut server_incoming) =
1232            server.add_test_connection(server_to_client_conn, executor.clone());
1233
1234        executor.spawn(io_task1).detach();
1235        executor.spawn(io_task2).detach();
1236        executor
1237            .spawn(async move { while client_incoming.next().await.is_some() {} })
1238            .detach();
1239
1240        let (drop_signal_tx, drop_signal_rx) = oneshot::channel::<()>();
1241        let server_task = executor.spawn({
1242            let server = server.clone();
1243            async move {
1244                let request = server_incoming
1245                    .next()
1246                    .await
1247                    .unwrap()
1248                    .into_any()
1249                    .downcast::<TypedEnvelope<proto::Test>>()
1250                    .unwrap();
1251                let receipt = request.receipt();
1252                server.respond(receipt, proto::Test { id: 1 }).unwrap();
1253
1254                // Wait until the consumer has dropped the stream.
1255                drop_signal_rx.await.ok();
1256
1257                // Send a non-terminal response after the consumer is gone. The
1258                // peer should detect that the receiver has been dropped and clean
1259                // up its bookkeeping. Crucially, we do NOT send EndStream here
1260                // because that would clean up via the terminal-response path and
1261                // mask the bug.
1262                server.respond(receipt, proto::Test { id: 2 }).unwrap();
1263
1264                // A Ping/Ack round-trip after the response acts as a sync
1265                // barrier: because messages over the in-memory connection are
1266                // delivered in order, by the time the client observes the Ack,
1267                // it has already processed the dropped response above.
1268                let ping = server_incoming
1269                    .next()
1270                    .await
1271                    .unwrap()
1272                    .into_any()
1273                    .downcast::<TypedEnvelope<proto::Ping>>()
1274                    .unwrap();
1275                server.respond(ping.receipt(), proto::Ack {}).unwrap();
1276
1277                // Prevent the connection from being dropped.
1278                server_incoming.next().await;
1279            }
1280        });
1281
1282        let mut stream = client
1283            .request_stream(client_to_server_conn_id, proto::Test { id: 0 })
1284            .await
1285            .unwrap();
1286
1287        assert_eq!(stream.next().await.unwrap().unwrap(), proto::Test { id: 1 });
1288
1289        // The stream is mid-flight, so the channel should be tracked.
1290        assert_eq!(
1291            client.pending_stream_request_count(client_to_server_conn_id),
1292            Some(1)
1293        );
1294
1295        drop(stream);
1296        drop_signal_tx.send(()).ok();
1297
1298        // Synchronization barrier: once this Ack arrives, the read loop has
1299        // already processed the orphaned stream response that came before it.
1300        client
1301            .request(client_to_server_conn_id, proto::Ping {})
1302            .await
1303            .unwrap();
1304
1305        assert_eq!(
1306            client.pending_stream_request_count(client_to_server_conn_id),
1307            Some(0),
1308            "stream channel should be removed once the consumer has dropped the stream"
1309        );
1310
1311        drop(server_task);
1312    }
1313
1314    #[gpui::test(iterations = 50)]
1315    async fn test_disconnect(cx: &mut TestAppContext) {
1316        let executor = cx.executor();
1317
1318        let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
1319
1320        let client = Peer::new(0);
1321        let (connection_id, io_handler, mut incoming) =
1322            client.add_test_connection(client_conn, executor.clone());
1323
1324        let (io_ended_tx, io_ended_rx) = oneshot::channel();
1325        executor
1326            .spawn(async move {
1327                io_handler.await.ok();
1328                io_ended_tx.send(()).unwrap();
1329            })
1330            .detach();
1331
1332        let (messages_ended_tx, messages_ended_rx) = oneshot::channel();
1333        executor
1334            .spawn(async move {
1335                incoming.next().await;
1336                messages_ended_tx.send(()).unwrap();
1337            })
1338            .detach();
1339
1340        client.disconnect(connection_id);
1341
1342        let _ = io_ended_rx.await;
1343        let _ = messages_ended_rx.await;
1344        assert!(
1345            server_conn
1346                .send(WebSocketMessage::Binary(vec![].into()))
1347                .await
1348                .is_err()
1349        );
1350    }
1351
1352    #[gpui::test(iterations = 50)]
1353    async fn test_io_error(cx: &mut TestAppContext) {
1354        let executor = cx.executor();
1355        let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
1356
1357        let client = Peer::new(0);
1358        let (connection_id, io_handler, mut incoming) =
1359            client.add_test_connection(client_conn, executor.clone());
1360        executor.spawn(io_handler).detach();
1361        executor
1362            .spawn(async move { incoming.next().await })
1363            .detach();
1364
1365        let response = executor.spawn(client.request(connection_id, proto::Ping {}));
1366        let _request = server_conn.rx.next().await.unwrap().unwrap();
1367
1368        drop(server_conn);
1369        assert_eq!(
1370            response.await.unwrap_err().to_string(),
1371            "connection was closed"
1372        );
1373    }
1374}
1375
Served at tenant.openagents/omega Member data and write actions are omitted.