Skip to content

Commit 8c3d13c

Browse files
authored
fix: hang when starting with nu shell (#272)
This patch fixes a bug where shpool would hang when trying to start a nu shell by default. This has to do with our startup sentinal injection. A workaround exists where you can just blank out the prompt prefix, but we should try to automatically do that much when working with nu. I should add a note to the troubleshooting wiki explaining this so that nu shell users don't get confused as to why shpool is not injecting a prompt automatically for them. Fixes #269
1 parent 99c6f09 commit 8c3d13c

File tree

5 files changed

+23
-14
lines changed

5 files changed

+23
-14
lines changed

libshpool/src/consts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ pub const STARTUP_SENTINEL: &str = "SHPOOL_STARTUP_SENTINEL";
3333
pub const PROMPT_SENTINEL: &str = "SHPOOL_PROMPT_SETUP_SENTINEL";
3434

3535
// A magic env var which indicates that a `shpool daemon` invocation should
36-
// actually just print the given sentinal then exit. We do this because
37-
// using `echo` will cause the sentinal value to appear multiple times
36+
// actually just print the given sentinel then exit. We do this because
37+
// using `echo` will cause the sentinel value to appear multiple times
3838
// in the output stream. For the same reason, we don't set the value
3939
// to an actual sentianl, but instead either "startup" or "prompt".
4040
pub const SENTINEL_FLAG_VAR: &str = "SHPOOL__INTERNAL__PRINT_SENTINEL";

libshpool/src/daemon/prompt.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ pub fn maybe_inject_prefix(
4343
prompt_prefix: &str,
4444
session_name: &str,
4545
) -> anyhow::Result<()> {
46-
if prompt_prefix.is_empty() {
47-
return Ok(());
48-
}
49-
5046
let shell_pid = pty_master.child_pid().ok_or(anyhow!("no child pid"))?;
5147
// scan for the startup sentinel so we know it is safe to sniff the shell
5248
let mut pty_master = pty_master.is_parent().context("expected parent")?;
@@ -180,9 +176,9 @@ pub struct SentinelScanner {
180176

181177
impl SentinelScanner {
182178
/// Create a new sentinel scanner.
183-
pub fn new(sentinal: &str) -> Self {
179+
pub fn new(sentinel: &str) -> Self {
184180
let mut scanner = Trie::new();
185-
scanner.insert(sentinal.bytes(), ());
181+
scanner.insert(sentinel.bytes(), ());
186182

187183
SentinelScanner { scanner, cursor: TrieCursor::Start, num_matches: 0 }
188184
}

libshpool/src/daemon/server.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,10 +852,16 @@ impl Server {
852852
}
853853
});
854854

855+
let prompt_prefix_is_blank =
856+
self.config.get().prompt_prefix.as_ref().map(|p| p.is_empty()).unwrap_or(false);
857+
let supports_sentinels =
858+
header.cmd.is_none() && !prompt_prefix_is_blank && !does_not_support_sentinels(&shell);
859+
info!("supports_sentianls={}", supports_sentinels);
860+
855861
// Inject the prompt prefix, if any. For custom commands, avoid doing this
856862
// since we have no idea what the command is so the shell code probably won't
857863
// work.
858-
if header.cmd.is_none() {
864+
if supports_sentinels {
859865
info!("injecting prompt prefix");
860866
let prompt_prefix = self
861867
.config
@@ -884,6 +890,7 @@ impl Server {
884890
heartbeat: heartbeat_tx,
885891
heartbeat_ack: heartbeat_ack_rx,
886892
}));
893+
887894
let mut session_inner = shell::SessionInner {
888895
name: header.name.clone(),
889896
shell_to_client_ctl: Arc::clone(&shell_to_client_ctl),
@@ -894,7 +901,7 @@ impl Server {
894901
term_db,
895902
daily_messenger: Arc::clone(&self.daily_messenger),
896903
needs_initial_motd_dump: dump_motd_on_new_session,
897-
custom_cmd: header.cmd.is_some(),
904+
supports_sentinels,
898905
};
899906
let child_pid = session_inner.pty_master.child_pid().ok_or(anyhow!("no child pid"))?;
900907
session_inner.shell_to_client_join_h =
@@ -1055,6 +1062,13 @@ impl Server {
10551062
}
10561063
}
10571064

1065+
// HACK: this is not a good way to detect shells that don't support our
1066+
// sentinel injection approach, but it is better than just hanging when a
1067+
// user tries to start one.
1068+
fn does_not_support_sentinels(shell: &str) -> bool {
1069+
shell.ends_with("nu")
1070+
}
1071+
10581072
#[instrument(skip_all)]
10591073
fn parse_connect_header(stream: &mut UnixStream) -> anyhow::Result<ConnectHeader> {
10601074
let header: ConnectHeader = protocol::decode_from(stream).context("parsing header")?;

libshpool/src/daemon/shell.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ pub struct SessionInner {
106106
pub term_db: Arc<termini::TermInfo>,
107107
pub daily_messenger: Arc<show_motd::DailyMessenger>,
108108
pub needs_initial_motd_dump: bool,
109-
pub custom_cmd: bool,
109+
pub supports_sentinels: bool,
110110

111111
/// The join handle for the always-on background shell->client thread.
112112
/// Only wrapped in an option so we can spawn the thread after
@@ -202,9 +202,7 @@ impl SessionInner {
202202

203203
// We only scan for the prompt sentinel if the user has not set up a
204204
// custom command or blanked out the prompt_prefix config option.
205-
let prompt_prefix_is_blank =
206-
self.config.get().prompt_prefix.as_ref().map(|p| p.is_empty()).unwrap_or(false);
207-
let mut has_seen_prompt_sentinel = self.custom_cmd || prompt_prefix_is_blank;
205+
let mut has_seen_prompt_sentinel = !self.supports_sentinels;
208206

209207
let daily_messenger = Arc::clone(&self.daily_messenger);
210208
let mut needs_initial_motd_dump = self.needs_initial_motd_dump;

shpool/tests/attach.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,7 @@ fn prompt_prefix_bash() -> anyhow::Result<()> {
11221122
let mut stdout = child.stdout.take().context("missing stdout")?;
11231123
let mut stdout_str = String::from("");
11241124
stdout.read_to_string(&mut stdout_str).context("slurping stdout")?;
1125+
eprintln!("stdout_str: {}", stdout_str);
11251126
let stdout_re = Regex::new(".*session_name=sh1 prompt>.*")?;
11261127
assert!(stdout_re.is_match(&stdout_str));
11271128

0 commit comments

Comments
 (0)