Skip to content

Commit c2e974b

Browse files
authored
defer restoration until initialization is done (#12279)
## Description Defer restoration of persisted indicies until initailization is done for remote daemons so we read the correct codebase indexing limit <!-- Please remember to add your design buddy onto the PR for review, if it contains any UI changes! --> ## Linked Issue N/A <!-- Link the GitHub issue this PR addresses. Before opening this PR, please confirm: --> - [ ] The linked issue is labeled `ready-to-spec` or `ready-to-implement`. - [ ] Where appropriate, screenshots or a short video of the implementation are included below (especially for user-visible or UI changes). ## Testing Previously, on startup, these would be erroring. Now they're all correctly green ![Screenshot 2026-06-05 at 2.57.32 PM.png](https://app.graphite.com/user-attachments/assets/607eb0ee-4220-4fef-a4b4-0f090cb5585a.png) <!-- How did you test this change? What automated tests did you add? If you didn't add any new tests, what's your justification for not adding any? Manual testing is required for changes that can be manually tested, and almost all changes can be manually tested. If your change can be manually tested, please include screenshots or a screen recording that show it working end to end. You can run the app locally using `./script/run` - see WARP.md for more details on how to get set up. --> - [x] I have manually tested my changes locally with `./script/run` ### Screenshots / Videos <!-- Attach screenshots or a short video demonstrating the change, where appropriate. Remove this section if it is not relevant to your PR. --> ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode <!-- ## Changelog Entries for Stable The entries below will be used when constructing a soft-copy of the stable release changelog. Leave blank or remove the lines if no entry in the stable changelog is needed. Entries should be on the same line, without the `{{` `}}` brackets. You can use multiple lines, even of the same type. The valid suffixes are: - NEW-FEATURE: for new, relatively sizable features. Features listed here will likely have docs / social media posts / marketing launches associated with them, so use sparingly. - IMPROVEMENT: for new functionality of existing features. - BUG-FIX: for fixes related to known bugs or regressions. - IMAGE: the image specified by the URL (hosted on GCP) will be added to Dev & Preview releases. For Stable releases, see the pinned doc in the #release Slack channel. - OZ: Oz-related updates. Use `CHANGELOG-OZ`. At most 4 Oz updates are shown in-app per release. - NONE: Explicitly opt out of changelog inclusion. Use `CHANGELOG-NONE` for PRs that should never appear in the changelog (e.g. refactors, internal tooling, CI changes). This prevents the changelog agent from inferring an entry. CHANGELOG-NEW-FEATURE: {{text goes here...}} CHANGELOG-IMPROVEMENT: {{text goes here...}} CHANGELOG-BUG-FIX: {{text goes here...}} CHANGELOG-BUG-FIX: {{more text goes here...}} CHANGELOG-IMAGE: {{GCP-hosted URL goes here...}} CHANGELOG-OZ: {{text goes here...}} CHANGELOG-NONE -->
1 parent e4ab191 commit c2e974b

5 files changed

Lines changed: 139 additions & 8 deletions

File tree

app/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1997,14 +1997,17 @@ pub(crate) fn initialize_app(
19971997
};
19981998

19991999
let codebase_limits = AIRequestUsageModel::as_ref(ctx).codebase_context_limits();
2000-
let codebase_index_config = CodebaseIndexManagerConfig::new(
2000+
let mut codebase_index_config = CodebaseIndexManagerConfig::new(
20012001
indices_to_restore,
20022002
codebase_limits.max_indices_allowed,
20032003
codebase_limits.max_files_per_repo,
20042004
codebase_limits.embedding_generation_batch_size,
20052005
server_api_provider.as_ref(ctx).get(),
20062006
launch_mode.supports_indexing(),
20072007
);
2008+
if matches!(launch_mode, LaunchMode::RemoteServerDaemon { .. }) {
2009+
codebase_index_config = codebase_index_config.defer_persisted_index_restore();
2010+
}
20082011
#[cfg(feature = "local_fs")]
20092012
if let Some(snapshot_storage) = daemon_codebase_index_snapshot_storage(launch_mode) {
20102013
return CodebaseIndexManager::new_with_snapshot_storage(

app/src/remote_server/server_model.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,9 @@ impl ServerModel {
14181418
log::info!("Handling Initialize (request_id={request_id})");
14191419
self.apply_initialize_auth(&msg);
14201420
Self::apply_codebase_index_limits(msg.codebase_index_limits.as_ref(), ctx);
1421+
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
1422+
manager.start_persisted_index_restore(ctx);
1423+
});
14211424

14221425
// Update crash reporting based on client-supplied preferences.
14231426
#[cfg(feature = "crash_reporting")]

crates/ai/src/index/full_source_code_embedding/manager.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ pub struct CodebaseIndexManagerConfig {
253253
embedding_generation_batch_size: usize,
254254
store_client: Arc<dyn StoreClient>,
255255
indexing_enabled: bool,
256+
restore_persisted_indices_on_startup: bool,
256257
}
257258

258259
impl CodebaseIndexManagerConfig {
@@ -271,8 +272,14 @@ impl CodebaseIndexManagerConfig {
271272
embedding_generation_batch_size,
272273
store_client,
273274
indexing_enabled,
275+
restore_persisted_indices_on_startup: true,
274276
}
275277
}
278+
279+
pub fn defer_persisted_index_restore(mut self) -> Self {
280+
self.restore_persisted_indices_on_startup = false;
281+
self
282+
}
276283
}
277284

278285
/// Manager for the codebase index states across the app.
@@ -361,6 +368,7 @@ impl CodebaseIndexManager {
361368
embedding_generation_batch_size,
362369
store_client,
363370
indexing_enabled,
371+
restore_persisted_indices_on_startup,
364372
} = config;
365373
cfg_if::cfg_if! {
366374
if #[cfg(feature = "local_fs")] {
@@ -416,7 +424,8 @@ impl CodebaseIndexManager {
416424
}
417425

418426
// For the moment, we've decided to load all snapshots regardless of the index count.
419-
let build_queue = BuildQueue::new_with_persisted(valid_metadata);
427+
let build_queue =
428+
BuildQueue::new_with_persisted(valid_metadata, restore_persisted_indices_on_startup);
420429

421430
let mut me = Self {
422431
codebase_indices: HashMap::new(),
@@ -433,10 +442,7 @@ impl CodebaseIndexManager {
433442
snapshot_storage,
434443
};
435444

436-
// Start building the first index in the queue.
437-
if let Some(next_repo) = me.build_queue.pick_next_sync() {
438-
me.build_and_sync_codebase_index(BuildSource::FromPersistedMetadata(next_repo), ctx);
439-
}
445+
me.start_next_queued_index(ctx);
440446

441447
me
442448
}
@@ -808,6 +814,15 @@ impl CodebaseIndexManager {
808814
self.indexing_enabled
809815
}
810816

817+
pub fn start_persisted_index_restore(&mut self, ctx: &mut ModelContext<Self>) {
818+
if !self.is_indexing_enabled() {
819+
return;
820+
}
821+
if self.build_queue.start() {
822+
self.start_next_queued_index(ctx);
823+
}
824+
}
825+
811826
pub fn index_directory(&mut self, directory: PathBuf, ctx: &mut ModelContext<Self>) -> bool {
812827
if !self.is_indexing_enabled() {
813828
return false;
@@ -1100,7 +1115,10 @@ impl CodebaseIndexManager {
11001115
let Ok(_) = self.get_codebase_index_internal(finished_repo) else {
11011116
return;
11021117
};
1118+
self.start_next_queued_index(ctx);
1119+
}
11031120

1121+
fn start_next_queued_index(&mut self, ctx: &mut ModelContext<Self>) {
11041122
if let Some(next_repo) = self.build_queue.pick_next_sync() {
11051123
self.build_and_sync_codebase_index(BuildSource::FromPersistedMetadata(next_repo), ctx);
11061124
}

crates/ai/src/index/full_source_code_embedding/manager_tests.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use std::path::{Path, PathBuf};
22
use std::sync::Arc;
33

4+
#[cfg(feature = "local_fs")]
5+
use chrono::Utc;
6+
#[cfg(feature = "local_fs")]
7+
use repo_metadata::DirectoryWatcher;
48
use warpui_core::App;
59

610
use super::{
@@ -174,6 +178,74 @@ fn initializes_with_injected_snapshot_storage_when_configured() {
174178
});
175179
}
176180

181+
#[test]
182+
fn persisted_index_restore_starts_on_startup_by_default() {
183+
App::test((), |app| async move {
184+
let manager = app.add_singleton_model(|ctx| {
185+
CodebaseIndexManager::new(
186+
Vec::new(),
187+
Some(1),
188+
1000,
189+
32,
190+
Arc::new(MockStoreClient),
191+
true,
192+
ctx,
193+
)
194+
});
195+
196+
manager.read(&app, |manager, _| {
197+
assert!(manager.build_queue.is_running());
198+
});
199+
});
200+
}
201+
202+
#[test]
203+
#[cfg(feature = "local_fs")]
204+
fn deferred_persisted_index_restore_starts_once() {
205+
App::test((), |mut app| async move {
206+
app.add_singleton_model(DirectoryWatcher::new);
207+
208+
let snapshot_dir = tempfile::tempdir().unwrap();
209+
let storage = SnapshotStorage::from_dir(snapshot_dir.path().join("daemon")).unwrap();
210+
let first_repo = tempfile::tempdir().unwrap();
211+
let second_repo = tempfile::tempdir().unwrap();
212+
let mut first_metadata = workspace_metadata(first_repo.path());
213+
first_metadata.modified_ts = Some(Utc::now());
214+
let mut second_metadata = workspace_metadata(second_repo.path());
215+
second_metadata.modified_ts = Some(Utc::now());
216+
std::fs::write(storage.snapshot_path(first_repo.path()), b"snapshot").unwrap();
217+
std::fs::write(storage.snapshot_path(second_repo.path()), b"snapshot").unwrap();
218+
219+
let manager = app.add_singleton_model(|ctx| {
220+
CodebaseIndexManager::new_with_snapshot_storage(
221+
CodebaseIndexManagerConfig::new(
222+
vec![first_metadata, second_metadata],
223+
Some(2),
224+
1000,
225+
32,
226+
Arc::new(MockStoreClient),
227+
true,
228+
)
229+
.defer_persisted_index_restore(),
230+
Some(storage),
231+
ctx,
232+
)
233+
});
234+
235+
manager.update(&mut app, |manager, ctx| {
236+
assert!(!manager.build_queue.is_running());
237+
assert_eq!(manager.build_queue.queued_metadata().into_iter().count(), 2);
238+
239+
manager.start_persisted_index_restore(ctx);
240+
assert!(manager.build_queue.is_running());
241+
assert_eq!(manager.build_queue.queued_metadata().into_iter().count(), 1);
242+
243+
manager.start_persisted_index_restore(ctx);
244+
assert_eq!(manager.build_queue.queued_metadata().into_iter().count(), 1);
245+
});
246+
});
247+
}
248+
177249
#[test]
178250
fn can_create_new_indices_honors_max_limit_when_enabled() {
179251
App::test((), |mut app| async move {

crates/ai/src/index/full_source_code_embedding/priority_queue.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ struct QueueEntry {
1818
metadata: WorkspaceMetadata,
1919
}
2020

21+
/// Controls whether queued builds may be consumed.
22+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
23+
enum BuildQueueState {
24+
Paused,
25+
#[default]
26+
Running,
27+
}
28+
2129
impl Hash for QueueEntry {
2230
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2331
self.metadata.path.hash(state);
@@ -35,6 +43,7 @@ impl Eq for QueueEntry {}
3543
#[derive(Debug, Default)]
3644
pub(super) struct BuildQueue {
3745
queue: PriorityQueue<QueueEntry, Priority>,
46+
state: BuildQueueState,
3847
}
3948

4049
impl BuildQueue {
@@ -46,20 +55,46 @@ impl BuildQueue {
4655
self.queue.iter().map(|(entry, _)| entry.metadata.clone())
4756
}
4857

49-
pub(super) fn new_with_persisted(snapshots_to_load: Vec<WorkspaceMetadata>) -> Self {
58+
pub(super) fn new_with_persisted(
59+
snapshots_to_load: Vec<WorkspaceMetadata>,
60+
start_immediately: bool,
61+
) -> Self {
5062
let mut queue = PriorityQueue::new();
5163
queue.extend(
5264
snapshots_to_load
5365
.into_iter()
5466
.sorted_by(WorkspaceMetadata::most_recently_touched)
5567
.map(|entry| (QueueEntry { metadata: entry }, Priority::PersistedSnapshot)),
5668
);
69+
let state = if start_immediately {
70+
BuildQueueState::Running
71+
} else {
72+
BuildQueueState::Paused
73+
};
74+
75+
Self { queue, state }
76+
}
77+
78+
pub(super) fn is_running(&self) -> bool {
79+
self.state == BuildQueueState::Running
80+
}
5781

58-
Self { queue }
82+
/// Starts consuming queued builds. Returns whether the queue transitioned to running.
83+
pub(super) fn start(&mut self) -> bool {
84+
match self.state {
85+
BuildQueueState::Paused => {
86+
self.state = BuildQueueState::Running;
87+
true
88+
}
89+
BuildQueueState::Running => false,
90+
}
5991
}
6092

6193
/// Pulls the next index root path to sync from the priority queue and returns it.
6294
pub fn pick_next_sync(&mut self) -> Option<WorkspaceMetadata> {
95+
if !self.is_running() {
96+
return None;
97+
}
6398
self.queue.pop().map(|(entry, _priority)| entry.metadata)
6499
}
65100

0 commit comments

Comments
 (0)