diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-18 18:03:57 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-18 18:03:57 +0900 |
| commit | 9b291d454b059977639db26c847af4e835cda0c3 (patch) | |
| tree | e54770570a80e67681f1614841156b864452713c /crates/shirabe/src/util/filesystem.rs | |
| parent | f8e385f7a1bd752d39f4d4f87b0439596839dde9 (diff) | |
| download | php-shirabe-9b291d454b059977639db26c847af4e835cda0c3.tar.gz php-shirabe-9b291d454b059977639db26c847af4e835cda0c3.tar.zst php-shirabe-9b291d454b059977639db26c847af4e835cda0c3.zip | |
refactor(downloader): take &self across the downloader hierarchy
Concurrent package operations call into the same downloader instances
through Rc<RefCell<dyn DownloaderInterface>>; with &mut self methods
every call holds a RefMut across its awaits, which panics with
'already mutably borrowed' the moment two operations overlap. This is
groundwork for fanning out InstallationManager's download/install
loops (same rework HttpDownloader/CurlDownloader already got).
- DownloaderInterface/ChangeReportInterface/ArchiveDownloader/
VcsDownloader methods now take &self; as_change_report_interface
returns &dyn instead of &mut dyn.
- Implementors move their genuinely mutable state behind cells:
FileDownloader.additional_cleanup_paths, the archive downloaders'
cleanup_executed, ZipDownloader.zip_archive_object,
VcsDownloaderBase.has_cleaned_changes, GitDownloader's stash/discard/
cache maps and GitUtil, SvnDownloader.cache_credentials,
PerforceDownloader.perforce. FileDownloader.io gains a RefCell layer
so get_local_changes can keep PHP's NullIO swap under &self.
- ProcessExecutor::execute_async now returns a future that captures
everything up front instead of borrowing the executor, and call
sites build the future before awaiting, so no borrow on the shared
executor is held while a subprocess runs.
- Filesystem::remove_directory_async becomes remove_directory_async_via
taking the Rc handle: the Filesystem is only borrowed for the sync
head/tail, never across the rm subprocess await (sync borrow_mut
users like rename/ensure_directory_exists would otherwise collide).
- DownloadManager async call sites hold shared borrows only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/util/filesystem.rs')
| -rw-r--r-- | crates/shirabe/src/util/filesystem.rs | 68 |
1 files changed, 39 insertions, 29 deletions
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs index e12d7fee..f3cb2e63 100644 --- a/crates/shirabe/src/util/filesystem.rs +++ b/crates/shirabe/src/util/filesystem.rs @@ -163,38 +163,48 @@ impl Filesystem { /// /// Uses the process component if proc_open is enabled on the PHP /// installation. - pub async fn remove_directory_async(&mut self, directory: &str) -> anyhow::Result<bool> { - if let Some(mock) = self.mock.as_mut() - && let Some(result) = mock.remove_directory_async_result - { - mock.remove_directory_async_calls += 1; - return Ok(result); - } + /// + /// Takes the shared handle instead of `&mut self`: the Filesystem is borrowed only for the + /// synchronous head and tail, never across the subprocess await, so sibling futures can keep + /// using the same `Rc<RefCell<Filesystem>>` while the removal runs. + pub async fn remove_directory_async_via( + this: &std::rc::Rc<std::cell::RefCell<Filesystem>>, + directory: &str, + ) -> anyhow::Result<bool> { + let (process_executor, cmd) = { + let mut fs = this.borrow_mut(); - let edge_case_result = self.remove_edge_cases(directory, true)?; - if let Some(r) = edge_case_result { - return Ok(r); - } + if let Some(mock) = fs.mock.as_mut() + && let Some(result) = mock.remove_directory_async_result + { + mock.remove_directory_async_calls += 1; + return Ok(result); + } - let cmd: Vec<String> = if Platform::is_windows() { - vec![ - "rmdir".to_string(), - "/S".to_string(), - "/Q".to_string(), - Platform::realpath(directory), - ] - } else { - vec!["rm".to_string(), "-rf".to_string(), directory.to_string()] + let edge_case_result = fs.remove_edge_cases(directory, true)?; + if let Some(r) = edge_case_result { + return Ok(r); + } + + let cmd: Vec<String> = if Platform::is_windows() { + vec![ + "rmdir".to_string(), + "/S".to_string(), + "/Q".to_string(), + Platform::realpath(directory), + ] + } else { + vec!["rm".to_string(), "-rf".to_string(), directory.to_string()] + }; + + (fs.get_process_handle(), cmd) }; - let process_executor = self.get_process_handle(); - let mut process = process_executor - .borrow() - .execute_async( - PhpMixed::List(cmd.iter().map(|s| PhpMixed::String(s.clone())).collect()), - None, - ) - .await?; + let process_future = process_executor.borrow().execute_async( + PhpMixed::List(cmd.iter().map(|s| PhpMixed::String(s.clone())).collect()), + None, + ); + let mut process = process_future.await?; // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache2(false, ""); @@ -203,7 +213,7 @@ impl Filesystem { return Ok(true); } - self.remove_directory_php(directory) + this.borrow_mut().remove_directory_php(directory) } /// Returns null when no edge case was hit. Otherwise a bool whether removal was successful |
