feat: page console history across compaction

This commit is contained in:
2026-09-30 08:13:07 +09:00
parent cafbb73a22
commit 34ed988b75
38 changed files with 3107 additions and 144 deletions
+17 -5
View File
@@ -37,23 +37,31 @@ use std::path::Path;
/// Read an existing retained file without updating its access timestamp.
/// Observation fails closed when the platform cannot provide that guarantee.
pub(crate) fn read_without_atime(path: &Path) -> io::Result<Vec<u8>> {
read_without_atime_bounded(path, u64::MAX)
}
/// Read at most `max_bytes + 1` retained bytes without updating access time.
/// The extra byte lets callers prove that the authoritative file exceeded a
/// limit without first racing a separate metadata length observation.
pub(crate) fn read_without_atime_bounded(path: &Path, max_bytes: u64) -> io::Result<Vec<u8>> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
use std::fs::OpenOptions;
use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
let mut file = OpenOptions::new()
let file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOATIME)
.open(path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
file.take(max_bytes.saturating_add(1))
.read_to_end(&mut bytes)?;
Ok(bytes)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
let _ = path;
let _ = (path, max_bytes);
Err(io::Error::new(
io::ErrorKind::Unsupported,
"read-only retained observation requires no-atime file reads",
@@ -88,8 +96,12 @@ pub use history::{
pub use logged_item::{LoggedContentPart, LoggedItem, LoggedRole, from_logged, to_logged};
pub use paste_artifact::PasteArtifactLimits;
pub use public_snapshot::{
DEFAULT_RETAINED_SNAPSHOT_MAX_BYTES, RetainedSessionIdentity, RetainedSessionSnapshot,
RetainedSnapshotReadError, project_session_snapshot, read_retained_session_snapshot,
DEFAULT_RETAINED_HISTORY_MAX_ENTRIES, DEFAULT_RETAINED_HISTORY_MAX_RESPONSE_BYTES,
DEFAULT_RETAINED_HISTORY_MAX_SCAN_BYTES, DEFAULT_RETAINED_HISTORY_MAX_SEGMENTS,
DEFAULT_RETAINED_HISTORY_PAGE_TURNS, DEFAULT_RETAINED_SNAPSHOT_MAX_BYTES,
MAX_RETAINED_HISTORY_PAGE_TURNS, RetainedHistoryReadError, RetainedHistoryReadLimits,
RetainedSessionIdentity, RetainedSessionSnapshot, RetainedSnapshotReadError,
project_session_snapshot, read_retained_session_history_page, read_retained_session_snapshot,
};
pub use segment::{
SegmentStartState, append_entry, append_system_item, classify_logged_history_entry,
File diff suppressed because it is too large Load Diff
+3
View File
@@ -33,6 +33,9 @@ pub enum StoreError {
#[error("log corrupted at line {line}: {message}")]
Corrupt { line: usize, message: String },
#[error("retained observation read limit exceeded")]
ReadLimitExceeded,
#[error("paste artifact storage is unavailable")]
PasteArtifactUnsupported,
@@ -111,6 +111,19 @@ impl WorkerSessionStore {
session_id: SessionId,
segment_id: SegmentId,
) -> Result<Vec<LogEntry>, StoreError> {
self.read_all_read_only_bounded(session_id, segment_id, u64::MAX)
.map(|(entries, _)| entries)
}
/// Read one retained Segment under an actual byte bound without touching
/// access time. The file descriptor read, rather than a preceding stat, is
/// the limit authority so concurrent appends cannot evade accounting.
pub fn read_all_read_only_bounded(
&self,
session_id: SessionId,
segment_id: SegmentId,
max_bytes: u64,
) -> Result<(Vec<LogEntry>, u64), StoreError> {
let retained_session_id = self.session_id.lock().map_err(|_| StoreError::Corrupt {
line: 0,
message: "Worker Session identity lock poisoned".to_string(),
@@ -123,14 +136,18 @@ impl WorkerSessionStore {
});
}
let path = self.log_path(segment_id);
let bytes = crate::read_without_atime(&path).map_err(|error| {
let bytes = crate::read_without_atime_bounded(&path, max_bytes).map_err(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
StoreError::NotFound(segment_id)
} else {
StoreError::Io(error)
}
})?;
parse_jsonl(&bytes)
let byte_len = bytes.len() as u64;
if byte_len > max_bytes {
return Err(StoreError::ReadLimitExceeded);
}
Ok((parse_jsonl(&bytes)?, byte_len))
}
pub fn segment_log_len(&self, segment_id: SegmentId) -> Result<u64, StoreError> {