Skip to content

Commit e7b8ab8

Browse files
authored
Sending sphinx packet independent of the receiver task (#210)
1 parent abbf704 commit e7b8ab8

File tree

4 files changed

+128
-88
lines changed

4 files changed

+128
-88
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/desktop/src/client/mix_traffic.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,7 @@ impl<'a> MixTrafficController<'static> {
5757
.await
5858
{
5959
Err(e) => error!("Failed to send sphinx packet to the gateway! - {:?}", e),
60-
Ok(was_successful) if !was_successful => {
61-
warn!("Sent sphinx packet to the gateway but it failed to get processed!")
62-
}
63-
Ok(was_successful) if was_successful => {
64-
trace!("Successfully forwarded sphinx packet to the gateway!")
65-
}
66-
Ok(_) => unreachable!("to shut up the compiler because all patterns ARE covered"),
60+
Ok(_) => trace!("We *might* have managed to forward sphinx packet to the gateway!"),
6761
}
6862
}
6963

common/client-libs/gateway-client/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ edition = "2018"
1010
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
1111
# the entire crate
1212
futures = "0.3"
13+
log = "0.4"
1314
tokio = { version = "0.2", features = ["macros", "rt-core", "stream", "sync", "time"] }
1415
tokio-tungstenite = "0.10"
1516

common/client-libs/gateway-client/src/lib.rs

+125-81
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use futures::stream::{SplitSink, SplitStream};
1516
use futures::{channel::mpsc, future::BoxFuture, FutureExt, SinkExt, StreamExt};
1617
use gateway_requests::auth_token::{AuthToken, AuthTokenConversionError};
1718
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
19+
use log::*;
1820
use nymsphinx::DestinationAddressBytes;
1921
use std::convert::TryFrom;
2022
use std::fmt::{self, Error, Formatter};
@@ -113,18 +115,101 @@ impl fmt::Display for GatewayClientError {
113115
}
114116
}
115117

118+
// We have ownership over sink half of the connection, but the stream is owned
119+
// by some other task, however, we can notify it to get the stream back.
120+
struct PartiallyDelegated<'a> {
121+
sink_half: SplitSink<WsConn, Message>,
122+
delegated_stream: (
123+
BoxFuture<'a, Result<SplitStream<WsConn>, GatewayClientError>>,
124+
Arc<Notify>,
125+
),
126+
}
127+
128+
impl<'a> PartiallyDelegated<'a> {
129+
// TODO: this can be potentially bad as we have no direct restrictions of ensuring it's called
130+
// within tokio runtime. Perhaps we should use the "old" way of passing explicit
131+
// runtime handle to the constructor and using that instead?
132+
fn split_and_listen_for_sphinx_packets(
133+
conn: WsConn,
134+
sphinx_packet_sender: SphinxPacketSender,
135+
) -> Result<Self, GatewayClientError> {
136+
// when called for, it NEEDS TO yield back the stream so that we could merge it and
137+
// read control request responses.
138+
let notify = Arc::new(Notify::new());
139+
let notify_clone = Arc::clone(&notify);
140+
141+
let (sink, mut stream) = conn.split();
142+
143+
let sphinx_receiver_future = async move {
144+
let mut should_return = false;
145+
while !should_return {
146+
tokio::select! {
147+
_ = notify_clone.notified() => {
148+
should_return = true;
149+
}
150+
msg = stream.next() => {
151+
if msg.is_none() {
152+
return Err(GatewayClientError::ConnectionAbruptlyClosed);
153+
}
154+
let msg = match msg.unwrap() {
155+
Ok(msg) => msg,
156+
Err(err) => {
157+
return Err(GatewayClientError::NetworkError(err));
158+
}
159+
};
160+
match msg {
161+
Message::Binary(bin_msg) => {
162+
// TODO: some batching mechanism to allow reading and sending more than
163+
// one packet at the time, because the receiver can easily handle it
164+
sphinx_packet_sender.unbounded_send(vec![bin_msg]).unwrap()
165+
},
166+
// I think that in the future we should perhaps have some sequence number system, i.e.
167+
// so each request/reponse pair can be easily identified, so that if messages are
168+
// not ordered (for some peculiar reason) we wouldn't lose anything.
169+
// This would also require NOT discarding any text responses here.
170+
Message::Text(_) => debug!("received a text message - probably a response to some previous query!"),
171+
_ => (),
172+
};
173+
}
174+
};
175+
}
176+
Ok(stream)
177+
};
178+
179+
let spawned_boxed_task = tokio::spawn(sphinx_receiver_future)
180+
.map(|join_handle| {
181+
join_handle.expect("task must have not failed to finish its execution!")
182+
})
183+
.boxed();
184+
185+
Ok(PartiallyDelegated {
186+
sink_half: sink,
187+
delegated_stream: (spawned_boxed_task, notify),
188+
})
189+
}
190+
191+
// if we want to send a message and don't care about response, we can don't need to reunite the split,
192+
// the sink itself is enough
193+
async fn send_without_response(&mut self, msg: Message) -> Result<(), GatewayClientError> {
194+
Ok(self.sink_half.send(msg).await?)
195+
}
196+
197+
async fn merge(self) -> Result<WsConn, GatewayClientError> {
198+
let (stream_fut, notify) = self.delegated_stream;
199+
notify.notify();
200+
let stream = stream_fut.await?;
201+
// the error is thrown when trying to reunite sink and stream that did not originate
202+
// from the same split which is impossible to happen here
203+
Ok(self.sink_half.reunite(stream).unwrap())
204+
}
205+
}
206+
116207
// we can either have the stream itself or an option to re-obtain it
117208
// by notifying the future owning it to finish the execution and awaiting the result
118209
// which should be almost immediate (or an invalid state which should never, ever happen)
119-
// TODO: perhaps restore the previous idea of Split(Stream, Sink) state to allow for
120-
// sending messages without waiting for any responses and having no effect on rate of
121-
// messages being pushed to us
122210
enum SocketState<'a> {
123211
Available(WsConn),
124-
Delegated(
125-
BoxFuture<'a, Result<WsConn, GatewayClientError>>,
126-
Arc<Notify>,
127-
),
212+
PartiallyDelegated(PartiallyDelegated<'a>),
128213
NotConnected,
129214
Invalid,
130215
}
@@ -137,16 +222,16 @@ impl<'a> SocketState<'a> {
137222
}
138223
}
139224

