aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/installer/installation_manager.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src/installer/installation_manager.rs')
-rw-r--r--crates/shirabe/src/installer/installation_manager.rs185
1 files changed, 82 insertions, 103 deletions
diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs
index ae0470a..7a05324 100644
--- a/crates/shirabe/src/installer/installation_manager.rs
+++ b/crates/shirabe/src/installer/installation_manager.rs
@@ -3,7 +3,6 @@
use crate::io::io_interface;
use anyhow::Result;
use indexmap::IndexMap;
-use shirabe_external_packages::react::promise;
use shirabe_external_packages::seld::signal::SignalHandler;
use shirabe_php_shim::{
InvalidArgumentException, PhpMixed, array_search_mixed, array_splice, array_unshift, count,
@@ -181,7 +180,10 @@ impl InstallationManager {
// @var array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
let mut cleanup_promises: IndexMap<
i64,
- Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>,
+ Box<
+ dyn Fn()
+ -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>,
+ >,
> = IndexMap::new();
let signal_handler = SignalHandler::create(
@@ -234,15 +236,17 @@ impl InstallationManager {
}
for batch_to_execute in batches {
- self.download_and_execute_batch(
- repo,
- batch_to_execute,
- &mut cleanup_promises,
- dev_mode,
- run_scripts,
- download_only,
- // TODO(phase-b): allOperations should be the original full list; would require clone
- vec![],
+ tokio::runtime::Runtime::new().unwrap().block_on(
+ self.download_and_execute_batch(
+ repo,
+ batch_to_execute,
+ &mut cleanup_promises,
+ dev_mode,
+ run_scripts,
+ download_only,
+ // TODO(phase-b): allOperations should be the original full list; would require clone
+ vec![],
+ ),
)?;
}
@@ -255,7 +259,9 @@ impl InstallationManager {
match result {
Ok(()) => {}
Err(e) => {
- self.run_cleanup(&cleanup_promises);
+ tokio::runtime::Runtime::new()
+ .unwrap()
+ .block_on(self.run_cleanup(&cleanup_promises));
return Err(e);
}
}
@@ -275,20 +281,22 @@ impl InstallationManager {
/// @param OperationInterface[] $operations List of operations to execute in this batch
/// @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
/// @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
- fn download_and_execute_batch(
+ async fn download_and_execute_batch(
&mut self,
repo: &mut dyn InstalledRepositoryInterface,
operations: IndexMap<i64, Box<dyn OperationInterface>>,
- cleanup_promises: &mut IndexMap<i64, Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>>,
+ cleanup_promises: &mut IndexMap<
+ i64,
+ Box<
+ dyn Fn()
+ -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>,
+ >,
+ >,
dev_mode: bool,
run_scripts: bool,
download_only: bool,
all_operations: Vec<Box<dyn OperationInterface>>,
) -> Result<()> {
- // TODO(phase-c-promise): Loop::wait-driven batch orchestration (download/cleanup promises);
- // rewrite to await collected futures once the async Loop boundary lands.
- let mut promises: Vec<Box<dyn PromiseInterface>> = vec![];
-
for (index, operation) in &operations {
let op_type = operation.get_operation_type();
@@ -316,35 +324,35 @@ impl InstallationManager {
}
let installer = self.get_installer(package.get_type())?;
- // TODO(phase-b): closure captures installer + package; needs Arc/Rc for shared state
+ // TODO(phase-b): closure captures installer + package; needs Rc-shared installer/package
let _ = installer;
let op_type_clone = op_type.clone();
- let cleanup: Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>> =
- Box::new(move || -> Option<Box<dyn PromiseInterface>> {
- // avoid calling cleanup if the download was not even initialized for a package
- // as without installation source configured nothing will work
- // TODO(phase-b): if (null === $package->getInstallationSource()) return \React\Promise\resolve(null);
- let _ = &op_type_clone;
- Some(promise::resolve(None))
- });
+ let cleanup: Box<
+ dyn Fn()
+ -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>,
+ > = Box::new(move || {
+ // avoid calling cleanup if the download was not even initialized for a package
+ // as without installation source configured nothing will work
+ // TODO(phase-b): if (null === $package->getInstallationSource()) return resolve(null);
+ let _ = &op_type_clone;
+ // TODO(phase-c-promise): build the real installer.cleanup() future once the installer
+ // can be shared into a 'static cleanup closure (Stage 2 Rc/Arc).
+ let fut: std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>> =
+ Box::pin(async { Ok(()) });
+ Some(fut)
+ });
cleanup_promises.insert(*index, cleanup);
if op_type != "uninstall" {
+ // TODO(phase-c-promise): PHP collects every download and runs them concurrently via
+ // Loop::wait; the single-threaded loop awaits each serially instead.
let installer = self.get_installer(package.get_type())?;
- let promise = installer.download(package, initial_package);
- if let Ok(Some(p)) = promise {
- promises.push(p);
- }
+ installer.download(package, initial_package).await?;
}
}
- // execute all downloads first
- if (promises.len() as i64) > 0 {
- self.wait_on_promises(promises);
- }
-
if download_only {
- self.run_cleanup(cleanup_promises);
+ self.run_cleanup(cleanup_promises).await;
return Ok(());
}
@@ -385,7 +393,8 @@ impl InstallationManager {
dev_mode,
run_scripts,
&all_operations,
- )?;
+ )
+ .await?;
}
Ok(())
@@ -394,18 +403,21 @@ impl InstallationManager {
/// @param OperationInterface[] $operations List of operations to execute in this batch
/// @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
/// @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
- fn execute_batch(
+ async fn execute_batch(
&mut self,
repo: &mut dyn InstalledRepositoryInterface,
operations: IndexMap<i64, Box<dyn OperationInterface>>,
- cleanup_promises: &IndexMap<i64, Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>>,
+ cleanup_promises: &IndexMap<
+ i64,
+ Box<
+ dyn Fn()
+ -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>,
+ >,
+ >,
dev_mode: bool,
run_scripts: bool,
all_operations: &[Box<dyn OperationInterface>],
) -> Result<()> {
- // TODO(phase-c-promise): Loop::wait-driven batch orchestration (prepare/install promises);
- // rewrite to await collected futures once the async Loop boundary lands.
- let mut promises: Vec<Box<dyn PromiseInterface>> = vec![];
let mut post_exec_callbacks: Vec<Box<dyn Fn()>> = vec![];
for (index, operation) in operations {
@@ -473,14 +485,13 @@ impl InstallationManager {
let _io = self.io.as_ref();
let installer = self.get_installer(package.get_type())?;
- let promise = installer.prepare(&op_type, package, initial_package);
- let promise = match promise {
- Ok(Some(p)) => p,
- Ok(None) => promise::resolve(None),
- Err(e) => return Err(e),
- };
+ // TODO(phase-c-promise): PHP chains prepare()->then(install/update/uninstall)->then(cleanup
+ // + repo.write); the single-threaded loop awaits prepare and leaves the rest as phase-b work.
+ installer
+ .prepare(&op_type, package, initial_package)
+ .await?;
- // TODO(phase-b): chain `.then(cb1).then(cb2)` with cleanup_promises[index], repo.write, etc.
+ // TODO(phase-b): chain the install/update/uninstall step with cleanup_promises[index], repo.write, etc.
let _ = cleanup_promises.get(&index);
let event_name_post = match op_type.as_str() {
@@ -497,13 +508,6 @@ impl InstallationManager {
// dispatcher.dispatch_package_event(event_name_post, dev_mode, repo, all_operations, operation);
}));
}
-
- promises.push(promise);
- }
-
- // execute all prepare => installs/updates/removes => cleanup steps
- if (promises.len() as i64) > 0 {
- self.wait_on_promises(promises);
}
Platform::workaround_filesystem_issues();
@@ -515,32 +519,6 @@ impl InstallationManager {
Ok(())
}
- /// @param array<PromiseInterface<void|null>> $promises
- fn wait_on_promises(&mut self, promises: Vec<Box<dyn PromiseInterface>>) {
- // TODO(phase-c-promise): thin wrapper over Loop::wait (the async boundary owned separately).
- let mut progress: Option<()> = None;
- // TODO(phase-b): self.io instanceof ConsoleIO downcast
- let io_is_console = false;
- if self.output_progress
- && io_is_console
- && Platform::get_env("CI").is_none()
- && !self.io.is_debug()
- && (promises.len() as i64) > 1
- {
- // TODO(phase-b): progress = self.io.get_progress_bar();
- progress = Some(());
- }
- // TODO(phase-b): pass actual ProgressBar when self.io.get_progress_bar() is implemented
- let _ = self.loop_.borrow_mut().wait(promises, None);
- if progress.is_some() {
- // progress.clear();
- // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing
- if !self.io.is_decorated() {
- self.io.write_error3("", true, io_interface::NORMAL);
- }
- }
- }
-
/// Executes download operation.
///
/// @phpstan-return PromiseInterface<void|null>|null
@@ -653,10 +631,8 @@ impl InstallationManager {
}
pub fn notify_installs(&mut self, _io: &dyn IOInterface) {
- // TODO(phase-c-promise): collects http_downloader.add() promises and drives them via Loop::wait;
- // rewrite to await the collected futures once the async Loop boundary lands.
- let mut promises: Vec<Box<dyn PromiseInterface>> = vec![];
-
+ // TODO(phase-c-promise): PHP collects every http_downloader.add() promise and runs them via
+ // Loop::wait; the single-threaded sync bridge block_on's each notification serially instead.
let result: Result<()> = (|| -> Result<()> {
for (repo_url, packages) in &self.notifiable_packages {
// non-batch API, deprecated
@@ -699,13 +675,13 @@ impl InstallationManager {
),
);
- promises.push(
+ tokio::runtime::Runtime::new().unwrap().block_on(
self.loop_
.borrow()
.get_http_downloader()
.borrow_mut()
- .add(&url, opts)?,
- );
+ .add(&url, opts),
+ )?;
}
continue;
@@ -771,17 +747,15 @@ impl InstallationManager {
PhpMixed::Array(http.into_iter().map(|(k, v)| (k, Box::new(v))).collect()),
);
- promises.push(
+ tokio::runtime::Runtime::new().unwrap().block_on(
self.loop_
.borrow()
.get_http_downloader()
.borrow_mut()
- .add(repo_url, opts)?,
- );
+ .add(repo_url, opts),
+ )?;
}
- let _ = self.loop_.borrow_mut().wait(promises, None);
-
Ok(())
})();
// PHP swallows the exception silently here
@@ -800,28 +774,33 @@ impl InstallationManager {
}
/// @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
- fn run_cleanup(
+ async fn run_cleanup(
&mut self,
- cleanup_promises: &IndexMap<i64, Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>>,
+ cleanup_promises: &IndexMap<
+ i64,
+ Box<
+ dyn Fn()
+ -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>,
+ >,
+ >,
) {
- // TODO(phase-c-promise): runs cleanup closures and drives them via Loop::wait;
- // rewrite once the async Loop boundary and async cleanup closures land.
- let mut promises: Vec<Box<dyn PromiseInterface>> = vec![];
+ let mut promises: Vec<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>> =
+ vec![];
self.loop_.borrow().abort_jobs();
for (_, cleanup) in cleanup_promises {
- // TODO(phase-b): React\Promise\Promise constructor with executor; emulate by wrapping cleanup()
+ // PHP wraps a missing cleanup promise in \React\Promise\resolve(null).
let promise = cleanup();
if let Some(p) = promise {
promises.push(p);
} else {
- promises.push(promise::resolve(None));
+ promises.push(Box::pin(async { Ok(()) }));
}
}
if (promises.len() as i64) > 0 {
- let _ = self.loop_.borrow_mut().wait(promises, None);
+ let _ = self.loop_.borrow_mut().wait(promises, None).await;
}
}
}