| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| use super::*; | |
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] | |
| async fn failed_initial_start_keeps_independent_initializers_and_original_cleanup_custody() { | |
| tokio::time::timeout(Duration::from_secs(20), async { | |
| let (directory, namespaces, store) = store().await; | |
| let provider = native_package(); | |
| let consumer = consumer(); | |
| let mut desired = input(&provider, &consumer); | |
| desired | |
| .instances | |
| .get_mut("provider") | |
| .unwrap() | |
| .configuration | |
| .values = BTreeMap::from([("fail".into(), json!(true))]); | |
| desired | |
| .instances | |
| .insert("healthy".into(), node(&provider, 4, true)); | |
| let mut reader = node(&consumer, 5, false); | |
| reader.bindings.insert( | |
| "echo".into(), | |
| vec![Target::Instance { | |
| path: vec!["healthy".into()], | |
| export: "echo".into(), | |
| }], | |
| ); | |
| desired.instances.insert("reader".into(), reader); | |
| let original = store | |
| .commit_application( | |
| Uuid::new_v4(), | |
| 0, | |
| desired.clone(), | |
| vec![], | |
| vec![provider.clone(), consumer.clone()], | |
| ) | |
| .await | |
| .unwrap(); | |
| let healthy_started = Arc::new(Semaphore::new(0)); | |
| let healthy_release = Arc::new(Semaphore::new(0)); | |
| let close_started = Arc::new(Semaphore::new(0)); | |
| let close_release = Arc::new(Semaphore::new(0)); | |
| let attempts = Arc::new(AtomicUsize::new(0)); | |
| let calls = Arc::new(AtomicUsize::new(0)); | |
| let lifetime = Arc::new(std::sync::Mutex::new(std::sync::Weak::<()>::new())); | |
| let mut native = NativeRegistry::default(); | |
| let started = healthy_started.clone(); | |
| let release = healthy_release.clone(); | |
| let closing = close_started.clone(); | |
| let closed = close_release.clone(); | |
| let initialized = attempts.clone(); | |
| let invoked = calls.clone(); | |
| let observed = lifetime.clone(); | |
| native | |
| .insert( | |
| "native.echo".into(), | |
| Arc::new(move |context, configuration| { | |
| let started = started.clone(); | |
| let release = release.clone(); | |
| let closing = closing.clone(); | |
| let closed = closed.clone(); | |
| let initialized = initialized.clone(); | |
| let invoked = invoked.clone(); | |
| let observed = observed.clone(); | |
| Box::pin(async move { | |
| if context.activation().instance() == &id(4) { | |
| started.add_permits(1); | |
| permit(&release).await; | |
| } else { | |
| initialized.fetch_add(1, Ordering::SeqCst); | |
| } | |
| if configuration.get("fail") == Some(&json!(true)) { | |
| let resource = Arc::new(()); | |
| *observed.lock().unwrap() = Arc::downgrade(&resource); | |
| NativeActivation { | |
| exports: Err("original initializer rejected".into()), | |
| owner: native_owner(move || { | |
| let closing = closing.clone(); | |
| let closed = closed.clone(); | |
| let resource = resource.clone(); | |
| Box::pin(async move { | |
| closing.add_permits(1); | |
| permit(&closed).await; | |
| drop(resource); | |
| Ok(()) | |
| }) | |
| }), | |
| } | |
| } else { | |
| NativeActivation { | |
| exports: Ok(BTreeMap::from([( | |
| "echo".into(), | |
| Published { | |
| contract: contract(MethodMode::Query), | |
| value: Arc::new(Echo { calls: invoked }) | |
| as Arc<dyn Handler>, | |
| }, | |
| )])), | |
| owner: native_owner(|| Box::pin(async { Ok(()) })), | |
| } | |
| } | |
| }) | |
| }), | |
| ) | |
| .unwrap(); | |
| let issuer = Issuer::default(); | |
| let app = Application::start_from_store( | |
| store.clone(), | |
| original.deployment, | |
| native, | |
| issuer.clone(), | |
| Arc::new(Grant(issuer.clone())), | |
| Some(maka_host_js::factory(Limits::default())), | |
| ) | |
| .await | |
| .unwrap(); | |
| let client = app.client(); | |
| permit(&healthy_started).await; | |
| assert!( | |
| app.ready() | |
| .await | |
| .unwrap_err() | |
| .contains("original initializer rejected") | |
| ); | |
| permit(&close_started).await; | |
| assert!( | |
| !client.snapshot().closing, | |
| "an independent startup failure must not stop the Host" | |
| ); | |
| assert_eq!(client.snapshot().effective, None); | |
| assert_eq!(client.snapshot().desired, Some(original.clone())); | |
| assert!(lifetime.lock().unwrap().upgrade().is_some()); | |
| let failed = client.activation(&id(1)).unwrap(); | |
| for id in [id(1), id(2), id(3)] { | |
| assert!(client.ready_instance(&id).await.is_err()); | |
| } | |
| use maka_host_runtime::application::CapturedImport; | |
| match client.capture_import(id(2), "echo".into()).await.unwrap() { | |
| CapturedImport::Unavailable { | |
| provider, | |
| export, | |
| contract: declared, | |
| } => { | |
| assert_eq!(provider, id(1)); | |
| assert_eq!(export, "echo"); | |
| assert_eq!(declared, contract(MethodMode::Query)); | |
| } | |
| CapturedImport::Ready(_) => panic!("failed dependency cannot mint a binding"), | |
| } | |
| assert!(client.capture_import(id(999), "echo".into()).await.is_err()); | |
| assert!( | |
| client | |
| .capture_import(id(2), "missing".into()) | |
| .await | |
| .is_err() | |
| ); | |
| healthy_release.add_permits(1); | |
| let reader = client.ready_instance(&id(5)).await.unwrap(); | |
| let healthy = client.activation(&id(4)).unwrap(); | |
| let binding = client | |
| .bind(reader.clone(), "echo".into()) | |
| .await | |
| .unwrap() | |
| .remove(0); | |
| assert!( | |
| client | |
| .start( | |
| binding.clone(), | |
| "echo".into(), | |
| json!("denied"), | |
| scope(&issuer, false), | |
| Attachments::new().into() | |
| ) | |
| .await | |
| .is_err() | |
| ); | |
| assert_eq!( | |
| client | |
| .start( | |
| binding.clone(), | |
| "echo".into(), | |
| json!("healthy"), | |
| scope(&issuer, true), | |
| Attachments::new().into() | |
| ) | |
| .await | |
| .unwrap() | |
| .result() | |
| .await | |
| .unwrap(), | |
| json!("healthy") | |
| ); | |
| assert_eq!(calls.load(Ordering::SeqCst), 1); | |
| assert!(client.activation(&id(2)).is_err()); | |
| assert!(client.activation(&id(3)).is_err()); | |
| app.adopt_committed(original.clone()).await.unwrap(); | |
| assert_eq!(attempts.load(Ordering::SeqCst), 1); | |
| assert!(app.ready().await.is_err()); | |
| assert_eq!( | |
| RootOwner::open(&directory.path().join("root"), &namespaces) | |
| .err() | |
| .unwrap() | |
| .kind(), | |
| std::io::ErrorKind::WouldBlock | |
| ); | |
| desired | |
| .instances | |
| .get_mut("provider") | |
| .unwrap() | |
| .configuration | |
| .values | |
| .clear(); | |
| let repaired = store | |
| .commit_application( | |
| original.deployment, | |
| 1, | |
| desired, | |
| vec![], | |
| vec![provider, consumer], | |
| ) | |
| .await | |
| .unwrap(); | |
| app.adopt_committed(repaired.clone()).await.unwrap(); | |
| { | |
| let ready = app.ready(); | |
| tokio::pin!(ready); | |
| assert!(futures_util::poll!(ready.as_mut()).is_pending()); | |
| assert_eq!(client.activation(&id(1)).unwrap(), failed); | |
| assert_eq!(client.activation(&id(4)).unwrap(), healthy); | |
| assert_eq!(client.activation(&id(5)).unwrap(), reader); | |
| assert_eq!(attempts.load(Ordering::SeqCst), 1); | |
| assert_eq!( | |
| client | |
| .start( | |
| binding, | |
| "echo".into(), | |
| json!("during repair"), | |
| scope(&issuer, true), | |
| Attachments::new().into() | |
| ) | |
| .await | |
| .unwrap() | |
| .result() | |
| .await | |
| .unwrap(), | |
| json!("during repair") | |
| ); | |
| assert!(lifetime.lock().unwrap().upgrade().is_some()); | |
| close_release.add_permits(1); | |
| ready.await.unwrap(); | |
| } | |
| assert_ne!(client.activation(&id(1)).unwrap(), failed); | |
| assert!(matches!( | |
| client.capture_import(id(2), "echo".into()).await.unwrap(), | |
| CapturedImport::Ready(_) | |
| )); | |
| assert_eq!(attempts.load(Ordering::SeqCst), 2); | |
| assert_eq!(client.activation(&id(4)).unwrap(), healthy); | |
| assert_eq!(client.snapshot().effective, Some(repaired)); | |
| assert!(client.snapshot().initialization_failures.is_empty()); | |
| assert!(client.ready_instance(&id(999)).await.is_err()); | |
| assert!(lifetime.lock().unwrap().upgrade().is_none()); | |
| app.shutdown().await.unwrap(); | |
| assert!(client.ready_instance(&id(4)).await.is_err()); | |
| drop((app, client)); | |
| close(store).await; | |
| drop(RootOwner::open(&directory.path().join("root"), &namespaces).unwrap()); | |
| }) | |
| .await | |
| .unwrap(); | |
| } | |
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] | |
| async fn startup_shared_vm_failure_leaves_independent_domain_and_pending_native_owner_available() { | |
| tokio::time::timeout(Duration::from_secs(20), async { | |
| let provider = package( | |
| "startup.shared", | |
| entry( | |
| true, | |
| Implementation::JavaScript { | |
| module: "host.mjs".into(), | |
| realm: Realm::Shared, | |
| }, | |
| MethodMode::Query, | |
| ), | |
| Some(format!( | |
| r#" | |
| export const service = {{ echo(input) {{ return input; }} }}; | |
| export function activate(config) {{ | |
| if(config.crash) while(true) {{}} | |
| return {{echo:{{contract:{},service:['service']}}}}; | |
| }} | |
| "#, | |
| serde_json::to_string(&contract(MethodMode::Query)).unwrap() | |
| )), | |
| ); | |
| let consumer = consumer(); | |
| let separate = package( | |
| "startup.dedicated", | |
| entry( | |
| false, | |
| Implementation::JavaScript { | |
| module: "host.mjs".into(), | |
| realm: Realm::Dedicated, | |
| }, | |
| MethodMode::Query, | |
| ), | |
| Some("export function activate() { return {}; }".into()), | |
| ); | |
| let native = native_package(); | |
| let mut desired = input(&provider, &consumer); | |
| desired | |
| .instances | |
| .get_mut("provider") | |
| .unwrap() | |
| .configuration | |
| .values | |
| .insert("crash".into(), json!(true)); | |
| desired | |
| .instances | |
| .insert("other".into(), node(&provider, 4, true)); | |
| desired | |
| .instances | |
| .get_mut("b") | |
| .unwrap() | |
| .bindings | |
| .get_mut("echo") | |
| .unwrap()[0] = Target::Instance { | |
| path: vec!["other".into()], | |
| export: "echo".into(), | |
| }; | |
| desired | |
| .instances | |
| .insert("healthy".into(), node(&native, 6, true)); | |
| let mut reader = node(&separate, 7, false); | |
| reader.bindings.get_mut("echo").unwrap()[0] = Target::Instance { | |
| path: vec!["healthy".into()], | |
| export: "echo".into(), | |
| }; | |
| desired.instances.insert("reader".into(), reader); | |
| let began = Arc::new(Semaphore::new(0)); | |
| let released = Arc::new(Semaphore::new(0)); | |
| let calls = Arc::new(AtomicUsize::new(0)); | |
| let mut registry = NativeRegistry::default(); | |
| let started = began.clone(); | |
| let release = released.clone(); | |
| let invoked = calls.clone(); | |
| registry | |
| .insert( | |
| "native.echo".into(), | |
| Arc::new(move |_, _| { | |
| let started = started.clone(); | |
| let release = release.clone(); | |
| let invoked = invoked.clone(); | |
| Box::pin(async move { | |
| started.add_permits(1); | |
| permit(&release).await; | |
| NativeActivation { | |
| exports: Ok(BTreeMap::from([( | |
| "echo".into(), | |
| Published { | |
| contract: contract(MethodMode::Query), | |
| value: Arc::new(Echo { calls: invoked }) as Arc<dyn Handler>, | |
| }, | |
| )])), | |
| owner: native_owner(|| Box::pin(async { Ok(()) })), | |
| } | |
| }) | |
| }), | |
| ) | |
| .unwrap(); | |
| let issuer = Issuer::default(); | |
| let app = Application::start( | |
| desired, | |
| vec![provider, consumer, separate, native], | |
| registry, | |
| issuer.clone(), | |
| Arc::new(Grant(issuer.clone())), | |
| Some(maka_host_js::factory(Limits { | |
| synchronous_slice: Duration::from_millis(200), | |
| ..Limits::default() | |
| })), | |
| ) | |
| .unwrap(); | |
| let client = app.client(); | |
| permit(&began).await; | |
| assert!(app.ready().await.is_err()); | |
| assert!(!client.snapshot().closing); | |
| while ![id(1), id(4)] | |
| .iter() | |
| .all(|id| client.snapshot().initialization_failures.contains(id)) | |
| { | |
| tokio::task::yield_now().await; | |
| } | |
| for id in [id(1), id(2), id(3), id(4)] { | |
| assert!(client.ready_instance(&id).await.is_err()); | |
| } | |
| released.add_permits(1); | |
| let reader = client.ready_instance(&id(7)).await.unwrap(); | |
| let binding = client.bind(reader, "echo".into()).await.unwrap().remove(0); | |
| assert_eq!( | |
| client | |
| .start( | |
| binding, | |
| "echo".into(), | |
| json!("independent"), | |
| scope(&issuer, true), | |
| Attachments::new().into() | |
| ) | |
| .await | |
| .unwrap() | |
| .result() | |
| .await | |
| .unwrap(), | |
| json!("independent") | |
| ); | |
| assert_eq!(calls.load(Ordering::SeqCst), 1); | |
| assert!(app.ready().await.is_err()); | |
| app.shutdown().await.unwrap(); | |
| }) | |
| .await | |
| .unwrap(); | |
| } |