fix: Retry loop safety, doctor model matching, regenerator robustness
Three defensive improvements from peer code review: Replace unreachable!() in GitLab client retry loops: Both request() and request_with_headers() had unreachable!() after their for loops. While the logic was sound (the final iteration always reaches the return/break), any refactor to the loop condition would turn this into a runtime panic. Restructured both to store last_response with explicit break, making the control flow self-documenting and the .expect() message useful if ever violated. Doctor model name comparison asymmetry: Ollama model names were stripped of their tag (:latest, :v1.5) for comparison, but the configured model name was compared as-is. A config value like "nomic-embed-text:v1.5" would never match. Now strips the tag from both sides before comparing. Regenerator savepoint cleanup and progress accuracy: - upsert_document's error path did ROLLBACK TO but never RELEASE, leaving a dangling savepoint that could nest on the next call. Added RELEASE after rollback so the connection is clean. - estimated_total for progress reporting was computed once at start but the dirty queue can grow during processing. Now recounts each loop iteration with max() so the progress fraction never goes backwards. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,7 @@ impl GitLabClient {
|
||||
/// Make an authenticated API request with automatic 429 retry.
|
||||
async fn request<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
|
||||
let url = format!("{}{}", self.base_url, path);
|
||||
let mut last_response = None;
|
||||
|
||||
for attempt in 0..=Self::MAX_RETRIES {
|
||||
let delay = self.rate_limiter.lock().await.check_delay();
|
||||
@@ -155,10 +156,15 @@ impl GitLabClient {
|
||||
continue;
|
||||
}
|
||||
|
||||
return self.handle_response(response, path).await;
|
||||
last_response = Some(response);
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable!("loop always returns")
|
||||
// Safety: the loop always executes at least once (0..=MAX_RETRIES)
|
||||
// and either sets last_response+break, or continues (only when
|
||||
// attempt < MAX_RETRIES). The final iteration always reaches break.
|
||||
self.handle_response(last_response.expect("retry loop ran at least once"), path)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Parse retry-after header from a 429 response, defaulting to 60s.
|
||||
@@ -543,6 +549,7 @@ impl GitLabClient {
|
||||
params: &[(&str, String)],
|
||||
) -> Result<(T, HeaderMap)> {
|
||||
let url = format!("{}{}", self.base_url, path);
|
||||
let mut last_response = None;
|
||||
|
||||
for attempt in 0..=Self::MAX_RETRIES {
|
||||
let delay = self.rate_limiter.lock().await.check_delay();
|
||||
@@ -577,12 +584,14 @@ impl GitLabClient {
|
||||
continue;
|
||||
}
|
||||
|
||||
let headers = response.headers().clone();
|
||||
let body = self.handle_response(response, path).await?;
|
||||
return Ok((body, headers));
|
||||
last_response = Some(response);
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable!("loop always returns")
|
||||
let response = last_response.expect("retry loop ran at least once");
|
||||
let headers = response.headers().clone();
|
||||
let body = self.handle_response(response, path).await?;
|
||||
Ok((body, headers))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user