Skip to content

Commit d9c3e27

Browse files
baszalmstranichmor
andauthored
fix(run): clear filesystem caches in between pixi run steps (#4523)
Co-authored-by: nichmor <[email protected]>
1 parent 688ffb1 commit d9c3e27

File tree

5 files changed

+118
-2
lines changed

5 files changed

+118
-2
lines changed

crates/pixi_cli/src/run.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ pub async fn execute(args: Args) -> miette::Result<()> {
266266
// Clear the current progress reports.
267267
lock_file.command_dispatcher.clear_reporter().await;
268268

269+
// Clear caches based on the filesystem. The tasks might change files on disk.
270+
lock_file.command_dispatcher.clear_filesystem_caches().await;
271+
269272
let command_env = get_task_env(
270273
&executable_task.run_environment,
271274
args.clean_env || executable_task.task().clean_env(),

crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ pub(crate) enum ForegroundMessage {
236236
InstallPixiEnvironment(InstallPixiEnvironmentTask),
237237
InstantiateToolEnvironment(Task<InstantiateToolEnvironmentSpec>),
238238
ClearReporter(oneshot::Sender<()>),
239+
#[from(ignore)]
240+
ClearFilesystemCaches(oneshot::Sender<()>),
239241
}
240242

241243
/// A message that is send to the background task to start solving a particular
@@ -361,6 +363,21 @@ impl CommandDispatcher {
361363
&self.data.discovery_cache
362364
}
363365

366+
/// Clears in-memory caches whose correctness depends on the filesystem.
367+
///
368+
/// This invalidates memoized results that are derived from files on disk so
369+
/// subsequent operations re-check the current state of the filesystem. It:
370+
/// - clears glob hash memoization (`GlobHashCache`) used for input file hashing
371+
/// - clears memoized SourceBuildCacheStatus results held by the processor,
372+
/// while preserving any in-flight queries
373+
pub async fn clear_filesystem_caches(&self) {
374+
if let Some(sender) = self.channel().sender() {
375+
let (tx, rx) = oneshot::channel();
376+
let _ = sender.send(ForegroundMessage::ClearFilesystemCaches(tx));
377+
let _ = rx.await;
378+
}
379+
}
380+
364381
/// Returns the download client used by the command dispatcher.
365382
pub fn download_client(&self) -> &ClientWithMiddleware {
366383
&self.data.download_client

crates/pixi_command_dispatcher/src/command_dispatcher_processor/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ impl CommandDispatcherProcessor {
391391
self.on_source_build_cache_status(task)
392392
}
393393
ForegroundMessage::ClearReporter(sender) => self.clear_reporter(sender),
394+
ForegroundMessage::ClearFilesystemCaches(sender) => {
395+
self.clear_filesystem_caches(sender)
396+
}
394397
ForegroundMessage::SourceMetadata(task) => self.on_source_metadata(task),
395398
ForegroundMessage::BackendSourceBuild(task) => self.on_backend_source_build(task),
396399
}
@@ -556,6 +559,17 @@ impl CommandDispatcherProcessor {
556559
let _ = sender.send(());
557560
}
558561

562+
/// Clears cached results based on the filesystem, preserving in-flight tasks.
563+
fn clear_filesystem_caches(&mut self, sender: oneshot::Sender<()>) {
564+
self.inner.glob_hash_cache.clear();
565+
566+
// Clear source build cache status, preserving in-flight tasks.
567+
self.source_build_cache_status
568+
.retain(|_, v| matches!(v, PendingDeduplicatingTask::Pending(_, _)));
569+
570+
let _ = sender.send(());
571+
}
572+
559573
/// Returns true if by following the parent chain of the `parent` context we
560574
/// stumble on `id`.
561575
pub fn contains_cycle<T: TryFrom<CommandDispatcherContext> + PartialEq>(

crates/pixi_command_dispatcher/tests/integration/main.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod event_tree;
44
use std::{
55
collections::HashMap,
66
path::{Path, PathBuf},
7+
// ptr,
78
str::FromStr,
89
};
910

@@ -14,14 +15,17 @@ use pixi_build_frontend::{
1415
};
1516
use pixi_command_dispatcher::{
1617
BuildEnvironment, CacheDirs, CommandDispatcher, Executor, InstallPixiEnvironmentSpec,
17-
InstantiateToolEnvironmentSpec, PixiEnvironmentSpec,
18+
InstantiateToolEnvironmentSpec, PackageIdentifier, PixiEnvironmentSpec,
19+
SourceBuildCacheStatusSpec,
1820
};
1921
use pixi_config::default_channel_config;
22+
use pixi_record::PinnedPathSpec;
2023
use pixi_spec::{GitReference, GitSpec, PathSpec, PixiSpec};
2124
use pixi_spec_containers::DependencyMap;
2225
use pixi_test_utils::format_diagnostic;
2326
use rattler_conda_types::{
24-
GenericVirtualPackage, PackageName, Platform, VersionSpec, prefix::Prefix,
27+
ChannelUrl, GenericVirtualPackage, PackageName, Platform, VersionSpec, VersionWithSource,
28+
prefix::Prefix,
2529
};
2630
use rattler_virtual_packages::{VirtualPackageOverrides, VirtualPackages};
2731
use url::Url;
@@ -533,3 +537,76 @@ pub async fn instantiate_backend_with_from_source() {
533537

534538
insta::assert_debug_snapshot!(err);
535539
}
540+
541+
#[tokio::test]
542+
async fn source_build_cache_status_clear_works() {
543+
let tmp_dir = tempfile::tempdir().unwrap();
544+
545+
let dispatcher = CommandDispatcher::builder()
546+
.with_cache_dirs(CacheDirs::new(tmp_dir.path().to_path_buf()))
547+
.finish();
548+
549+
let host = Platform::current();
550+
let build_env = BuildEnvironment {
551+
host_platform: host,
552+
build_platform: host,
553+
build_virtual_packages: vec![],
554+
host_virtual_packages: vec![],
555+
};
556+
557+
let pkg = PackageIdentifier {
558+
name: PackageName::try_from("dummy-pkg").unwrap(),
559+
version: VersionWithSource::from_str("0.0.0").unwrap(),
560+
build: "0".to_string(),
561+
subdir: host.to_string(),
562+
};
563+
564+
let spec = SourceBuildCacheStatusSpec {
565+
package: pkg,
566+
source: PinnedPathSpec {
567+
path: tmp_dir.path().to_string_lossy().into_owned().into(),
568+
}
569+
.into(),
570+
channels: Vec::<ChannelUrl>::new(),
571+
build_environment: build_env,
572+
channel_config: default_channel_config(),
573+
enabled_protocols: Default::default(),
574+
};
575+
576+
let first = dispatcher
577+
.source_build_cache_status(spec.clone())
578+
.await
579+
.expect("query succeeds");
580+
581+
// Create a weak reference to track that the original Arc is dropped
582+
// after clearing the cache
583+
let weak_first = std::sync::Arc::downgrade(&first);
584+
585+
let second = dispatcher
586+
.source_build_cache_status(spec.clone())
587+
.await
588+
.expect("query succeeds");
589+
590+
// Cached result should return the same Arc
591+
assert!(std::sync::Arc::ptr_eq(&first, &second));
592+
593+
// now drop the cached entries to release the Arc
594+
// which will unlock the fd locks that we hold on the cache files
595+
drop(first);
596+
drop(second);
597+
598+
// Clear and expect a fresh Arc on next query
599+
dispatcher.clear_filesystem_caches().await;
600+
601+
let _third = dispatcher
602+
.source_build_cache_status(spec)
603+
.await
604+
.expect("query succeeds");
605+
606+
// Check if the original Arc is truly gone
607+
// and we have a fresh one
608+
assert!(
609+
weak_first.upgrade().is_none(),
610+
"Original Arc should be deallocated after cache clear"
611+
);
612+
}

crates/pixi_glob/src/glob_hash_cache.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,9 @@ impl GlobHashCache {
129129
}
130130
}
131131
}
132+
133+
/// Clears all memoized glob hashes. In-flight computations are unaffected.
134+
pub fn clear(&self) {
135+
self.cache.clear();
136+
}
132137
}

0 commit comments

Comments
 (0)