140-
fn is_delegated(&self) -> bool {
225+
fn is_partially_delegated(&self) -> bool {
141226
match self {
142-
SocketState::Delegated(_, _) => true,
227+
SocketState::PartiallyDelegated(_) => true,
143228
_ => false,
144229
}
145230
}
146231

147232
fn is_established(&self) -> bool {
148233
match self {
149-
SocketState::Available(_) | SocketState::Delegated(_, _) => true,
234+
SocketState::Available(_) | SocketState::PartiallyDelegated(_) => true,
150235
_ => false,
151236
}
152237
}
@@ -249,25 +334,22 @@ where
249334
res.expect("response value should have been written in one of the branches!. If you see this error, please report a bug!")
250335
}
251336

252-
// If we want to send a message, we need to have a full control over the socket,
337+
// If we want to send a message (with response), we need to have a full control over the socket,
253338
// as we need to be able to write the request and read the subsequent response
254339
async fn send_websocket_message(
255340
&mut self,
256341
msg: Message,
257342
) -> Result<ServerResponse, GatewayClientError> {
258343
let mut should_restart_sphinx_listener = false;
259-
if self.connection.is_delegated() {
344+
if self.connection.is_partially_delegated() {
260345
self.recover_socket_connection().await?;
261346
should_restart_sphinx_listener = true;
262347
}
263348

264349
let conn = match self.connection {
265350
SocketState::Available(ref mut conn) => conn,
266-
SocketState::Delegated(_, _) => {
267-
return Err(GatewayClientError::ConnectionInInvalidState)
268-
}
269-
SocketState::Invalid => return Err(GatewayClientError::ConnectionInInvalidState),
270351
SocketState::NotConnected => return Err(GatewayClientError::ConnectionNotEstablished),
352+
_ => return Err(GatewayClientError::ConnectionInInvalidState),
271353
};
272354
conn.send(msg).await?;
273355
let response = self.read_control_response().await;
@@ -278,13 +360,18 @@ where
278360
response
279361
}
280362

281-
// next on TODO list:
282-
// so that presumably we could increase our sending/receiving rate
283363
async fn send_websocket_message_without_response(
284364
&mut self,
285365
msg: Message,
286366
) -> Result<(), GatewayClientError> {
287-
unimplemented!()
367+
match self.connection {
368+
SocketState::Available(ref mut conn) => Ok(conn.send(msg).await?),
369+
SocketState::PartiallyDelegated(ref mut partially_delegated) => {
370+
partially_delegated.send_without_response(msg).await
371+
}
372+
SocketState::NotConnected => Err(GatewayClientError::ConnectionNotEstablished),
373+
_ => Err(GatewayClientError::ConnectionInInvalidState),
374+
}
288375
}
289376

290377
pub async fn register(&mut self) -> Result<AuthToken, GatewayClientError> {
@@ -348,102 +435,59 @@ where
348435
}
349436
}
350437

