Skip to content

Commit 63e13b4

Browse files
committed
feat(core): add post-round trace action to run_with and spawn_with callbacks`
1 parent e7cb768 commit 63e13b4

3 files changed

Lines changed: 127 additions & 34 deletions

File tree

crates/trippy-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub use probe::{
8282
ProbeComplete, ProbeStatus, UnknownExtension,
8383
};
8484
pub use state::{Hop, NatStatus, State};
85-
pub use strategy::{CompletionReason, Round, Strategy};
85+
pub use strategy::{Action, CompletionReason, Round, Strategy};
8686
pub use tracer::Tracer;
8787
pub use types::{
8888
Dscp, Ecn, Flags, MaxInflight, MaxRounds, PacketSize, PayloadPattern, Port, RoundId, Sequence,

crates/trippy-core/src/strategy.rs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,29 @@ pub enum CompletionReason {
4949
RoundTimeLimitExceeded,
5050
}
5151

52+
/// The action to take after the completion of a round.
53+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
54+
pub enum Action {
55+
/// Continue tracing.
56+
Continue,
57+
/// Stop tracing.
58+
Stop,
59+
}
60+
61+
impl From<()> for Action {
62+
fn from((): ()) -> Self {
63+
Self::Continue
64+
}
65+
}
66+
5267
/// Trace a path to a target.
5368
#[derive(Debug, Clone)]
5469
pub struct Strategy<F> {
5570
config: StrategyConfig,
5671
publish: F,
5772
}
5873

59-
impl<F: Fn(&Round<'_>)> Strategy<F> {
74+
impl<F: Fn(&Round<'_>) -> Action> Strategy<F> {
6075
#[instrument(skip_all, level = "trace")]
6176
pub fn new(config: &StrategyConfig, publish: F) -> Self {
6277
tracing::debug!(?config);
@@ -192,8 +207,8 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
192207
let round_max = round_duration > self.config.max_round_duration;
193208
let target_found = st.target_found();
194209
if round_min && grace_exceeded && target_found || round_max {
195-
self.publish_trace(st);
196-
st.advance_round(self.config.first_ttl);
210+
let action = self.publish_trace(st);
211+
st.advance_round(self.config.first_ttl, action);
197212
}
198213
}
199214

@@ -202,7 +217,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
202217
/// If the round completed without receiving an `EchoReply` from the target host then we also
203218
/// publish the next `ProbeStatus` which is assumed to represent the TTL of the target host.
204219
#[instrument(skip(self, state), level = "trace")]
205-
fn publish_trace(&self, state: &TracerState) {
220+
fn publish_trace(&self, state: &TracerState) -> Action {
206221
let max_received_ttl = if let Some(target_ttl) = state.target_ttl() {
207222
target_ttl
208223
} else {
@@ -220,7 +235,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
220235
} else {
221236
CompletionReason::RoundTimeLimitExceeded
222237
};
223-
(self.publish)(&Round::new(probes, largest_ttl, reason));
238+
(self.publish)(&Round::new(probes, largest_ttl, reason))
224239
}
225240

226241
/// Check if the `TraceId` matches the expected value for this tracer.
@@ -840,7 +855,7 @@ mod tests {
840855
protocol: Protocol::Tcp,
841856
..Default::default()
842857
};
843-
let tracer = Strategy::new(&config, |_| {});
858+
let tracer = Strategy::new(&config, |_| Action::Continue);
844859
let mut state = TracerState::new(config);
845860
tracer.send_request(&mut network, &mut state)?;
846861
tracer.recv_response(&mut network, &mut state)?;
@@ -870,7 +885,7 @@ mod state {
870885
use crate::probe::{Probe, ProbeStatus};
871886
use crate::strategy::{StrategyConfig, StrategyResponse};
872887
use crate::types::{MaxRounds, Port, RoundId, Sequence, TimeToLive, TraceId};
873-
use crate::{Flags, MultipathStrategy, PortDirection, Protocol};
888+
use crate::{Action, Flags, MultipathStrategy, PortDirection, Protocol};
874889
use std::array::from_fn;
875890
use std::net::IpAddr;
876891
use std::time::SystemTime;
@@ -929,6 +944,8 @@ mod state {
929944
target_ttl: Option<TimeToLive>,
930945
/// The timestamp of the echo response packet.
931946
received_time: Option<SystemTime>,
947+
/// The action to take before starting the next round.
948+
next_round_action: Action,
932949
}
933950

934951
impl TracerState {
@@ -945,6 +962,7 @@ mod state {
945962
max_received_ttl: None,
946963
target_ttl: None,
947964
received_time: None,
965+
next_round_action: Action::Continue,
948966
}
949967
}
950968

@@ -995,11 +1013,12 @@ mod state {
9951013
}
9961014

9971015
/// Are all rounds complete?
998-
pub const fn finished(&self, max_rounds: Option<MaxRounds>) -> bool {
999-
match max_rounds {
1000-
None => false,
1001-
Some(max_rounds) => self.round.0 > max_rounds.0.get() - 1,
1002-
}
1016+
pub fn finished(&self, max_rounds: Option<MaxRounds>) -> bool {
1017+
self.next_round_action == Action::Stop
1018+
|| match max_rounds {
1019+
None => false,
1020+
Some(max_rounds) => self.round.0 > max_rounds.0.get() - 1,
1021+
}
10031022
}
10041023

10051024
/// Create and return the next `Probe` at the current `sequence` and `ttl`.
@@ -1257,7 +1276,8 @@ mod state {
12571276
/// reset it here. We do this here to avoid having to deal with the sequence number
12581277
/// wrapping during a round, which is more problematic.
12591278
#[instrument(skip(self), level = "trace")]
1260-
pub fn advance_round(&mut self, first_ttl: TimeToLive) {
1279+
pub fn advance_round(&mut self, first_ttl: TimeToLive, next_round_action: Action) {
1280+
self.next_round_action = next_round_action;
12611281
if self.sequence >= self.max_sequence() {
12621282
self.sequence = self.config.initial_sequence;
12631283
}
@@ -1375,7 +1395,7 @@ mod state {
13751395
}
13761396

13771397
// Advance to the next round
1378-
state.advance_round(TimeToLive(1));
1398+
state.advance_round(TimeToLive(1), Action::Continue);
13791399

13801400
// Validate the `TracerState` after the round update
13811401
assert_eq!(state.round, RoundId(1));
@@ -1509,7 +1529,7 @@ mod state {
15091529
}
15101530

15111531
// Advance the round, which will wrap the sequence back to `initial_sequence`
1512-
state.advance_round(TimeToLive(1));
1532+
state.advance_round(TimeToLive(1), Action::Continue);
15131533
assert_eq!(state.round, RoundId(1));
15141534
assert_eq!(state.sequence, initial_sequence);
15151535
assert_eq!(state.round_sequence, initial_sequence);
@@ -1547,7 +1567,7 @@ mod state {
15471567
for _ in 0..max_probe_per_round {
15481568
let _probe = state.next_probe(SystemTime::now());
15491569
}
1550-
state.advance_round(TimeToLive(1));
1570+
state.advance_round(TimeToLive(1), Action::Continue);
15511571
}
15521572
assert_eq!(state.round, RoundId(2000));
15531573
assert_eq!(state.round_sequence, Sequence(33434));
@@ -1564,7 +1584,7 @@ mod state {
15641584
for _ in 0..rng.random_range(0..max_probe_per_round) {
15651585
state.next_probe(SystemTime::now());
15661586
}
1567-
state.advance_round(TimeToLive(1));
1587+
state.advance_round(TimeToLive(1), Action::Continue);
15681588
}
15691589
}
15701590

@@ -1578,7 +1598,7 @@ mod state {
15781598
_ = state.next_probe(SystemTime::now());
15791599
_ = state.reissue_probe(SystemTime::now());
15801600
}
1581-
state.advance_round(TimeToLive(1));
1601+
state.advance_round(TimeToLive(1), Action::Continue);
15821602
}
15831603
assert_eq!(state.round, RoundId(2000));
15841604
assert_eq!(state.round_sequence, Sequence(57310));
@@ -1600,7 +1620,7 @@ mod state {
16001620
for _ in 0..55 {
16011621
_ = state.next_probe(SystemTime::now());
16021622
}
1603-
state.advance_round(TimeToLive(1));
1623+
state.advance_round(TimeToLive(1), Action::Continue);
16041624
assert!(!state.in_round(Sequence(64491)));
16051625
}
16061626

crates/trippy-core/src/tracer.rs

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::error::Result;
22
use crate::{
3-
Error, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy, PacketSize,
3+
Action, Error, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy, PacketSize,
44
PayloadPattern, PortDirection, PrivilegeMode, Protocol, Round, Sequence, State, TimeToLive,
55
TraceId, TypeOfService,
66
};
@@ -137,9 +137,12 @@ impl Tracer {
137137
/// retrieved using the [`Tracer::snapshot`] method.
138138
///
139139
/// This method will additionally call the provided function for each round
140-
/// that is completed. This can be useful if you want to gather round state
140+
/// that is completed. This can be useful if you want to gather round state
141141
/// manually if the tracer is run indefinitely (by not setting
142-
/// [`crate::Builder::max_rounds`])
142+
/// [`crate::Builder::max_rounds`]).
143+
///
144+
/// The callback may either return `()` to continue tracing, or return
145+
/// [`Action`] to decide whether tracing should continue after each round.
143146
///
144147
/// # Example
145148
///
@@ -159,10 +162,39 @@ impl Tracer {
159162
/// # }
160163
/// ```
161164
///
165+
/// The following will stop after the first round for which `stop` is set:
166+
///
167+
/// # Example
168+
///
169+
/// ```no_run
170+
/// # fn main() -> anyhow::Result<()> {
171+
/// # use std::net::IpAddr;
172+
/// # use std::str::FromStr;
173+
/// # use std::sync::atomic::{AtomicBool, Ordering};
174+
/// use trippy_core::{Builder, Action};
175+
///
176+
/// let addr = IpAddr::from_str("1.1.1.1")?;
177+
/// let stop = AtomicBool::new(false);
178+
/// let tracer = Builder::new(addr).build()?;
179+
/// tracer.run_with(|_| {
180+
/// if stop.load(Ordering::Relaxed) {
181+
/// Action::Stop
182+
/// } else {
183+
/// Action::Continue
184+
/// }
185+
/// })?;
186+
/// # Ok(())
187+
/// # }
188+
/// ```
189+
///
162190
/// # See Also
163191
///
164192
/// - [`Tracer::run`] - Run the tracer without a custom round handler.
165-
pub fn run_with<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
193+
pub fn run_with<F, R>(&self, func: F) -> Result<()>
194+
where
195+
F: Fn(&Round<'_>) -> R,
196+
R: Into<Action>,
197+
{
166198
self.inner.run_with(func)
167199
}
168200

@@ -249,13 +281,47 @@ impl Tracer {
249281
/// # }
250282
/// ```
251283
///
284+
/// The callback may either return `()` to continue tracing, or return
285+
/// [`Action`] to decide whether tracing should continue after each round.
286+
///
287+
/// # Example
288+
///
289+
/// ```no_run
290+
/// # fn main() -> anyhow::Result<()> {
291+
/// # use std::net::IpAddr;
292+
/// # use std::str::FromStr;
293+
/// # use std::sync::{
294+
/// # Arc,
295+
/// # atomic::{AtomicBool, Ordering},
296+
/// # };
297+
/// use trippy_core::{Builder, Action};
298+
///
299+
/// let addr = IpAddr::from_str("1.1.1.1")?;
300+
/// let stop = Arc::new(AtomicBool::new(false));
301+
/// let stop_for_trace = Arc::clone(&stop);
302+
/// let (tracer, handle) = Builder::new(addr)
303+
/// .build()?
304+
/// .spawn_with(move |_| {
305+
/// if stop_for_trace.load(Ordering::Relaxed) {
306+
/// Action::Stop
307+
/// } else {
308+
/// Action::Continue
309+
/// }
310+
/// })?;
311+
/// stop.store(true, Ordering::Relaxed);
312+
/// handle.join().unwrap()?;
313+
/// # Ok(())
314+
/// # }
315+
/// ```
316+
///
252317
/// # See Also
253318
///
254319
/// - [`Tracer::spawn`] - Spawn the tracer on a new thread without a custom round handler.
255-
pub fn spawn_with<F: Fn(&Round<'_>) + Send + 'static>(
256-
self,
257-
func: F,
258-
) -> Result<(Self, JoinHandle<Result<()>>)> {
320+
pub fn spawn_with<F, R>(self, func: F) -> Result<(Self, JoinHandle<Result<()>>)>
321+
where
322+
F: Fn(&Round<'_>) -> R + Send + 'static,
323+
R: Into<Action> + Send + 'static,
324+
{
259325
let tracer = self.clone();
260326
let handle = thread::Builder::new()
261327
.name(format!("tracer-{}", self.trace_identifier().0))
@@ -425,7 +491,7 @@ mod inner {
425491
use crate::error::Result;
426492
use crate::net::{PlatformImpl, SocketImpl};
427493
use crate::{
428-
Channel, Error, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy,
494+
Action, Channel, Error, IcmpExtensionParseMode, MaxInflight, MaxRounds, MultipathStrategy,
429495
PacketSize, PayloadPattern, PortDirection, PrivilegeMode, Protocol, Round, Sequence,
430496
SourceAddr, State, Strategy, TimeToLive, TraceId, TypeOfService,
431497
};
@@ -530,12 +596,15 @@ mod inner {
530596

531597
#[instrument(skip_all, level = "trace")]
532598
pub(super) fn run(&self) -> Result<()> {
533-
self.run_internal(|_| ())
534-
.map_err(|err| self.handle_error(err))
599+
self.run_with(|_| ())
535600
}
536601

537602
#[instrument(skip_all, level = "trace")]
538-
pub(super) fn run_with<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
603+
pub(super) fn run_with<F, R>(&self, func: F) -> Result<()>
604+
where
605+
F: Fn(&Round<'_>) -> R,
606+
R: Into<Action>,
607+
{
539608
self.run_internal(func)
540609
.map_err(|err| self.handle_error(err))
541610
}
@@ -646,7 +715,11 @@ mod inner {
646715
}
647716

648717
#[instrument(skip_all, level = "trace")]
649-
fn run_internal<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
718+
fn run_internal<F, R>(&self, func: F) -> Result<()>
719+
where
720+
F: Fn(&Round<'_>) -> R,
721+
R: Into<Action>,
722+
{
650723
// if we are given a source address, validate it otherwise
651724
// discover it based on the target address and interface.
652725
let source_addr = match self.source_addr {
@@ -668,7 +741,7 @@ mod inner {
668741
let strategy_config = self.make_strategy_config();
669742
let strategy = Strategy::new(&strategy_config, |round| {
670743
self.handler(round);
671-
func(round);
744+
func(round).into()
672745
});
673746
strategy.run(channel)?;
674747
Ok(())

0 commit comments

Comments
 (0)