Skip to content

Commit 4fac8a6

Browse files
feat(ui): persist all logs (#7822)
### Description This PR changes UI behavior so all task output gets persisted to terminal after the run is finished. Non-goals of the PR: respecting `--output-mode` for what logs get persisted. This PR also ditches the "priority" channel for communicating with the rendering thread. This was intended to avoid a scenario where tasks were producing so many events that it took awhile to get to a "stop" event. In reality this introduces so many race conditions of a "stop" event causing events that happened not to be processed leading to missing information e.g. dropping the last line of task output since the stop event takes priority. ### Testing Instructions Run `turbo` and verify that logs get persisted under various scenarios: Cache misses: <img width="1124" alt="Screenshot 2024-03-22 at 1 56 35 PM" src="https://github.com/vercel/turbo/assets/4131117/313aa99f-72d8-49b9-b61b-d75d5c9632fa"> Failures (not displaying tasks that didn't start): <img width="1130" alt="Screenshot 2024-03-22 at 1 55 59 PM" src="https://github.com/vercel/turbo/assets/4131117/ee019c49-d7a4-4370-a44f-ef4da764bfda"> Cache hits: <img width="1128" alt="Screenshot 2024-03-22 at 1 55 25 PM" src="https://github.com/vercel/turbo/assets/4131117/097c1ea8-02c7-46b0-bb16-6fbcc82cf292"> Closes TURBO-2692
1 parent c7f8019 commit 4fac8a6

File tree

5 files changed

+75
-27
lines changed

5 files changed

+75
-27
lines changed

crates/turborepo-lib/src/task_graph/visitor.rs

-6
Original file line numberDiff line numberDiff line change
@@ -982,12 +982,6 @@ impl ExecContext {
982982
if let Err(e) = stdout_writer.flush() {
983983
error!("error flushing logs: {e}");
984984
}
985-
if let TaskOutput::UI(task) = output_client {
986-
let mut persisted_ui = self.prefixed_ui(task.stdout(), task.stdout());
987-
let _ = self
988-
.task_cache
989-
.replay_log_file(persisted_ui.output_prefixed_writer());
990-
}
991985
if let Err(e) = self
992986
.task_cache
993987
.on_error(prefixed_ui.output_prefixed_writer())

crates/turborepo-ui/src/tui/app.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ fn run_app_inner<B: Backend>(
120120
let mut last_render = Instant::now();
121121

122122
while let Some(event) = poll(app.interact, &receiver, last_render + FRAMERATE) {
123-
if let Some(message) = update(&mut app, event)? {
123+
if let Some(message) = update(terminal, &mut app, event)? {
124124
persist_bytes(terminal, &message)?;
125125
}
126126
if app.done {
@@ -132,6 +132,9 @@ fn run_app_inner<B: Backend>(
132132
}
133133
}
134134

135+
let started_tasks = app.table.tasks_started().collect();
136+
app.pane.render_remaining(started_tasks, terminal)?;
137+
135138
Ok(())
136139
}
137140

@@ -175,7 +178,8 @@ fn cleanup<B: Backend + io::Write>(mut terminal: Terminal<B>) -> io::Result<()>
175178
Ok(())
176179
}
177180

178-
fn update(
181+
fn update<B: Backend>(
182+
terminal: &mut Terminal<B>,
179183
app: &mut App<Box<dyn io::Write + Send>>,
180184
event: Event,
181185
) -> Result<Option<Vec<u8>>, Error> {
@@ -197,6 +201,7 @@ fn update(
197201
}
198202
Event::EndTask { task } => {
199203
app.table.finish_task(&task)?;
204+
app.pane.render_screen(&task, terminal)?;
200205
}
201206
Event::Up => {
202207
app.previous();

crates/turborepo-ui/src/tui/handle.rs

+6-17
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ use crate::LineWriter;
1010
#[derive(Debug, Clone)]
1111
pub struct AppSender {
1212
primary: mpsc::Sender<Event>,
13-
priority: mpsc::Sender<Event>,
1413
}
1514

1615
/// Struct for receiving app events
1716
pub struct AppReceiver {
1817
primary: mpsc::Receiver<Event>,
19-
priority: mpsc::Receiver<Event>,
2018
}
2119

2220
/// Struct for sending events related to a specific task
@@ -45,15 +43,12 @@ impl AppSender {
4543
/// AppReceiver should be passed to `crate::tui::run_app`
4644
pub fn new() -> (Self, AppReceiver) {
4745
let (primary_tx, primary_rx) = mpsc::channel();
48-
let (priority_tx, priority_rx) = mpsc::channel();
4946
(
5047
Self {
5148
primary: primary_tx,
52-
priority: priority_tx,
5349
},
5450
AppReceiver {
5551
primary: primary_rx,
56-
priority: priority_rx,
5752
},
5853
)
5954
}
@@ -69,9 +64,8 @@ impl AppSender {
6964

7065
/// Stop rendering TUI and restore terminal to default configuration
7166
pub fn stop(&self) {
72-
// Send stop events in both channels, if receiver has dropped ignore error as
67+
// Send stop event, if receiver has dropped ignore error as
7368
// it'll be a no-op.
74-
self.priority.send(Event::Stop).ok();
7569
self.primary.send(Event::Stop).ok();
7670
}
7771
}
@@ -80,15 +74,10 @@ impl AppReceiver {
8074
/// Receive an event, producing a tick event if no events are received by
8175
/// the deadline.
8276
pub fn recv(&self, deadline: Instant) -> Result<Event, mpsc::RecvError> {
83-
// If there's an event in the priority queue take from that first
84-
if let Ok(event) = self.priority.try_recv() {
85-
Ok(event)
86-
} else {
87-
match self.primary.recv_deadline(deadline) {
88-
Ok(event) => Ok(event),
89-
Err(mpsc::RecvTimeoutError::Timeout) => Ok(Event::Tick),
90-
Err(mpsc::RecvTimeoutError::Disconnected) => Err(mpsc::RecvError),
91-
}
77+
match self.primary.recv_deadline(deadline) {
78+
Ok(event) => Ok(event),
79+
Err(mpsc::RecvTimeoutError::Timeout) => Ok(Event::Tick),
80+
Err(mpsc::RecvTimeoutError::Disconnected) => Err(mpsc::RecvError),
9281
}
9382
}
9483
}
@@ -183,7 +172,7 @@ impl std::io::Write for PersistedWriterInner {
183172
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
184173
let bytes = buf.to_vec();
185174
self.handle
186-
.priority
175+
.primary
187176
.send(Event::Log { message: bytes })
188177
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "receiver dropped"))?;
189178
Ok(buf.len())

crates/turborepo-ui/src/tui/pane.rs

+55-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
use std::{collections::BTreeMap, io::Write};
1+
use std::{
2+
collections::{BTreeMap, HashSet},
3+
io::Write,
4+
};
25

36
use ratatui::{
7+
backend::Backend,
48
style::Style,
5-
widgets::{Block, Borders, Widget},
9+
widgets::{
10+
block::{Position, Title},
11+
Block, Borders, Widget,
12+
},
13+
Terminal,
614
};
715
use tracing::debug;
816
use tui_term::widget::PseudoTerminal;
@@ -23,6 +31,7 @@ struct TerminalOutput<W> {
2331
cols: u16,
2432
parser: vt100::Parser,
2533
stdin: Option<W>,
34+
has_been_persisted: bool,
2635
}
2736

2837
impl<W> TerminalPane<W> {
@@ -102,6 +111,28 @@ impl<W> TerminalPane<W> {
102111
Ok(())
103112
}
104113

114+
pub fn render_screen<B: Backend>(
115+
&mut self,
116+
task_name: &str,
117+
terminal: &mut Terminal<B>,
118+
) -> Result<(), Error> {
119+
let task = self.task_mut(task_name)?;
120+
task.persist_screen(task_name, terminal)
121+
}
122+
123+
pub fn render_remaining<B: Backend>(
124+
&mut self,
125+
started_tasks: HashSet<&str>,
126+
terminal: &mut Terminal<B>,
127+
) -> Result<(), Error> {
128+
for (task_name, task) in self.tasks.iter_mut() {
129+
if !task.has_been_persisted && started_tasks.contains(task_name.as_str()) {
130+
task.persist_screen(task_name, terminal)?;
131+
}
132+
}
133+
Ok(())
134+
}
135+
105136
fn selected(&self) -> Option<(&String, &TerminalOutput<W>)> {
106137
let task_name = self.displayed.as_deref()?;
107138
self.tasks.get_key_value(task_name)
@@ -141,6 +172,7 @@ impl<W> TerminalOutput<W> {
141172
stdin,
142173
rows,
143174
cols,
175+
has_been_persisted: false,
144176
}
145177
}
146178

@@ -151,6 +183,27 @@ impl<W> TerminalOutput<W> {
151183
self.rows = rows;
152184
self.cols = cols;
153185
}
186+
187+
fn persist_screen<B: Backend>(
188+
&mut self,
189+
task_name: &str,
190+
terminal: &mut Terminal<B>,
191+
) -> Result<(), Error> {
192+
let screen = self.parser.entire_screen();
193+
let (rows, _) = screen.size();
194+
let mut cursor = tui_term::widget::Cursor::default();
195+
cursor.hide();
196+
let title = format!(" {task_name} >");
197+
let block = Block::default()
198+
.borders(Borders::ALL)
199+
.title(title.as_str())
200+
.title(Title::from(title.as_str()).position(Position::Bottom));
201+
let term = PseudoTerminal::new(&screen).cursor(cursor).block(block);
202+
terminal.insert_before(rows as u16, |buf| term.render(buf.area, buf))?;
203+
self.has_been_persisted = true;
204+
205+
Ok(())
206+
}
154207
}
155208

156209
impl<W> Widget for &TerminalPane<W> {

crates/turborepo-ui/src/tui/table.rs

+7
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ impl TaskTable {
180180
}
181181
}
182182

183+
pub fn tasks_started(&self) -> impl Iterator<Item = &str> + '_ {
184+
self.finished
185+
.iter()
186+
.map(|task| task.name())
187+
.chain(self.running.iter().map(|task| task.name()))
188+
}
189+
183190
fn finished_rows(&self, duration_width: u16) -> impl Iterator<Item = Row> + '_ {
184191
self.finished.iter().map(move |task| {
185192
Row::new(vec![

0 commit comments

Comments
 (0)