df_hyperliquid_core/client/native/
socket.rs1use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8use crate::live::{Event, FeedError, PING, Post, Refused, Session, Subscription, Tick};
9use futures_util::{SinkExt, StreamExt};
10use tokio::{
11 net::TcpStream,
12 sync::{mpsc, oneshot},
13 task::JoinHandle,
14 time::{Instant, sleep_until},
15};
16use tokio_tungstenite::{MaybeTlsStream, WebSocketStream, connect_async, tungstenite::Message};
17
18use crate::client::native::clock;
19
20enum Command {
21 Subscribe(Subscription, oneshot::Sender<Result<(), Refused>>),
22 Unsubscribe(Subscription),
23 Post(Post, oneshot::Sender<Result<u64, Refused>>),
24}
25
26pub struct Feed {
28 commands: mpsc::UnboundedSender<Command>,
29 events: mpsc::UnboundedReceiver<Event>,
30 task: JoinHandle<()>,
31}
32
33impl Feed {
34 #[must_use]
36 pub fn start(session: Session) -> Feed {
37 let url = session.url();
38 Feed::start_at(url, session)
39 }
40
41 #[must_use]
43 pub fn start_at(url: impl Into<String>, session: Session) -> Feed {
44 let (commands, inbox) = mpsc::unbounded_channel();
45 let (outbox, events) = mpsc::unbounded_channel();
46 let task = tokio::spawn(run(url.into(), session, inbox, outbox));
47 Feed { commands, events, task }
48 }
49
50 pub async fn subscribe(&self, subscription: Subscription) -> Result<(), FeedError> {
53 let (reply, answer) = oneshot::channel();
54 self.commands.send(Command::Subscribe(subscription, reply)).map_err(|_| FeedError::Stopped)?;
55 Ok(answer.await.map_err(|_| FeedError::Stopped)??)
56 }
57
58 pub fn unsubscribe(&self, subscription: Subscription) -> Result<(), FeedError> {
59 self.commands.send(Command::Unsubscribe(subscription)).map_err(|_| FeedError::Stopped)
60 }
61
62 pub async fn post(&self, request: Post) -> Result<u64, FeedError> {
65 let (reply, answer) = oneshot::channel();
66 self.commands.send(Command::Post(request, reply)).map_err(|_| FeedError::Stopped)?;
67 Ok(answer.await.map_err(|_| FeedError::Stopped)??)
68 }
69
70 pub async fn next(&mut self) -> Option<Event> {
72 self.events.recv().await
73 }
74
75 pub fn close(self) {
77 drop(self);
78 }
79}
80
81impl Drop for Feed {
82 fn drop(&mut self) {
83 self.task.abort();
84 }
85}
86
87type Socket = WebSocketStream<MaybeTlsStream<TcpStream>>;
88
89enum Outcome {
91 Lost(String),
92 Stop,
93}
94
95async fn run(
96 url: String,
97 mut session: Session,
98 mut commands: mpsc::UnboundedReceiver<Command>,
99 events: mpsc::UnboundedSender<Event>,
100) {
101 loop {
102 let dial = connect_async(url.as_str());
105 tokio::pin!(dial);
106 let dialled = loop {
107 tokio::select! {
108 dialled = &mut dial => break dialled,
109 command = commands.recv() => match command {
110 Some(command) => {
111 handle(&mut session, command);
112 }
113 None => return,
114 },
115 }
116 };
117 let reason = match dialled {
118 Ok((mut socket, _)) => match open(&mut socket, &mut session, &mut commands, &events).await {
119 Outcome::Lost(reason) => reason,
120 Outcome::Stop => return,
121 },
122 Err(error) => error.to_string(),
123 };
124 let retry = session.closed(clock(), random());
125 let until = Instant::now() + Duration::from_millis(retry.after_ms);
126 if events.send(Event::Closed { reason, retry }).is_err() {
127 return;
128 }
129 loop {
131 tokio::select! {
132 () = sleep_until(until) => break,
133 command = commands.recv() => match command {
134 Some(command) => {
135 handle(&mut session, command);
136 }
137 None => return,
138 },
139 }
140 }
141 }
142}
143
144async fn open(
145 socket: &mut Socket,
146 session: &mut Session,
147 commands: &mut mpsc::UnboundedReceiver<Command>,
148 events: &mpsc::UnboundedSender<Event>,
149) -> Outcome {
150 for frame in session.opened(clock()) {
151 if let Err(error) = socket.send(Message::text(frame)).await {
152 return Outcome::Lost(error.to_string());
153 }
154 }
155 if events.send(Event::Opened).is_err() {
156 return Outcome::Stop;
157 }
158 loop {
159 let wake = session.due().map(|at| Instant::now() + Duration::from_millis(at.saturating_sub(clock())));
160 tokio::select! {
161 message = socket.next() => match message {
162 Some(Ok(Message::Text(text))) => {
163 let event = match session.received(clock(), &text) {
164 Ok(frame) => Event::Frame(Box::new(frame)),
165 Err(bad) => Event::Unreadable(bad),
166 };
167 if events.send(event).is_err() {
168 return Outcome::Stop;
169 }
170 }
171 Some(Ok(Message::Close(frame))) => {
172 return Outcome::Lost(frame.map_or_else(|| "the venue closed the socket".to_owned(), |frame| frame.reason.to_string()));
173 }
174 Some(Ok(_)) => {}
176 Some(Err(error)) => return Outcome::Lost(error.to_string()),
177 None => return Outcome::Lost("the socket ended".to_owned()),
178 },
179 command = commands.recv() => match command {
180 Some(command) => {
181 if let Some(frame) = handle(session, command)
182 && let Err(error) = socket.send(Message::text(frame)).await
183 {
184 return Outcome::Lost(error.to_string());
185 }
186 }
187 None => {
188 let _ = socket.close(None).await;
189 return Outcome::Stop;
190 }
191 },
192 () = until(wake) => match session.tick(clock()) {
193 Tick::Wait => {}
194 Tick::Ping => {
195 if let Err(error) = socket.send(Message::text(PING)).await {
196 return Outcome::Lost(error.to_string());
197 }
198 }
199 Tick::Reconnect => {
200 let _ = socket.close(None).await;
201 return Outcome::Lost("nothing arrived for too long".to_owned());
202 }
203 },
204 }
205 }
206}
207
208fn handle(session: &mut Session, command: Command) -> Option<String> {
211 match command {
212 Command::Subscribe(subscription, reply) => match session.subscribe(subscription) {
213 Ok(frame) => {
214 let _ = reply.send(Ok(()));
215 frame
216 }
217 Err(refused) => {
218 let _ = reply.send(Err(refused));
219 None
220 }
221 },
222 Command::Unsubscribe(subscription) => session.unsubscribe(&subscription),
223 Command::Post(request, reply) => match session.post(&request) {
224 Ok((id, frame)) => {
225 let _ = reply.send(Ok(id));
226 Some(frame)
227 }
228 Err(refused) => {
229 let _ = reply.send(Err(refused));
230 None
231 }
232 },
233 }
234}
235
236async fn until(wake: Option<Instant>) {
237 match wake {
238 Some(at) => sleep_until(at).await,
239 None => std::future::pending().await,
240 }
241}
242
243fn random() -> u64 {
246 let nanos = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |since| u64::from(since.subsec_nanos()));
247 nanos.wrapping_mul(0x9e37_79b9_7f4a_7c15) ^ clock().rotate_left(29)
248}
249
250#[cfg(test)]
251#[path = "tests/socket.rs"]
252mod tests;