mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Moving updated-at timestamps to unique millisecond times (#17489)
To allow the ability to have guaranteed-unique cursors, we make two important updates: * Add new updated_at_ms and created_at_ms columns that are in millisecond precision * Guarantee uniqueness -- if multiple items are inserted at the same millisecond, bump the new one by one millisecond until it becomes unique This lets us use single-number cursors for forwards and backwards paging through resultsets and guarantee that the cursor is a fixed point to do (timestamp > cursor) and get new items only. This updated implementation is backwards-compatible since multiple appservers can be running and won't handle the previous method well.
This commit is contained in:
@@ -71,7 +71,6 @@ pub struct ThreadItem {
|
||||
/// created_at comes from the filename timestamp with second precision.
|
||||
pub created_at: Option<String>,
|
||||
/// RFC3339 timestamp string for the most recent update (from file mtime).
|
||||
/// updated_at is truncated to second precision to match created_at.
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
@@ -292,7 +291,10 @@ impl<'de> serde::Deserialize<'de> for Cursor {
|
||||
|
||||
impl From<codex_state::Anchor> for Cursor {
|
||||
fn from(anchor: codex_state::Anchor) -> Self {
|
||||
let ts = OffsetDateTime::from_unix_timestamp(anchor.ts.timestamp())
|
||||
let ts = anchor
|
||||
.ts
|
||||
.timestamp_nanos_opt()
|
||||
.and_then(|nanos| OffsetDateTime::from_unix_timestamp_nanos(nanos as i128).ok())
|
||||
.unwrap_or(OffsetDateTime::UNIX_EPOCH);
|
||||
Self::new(ts, anchor.id)
|
||||
}
|
||||
@@ -1156,17 +1158,16 @@ async fn file_modified_time(path: &Path) -> io::Result<Option<OffsetDateTime>> {
|
||||
return Ok(None);
|
||||
};
|
||||
let dt = OffsetDateTime::from(modified);
|
||||
// Truncate to seconds so ordering and cursor comparisons align with the
|
||||
// cursor timestamp format (which exposes seconds), keeping pagination stable.
|
||||
Ok(truncate_to_seconds(dt))
|
||||
Ok(truncate_to_millis(dt))
|
||||
}
|
||||
|
||||
fn format_rfc3339(dt: OffsetDateTime) -> Option<String> {
|
||||
dt.format(&Rfc3339).ok()
|
||||
}
|
||||
|
||||
fn truncate_to_seconds(dt: OffsetDateTime) -> Option<OffsetDateTime> {
|
||||
dt.replace_nanosecond(0).ok()
|
||||
fn truncate_to_millis(dt: OffsetDateTime) -> Option<OffsetDateTime> {
|
||||
let millis_nanos = (dt.nanosecond() / 1_000_000) * 1_000_000;
|
||||
dt.replace_nanosecond(millis_nanos).ok()
|
||||
}
|
||||
|
||||
async fn find_thread_path_by_id_str_in_subdir(
|
||||
|
||||
@@ -371,7 +371,7 @@ fn backfill_watermark_for_path(codex_home: &Path, path: &Path) -> String {
|
||||
async fn file_modified_time_utc(path: &Path) -> Option<DateTime<Utc>> {
|
||||
let modified = tokio::fs::metadata(path).await.ok()?.modified().ok()?;
|
||||
let updated_at: DateTime<Utc> = modified.into();
|
||||
updated_at.with_nanosecond(0)
|
||||
Some(updated_at)
|
||||
}
|
||||
|
||||
fn parse_timestamp_to_utc(ts: &str) -> Option<DateTime<Utc>> {
|
||||
@@ -381,7 +381,7 @@ fn parse_timestamp_to_utc(ts: &str) -> Option<DateTime<Utc>> {
|
||||
return dt.with_nanosecond(0);
|
||||
}
|
||||
if let Ok(dt) = DateTime::parse_from_rfc3339(ts) {
|
||||
return dt.with_timezone(&Utc).with_nanosecond(0);
|
||||
return Some(dt.with_timezone(&Utc));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1252,7 +1252,7 @@ impl From<codex_state::ThreadsPage> for ThreadsPage {
|
||||
model_provider: Some(item.model_provider),
|
||||
cli_version: Some(item.cli_version),
|
||||
created_at: Some(item.created_at.to_rfc3339_opts(SecondsFormat::Secs, true)),
|
||||
updated_at: Some(item.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true)),
|
||||
updated_at: Some(item.updated_at.to_rfc3339_opts(SecondsFormat::Millis, true)),
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::list::ThreadSortKey;
|
||||
use crate::metadata;
|
||||
use chrono::DateTime;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::Timelike;
|
||||
use chrono::Utc;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
@@ -131,8 +130,7 @@ fn cursor_to_anchor(cursor: Option<&Cursor>) -> Option<codex_state::Anchor> {
|
||||
dt.with_timezone(&Utc)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
.with_nanosecond(0)?;
|
||||
};
|
||||
Some(codex_state::Anchor { ts, id })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user