Skip to content

Commit b059ae4

Browse files
eddietejedaclaude
andauthored
feat(update): background update check with post-command notice (#104)
Spawn the GitHub release check in a background thread before the command runs, then join it (recv_timeout 6 s) after the command finishes so the notice always appears at the bottom of output. Uses mpsc::Receiver instead of JoinHandle::join to prevent blocking if the HTTP request stalls past its own 5 s timeout. Unifies the notice message to "hotdata update" for all install methods. Co-authored-by: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0cbcf47 commit b059ae4

2 files changed

Lines changed: 45 additions & 24 deletions

File tree

src/main.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,15 @@ fn main() {
172172
skill::maybe_auto_update_after_cli_upgrade();
173173
}
174174

175-
// Quiet update-available notice. Skip during `hotdata update` itself so
176-
// we don't talk over the updater's own output.
177-
if !matches!(&cli.command, Some(Commands::Update)) {
178-
update::maybe_print_update_notice();
179-
}
175+
// Kick off the update check in the background so it runs concurrently
176+
// with the command. We join and print after the command finishes so the
177+
// notice always appears at the bottom of the output. Skipped during
178+
// `hotdata update` itself so it doesn't talk over the updater's output.
179+
let update_handle = if !matches!(&cli.command, Some(Commands::Update)) {
180+
update::spawn_update_check()
181+
} else {
182+
None
183+
};
180184

181185
match cli.command {
182186
None => {
@@ -1008,6 +1012,9 @@ fn main() {
10081012
Commands::Update => update::run_update(),
10091013
},
10101014
}
1015+
1016+
// Print update notice after command output (joined from background thread).
1017+
update::maybe_print_update_notice(update_handle);
10111018
}
10121019

10131020
/// Parse a database target like `airbnb.listings` or `airbnb.public.listings`

src/update.rs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -120,31 +120,45 @@ fn stderr_is_tty() -> bool {
120120
std::io::stderr().is_terminal()
121121
}
122122

123-
/// Print a one-line notice if a newer release exists. No-op when stderr
124-
/// isn't a TTY, when --no-input is set, or when the cache says we're up
125-
/// to date. Best-effort: network/cache errors are swallowed silently so
126-
/// commands never fail because of the update check.
127-
pub fn maybe_print_update_notice() {
128-
if !stderr_is_tty() {
129-
return;
130-
}
131-
if !util::is_interactive() {
132-
return;
133-
}
134-
if std::env::var_os("HOTDATA_NO_UPDATE_CHECK").is_some() {
135-
return;
123+
fn should_check() -> bool {
124+
stderr_is_tty()
125+
&& util::is_interactive()
126+
&& std::env::var_os("HOTDATA_NO_UPDATE_CHECK").is_none()
127+
}
128+
129+
/// How long `maybe_print_update_notice` will wait for the background thread
130+
/// before giving up. In practice the thread finishes well within this window
131+
/// because `fetch_latest_version` has its own 5-second HTTP timeout and cache
132+
/// hits resolve in microseconds.
133+
const NOTICE_WAIT_MS: u64 = 6_000;
134+
135+
/// Spawn a background thread that checks for a newer release. Returns a
136+
/// channel receiver that `maybe_print_update_notice` can poll after the
137+
/// command runs. No-op (returns None) when stderr isn't a TTY, `--no-input`
138+
/// is set, or `HOTDATA_NO_UPDATE_CHECK` is set.
139+
pub fn spawn_update_check() -> Option<std::sync::mpsc::Receiver<Option<Version>>> {
140+
if !should_check() {
141+
return None;
136142
}
137-
let Some(latest) = cached_latest_if_newer() else {
143+
let (tx, rx) = std::sync::mpsc::channel();
144+
std::thread::spawn(move || {
145+
let _ = tx.send(cached_latest_if_newer());
146+
});
147+
Some(rx)
148+
}
149+
150+
/// Poll the receiver returned by `spawn_update_check` and print a one-line
151+
/// notice if a newer release was found. Call this *after* the command has
152+
/// produced its own output so the notice appears at the bottom.
153+
pub fn maybe_print_update_notice(rx: Option<std::sync::mpsc::Receiver<Option<Version>>>) {
154+
let Some(rx) = rx else { return };
155+
let Ok(Some(latest)) = rx.recv_timeout(Duration::from_millis(NOTICE_WAIT_MS)) else {
138156
return;
139157
};
140-
let how = match detect_install_method() {
141-
InstallMethod::Homebrew => format!("Run: brew upgrade {HOMEBREW_FORMULA}"),
142-
InstallMethod::Other => "Run: hotdata update".to_string(),
143-
};
144158
eprintln!(
145159
"{}",
146160
format!(
147-
"A new version of hotdata is available (v{CURRENT_VERSION} → v{latest}). {how}"
161+
"\nA new version of hotdata is available (v{CURRENT_VERSION} → v{latest}). Run: hotdata update"
148162
)
149163
.yellow()
150164
);

0 commit comments

Comments
 (0)