Fix clippy warnings, improve test robustness, and clean up proxy code

- task_pruner: use poison-aware lock recovery (unwrap_or_else) for GAUGE_LOCK
- task_pruner: add spawn_pruner lifecycle tests (run+stop, drop+stop)
- proxy/client: remove unused timeout_ms field, suppress dead_code on preflight_url
- proxy/search: fix serde rename for rankingScore field
- proxy/indexes: fix clippy unnecessary_lazy_evaluations warning

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-19 04:53:45 -04:00
parent 17d02b97f8
commit de1f37c8b3
6 changed files with 51 additions and 13 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
483f821dc14b795fd5b7f94a5343dc8bf08dd4d6
17d02b97f8fe4c6aeb1f2c01895ad20872cf3efd

View file

@ -231,7 +231,7 @@ mod tests {
/// next pruner cycle drops all 10k.
#[test]
fn pruner_deletes_10k_old_terminal_tasks() {
let _lock = GAUGE_LOCK.lock().unwrap();
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = test_store();
let eight_days_ms: i64 = 8 * 24 * 3600 * 1000;
let old_time = now() - eight_days_ms;
@ -260,6 +260,7 @@ mod tests {
/// Acceptance: A single in-flight `processing` task at created_at = now - 10d is preserved.
#[test]
fn pruner_preserves_processing_tasks() {
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = test_store();
let ten_days_ms: i64 = 10 * 24 * 3600 * 1000;
let old_time = now() - ten_days_ms;
@ -286,6 +287,7 @@ mod tests {
/// Acceptance: Pruner advisory lock prevents two instances pruning simultaneously.
#[test]
fn advisory_lock_prevents_concurrent_pruning() {
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = test_store();
let ten_days_ms: i64 = 10 * 24 * 3600 * 1000;
let old_time = now() - ten_days_ms;
@ -317,7 +319,7 @@ mod tests {
/// Acceptance: miroir_task_registry_size gauge drops after a prune cycle.
#[test]
fn gauge_drops_after_prune() {
let _lock = GAUGE_LOCK.lock().unwrap();
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = test_store();
let ten_days_ms: i64 = 10 * 24 * 3600 * 1000;
let old_time = now() - ten_days_ms;
@ -343,6 +345,7 @@ mod tests {
/// Test that pruner respects batch_size — multiple iterations needed.
#[test]
fn pruner_batches_correctly() {
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = test_store();
let ten_days_ms: i64 = 10 * 24 * 3600 * 1000;
let old_time = now() - ten_days_ms;
@ -358,4 +361,42 @@ mod tests {
assert_eq!(deleted, 25); // all deleted via multiple batches
assert_eq!(store.task_count().unwrap(), 0);
}
/// Acceptance: spawn_pruner runs in background, PrunerHandle::stop joins cleanly.
#[test]
fn spawn_pruner_runs_and_stops() {
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = Arc::new(test_store());
let ten_days_ms: i64 = 10 * 24 * 3600 * 1000;
let old_time = now() - ten_days_ms;
for i in 0..5 {
insert_task(store.as_ref(), &format!("old-{i}"), old_time, "succeeded");
}
let mut cfg = default_cfg();
cfg.prune_interval_s = 1;
let mut handle = spawn_pruner(store.clone(), cfg);
// Give the pruner a moment to run at least one cycle
thread::sleep(Duration::from_millis(200));
handle.stop();
// Old tasks should be pruned
assert_eq!(store.task_count().unwrap(), 0);
}
/// Acceptance: dropping PrunerHandle signals stop and joins.
#[test]
fn pruner_handle_drop_stops_thread() {
let _lock = GAUGE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let store = Arc::new(test_store());
let mut cfg = default_cfg();
cfg.prune_interval_s = 600; // long interval so it sleeps in the loop
{
let _handle = spawn_pruner(store, cfg);
// handle dropped here
}
// Thread should have stopped — if this hangs, the test will time out
}
}

View file

@ -11,7 +11,6 @@ use std::time::Duration;
pub struct HttpClient {
client: Client,
master_key: String,
timeout_ms: u64,
}
impl HttpClient {
@ -22,11 +21,7 @@ impl HttpClient {
.build()
.expect("Failed to create HTTP client");
Self {
client,
master_key,
timeout_ms,
}
Self { client, master_key }
}
/// Build the search URL for a node and index.
@ -35,6 +30,7 @@ impl HttpClient {
}
/// Build the preflight URL for a node and index.
#[allow(dead_code)]
fn preflight_url(&self, address: &str, index_uid: &str) -> String {
format!("{}/indexes/{}/_preflight", address.trim_end_matches('/'), index_uid)
}

View file

@ -181,7 +181,7 @@ async fn preflight_handler(
let node = config
.nodes
.first()
.ok_or_else(|| StatusCode::INTERNAL_SERVER_ERROR)?;
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
let client = MeilisearchClient::new(config.node_master_key.clone());

View file

@ -57,7 +57,8 @@ struct SearchRequestBody {
limit: Option<usize>,
filter: Option<Value>,
facets: Option<Vec<String>>,
rankingScore: Option<bool>,
#[serde(rename = "rankingScore")]
ranking_score: Option<bool>,
#[serde(flatten)]
rest: Value,
}
@ -107,7 +108,7 @@ async fn search_handler(
limit: body.limit.unwrap_or(20),
filter: body.filter,
facets: body.facets,
ranking_score: body.rankingScore.unwrap_or(false),
ranking_score: body.ranking_score.unwrap_or(false),
body: body.rest,
global_idf: None, // Will be populated by dfs_query_then_fetch_search
};