Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions async-nats/src/jetstream/consumer/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use std::{sync::atomic::Ordering, time::Duration};
#[cfg(feature = "server_2_11")]
use time::{serde::rfc3339, OffsetDateTime};
use tokio::{sync::oneshot::error::TryRecvError, task::JoinHandle};
use tracing::{debug, trace};
use tracing::{debug, error, trace};

const ORDERED_IDLE_HEARTBEAT: Duration = Duration::from_secs(5);

Expand Down Expand Up @@ -153,10 +153,14 @@ impl futures_util::Stream for Messages {
// TODO store pending_publish as a future and return errors from it
let client = self.context.client.clone();
tokio::task::spawn(async move {
client
.publish(subject, Bytes::from_static(b""))
.await
.unwrap();
if let Err(err) =
client.publish(subject, Bytes::from_static(b"")).await
{
error!(
"failed to respond to idle heartbeat: {}",
err
);
}
});
}

Comment on lines 153 to 166

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 unwrap() on publish inside spawned heartbeat responder task can panic the task silently

In Messages::poll_next (impl of futures_util::Stream for Messages), replaced the .publish(subject, Bytes::from_static(b"")).await.unwrap() call inside the spawned heartbeat-responder tokio::task::spawn with an if let Err(err) = ... { error!(...) } pattern, matching the existing non-panicking style used for the analogous flow-control publish in Ordered::poll_next (which uses .ok()). This removes the panic-on-publish-failure path while still surfacing the failure via a log line instead of silently dropping it. Added error to the tracing import list at the top of the file to support the new log call. No other behavior changed.

πŸ€– Prompt for AI agents
In async-nats/src/jetstream/consumer/push.rs around line 152, review and complete this code-review fix: unwrap() on publish inside spawned heartbeat responder task can panic the task silently.
What the draft fix changed: In `Messages::poll_next` (impl of `futures_util::Stream for Messages`), replaced the `.publish(subject, Bytes::from_static(b"")).await.unwrap()` call inside the spawned heartbeat-responder `tokio::task::spawn` with an `if let Err(err) = ... { error!(...) }` pattern, matching the existing non-panicking style used for the analogous flow-control publish in `Ordered::poll_next` (which uses `.ok()`). This removes the panic-on-publish-failure path while still surfacing the failure via a log line instead of silently dropping it. Added `error` to the `tracing` import list at the top of the file to support the new log call. No other behavior changed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 85 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
18 changes: 15 additions & 3 deletions async-nats/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,16 +380,24 @@ impl Service {
loop {
tokio::select! {
Some(ping) = pings.next() => {
let Some(reply) = ping.reply else {
debug!("ignoring PING request without a reply subject");
continue;
};
let pong = serde_json::to_vec(&PingResponse{
kind: "io.nats.micro.v1.ping_response".to_string(),
name: info.name.clone(),
id: info.id.clone(),
version: info.version.clone(),
metadata: info.metadata.clone(),
})?;
client.publish(ping.reply.unwrap(), pong.into()).await?;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 unwrap() on reply subject in service verb dispatch loop can panic the whole service task

In Service::add's spawned task (the tokio::select! loop handling PING/INFO/STATS verb subscriptions), replaced the three .reply.unwrap() calls with let Some(reply) = <msg>.reply else { debug!(...); continue; }; guards for the PING, INFO, and STATS branches respectively. Each branch now skips processing (via continue, logging at debug level) instead of panicking when a request lacks a reply subject, and uses the extracted reply variable for the subsequent client.publish(...) call. This prevents a malformed request from aborting the whole background service task while leaving all other behavior (response payloads, stats updates) unchanged.

πŸ€– Prompt for AI agents
In async-nats/src/service/mod.rs around line 390, review and complete this code-review fix: unwrap() on reply subject in service verb dispatch loop can panic the whole service task.
What the draft fix changed: In `Service::add`'s spawned task (the `tokio::select!` loop handling PING/INFO/STATS verb subscriptions), replaced the three `.reply.unwrap()` calls with `let Some(reply) = <msg>.reply else { debug!(...); continue; };` guards for the PING, INFO, and STATS branches respectively. Each branch now skips processing (via `continue`, logging at debug level) instead of panicking when a request lacks a reply subject, and uses the extracted `reply` variable for the subsequent `client.publish(...)` call. This prevents a malformed request from aborting the whole background service task while leaving all other behavior (response payloads, stats updates) unchanged.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 85 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

client.publish(reply, pong.into()).await?;
},
Some(info_request) = infos.next() => {
let Some(reply) = info_request.reply else {
debug!("ignoring INFO request without a reply subject");
continue;
};
let info = info.clone();

let endpoints: Vec<endpoint::Info> = {
Expand All @@ -407,9 +415,13 @@ impl Service {
..info
};
let info_json = serde_json::to_vec(&info).map(Bytes::from)?;
client.publish(info_request.reply.unwrap(), info_json.clone()).await?;
client.publish(reply, info_json.clone()).await?;
},
Some(stats_request) = stats.next() => {
let Some(reply) = stats_request.reply else {
debug!("ignoring STATS request without a reply subject");
continue;
};
if let Some(stats_callback) = stats_callback.as_mut() {
let mut endpoint_stats_locked = endpoints_state.lock().unwrap();
for (key, value) in &mut endpoint_stats_locked.endpoints {
Expand All @@ -425,7 +437,7 @@ impl Service {
started,
endpoints: endpoints_state.lock().unwrap().endpoints.values().cloned().map(Into::into).collect(),
})?;
client.publish(stats_request.reply.unwrap(), stats.into()).await?;
client.publish(reply, stats.into()).await?;
},
else => break,
}
Expand Down
Loading