Skip to content

Commit 7629a31

Browse files
Arc based event iterator (#772)
1 parent 974958a commit 7629a31

2 files changed

Lines changed: 167 additions & 0 deletions

File tree

timely/examples/logging_replay.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Demonstrates cross-thread capture and replay of timely logging events.
2+
//!
3+
//! A source timely instance (2 workers) runs a simple dataflow and captures its
4+
//! logging events using thread-safe `link_sync::EventLink`s. A sink timely instance
5+
//! (1 worker) replays those events and counts them.
6+
7+
use std::sync::Arc;
8+
use std::time::Duration;
9+
10+
use timely::dataflow::operators::{Exchange, Inspect, ToStream};
11+
use timely::dataflow::operators::capture::event::link_sync::EventLink;
12+
use timely::dataflow::operators::capture::Replay;
13+
use timely::logging::{BatchLogger, TimelyEventBuilder, TimelyEvent};
14+
15+
fn main() {
16+
17+
let source_workers = 2usize;
18+
let sink_workers = 1usize;
19+
20+
// One EventLink per source worker, shared between source (writer) and sink (reader).
21+
let event_links: Vec<_> = (0..source_workers)
22+
.map(|_| Arc::new(EventLink::<Duration, Vec<(Duration, TimelyEvent)>>::new()))
23+
.collect();
24+
25+
// Clone reader handles (they start at the head; the writer will advance past them).
26+
let readers: Vec<_> = event_links.iter().map(Arc::clone).collect();
27+
28+
std::thread::scope(|scope| {
29+
30+
// --- Source instance: 2 workers producing logging events ---
31+
let source = scope.spawn(move || {
32+
timely::execute(timely::Config::process(source_workers), move |worker| {
33+
34+
// Install logging: capture timely events into our shared EventLink.
35+
let link = event_links[worker.index()].clone();
36+
let mut logger = BatchLogger::new(link);
37+
worker.log_register()
38+
.unwrap()
39+
.insert::<TimelyEventBuilder, _>("timely", move |time, data| {
40+
logger.publish_batch(time, data);
41+
});
42+
43+
// A trivial dataflow to generate some logging activity.
44+
worker.dataflow::<u64,_,_>(|scope| {
45+
(0..100u64)
46+
.to_stream(scope)
47+
.container::<Vec<_>>()
48+
.exchange(|&x| x)
49+
.inspect(|_x| { });
50+
});
51+
52+
}).expect("source execution failed");
53+
});
54+
55+
// --- Sink instance: 1 worker replaying the captured logs ---
56+
let sink = scope.spawn(move || {
57+
timely::execute(timely::Config::process(sink_workers), move |worker| {
58+
59+
// Each sink worker replays a disjoint subset of the source streams.
60+
let replayers: Vec<_> = readers.iter().enumerate()
61+
.filter(|(i, _)| i % worker.peers() == worker.index())
62+
.map(|(_, r)| Arc::clone(r))
63+
.collect();
64+
65+
worker.dataflow::<Duration,_,_>(|scope| {
66+
replayers
67+
.replay_into(scope)
68+
.inspect(|event| {
69+
println!(" {:?}", event);
70+
});
71+
});
72+
73+
}).expect("sink execution failed");
74+
});
75+
76+
source.join().expect("source panicked");
77+
sink.join().expect("sink panicked");
78+
});
79+
80+
println!("Done.");
81+
}

timely/src/dataflow/operators/core/capture/event.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,92 @@ pub mod link {
128128
}
129129
}
130130

131+
/// A thread-safe linked-list event pusher and iterator.
132+
pub mod link_sync {
133+
134+
use std::borrow::Cow;
135+
use std::sync::{Arc, Mutex};
136+
137+
use super::{Event, EventPusher, EventIterator};
138+
139+
/// A linked list of Event<T, C> usable across threads.
140+
pub struct EventLink<T, C> {
141+
/// An event, if one exists.
142+
///
143+
/// An event might not exist, if either we want to insert a `None` and have the output iterator pause,
144+
/// or in the case of the very first linked list element, which has no event when constructed.
145+
pub event: Option<Event<T, C>>,
146+
/// The next event, if it exists.
147+
pub next: Mutex<Option<Arc<EventLink<T, C>>>>,
148+
}
149+
150+
impl<T, C> EventLink<T, C> {
151+
/// Allocates a new `EventLink`.
152+
pub fn new() -> EventLink<T, C> {
153+
EventLink { event: None, next: Mutex::new(None) }
154+
}
155+
}
156+
157+
impl<T, C> EventPusher<T, C> for Arc<EventLink<T, C>> {
158+
fn push(&mut self, event: Event<T, C>) {
159+
let mut guard = self.next.lock().unwrap();
160+
*guard = Some(Arc::new(EventLink { event: Some(event), next: Mutex::new(None) }));
161+
let next = Arc::clone(guard.as_ref().unwrap());
162+
drop(guard);
163+
*self = next;
164+
}
165+
}
166+
167+
impl<T: Clone, C: Clone> EventIterator<T, C> for Arc<EventLink<T, C>> {
168+
fn next(&mut self) -> Option<Cow<'_, Event<T, C>>> {
169+
let is_some = self.next.lock().unwrap().is_some();
170+
if is_some {
171+
let next = Arc::clone(self.next.lock().unwrap().as_ref().unwrap());
172+
*self = next;
173+
if let Some(this) = Arc::get_mut(self) {
174+
this.event.take().map(Cow::Owned)
175+
}
176+
else {
177+
self.event.as_ref().map(Cow::Borrowed)
178+
}
179+
}
180+
else {
181+
None
182+
}
183+
}
184+
}
185+
186+
// Drop implementation to prevent stack overflow through naive drop impl.
187+
impl<T, C> Drop for EventLink<T, C> {
188+
fn drop(&mut self) {
189+
while let Some(link) = self.next.get_mut().unwrap().take() {
190+
if let Ok(head) = Arc::try_unwrap(link) {
191+
*self = head;
192+
}
193+
}
194+
}
195+
}
196+
197+
impl<T, C> Default for EventLink<T, C> {
198+
fn default() -> Self {
199+
Self::new()
200+
}
201+
}
202+
203+
#[test]
204+
fn avoid_stack_overflow_in_drop() {
205+
#[cfg(miri)]
206+
let limit = 1_000;
207+
#[cfg(not(miri))]
208+
let limit = 1_000_000;
209+
let mut event1 = Arc::new(EventLink::<(),()>::new());
210+
let _event2 = Arc::clone(&event1);
211+
for _ in 0 .. limit {
212+
event1.push(Event::Progress(vec![]));
213+
}
214+
}
215+
}
216+
131217
/// A binary event pusher and iterator.
132218
pub mod binary {
133219

0 commit comments

Comments
 (0)