From 8b98b5e1ea7ecfafc236aedaa3597a0fea953e71 Mon Sep 17 00:00:00 2001 From: Donemmanuelo Date: Thu, 3 Apr 2025 11:24:03 +0100 Subject: [PATCH 01/13] small documentation on block_on function --- docs/block_on.md | 36 ++++++++++++++++++++++++++++++++++++ src/bin/server/main.rs | 6 +++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 docs/block_on.md diff --git a/docs/block_on.md b/docs/block_on.md new file mode 100644 index 0000000..7e93ce3 --- /dev/null +++ b/docs/block_on.md @@ -0,0 +1,36 @@ +# Overview of block_on function +## What's block_on +The block_on function is an synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on function is part of the tokio or async-std crates, not part of the standard library. + +## Why block_on +In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when executing a synchronous function: the caller only resumes when the operation is completed, what if we want our thread to do something else while the operating system does it work, we will need to use new I/O library that provides an asynchronous version of this function, Rust approach of supporting asynchronous operation is by introducing a trait, std::future::Future. A Future represent an operation you can test for completion. So with Future, you can use the current thread and do some other job, but using futures seems challenging to use because, you keep on polling other jobs when a future is still pending, keeping track of previous futures who's pending and what should be done once its finish, somehow ruin the simplicity of the asynchronous function. Well this buck is solve using .await expression. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for value. +Consider the example below: +```sh +use async_std::io::prelude::*; +use async_std::net; +async fn cheapo_request(host: &str, port: u16, path: &str) -> std::io::Result { + let mut socket = net::TcpStream::connect((host, port)).await?; + let request = format!("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", path, host); + socket.write_all(request.as_bytes()).await?; + socket.shutdown(net::Shutdown::Write)?; + let mut response = String::new(); + socket.read_to_string(&mut response).await?; + Ok(response) +} +``` + +```sh +fn main() -> std::io::Result<()> { + use async_std::task; + // `block_on` is used here to run an async function cheapo_request in a synchronous context. + let response = task::block_on(cheapo_request("example.com", 80, "/"))?; + println!("{}", response); + Ok(()) +} +``` + +We can call the function cheapo_request from an ordinary, synchronous function(like the main, for example), using the async_std's tasks::block_on function, which takes a future and poll it until it return a value as seen above. + +So in summary, the block_on function in used to execute asynchronous block synchronousely in rust. + + diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index 304432a..eedfd2f 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs @@ -1,12 +1,11 @@ pub mod connection; -pub mod group_table; pub mod group; +pub mod group_table; use connection::serve; - -use async_std::prelude::*; use async_std::net::TcpListener; +use async_std::prelude::*; use async_std::task; use std::sync::Arc; @@ -35,3 +34,4 @@ fn log_error(result: anyhow::Result<()>) { eprintln!("Error: {}", error); } } + From 965a730f5753d48de5560b9b0e755848201af944 Mon Sep 17 00:00:00 2001 From: Donemmanuelo Date: Thu, 3 Apr 2025 13:47:23 +0100 Subject: [PATCH 02/13] small documentation on block_on function --- docs/block_on.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/block_on.md b/docs/block_on.md index 7e93ce3..44fa40c 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -34,3 +34,6 @@ We can call the function cheapo_request from an ordinary, synchronous function(l So in summary, the block_on function in used to execute asynchronous block synchronousely in rust. + + + From 71ed75c52f8a9ab88b475bec5dd94c47eedd8e71 Mon Sep 17 00:00:00 2001 From: Donemmanuelo Date: Thu, 3 Apr 2025 14:10:55 +0100 Subject: [PATCH 03/13] small documentation on block_on function --- docs/block_on.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/block_on.md b/docs/block_on.md index 44fa40c..7e93ce3 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -34,6 +34,3 @@ We can call the function cheapo_request from an ordinary, synchronous function(l So in summary, the block_on function in used to execute asynchronous block synchronousely in rust. - - - From 3630d2f04933b890a8c0af258ec4120c1e7a1d55 Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Mon, 28 Apr 2025 13:52:58 +0100 Subject: [PATCH 04/13] small documentation on block_on function --- src/bin/server/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index de510ba..c02208c 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs @@ -38,7 +38,6 @@ fn main() -> anyhow::Result<()> { /// Logs errors from client handler tasks. fn log_error(result: anyhow::Result<()>) { if let Err(error) = result { - eprintln!("Error: {}", error); + eprintln!("Error: {:?}", error); } } - From dc73c23306945ea1294dc211ed65da7a1a6b8c97 Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Tue, 29 Apr 2025 12:38:09 +0100 Subject: [PATCH 05/13] Update block_on.md --- docs/block_on.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/block_on.md b/docs/block_on.md index 7e93ce3..eadcb54 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -3,7 +3,7 @@ The block_on function is an synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on function is part of the tokio or async-std crates, not part of the standard library. ## Why block_on -In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when executing a synchronous function: the caller only resumes when the operation is completed, what if we want our thread to do something else while the operating system does it work, we will need to use new I/O library that provides an asynchronous version of this function, Rust approach of supporting asynchronous operation is by introducing a trait, std::future::Future. A Future represent an operation you can test for completion. So with Future, you can use the current thread and do some other job, but using futures seems challenging to use because, you keep on polling other jobs when a future is still pending, keeping track of previous futures who's pending and what should be done once its finish, somehow ruin the simplicity of the asynchronous function. Well this buck is solve using .await expression. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for value. +In a sense, asynchronous function just pass the buck, this buck is simply the fact that, when executing a synchronous function: the caller only resumes when the operation is completed, what if we want our thread to do something else while the operating system is doing his work, we will need to use new I/O library that provides an asynchronous version of this function, Rust approach of supporting asynchronous operation is by introducing a trait, std::future::Future. A Future represent an operation you can test for completion. So with Future, you can always know the state of the current thread inother to do other jobs, but using futures seems challenging because, you keep on polling other jobs while a future is still pending, keeping track of previous futures who are pending and what should be done once there are finished and poll it again, and this somehow ruin the simplicity of the function. Good news, asynchronous function is there, this buck is solved using .await expression which pause the execution of this async function until the awaited value is ready before resuming back it execution. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for value and in this case block_on is our waiter. Consider the example below: ```sh use async_std::io::prelude::*; From e2dbd3c7332690342b7889126ea5d87b9b1668cb Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Fri, 2 May 2025 13:59:48 +0100 Subject: [PATCH 06/13] Update block_on.md --- docs/block_on.md | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/block_on.md b/docs/block_on.md index eadcb54..a443c73 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -1,9 +1,36 @@ # Overview of block_on function ## What's block_on -The block_on function is an synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on function is part of the tokio or async-std crates, not part of the standard library. +The block_on function is a synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on function is part of the tokio or async-std crates, not part of the standard library. ## Why block_on -In a sense, asynchronous function just pass the buck, this buck is simply the fact that, when executing a synchronous function: the caller only resumes when the operation is completed, what if we want our thread to do something else while the operating system is doing his work, we will need to use new I/O library that provides an asynchronous version of this function, Rust approach of supporting asynchronous operation is by introducing a trait, std::future::Future. A Future represent an operation you can test for completion. So with Future, you can always know the state of the current thread inother to do other jobs, but using futures seems challenging because, you keep on polling other jobs while a future is still pending, keeping track of previous futures who are pending and what should be done once there are finished and poll it again, and this somehow ruin the simplicity of the function. Good news, asynchronous function is there, this buck is solved using .await expression which pause the execution of this async function until the awaited value is ready before resuming back it execution. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for value and in this case block_on is our waiter. +In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when + +executing a synchronous function: the caller only resumes when the operation is completed, what if we + +want our thread to do something else while the operating system is doing his work, we will need to use + +new I/O library that provides an asynchronous version of this function, Rust approach of supporting + +asynchronous operation is by introducing a trait, std::future::Future. A Future represent an operation + +you can test for completion. So with Future, you can always know the state of the current thread inother + +to do other jobs, but using futures seems challenging because, you keep on polling other jobs while a + +future is still pending, keeping track of previous futures who are pending and what should be done once + +there are finished and poll it again, and this somehow ruin the simplicity of the function. Good news, + +asynchronous function is there, this buck is solved using .await expression which pause the execution of + +this async function until the awaited value is ready before resuming back it execution. it's true that + +it easy to get the value of an async function: just await it. But async function itself return a future, + +so it's now the caller's job to do the polling somehow, thus someone has to wait for value and in this + +case block_on is our waiter. + Consider the example below: ```sh use async_std::io::prelude::*; @@ -29,8 +56,12 @@ fn main() -> std::io::Result<()> { } ``` -We can call the function cheapo_request from an ordinary, synchronous function(like the main, for example), using the async_std's tasks::block_on function, which takes a future and poll it until it return a value as seen above. +We can call the function cheapo_request from an ordinary, synchronous function(like the main, for + +example), using the async_std's task::block_on function, which takes a future and poll it until it return + +a value as seen above. -So in summary, the block_on function in used to execute asynchronous block synchronousely in rust. +So in summary, the block_on function is used to execute asynchronous block synchronously in rust. From 3048f0ae3b77deef470e406d2c38f09df3f18476 Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Fri, 2 May 2025 14:00:16 +0100 Subject: [PATCH 07/13] Update block_on.md --- docs/block_on.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/block_on.md b/docs/block_on.md index a443c73..5dc54e0 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -1,6 +1,10 @@ # Overview of block_on function ## What's block_on -The block_on function is a synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on function is part of the tokio or async-std crates, not part of the standard library. +The block_on function is a synchronous function that produces a final value of an asynchronous function, + +you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on + +function is part of the tokio or async-std crates, not part of the standard library. ## Why block_on In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when From f7c80d14325422d9f9a7e9e3e437bb3ddf725fcc Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Fri, 2 May 2025 14:01:23 +0100 Subject: [PATCH 08/13] Update block_on.md --- docs/block_on.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/block_on.md b/docs/block_on.md index 5dc54e0..9f4e251 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -27,7 +27,7 @@ there are finished and poll it again, and this somehow ruin the simplicity of th asynchronous function is there, this buck is solved using .await expression which pause the execution of -this async function until the awaited value is ready before resuming back it execution. it's true that +this async function until the awaited value is ready before resuming back it execution. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, From 7a31f56fca0640e9b7b2b01b604d32ee2590e85c Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Fri, 2 May 2025 14:02:38 +0100 Subject: [PATCH 09/13] Update block_on.md --- docs/block_on.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/block_on.md b/docs/block_on.md index 9f4e251..546a934 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -1,5 +1,7 @@ # Overview of block_on function + ## What's block_on + The block_on function is a synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on @@ -7,6 +9,7 @@ you can think of it as an adapter from the asynchronous world to the synchronous function is part of the tokio or async-std crates, not part of the standard library. ## Why block_on + In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when executing a synchronous function: the caller only resumes when the operation is completed, what if we From 7079d7cccd562110e0cc3b625429840581eee9d7 Mon Sep 17 00:00:00 2001 From: donemmanuelo Date: Fri, 2 May 2025 14:09:36 +0100 Subject: [PATCH 10/13] Update block_on.md --- docs/block_on.md | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/docs/block_on.md b/docs/block_on.md index 546a934..4bcb7cf 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -3,40 +3,18 @@ ## What's block_on The block_on function is a synchronous function that produces a final value of an asynchronous function, - you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on - function is part of the tokio or async-std crates, not part of the standard library. ## Why block_on In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when - executing a synchronous function: the caller only resumes when the operation is completed, what if we - want our thread to do something else while the operating system is doing his work, we will need to use - new I/O library that provides an asynchronous version of this function, Rust approach of supporting +asynchronous operation is by introducing a trait, std::future::Future. -asynchronous operation is by introducing a trait, std::future::Future. A Future represent an operation - -you can test for completion. So with Future, you can always know the state of the current thread inother - -to do other jobs, but using futures seems challenging because, you keep on polling other jobs while a - -future is still pending, keeping track of previous futures who are pending and what should be done once - -there are finished and poll it again, and this somehow ruin the simplicity of the function. Good news, - -asynchronous function is there, this buck is solved using .await expression which pause the execution of - -this async function until the awaited value is ready before resuming back it execution. it's true that - -it easy to get the value of an async function: just await it. But async function itself return a future, - -so it's now the caller's job to do the polling somehow, thus someone has to wait for value and in this - -case block_on is our waiter. +A Future represent an operation you can test for completion. So with Future, you can always know the state of the current thread inother to do other jobs, but using futures seems challenging because, you keep on polling other jobs while a future is still pending, keeping track of previous futures who are pending and what should be done once there are finished and poll it again, and this somehow ruin the simplicity of the function. Good news, asynchronous function is there, this buck is solved using .await expression which pause the execution of this async function until the awaited value is ready before resuming back it execution. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for value and in this case block_on is our waiter. Consider the example below: ```sh @@ -64,9 +42,7 @@ fn main() -> std::io::Result<()> { ``` We can call the function cheapo_request from an ordinary, synchronous function(like the main, for - example), using the async_std's task::block_on function, which takes a future and poll it until it return - a value as seen above. So in summary, the block_on function is used to execute asynchronous block synchronously in rust. From c269172755c37e5a0e26d12bfd899570f45ebc8a Mon Sep 17 00:00:00 2001 From: chojuninengu Date: Sun, 9 Nov 2025 18:19:41 +0100 Subject: [PATCH 11/13] feat: implement client send_commands and server handle_subscriber functions - Replace todo!() in client.rs send_commands with full command parsing implementation - Add support for /join, /post, /help, and /quit commands - Implement handle_subscriber function in server group.rs for message broadcasting - Add comprehensive tests for command parsing functionality - Fix async-std stdin usage with proper BufReader and BufReadExt imports Resolves the 'not yet implemented' crash when running the client Both client and server now fully functional for group chat operations --- src/bin/client.rs | 164 ++++++++++++++++++++++++++++++++++++++-- src/bin/server/group.rs | 48 ++++++++++-- 2 files changed, 201 insertions(+), 11 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index a9b9ff4..742de1e 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,7 +1,8 @@ #![allow(dead_code, unused_variables, unused_mut)] // Suppresses warnings -use async_chat::{FromServer, utils}; -use async_std::{io::BufReader, net, prelude::FutureExt, stream::StreamExt, task}; +use async_chat::{FromClient, FromServer, utils}; +use async_std::{io::{BufReader, BufReadExt, stdin}, net, prelude::FutureExt, stream::StreamExt, task}; +use std::sync::Arc; /// Client binary for connecting to the async chat server. /// @@ -23,11 +24,104 @@ fn main() -> anyhow::Result<()> { }) } -/// Reads user input (planned via `clap`) and sends commands to the server. -async fn send_commands(_to_server: net::TcpStream) -> anyhow::Result<()> { - // TODO: Implement use clap to parse command line arguments and print help message - todo!() +/// Reads user input and sends commands to the server. +/// +/// Commands: +/// - `/join ` - Join a chat group +/// - `/post ` - Post a message to a group +/// - `/help` - Show help message +/// - `/quit` - Exit the client +async fn send_commands(to_server: net::TcpStream) -> anyhow::Result<()> { + let mut to_server = to_server; + println!("Welcome to Async Chat!"); + println!("Commands:"); + println!(" /join - Join a chat group"); + println!(" /post - Post a message to a group"); + println!(" /help - Show this help message"); + println!(" /quit - Exit the client"); + println!(); + + let stdin = BufReader::new(stdin()); + let mut lines = stdin.lines(); + + while let Some(line_result) = lines.next().await { + let line = line_result?; + let line = line.trim(); + + if line.is_empty() { + continue; + } + + if line == "/quit" { + println!("Goodbye!"); + break; + } + + if line == "/help" { + println!("Commands:"); + println!(" /join - Join a chat group"); + println!(" /post - Post a message to a group"); + println!(" /help - Show this help message"); + println!(" /quit - Exit the client"); + continue; + } + + let command = parse_command(line); + match command { + Ok(from_client) => { + if let Err(e) = utils::send_as_json(&mut to_server, &from_client).await { + eprintln!("Failed to send command: {}", e); + break; + } + } + Err(e) => { + eprintln!("Error: {}", e); + eprintln!("Type /help for available commands."); + } + } + } + + Ok(()) +} + +/// Parses a command line input into a FromClient message. +/// +/// # Arguments +/// * `input` - The user input string to parse +/// +/// # Returns +/// A Result containing either a FromClient message or an error string +fn parse_command(input: &str) -> Result { + let parts: Vec<&str> = input.splitn(3, ' ').collect(); + + match parts.as_slice() { + ["/join", group_name] => { + if group_name.is_empty() { + return Err("Group name cannot be empty".to_string()); + } + Ok(FromClient::Join { + group_name: Arc::new(group_name.to_string()), + }) + } + ["/post", group_name, message] => { + if group_name.is_empty() { + return Err("Group name cannot be empty".to_string()); + } + if message.is_empty() { + return Err("Message cannot be empty".to_string()); + } + Ok(FromClient::Post { + group_name: Arc::new(group_name.to_string()), + message: Arc::new(message.to_string()), + }) + } + ["/join"] => Err("Usage: /join ".to_string()), + ["/post"] => Err("Usage: /post ".to_string()), + ["/post", _] => Err("Usage: /post ".to_string()), + _ => Err(format!("Unknown command: '{}'. Type /help for available commands.", input)), + } } + /// Handles responses from the server and prints them to stdout as they arrive. async fn handle_replies(from_server: net::TcpStream) -> anyhow::Result<()> { let buffered = BufReader::new(from_server); @@ -50,3 +144,61 @@ async fn handle_replies(from_server: net::TcpStream) -> anyhow::Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_join_command() { + let result = parse_command("/join general"); + assert!(result.is_ok()); + match result.unwrap() { + FromClient::Join { group_name } => { + assert_eq!(*group_name, "general".to_string()); + } + _ => panic!("Expected Join command"), + } + } + + #[test] + fn test_parse_post_command() { + let result = parse_command("/post general Hello world!"); + assert!(result.is_ok()); + match result.unwrap() { + FromClient::Post { group_name, message } => { + assert_eq!(*group_name, "general".to_string()); + assert_eq!(*message, "Hello world!".to_string()); + } + _ => panic!("Expected Post command"), + } + } + + #[test] + fn test_parse_invalid_command() { + let result = parse_command("/invalid"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Unknown command")); + } + + #[test] + fn test_parse_join_without_group() { + let result = parse_command("/join"); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "Usage: /join "); + } + + #[test] + fn test_parse_post_without_message() { + let result = parse_command("/post general"); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "Usage: /post "); + } + + #[test] + fn test_parse_post_without_group() { + let result = parse_command("/post"); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "Usage: /post "); + } +} diff --git a/src/bin/server/group.rs b/src/bin/server/group.rs index 0e93150..2ad43bd 100644 --- a/src/bin/server/group.rs +++ b/src/bin/server/group.rs @@ -46,11 +46,49 @@ impl Group { /// Handles the lifecycle of a subscriber: receiving messages and sending them over their connection. /// -/// This is a stub — should be implemented to read from the `receiver` and forward messages to `outbound`. +/// Receives messages from the broadcast channel and forwards them to the client connection. +/// Exits when the client disconnects or an error occurs. async fn handle_subscriber( - _group_name: Arc, - _receiver: broadcast::Receiver>, - _outbound: Arc, + group_name: Arc, + mut receiver: broadcast::Receiver>, + outbound: Arc, ) { - todo!() + use async_chat::FromServer; + + loop { + match receiver.recv().await { + Ok(message) => { + let server_message = FromServer::Message { + group_name: group_name.clone(), + message, + }; + + // Send the message to the client + if let Err(e) = outbound.send(server_message).await { + eprintln!("Failed to send message to client in group '{}': {}", group_name, e); + break; // Exit the loop if we can't send to the client + } + } + Err(broadcast::error::RecvError::Lagged(skipped)) => { + // Client was too slow, some messages were skipped + eprintln!("Client in group '{}' lagged behind, skipped {} messages", group_name, skipped); + + let error_message = FromServer::Error( + format!("You were lagging behind and missed {} messages", skipped) + ); + + if let Err(e) = outbound.send(error_message).await { + eprintln!("Failed to send lag error to client in group '{}': {}", group_name, e); + break; + } + } + Err(broadcast::error::RecvError::Closed) => { + // The broadcast channel was closed (group was deleted) + eprintln!("Broadcast channel for group '{}' was closed", group_name); + break; + } + } + } + + eprintln!("Subscriber handler for group '{}' exited", group_name); } From 483951522d06df1457b7a13d919430697bc8c5c3 Mon Sep 17 00:00:00 2001 From: chojuninengu Date: Mon, 10 Nov 2025 08:41:29 +0100 Subject: [PATCH 12/13] style: fix code formatting issues - Run cargo fmt to fix all formatting violations - Ensure CI pipeline passes formatting checks - No functional changes, only code style improvements --- src/bin/client.rs | 26 +++++++++++++++++++------- src/bin/server/group.rs | 30 ++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 742de1e..e979fcf 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,7 +1,13 @@ #![allow(dead_code, unused_variables, unused_mut)] // Suppresses warnings use async_chat::{FromClient, FromServer, utils}; -use async_std::{io::{BufReader, BufReadExt, stdin}, net, prelude::FutureExt, stream::StreamExt, task}; +use async_std::{ + io::{BufReadExt, BufReader, stdin}, + net, + prelude::FutureExt, + stream::StreamExt, + task, +}; use std::sync::Arc; /// Client binary for connecting to the async chat server. @@ -25,7 +31,7 @@ fn main() -> anyhow::Result<()> { } /// Reads user input and sends commands to the server. -/// +/// /// Commands: /// - `/join ` - Join a chat group /// - `/post ` - Post a message to a group @@ -85,15 +91,15 @@ async fn send_commands(to_server: net::TcpStream) -> anyhow::Result<()> { } /// Parses a command line input into a FromClient message. -/// +/// /// # Arguments /// * `input` - The user input string to parse -/// +/// /// # Returns /// A Result containing either a FromClient message or an error string fn parse_command(input: &str) -> Result { let parts: Vec<&str> = input.splitn(3, ' ').collect(); - + match parts.as_slice() { ["/join", group_name] => { if group_name.is_empty() { @@ -118,7 +124,10 @@ fn parse_command(input: &str) -> Result { ["/join"] => Err("Usage: /join ".to_string()), ["/post"] => Err("Usage: /post ".to_string()), ["/post", _] => Err("Usage: /post ".to_string()), - _ => Err(format!("Unknown command: '{}'. Type /help for available commands.", input)), + _ => Err(format!( + "Unknown command: '{}'. Type /help for available commands.", + input + )), } } @@ -166,7 +175,10 @@ mod tests { let result = parse_command("/post general Hello world!"); assert!(result.is_ok()); match result.unwrap() { - FromClient::Post { group_name, message } => { + FromClient::Post { + group_name, + message, + } => { assert_eq!(*group_name, "general".to_string()); assert_eq!(*message, "Hello world!".to_string()); } diff --git a/src/bin/server/group.rs b/src/bin/server/group.rs index 2ad43bd..eb5b5fa 100644 --- a/src/bin/server/group.rs +++ b/src/bin/server/group.rs @@ -54,7 +54,7 @@ async fn handle_subscriber( outbound: Arc, ) { use async_chat::FromServer; - + loop { match receiver.recv().await { Ok(message) => { @@ -62,23 +62,33 @@ async fn handle_subscriber( group_name: group_name.clone(), message, }; - + // Send the message to the client if let Err(e) = outbound.send(server_message).await { - eprintln!("Failed to send message to client in group '{}': {}", group_name, e); + eprintln!( + "Failed to send message to client in group '{}': {}", + group_name, e + ); break; // Exit the loop if we can't send to the client } } Err(broadcast::error::RecvError::Lagged(skipped)) => { // Client was too slow, some messages were skipped - eprintln!("Client in group '{}' lagged behind, skipped {} messages", group_name, skipped); - - let error_message = FromServer::Error( - format!("You were lagging behind and missed {} messages", skipped) + eprintln!( + "Client in group '{}' lagged behind, skipped {} messages", + group_name, skipped ); - + + let error_message = FromServer::Error(format!( + "You were lagging behind and missed {} messages", + skipped + )); + if let Err(e) = outbound.send(error_message).await { - eprintln!("Failed to send lag error to client in group '{}': {}", group_name, e); + eprintln!( + "Failed to send lag error to client in group '{}': {}", + group_name, e + ); break; } } @@ -89,6 +99,6 @@ async fn handle_subscriber( } } } - + eprintln!("Subscriber handler for group '{}' exited", group_name); } From bb19b7c3b581dd219adb81b783718aeec8bae66b Mon Sep 17 00:00:00 2001 From: chojuninengu Date: Tue, 11 Nov 2025 10:51:34 +0100 Subject: [PATCH 13/13] docs: fix typos and grammar in block_on.md - Fix grammatical errors and improve readability - Correct subject-verb agreement issues - Fix pronoun usage and punctuation - Improve sentence structure and flow - Capitalize 'Rust' properly throughout the document --- docs/block_on.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/block_on.md b/docs/block_on.md index 4bcb7cf..4b1e902 100644 --- a/docs/block_on.md +++ b/docs/block_on.md @@ -1,6 +1,6 @@ # Overview of block_on function -## What's block_on +## What is block_on The block_on function is a synchronous function that produces a final value of an asynchronous function, you can think of it as an adapter from the asynchronous world to the synchronous world. The block_on @@ -8,13 +8,9 @@ function is part of the tokio or async-std crates, not part of the standard libr ## Why block_on -In a sense, asynchronous function just pass the buck, this buck is simply due to the fact that, when -executing a synchronous function: the caller only resumes when the operation is completed, what if we -want our thread to do something else while the operating system is doing his work, we will need to use -new I/O library that provides an asynchronous version of this function, Rust approach of supporting -asynchronous operation is by introducing a trait, std::future::Future. +In a sense, asynchronous functions just pass the buck. This buck is simply due to the fact that when executing a synchronous function, the caller only resumes when the operation is completed. What if we want our thread to do something else while the operating system is doing its work? We will need to use a new I/O library that provides an asynchronous version of this function. Rust's approach to supporting asynchronous operations is by introducing a trait: std::future::Future. -A Future represent an operation you can test for completion. So with Future, you can always know the state of the current thread inother to do other jobs, but using futures seems challenging because, you keep on polling other jobs while a future is still pending, keeping track of previous futures who are pending and what should be done once there are finished and poll it again, and this somehow ruin the simplicity of the function. Good news, asynchronous function is there, this buck is solved using .await expression which pause the execution of this async function until the awaited value is ready before resuming back it execution. it's true that it easy to get the value of an async function: just await it. But async function itself return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for value and in this case block_on is our waiter. +A Future represents an operation you can test for completion. So with Future, you can always know the state of the current thread in order to do other jobs, but using futures seems challenging because you keep on polling other jobs while a future is still pending, keeping track of previous futures that are pending and what should be done once they are finished and poll it again, and this somehow ruins the simplicity of the function. Good news: asynchronous functions are there! This buck is solved using the .await expression which pauses the execution of this async function until the awaited value is ready before resuming its execution. It's true that it's easy to get the value of an async function: just await it. But async functions themselves return a future, so it's now the caller's job to do the polling somehow, thus someone has to wait for the value and in this case block_on is our waiter. Consider the example below: ```sh @@ -41,10 +37,6 @@ fn main() -> std::io::Result<()> { } ``` -We can call the function cheapo_request from an ordinary, synchronous function(like the main, for -example), using the async_std's task::block_on function, which takes a future and poll it until it return -a value as seen above. - -So in summary, the block_on function is used to execute asynchronous block synchronously in rust. - +We can call the function cheapo_request from an ordinary, synchronous function (like main, for example), using async_std's task::block_on function, which takes a future and polls it until it returns a value as seen above. +So in summary, the block_on function is used to execute asynchronous blocks synchronously in Rust.