Skip to content

Commit b62e527

Browse files
committed
dev(owned): trying to have a stream and a sink at the same time using a mutex. but it deadlocks
1 parent 79de87f commit b62e527

5 files changed

Lines changed: 176 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ httparse = { version = "1.10.1", default-features = false }
2020
base64 = { version = "0.22.1", default-features = false }
2121
sha1 = { version = "0.10.6", default-features = false }
2222
heapless = { version = "0.9.1", default-features = false }
23+
embassy-sync = { version = "0.7.2", default-features = false }
24+
futures = { version = "0.3.31", default-features = false }
2325

2426
[dev-dependencies]
2527
rand = { version = "0.9.1", features = ["std_rng"] }

examples/owned.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! Run with
2+
//!
3+
//! ```not_rust
4+
//! cargo run --example owned
5+
//! ```
6+
7+
use std::pin::pin;
8+
9+
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
10+
use embedded_io_adapters::tokio_1::FromTokio;
11+
use futures::{SinkExt, StreamExt};
12+
use rand::{SeedableRng, rngs::StdRng};
13+
use tokio::net::TcpStream;
14+
use websocketz::{Message, WebSocket, http::Header, options::ConnectOptions};
15+
16+
#[tokio::main]
17+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
18+
let domain = "websockets.chilkat.io";
19+
20+
let addr = tokio::net::lookup_host((domain, 80))
21+
.await?
22+
.next()
23+
.ok_or("Failed to resolve domain")?;
24+
25+
let stream = TcpStream::connect(addr).await?;
26+
27+
let read_buf = &mut [0u8; 8192 * 2];
28+
let write_buf = &mut [0u8; 8192 * 2];
29+
let fragments_buf = &mut [0u8; 8192 * 2];
30+
let rng = StdRng::from_os_rng();
31+
32+
let websocketz = WebSocket::connect::<16>(
33+
ConnectOptions::default()
34+
.with_path_unchecked("/wsChilkatEcho.ashx")
35+
.with_headers(&[Header {
36+
name: "Host",
37+
value: domain.as_bytes(),
38+
}]),
39+
FromTokio::new(stream),
40+
rng,
41+
read_buf,
42+
write_buf,
43+
fragments_buf,
44+
)
45+
.await?;
46+
47+
println!(
48+
"Number of framable bytes after handshake: {}",
49+
websocketz.framable()
50+
);
51+
52+
let websocketz = websocketz.owned::<1024, NoopRawMutex>();
53+
54+
let (stream, sink) = websocketz.split();
55+
let (mut stream, mut sink) = (pin!(stream), pin!(sink));
56+
57+
sink.send(Message::Text("Hello, WebSocket!")).await?;
58+
59+
loop {
60+
tokio::select! {
61+
msg = stream.next() => match msg.transpose()? {
62+
None => {
63+
println!("EOF");
64+
65+
break;
66+
}
67+
Some(msg) => {
68+
println!("Received message: {msg:?}");
69+
}
70+
},
71+
_ = tokio::time::sleep(tokio::time::Duration::from_secs(1)) => {
72+
println!("Sending message...");
73+
74+
sink.send(Message::Text("Hello, WebSocket!")).await?; // This deadlocks
75+
76+
println!("Message sent!");
77+
}
78+
}
79+
}
80+
81+
Ok(())
82+
}

