Compare commits

...

4 Commits

Author SHA1 Message Date
Ahmed Ibrahim
88eb9f2df3 feedback 2025-12-02 10:57:33 -08:00
Ahmed Ibrahim
19633ee51b blocking 2025-12-01 13:54:32 -08:00
Ahmed Ibrahim
50cff7b1af blocking 2025-12-01 13:26:50 -08:00
Ahmed Ibrahim
0809a45e1c blocking 2025-12-01 13:23:30 -08:00
3 changed files with 66 additions and 44 deletions

View File

@@ -277,9 +277,9 @@ fn track_descendants(kq: libc::c_int, root_pid: i32) -> HashSet<i32> {
#[cfg(test)]
mod tests {
use super::*;
use std::process::Command;
use std::process::Stdio;
use std::time::Duration;
use tokio::process::Command;
#[test]
fn pid_is_alive_detects_current_process() {
@@ -288,15 +288,15 @@ mod tests {
}
#[cfg(target_os = "macos")]
#[test]
fn list_child_pids_includes_spawned_child() {
#[tokio::test]
async fn list_child_pids_includes_spawned_child() {
let mut child = Command::new("/bin/sleep")
.arg("5")
.stdin(Stdio::null())
.spawn()
.expect("failed to spawn child process");
let child_pid = child.id() as i32;
let child_pid = child.id().expect("spawned child should have a pid") as i32;
let parent_pid = std::process::id() as i32;
let mut found = false;
@@ -305,11 +305,11 @@ mod tests {
found = true;
break;
}
std::thread::sleep(Duration::from_millis(10));
tokio::time::sleep(Duration::from_millis(10)).await;
}
let _ = child.kill();
let _ = child.wait();
let _ = child.kill().await;
let _ = child.wait().await;
assert!(found, "expected to find child pid {child_pid} in list");
}
@@ -325,10 +325,10 @@ mod tests {
.spawn()
.expect("failed to spawn child process");
let child_pid = child.id() as i32;
let child_pid = child.id().expect("spawned child should have a pid") as i32;
let parent_pid = std::process::id() as i32;
let _ = child.wait();
let _ = child.wait().await;
let seen = tracker.stop().await;
@@ -356,7 +356,11 @@ mod tests {
.spawn()
.expect("failed to spawn bash");
let output = child.wait_with_output().unwrap().stdout;
let output = child
.wait_with_output()
.await
.expect("failed to wait for bash child")
.stdout;
let subshell_pid = String::from_utf8_lossy(&output)
.trim()
.parse::<i32>()

View File

@@ -50,10 +50,10 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use tokio::select;
use tokio::sync::mpsc::unbounded_channel;
use tokio::time::sleep;
#[cfg(not(debug_assertions))]
use crate::history_cell::UpdateAvailableHistoryCell;
@@ -502,9 +502,9 @@ impl App {
{
let tx = self.app_event_tx.clone();
let running = self.commit_anim_running.clone();
thread::spawn(move || {
tokio::spawn(async move {
while running.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(50));
sleep(Duration::from_millis(50)).await;
tx.send(AppEvent::CommitTick);
}
});

View File

@@ -25,8 +25,9 @@ use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use tokio::task::spawn_blocking;
use tokio::time::sleep;
use crate::app_event::AppEvent;
use crate::app_event_sender::AppEventSender;
@@ -117,16 +118,16 @@ impl FileSearchManager {
let state = self.state.clone();
let search_dir = self.search_dir.clone();
let tx_clone = self.app_tx.clone();
thread::spawn(move || {
tokio::spawn(async move {
// Always do a minimum debounce, but then poll until the
// `active_search` is cleared.
thread::sleep(FILE_SEARCH_DEBOUNCE);
sleep(FILE_SEARCH_DEBOUNCE).await;
loop {
#[expect(clippy::unwrap_used)]
if state.lock().unwrap().active_search.is_none() {
break;
}
thread::sleep(ACTIVE_SEARCH_COMPLETE_POLL_INTERVAL);
sleep(ACTIVE_SEARCH_COMPLETE_POLL_INTERVAL).await;
}
// The debounce timer has expired, so start a search using the
@@ -162,36 +163,53 @@ impl FileSearchManager {
cancellation_token: Arc<AtomicBool>,
search_state: Arc<Mutex<SearchState>>,
) {
let compute_indices = true;
std::thread::spawn(move || {
let matches = file_search::run(
&query,
MAX_FILE_SEARCH_RESULTS,
&search_dir,
Vec::new(),
NUM_FILE_SEARCH_THREADS,
cancellation_token.clone(),
compute_indices,
true,
)
.map(|res| res.matches)
.unwrap_or_default();
tokio::spawn({
let query_for_result = query.clone();
let cancellation_token_for_result = Arc::clone(&cancellation_token);
let search_state = Arc::clone(&search_state);
async move {
let compute_indices = true;
let matches = spawn_blocking({
let query_for_search = query;
let search_dir = search_dir.clone();
let cancellation_token_for_search = Arc::clone(&cancellation_token);
move || {
file_search::run(
&query_for_search,
MAX_FILE_SEARCH_RESULTS,
&search_dir,
Vec::new(),
NUM_FILE_SEARCH_THREADS,
cancellation_token_for_search,
compute_indices,
true,
)
.map(|res| res.matches)
.unwrap_or_default()
}
})
.await
.unwrap_or_default();
let is_cancelled = cancellation_token.load(Ordering::Relaxed);
if !is_cancelled {
tx.send(AppEvent::FileSearchResult { query, matches });
}
let is_cancelled = cancellation_token_for_result.load(Ordering::Relaxed);
if !is_cancelled {
tx.send(AppEvent::FileSearchResult {
query: query_for_result,
matches,
});
}
// Reset the active search state. Do a pointer comparison to verify
// that we are clearing the ActiveSearch that corresponds to the
// cancellation token we were given.
{
#[expect(clippy::unwrap_used)]
let mut st = search_state.lock().unwrap();
if let Some(active_search) = &st.active_search
&& Arc::ptr_eq(&active_search.cancellation_token, &cancellation_token)
// Reset the active search state. Do a pointer comparison to verify
// that we are clearing the ActiveSearch that corresponds to the
// cancellation token we were given.
{
st.active_search = None;
#[expect(clippy::unwrap_used)]
let mut st = search_state.lock().unwrap();
if let Some(active_search) = &st.active_search
&& Arc::ptr_eq(&active_search.cancellation_token, &cancellation_token)
{
st.active_search = None;
}
}
}
});