1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
use crate::{os_input_output::AsyncReader, screen::ScreenInstruction, thread_bus::ThreadSenders};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::time::{Duration, Instant};
use tokio::task;
use zellij_utils::{
errors::{get_current_ctx, prelude::*, ContextType},
logging::debug_to_file,
};
pub(crate) struct TerminalBytes {
terminal_id: u32,
senders: ThreadSenders,
async_reader: Box<dyn AsyncReader>,
debug: bool,
activity_flag: Arc<AtomicBool>,
}
impl TerminalBytes {
pub fn new(
terminal_id: u32,
async_reader: Box<dyn AsyncReader>,
senders: ThreadSenders,
debug: bool,
activity_flag: Arc<AtomicBool>,
) -> Self {
TerminalBytes {
terminal_id,
senders,
debug,
async_reader,
activity_flag,
}
}
pub async fn listen(&mut self) -> Result<()> {
// This function reads bytes from the pty and then sends them as
// ScreenInstruction::PtyBytes to screen to be parsed there
// We also send a separate instruction to Screen to render as ScreenInstruction::Render
//
// We endeavour to send a Render instruction to screen immediately after having send bytes
// to parse - this is so that the rendering is quick and smooth. However, this can cause
// latency if the screen is backed up. For this reason, if we detect a peak in the time it
// takes to send the render instruction, we assume the screen thread is backed up and so
// only send a render instruction sparingly, giving screen time to process bytes and render
// while still allowing the user to see an indication that things are happening (the
// sparing render instructions)
let err_context = || "failed to listen for bytes from PTY".to_string();
let mut err_ctx = get_current_ctx();
err_ctx.add_call(ContextType::AsyncTask);
let mut buf = [0u8; 65536];
loop {
match self.async_reader.read(&mut buf).await {
Ok(0) => break, // EOF
Err(err) => {
log::error!("{}", err);
break;
},
Ok(n_bytes) => {
self.activity_flag.store(true, Ordering::Relaxed);
let bytes = &buf[..n_bytes];
if self.debug {
let _ = debug_to_file(bytes, self.terminal_id as i32);
}
self.async_send_to_screen(ScreenInstruction::PtyBytes(
self.terminal_id,
bytes.to_vec(),
))
.await
.with_context(err_context)?;
},
}
}
// Ignore any errors that happen here.
// We only leave the loop above when the pane exits. This can happen in a lot of ways, but
// the most problematic is when quitting zellij with `Ctrl+q`. That is because the channel
// for `Screen` will have exited already, so this send *will* fail. This isn't a problem
// per-se because the application terminates anyway, but it will print a lengthy error
// message into the log for every pane that was still active when we quit the application.
// This:
//
// 1. Makes the log rather pointless, because even when the application exits "normally",
// there will be errors inside and
// 2. Leaves the impression we have a bug in the code and can't terminate properly
//
// FIXME: Ideally we detect whether the application is being quit and only ignore the error
// in that particular case?
let _ = self.async_send_to_screen(ScreenInstruction::Render).await;
Ok(())
}
async fn async_send_to_screen(
&self,
screen_instruction: ScreenInstruction,
) -> Result<Duration> {
// returns the time it blocked the thread for
let sent_at = Instant::now();
let senders = self.senders.clone();
task::spawn_blocking(move || senders.send_to_screen(screen_instruction))
.await
.context("failed to async-send to screen")?
.context("failed to block on sending message to screen")?;
Ok(sent_at.elapsed())
}
}
|