351-
// TODO: make it optionally use `send_websocket_message_without_response`
438+
// TODO: possibly make responses optional
352439
pub async fn send_sphinx_packet(
353440
&mut self,
354441
address: SocketAddr,
355442
packet: Vec<u8>,
356-
) -> Result<bool, GatewayClientError> {
443+
) -> Result<(), GatewayClientError> {
357444
if !self.authenticated {
358445
return Err(GatewayClientError::NotAuthenticated);
359446
}
360447
if !self.connection.is_established() {
361448
return Err(GatewayClientError::ConnectionNotEstablished);
362449
}
363450
let msg = BinaryRequest::new_forward_request(address, packet).into();
364-
match self.send_websocket_message(msg).await? {
365-
ServerResponse::Send { status } => Ok(status),
366-
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
367-
_ => unreachable!(),
368-
}
451+
self.send_websocket_message_without_response(msg).await
369452
}
370453

371454
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
372455
if self.connection.is_available() {
373456
return Ok(());
374457
}
375-
if !self.connection.is_delegated() {
458+
if !self.connection.is_partially_delegated() {
376459
return Err(GatewayClientError::ConnectionInInvalidState);
377460
}
378461

379-
let (conn_fut, notify) = match std::mem::replace(&mut self.connection, SocketState::Invalid)
380-
{
381-
SocketState::Delegated(conn_fut, notify) => (conn_fut, notify),
462+
let conn = match std::mem::replace(&mut self.connection, SocketState::Invalid) {
463+
SocketState::PartiallyDelegated(delegated_conn) => delegated_conn.merge().await?,
382464
_ => unreachable!(),
383465
};
384466

385-
// tell the future to wrap up whatever it's doing now
386-
notify.notify();
387-
self.connection = SocketState::Available(conn_fut.await?);
467+
self.connection = SocketState::Available(conn);
388468
Ok(())
389469
}
390470

391-
// TODO: this can be potentially bad as we have no direct restrictions of ensuring it's called
392-
// within tokio runtime. Perhaps we should use the "old" way of passing explicit
393-
// runtime handle to the constructor and using that instead?
394471
fn start_listening_for_sphinx_packets(&mut self) -> Result<(), GatewayClientError> {
472+
if self.connection.is_partially_delegated() {
473+
return Ok(());
474+
}
395475
if !self.connection.is_available() {
396476
return Err(GatewayClientError::ConnectionInInvalidState);
397477
}
398478

399-
// when called for, it NEEDS TO yield back the stream so that we could merge it and
400-
// read control request responses.
401-
let notify = Arc::new(Notify::new());
402-
let notify_clone = Arc::clone(&notify);
403-
404-
let mut extracted_connection =
479+
let partially_delegated =
405480
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
406-
SocketState::Available(conn) => conn,
407-
_ => unreachable!(), // impossible due to initial check
481+
SocketState::Available(conn) => {
482+
PartiallyDelegated::split_and_listen_for_sphinx_packets(
483+
conn,
484+
self.sphinx_packet_sender.clone(),
485+
)?
486+
}
487+
_ => unreachable!(),
408488
};
409489

410-
let sphinx_packet_sender = self.sphinx_packet_sender.clone();
411-
let sphinx_receiver_future = async move {
412-
let mut should_return = false;
413-
while !should_return {
414-
tokio::select! {
415-
_ = notify_clone.notified() => {
416-
should_return = true;
417-
}
418-
msg = extracted_connection.next() => {
419-
if msg.is_none() {
420-
return Err(GatewayClientError::ConnectionAbruptlyClosed);
421-
}
422-
let msg = match msg.unwrap() {
423-
Ok(msg) => msg,
424-
Err(err) => {
425-
return Err(GatewayClientError::NetworkError(err));
426-
}
427-
};
428-
match msg {
429-
Message::Binary(bin_msg) => {
430-
sphinx_packet_sender.unbounded_send(vec![bin_msg]).unwrap()
431-
}
432-
_ => (),
433-
};
434-
}
435-
};
436-
}
437-
Ok(extracted_connection)
438-
};
439-
440-
let spawned_boxed_task = tokio::spawn(sphinx_receiver_future)
441-
.map(|join_handle| {
442-
join_handle.expect("task must have not failed to finish its execution!")
443-
})
444-
.boxed();
445-
446-
self.connection = SocketState::Delegated(spawned_boxed_task, notify);
490+
self.connection = SocketState::PartiallyDelegated(partially_delegated);
447491
Ok(())
448492
}
449493
}

0 commit comments

Comments
 (0)