mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-04-13 04:05:52 +08:00
Keep latest-session timestamps increasing under tight loops
The next repo-local sweep target was ROADMAP #73: repeated backlog sweeps exposed that session writes could share the same wall-clock millisecond, which made semantic recency fragile and forced the resume-latest regression to sleep between saves. The fix makes session timestamps monotonic within the process and removes the timing hack from the test so latest-session selection stays stable under tight loops. Constraint: Preserve the existing session file format while changing only the timestamp source semantics Rejected: Keep the sleep-based test workaround | hides the real ordering hazard instead of fixing timestamp generation Confidence: high Scope-risk: narrow Reversibility: clean Directive: Any future session-recency logic must keep `current_time_millis`, ordering tests, and latest-session expectations aligned Tested: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; architect review APPROVE Not-tested: Cross-process monotonicity when multiple binaries write sessions concurrently
This commit is contained in:
parent
8f53524bd3
commit
2e34949507
@ -517,3 +517,4 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
|
||||
71. **Wrong-task prompt receipt is not detected before execution** — **done (verified 2026-04-12):** worker boot prompt dispatch now accepts an optional structured `task_receipt` (`repo`, `task_kind`, `source_surface`, `expected_artifacts`, `objective_preview`) and treats mismatched visible prompt context as a `WrongTask` prompt-delivery failure before execution continues. The prompt-delivery payload now records `observed_prompt_preview` plus the expected receipt, and regression coverage locks both the existing shell/wrong-target paths and the new KakaoTalk-style wrong-task mismatch case. **Original filing below.**
|
||||
|
||||
72. **`latest` managed-session selection depends on filesystem mtime before semantic session recency** — **done (verified 2026-04-12):** managed-session summaries now carry `updated_at_ms`, `SessionStore::list_sessions()` sorts by semantic recency before filesystem mtime, and regression coverage locks the case where `latest` must prefer the newer session payload even when file mtimes point the other way. The CLI session-summary wrapper now stays in sync with the runtime field so `latest` resolution uses the same ordering signal everywhere. **Original filing below.**
|
||||
73. **Session timestamps are not monotonic enough for latest-session ordering under tight loops** — **done (verified 2026-04-12):** runtime session timestamps now use a process-local monotonic millisecond source, so back-to-back saves still produce increasing `updated_at_ms` even when the wall clock does not advance. The temporary sleep hack was removed from the resume-latest regression, and fresh workspace verification stayed green with the semantic-recency ordering path from #72. **Original filing below.**
|
||||
|
||||
@ -13,6 +13,7 @@ const SESSION_VERSION: u32 = 1;
|
||||
const ROTATE_AFTER_BYTES: u64 = 256 * 1024;
|
||||
const MAX_ROTATED_FILES: usize = 3;
|
||||
static SESSION_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
static LAST_TIMESTAMP_MS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Speaker role associated with a persisted conversation message.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -1030,10 +1031,27 @@ fn normalize_optional_string(value: Option<String>) -> Option<String> {
|
||||
}
|
||||
|
||||
fn current_time_millis() -> u64 {
|
||||
SystemTime::now()
|
||||
let wall_clock = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut candidate = wall_clock;
|
||||
loop {
|
||||
let previous = LAST_TIMESTAMP_MS.load(Ordering::Relaxed);
|
||||
if candidate <= previous {
|
||||
candidate = previous.saturating_add(1);
|
||||
}
|
||||
match LAST_TIMESTAMP_MS.compare_exchange(
|
||||
previous,
|
||||
candidate,
|
||||
Ordering::SeqCst,
|
||||
Ordering::SeqCst,
|
||||
) {
|
||||
Ok(_) => return candidate,
|
||||
Err(actual) => candidate = actual.saturating_add(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_session_id() -> String {
|
||||
@ -1125,8 +1143,8 @@ fn cleanup_rotated_logs(path: &Path) -> Result<(), SessionError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
cleanup_rotated_logs, rotate_session_file_if_needed, ContentBlock, ConversationMessage,
|
||||
MessageRole, Session, SessionFork,
|
||||
cleanup_rotated_logs, current_time_millis, rotate_session_file_if_needed, ContentBlock,
|
||||
ConversationMessage, MessageRole, Session, SessionFork,
|
||||
};
|
||||
use crate::json::JsonValue;
|
||||
use crate::usage::TokenUsage;
|
||||
@ -1134,6 +1152,16 @@ mod tests {
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[test]
|
||||
fn session_timestamps_are_monotonic_under_tight_loops() {
|
||||
let first = current_time_millis();
|
||||
let second = current_time_millis();
|
||||
let third = current_time_millis();
|
||||
|
||||
assert!(first < second);
|
||||
assert!(second < third);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persists_and_restores_session_jsonl() {
|
||||
let mut session = Session::new();
|
||||
|
||||
@ -3,8 +3,6 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Output};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use runtime::ContentBlock;
|
||||
@ -193,7 +191,6 @@ fn resume_latest_restores_the_most_recent_managed_session() {
|
||||
older
|
||||
.save_to_path(&older_path)
|
||||
.expect("older session should persist");
|
||||
thread::sleep(Duration::from_millis(2));
|
||||
|
||||
let mut newer = workspace_session(&project_dir).with_persistence_path(&newer_path);
|
||||
newer
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user