mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(net): bounded request time, curl size and time caps, lock waits, sane prices
From the full-repo Codex audit: - MSN and Yahoo clients set connect and body timeouts but none for the response headers, so a server that stalled before answering hung the call; four such calls take every MCP call slot. Add a 30 s overall deadline. Every curl-impersonate run gets --max-time 600 and a 256 MiB --max-filesize, and output above that is rejected (curl cannot cap a response without a length); the Yahoo cookie fetch gets --max-time. - Ownership commands blocked on the database lock without limit, so a long sync or import, or a stuck process, held MCP call slots indefinitely. Wait up to 30 s, then fail with a "busy, try again" error. - A price of 1e20 saturated `as i64` and the change subtraction then overflowed. Prices that are not finite, negative, or above 1e12 IDR are treated as missing, and a quote's own price as an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
94aec74b09
commit
66207d24b6
6 changed files with 144 additions and 34 deletions
|
|
@ -484,6 +484,14 @@ fn lock_path(db_path: &Path) -> PathBuf {
|
|||
/// not need write access, so readers fall back to opening an existing lock
|
||||
/// file read-only (e.g. one created by another user).
|
||||
fn lock_at(db_path: &Path, exclusive: bool) -> Result<fs::File, IdxError> {
|
||||
lock_at_within(db_path, exclusive, LOCK_WAIT)
|
||||
}
|
||||
|
||||
fn lock_at_within(
|
||||
db_path: &Path,
|
||||
exclusive: bool,
|
||||
wait: std::time::Duration,
|
||||
) -> Result<fs::File, IdxError> {
|
||||
let path = lock_path(db_path);
|
||||
let opened = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
|
|
@ -496,15 +504,38 @@ fn lock_at(db_path: &Path, exclusive: bool) -> Result<fs::File, IdxError> {
|
|||
.map_err(|e| IdxError::Io(format!("failed to open ownership lock: {e}")))?,
|
||||
Err(e) => return Err(IdxError::Io(format!("failed to open ownership lock: {e}"))),
|
||||
};
|
||||
if exclusive {
|
||||
file.lock()
|
||||
} else {
|
||||
file.lock_shared()
|
||||
// Wait a bounded time: a blocking lock behind a long sync or import (or
|
||||
// a stuck process) would hold every MCP call slot indefinitely.
|
||||
let deadline = std::time::Instant::now() + wait;
|
||||
loop {
|
||||
let attempt = if exclusive {
|
||||
file.try_lock()
|
||||
} else {
|
||||
file.try_lock_shared()
|
||||
};
|
||||
match attempt {
|
||||
Ok(()) => return Ok(file),
|
||||
Err(fs::TryLockError::WouldBlock) if std::time::Instant::now() < deadline => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
Err(fs::TryLockError::WouldBlock) => {
|
||||
return Err(IdxError::Io(format!(
|
||||
"ownership database is busy (another idx is syncing or importing); try again in a moment ({})",
|
||||
path.display()
|
||||
)));
|
||||
}
|
||||
Err(fs::TryLockError::Error(e)) => {
|
||||
return Err(IdxError::Io(format!(
|
||||
"failed to lock ownership database: {e}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
.map_err(|e| IdxError::Io(format!("failed to lock ownership database: {e}")))?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
/// How long ownership commands wait for another process's lock.
|
||||
const LOCK_WAIT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
|
||||
/// True when the lock file is absent and cannot be created.
|
||||
fn unlockable(db_path: &Path) -> bool {
|
||||
let path = lock_path(db_path);
|
||||
|
|
@ -780,3 +811,21 @@ mod tests {
|
|||
assert!(parse_report_family("above10").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod lock_tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use super::lock_at_within;
|
||||
|
||||
#[test]
|
||||
fn a_held_lock_times_out_with_a_busy_error() {
|
||||
let db = std::env::temp_dir().join(format!("idx-lock-{}.db", fastrand::u64(..)));
|
||||
let _held = lock_at_within(&db, true, Duration::ZERO).unwrap();
|
||||
|
||||
let err = lock_at_within(&db, false, Duration::from_millis(200)).unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("busy"), "{err}");
|
||||
let _ = std::fs::remove_file(super::lock_path(&db));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue