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}