diff --git a/advanced/signal-protocol.mdx b/advanced/signal-protocol.mdx index 64256d2..89f2977 100644 --- a/advanced/signal-protocol.mdx +++ b/advanced/signal-protocol.mdx @@ -661,7 +661,7 @@ This mechanism ensures that group messages from newly-paired companion devices a // src/message.rs — simplified flow async fn handle_unknown_device_sync(&self, info: &Arc) { let user_jid = info.source.sender.to_non_ad(); - if !self.pending_device_sync.add(user_jid.clone()).await { + if !self.pending_device_sync.add(&user_jid) { return; // already queued, dedup } if info.is_offline { diff --git a/advanced/websocket-handling.mdx b/advanced/websocket-handling.mdx index 9cd53d2..1e0b847 100644 --- a/advanced/websocket-handling.mdx +++ b/advanced/websocket-handling.mdx @@ -777,7 +777,7 @@ impl Client { ).await?; // Store socket and start receivers - self.noise_socket.store(Some(Arc::clone(&noise_socket))); + *self.noise_socket.lock().unwrap_or_else(|p| p.into_inner()) = Some(Arc::clone(&noise_socket)); self.start_frame_receiver(events, noise_socket).await; Ok(()) @@ -795,7 +795,7 @@ async fn read_messages_loop(self: &Arc) -> Result<(), anyhow::Error> { .ok_or_else(|| anyhow!("Cannot start message loop: not connected"))?; // Noise socket is stable for the lifetime of this loop; resolve once // instead of locking the mutex on every inbound frame. - let noise_socket = self.get_noise_socket().await + let noise_socket = self.get_noise_socket() .map_err(|_| anyhow!("Cannot start message loop: no noise socket"))?; let mut frame_decoder = FrameDecoder::new(); @@ -874,8 +874,7 @@ Frame decryption is always sequential (noise protocol counter ordering), but nod ```rust impl Client { pub async fn send_node(&self, node: &Node) -> Result<()> { - let noise_socket = self.noise_socket.load() - .ok_or_else(|| anyhow!("not connected"))?; + let noise_socket = self.get_noise_socket()?; // Marshal node to binary with auto-sized buffer let plaintext_buf = marshal_auto(node)?; @@ -898,7 +897,7 @@ impl Client { async fn cleanup_connection_state(&self) { self.shutdown_notifier.notify(usize::MAX); *self.transport.lock().await = None; - *self.noise_socket.lock().await = None; + *self.noise_socket.lock().unwrap_or_else(|p| p.into_inner()) = None; self.is_connected.store(false, Ordering::Release); // Drop per-chat lane senders so workers exit via channel close. diff --git a/api/chatstate.mdx b/api/chatstate.mdx index 8732f09..36f2e63 100644 --- a/api/chatstate.mdx +++ b/api/chatstate.mdx @@ -143,7 +143,7 @@ client.register_chatstate_handler(Arc::new(|event: ChatStateEvent| { } _ => {} } -})).await; +})); ``` ### ReceivedChatState @@ -412,4 +412,4 @@ tokio::time::sleep(Duration::from_secs(3)).await; client.chatstate().send_paused(&recipient).await?; println!("Recording stopped (would send audio here)"); -``` \ No newline at end of file +``` diff --git a/api/client.mdx b/api/client.mdx index fc95d10..8ba3762 100644 --- a/api/client.mdx +++ b/api/client.mdx @@ -1808,13 +1808,14 @@ See [ChannelEventHandler](/concepts/events#channeleventhandler) for details. ### register_chatstate_handler ```rust -pub async fn register_chatstate_handler( - &self, - handler: Arc, -) +pub fn register_chatstate_handler(&self, handler: Arc) ``` -Registers a handler for chat state events (typing indicators). The handler is wrapped in `Arc` for thread-safe sharing across the event dispatching system. +Register a handler for chat state events (typing indicators). Pass the handler in an `Arc` so the event dispatcher can share it across threads. Copy-on-write registration lets event dispatch continue while you register another handler. When no handler is registered, the client uses a lock-free fast path. + + +**Breaking change (as of PR #1227):** `register_chatstate_handler` is no longer `async`. Drop the `.await` at call sites: `client.register_chatstate_handler(handler)`. + ### set_raw_node_forwarding diff --git a/concepts/architecture.mdx b/concepts/architecture.mdx index c86af96..2292274 100644 --- a/concepts/architecture.mdx +++ b/concepts/architecture.mdx @@ -148,14 +148,14 @@ See [custom backends](/guides/custom-backends) for implementing your own runtime **Location:** `src/client.rs` -**Purpose:** Orchestrates connection lifecycle, event bus, and high-level operations. Uses `async-lock` (runtime-agnostic) for all internal synchronization instead of Tokio-specific primitives. +**Purpose:** Orchestrates connection lifecycle, event bus, and high-level operations. The synchronization primitive follows the shape of the state, not a single default. State whose critical section awaits uses `async-lock` (runtime-agnostic, not Tokio-specific). State whose critical section never awaits — a clone, a store, a set op — uses a `std::sync` lock instead. State that is built once and never replaced uses `std::sync::OnceLock`. On a path that must produce a `Send` future (e.g. a spawned task), a `std::sync::MutexGuard` isn't `Send`, so holding one across an `.await` there is a compile error rather than something a reviewer has to catch by hand ([#1227](https://github.com/oxidezap/whatsapp-rust/pull/1227)). ```rust pub struct Client { pub(crate) core: wacore::client::CoreClient, pub(crate) persistence_manager: Arc, pub(crate) media_conn: Arc>>, - pub(crate) noise_socket: Arc>>>, + pub(crate) noise_socket: Arc>>>, // ... connection state, caches, locks } ``` @@ -513,7 +513,7 @@ When online (not during offline sync), unknown devices trigger an immediate back ```rust // src/pending_device_sync.rs pub(crate) struct PendingDeviceSync { - pending: async_lock::Mutex>, + pending: std::sync::Mutex>, } ```