Skip to main content

gpui_query/core/
request.rs

1//! Request lifecycle primitives for the query system.
2//!
3//! This module provides the core types that govern how async requests are
4//! identified, sequenced, and completed within the query framework:
5//!
6//! - [`RequestId`] — a unique, ordered identifier for each in-flight request.
7//! - [`RequestSequencer`] — a monotonic generator of `RequestId` values, scoped
8//!   per resource to guarantee uniqueness even after sequence overflow.
9//! - [`RequestGuard`] — a single-use capability token that enforces the two-phase
10//!   completion protocol (accept → complete).
11//! - [`QueryTimestamp`] — a millisecond-precision timestamp used for cache
12//!   freshness and staleness calculations.
13//!
14//! # Two-phase completion protocol
15//!
16//! The query system uses a two-phase protocol to safely complete async work:
17//!
18//! 1. **Accept**: Call [`QueryResource::accept_current_request`] with a
19//!    [`RequestId`]. If the request is still active (not replaced or cancelled),
20//!    this returns `Some(RequestGuard)`. Otherwise it returns `None`.
21//!
22//! 2. **Complete**: Pass the [`RequestGuard`] (by value) to one of the
23//!    completion methods: [`QueryResource::complete_success`],
24//!    [`QueryResource::complete_failure`],
25//!    [`QueryResource::complete_success_optional`], or
26//!    [`QueryResource::complete_failure_with_data`]. The guard is consumed,
27//!    preventing double-completion.
28//!
29//! Convenience methods like [`QueryResource::complete_current_success`] combine
30//! both phases into a single call.
31//!
32//! [`QueryResource`]: super::QueryResource
33
34use serde::{Deserialize, Serialize};
35use std::num::NonZero;
36
37/// A unique identifier for an in-flight request.
38///
39/// Combines a scope id (per-resource) with a monotonically increasing sequence.
40/// Two `RequestId` values are equal only when both scope and sequence match.
41/// Ordering is lexicographic: scope first, then sequence.
42///
43/// # Example
44///
45/// ```
46/// use gpui_query::core::RequestId;
47/// use std::num::NonZero;
48///
49/// let id = RequestId::scoped(NonZero::new(1).unwrap(), 42);
50/// assert_eq!(id.scope_id(), NonZero::new(1).unwrap());
51/// assert_eq!(id.value(), 42);
52/// assert_eq!(id.label(), "1:42");
53/// ```
54#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
55#[must_use]
56pub struct RequestId {
57    scope_id: NonZero<u64>,
58    sequence: u64,
59}
60
61impl RequestId {
62    /// Create a request id with explicit scope and sequence.
63    ///
64    /// The scope must be non-zero; passing a zero scope would violate the
65    /// `NonZero<u64>` niche invariant, so it is taken as `NonZero<u64>` directly.
66    pub fn scoped(scope_id: NonZero<u64>, sequence: u64) -> Self {
67        Self { scope_id, sequence }
68    }
69
70    /// The sequence number within this scope.
71    pub fn value(self) -> u64 {
72        self.sequence
73    }
74
75    /// The scope identifier.
76    ///
77    /// Returns the scope as `NonZero<u64>`. Use `.get()` if a plain `u64` is needed.
78    pub fn scope_id(self) -> NonZero<u64> {
79        self.scope_id
80    }
81
82    /// Human-readable label for diagnostics.
83    ///
84    /// Thin wrapper around the [`Display`](std::fmt::Display) impl that
85    /// allocates a `String`. Prefer `format!("{id}")` or writing directly to
86    /// a formatter to avoid the heap allocation for log/diagnostic callers.
87    // Audit fix #45: keep label for backward compat; Display writes directly.
88    pub fn label(self) -> String {
89        self.to_string()
90    }
91}
92
93impl std::fmt::Display for RequestId {
94    /// Reproduces the exact `"{scope}:{sequence}"` label format.
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        // Audit fix #45: write directly to the formatter, avoiding a String alloc.
97        write!(f, "{}:{}", self.scope_id, self.sequence)
98    }
99}
100
101/// Monotonic request id generator scoped to a single resource.
102///
103/// Each `RequestSequencer` produces a stream of [`RequestId`] values that are
104/// unique within the resource's lifetime. The sequence counter increments
105/// from 1; when it would overflow `u64::MAX`, the scope advances to avoid
106/// producing duplicate ids.
107///
108/// # Scope advancement
109///
110/// When the sequence counter reaches `u64::MAX`, [`next_request`](Self::next_request)
111/// calls [`advance_scope`](Self::advance_scope), which increments `scope_id`
112/// and resets `next_request_id` to 1. This guarantees uniqueness across
113/// the entire lifetime of the sequencer.
114///
115/// # Theoretical wrap-around
116///
117/// If `scope_id` itself overflows `u64::MAX`, it wraps back to 1 and
118/// `next_request_id` is reset to 1. This means a new `RequestId(1, 1)` could
119/// theoretically collide with a very old `RequestId(1, 1)` still held by a
120/// long-running future. In practice, reaching `u64::MAX` requests per scope
121/// is essentially impossible, so this is not a practical concern. For
122/// extremely long-lived processes (e.g., a server running for decades), the
123/// collision risk remains theoretical but documented here for completeness.
124#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
125pub struct RequestSequencer {
126    pub(crate) scope_id: NonZero<u64>,
127    pub(crate) next_request_id: u64,
128}
129
130impl Default for RequestSequencer {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136impl RequestSequencer {
137    /// Create a new sequencer starting at scope 1, sequence 1.
138    pub fn new() -> Self {
139        Self {
140            scope_id: NonZero::new(1).unwrap(),
141            next_request_id: 1,
142        }
143    }
144
145    /// Generate the next request id.
146    ///
147    /// The sequence counter increments with each call. When it reaches
148    /// `u64::MAX`, the scope advances automatically before the next call
149    /// produces a duplicate.
150    pub fn next_request(&mut self) -> RequestId {
151        let request_id = RequestId::scoped(self.scope_id, self.next_request_id);
152        if self.next_request_id == u64::MAX {
153            self.advance_scope();
154        } else {
155            self.next_request_id += 1;
156        }
157        request_id
158    }
159
160    /// Advance to a new scope when the sequence overflows.
161    ///
162    /// Increments `scope_id` via checked addition. If `scope_id` itself
163    /// overflows (astronomically unlikely), it wraps to 1 and the sequence
164    /// resets, as documented on the struct.
165    pub fn advance_scope(&mut self) {
166        self.scope_id = NonZero::new(self.scope_id.get().checked_add(1).unwrap_or(1))
167            .unwrap_or(NonZero::new(1).unwrap());
168        self.next_request_id = 1;
169    }
170
171    /// Whether the given request id belongs to the current scope.
172    pub fn is_current_scope(&self, request_id: RequestId) -> bool {
173        request_id.scope_id == self.scope_id
174    }
175}
176
177/// A timestamp for query operations, in milliseconds since UNIX epoch.
178///
179/// Used for cache freshness checks (TTL, stale-while-revalidate) and for
180/// recording when data was last updated. Obtain the current time via
181/// `QueryTimestamp::from_millis(...)` using your application's clock.
182#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
183pub struct QueryTimestamp(u64);
184
185impl QueryTimestamp {
186    /// Create a timestamp from milliseconds.
187    pub fn from_millis(value: u64) -> Self {
188        Self(value)
189    }
190
191    /// The timestamp in milliseconds.
192    pub fn as_millis(self) -> u64 {
193        self.0
194    }
195
196    /// Compute elapsed time since an earlier timestamp.
197    pub(super) fn elapsed_since(self, earlier: Self) -> Option<u64> {
198        self.0.checked_sub(earlier.0)
199    }
200}
201
202impl From<u64> for QueryTimestamp {
203    fn from(value: u64) -> Self {
204        Self::from_millis(value)
205    }
206}
207
208/// A single-use capability token proving the holder owns the current request.
209///
210/// Created by [`QueryResource::accept_current_request`], consumed by one of the
211/// `complete_*` methods. The guard is **moved** (not copied) into the
212/// completion method, which enforces the two-phase protocol at the type level:
213/// once a guard is used, it cannot be used again.
214///
215/// # Two-phase protocol
216///
217/// 1. **Accept**: `resource.accept_current_request(request_id)` validates that
218///    the request is still active and returns `Some(RequestGuard)`.
219/// 2. **Complete**: `resource.complete_success(guard, data, now_ms)` consumes
220///    the guard and applies the result. Attempting to use the guard again is a
221///    compile error because it has been moved.
222///
223/// # Why not `Copy`?
224///
225/// Previous versions derived `Clone` + `Copy`, which allowed the same guard to
226/// be passed to multiple `complete_*` calls. While the second call would be a
227/// no-op (the resource already cleared `active_request_id`), it was wasteful
228/// and could mask bugs. Taking the guard by value prevents this entirely.
229///
230/// [`QueryResource`]: super::QueryResource
231/// [`QueryResource::accept_current_request`]: super::QueryResource::accept_current_request
232#[derive(Debug, PartialEq, Eq)]
233#[must_use]
234pub struct RequestGuard {
235    request_id: RequestId,
236}
237
238impl RequestGuard {
239    pub(super) fn new(request_id: RequestId) -> Self {
240        Self { request_id }
241    }
242
243    /// The request id this guard protects (borrowed).
244    pub fn request_id(&self) -> RequestId {
245        self.request_id
246    }
247
248    /// Consume the guard and return the request id.
249    ///
250    /// Useful when you want to extract the id and discard the guard.
251    pub fn into_request_id(self) -> RequestId {
252        self.request_id
253    }
254}