src/functions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl ReadAutoCaller {
6161
// TODO: see comments in on WebSocket::next()
6262
// TODO: delete
6363
#[allow(clippy::too_many_arguments)]
64-
pub async fn call_next<const N: usize, F, RW, Rng>(
64+
pub async fn call_owned<const N: usize, F, RW, Rng>(
6565
&self,
6666
auto: F,
6767
websocket: &mut WebSocketCore<'_, RW, Rng>,

src/websocket.rs

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
use core::cell::RefCell;
2-
1+
use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex};
32
use embedded_io_async::{Read, Write};
43
use framez::{
54
Framed,
65
state::{ReadState, ReadWriteState, WriteState},
76
};
7+
use futures::{Sink, Stream};
88
use rand::RngCore;
99

1010
use crate::{
1111
FragmentsState, Frame, FramesCodec, Message, OnFrame, OwnedMessage, WebSocketCore,
1212
error::{Error, ProtocolError},
1313
http::{Request, Response},
14-
next,
1514
options::{AcceptOptions, ConnectOptions},
1615
};
1716

@@ -309,6 +308,16 @@ impl<'buf, RW, Rng> WebSocket<'buf, RW, Rng> {
309308
pub const fn caller(&self) -> crate::functions::ReadAutoCaller {
310309
crate::functions::ReadAutoCaller
311310
}
311+
312+
/// TODO
313+
pub fn owned<const N: usize, M>(self) -> OwnedWebSocket<'buf, N, RW, Rng, M>
314+
where
315+
M: RawMutex,
316+
{
317+
OwnedWebSocket {
318+
inner: Mutex::new(self),
319+
}
320+
}
312321
}
313322

314323
/// Read half of a WebSocket connection.
@@ -465,28 +474,25 @@ impl<'buf, RW, Rng> WebSocketWrite<'buf, RW, Rng> {
465474
}
466475

467476
#[derive(Debug)]
468-
pub struct OwnedWebSocket<'buf, const N: usize, RW, Rng> {
469-
// XXX: the idea of the refcell is to have a stream and a sink at the same time using the same core
470-
// but this would panic when using select on the stream and then using the sink to send a message, because the stream would have a mutable reference to the core and then the sink would try to borrow it again.
471-
// See clippy warning
472-
// I think we can only have a stream or a sink at a time, mutably borrowing the core
473-
core: RefCell<WebSocket<'buf, RW, Rng>>,
477+
pub struct OwnedWebSocket<'buf, const N: usize, RW, Rng, Mtx: RawMutex> {
478+
inner: Mutex<Mtx, WebSocket<'buf, RW, Rng>>,
474479
}
475480

476-
impl<'buf, const N: usize, RW, Rng> OwnedWebSocket<'buf, N, RW, Rng>
481+
impl<'buf, const N: usize, RW, Rng, Mtx> OwnedWebSocket<'buf, N, RW, Rng, Mtx>
477482
where
478483
RW: Read + Write,
479484
Rng: RngCore,
485+
Mtx: RawMutex,
480486
{
481487
pub async fn next(
482488
&self,
483489
) -> Option<Result<Result<OwnedMessage<N>, heapless::CapacityError>, Error<RW::Error>>> {
484-
let mut websocket = self.core.borrow_mut();
490+
let mut websocket = self.inner.lock().await;
485491

486492
match 'next: loop {
487493
match websocket
488494
.caller()
489-
.call_next::<N, _, _, _>(websocket.auto(), &mut websocket.core)
495+
.call_owned::<N, _, _, _>(websocket.auto(), &mut websocket.core)
490496
.await
491497
{
492498
Some(Ok(None)) => continue 'next,
@@ -500,4 +506,45 @@ where
500506
Some(Err(err)) => Some(Err(err)),
501507
}
502508
}
509+
510+
pub async fn send(&self, message: Message<'_>) -> Result<(), Error<RW::Error>> {
511+
let mut websocket = self.inner.lock().await;
512+
513+
websocket.send(message).await
514+
}
515+
516+
pub fn stream(
517+
&self,
518+
) -> impl Stream<
519+
Item = Result<Result<OwnedMessage<N>, heapless::CapacityError>, Error<RW::Error>>,
520+
> + '_ {
521+
futures::stream::unfold((self, false), move |(this, errored)| async move {
522+
if errored {
523+
return None;
524+
}
525+
526+
match this.next().await {
527+
Some(Ok(item)) => Some((Ok(item), (this, false))),
528+
Some(Err(err)) => Some((Err(err), (this, true))),
529+
None => None,
530+
}
531+
})
532+
}
533+
534+
pub fn sink(&self) -> impl Sink<Message<'_>, Error = Error<RW::Error>> + '_ {
535+
futures::sink::unfold(self, |this, item: Message<'_>| async move {
536+
this.send(item).await?;
537+
538+
Ok(this)
539+
})
540+
}
541+
542+
pub fn split(
543+
&self,
544+
) -> (
545+
impl Stream<Item = Result<Result<OwnedMessage<N>, heapless::CapacityError>, Error<RW::Error>>>,
546+
impl Sink<Message<'_>, Error = Error<RW::Error>>,
547+
) {
548+
(self.stream(), self.sink())
549+
}
503550
}

0 commit comments

Comments
 (0)