blob: 4919b344efd8b2bd1813167c874c8b86b3299b76 [file]
/*
* 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::*;
use maka_event_log::{
EventLog,
root::{RootNamespaces, RootOwner},
};
#[tokio::test]
async fn stored_source_and_lease_survive_abandoned_query_resources_and_native_close() {
tokio::time::timeout(Duration::from_secs(20), async {
let dir = tempfile::tempdir().unwrap();
let namespaces = RootNamespaces {
ownership: dir.path().join("owners"),
control: dir.path().join("control"),
};
let root = dir.path().join("root");
let owner = Arc::new(RootOwner::create(&root, &namespaces).unwrap());
let store = Arc::new(EventLog::for_root(owner.clone()).await.unwrap());
drop(owner);
let provider = native_package();
let consumer = consumer();
let deployment = Uuid::new_v4();
let source = store
.commit_application(
deployment,
0,
input(&provider, &consumer),
vec![],
vec![provider, consumer],
)
.await
.unwrap();
let started = Arc::new(Semaphore::new(0));
let release = Arc::new(Semaphore::new(0));
let resource_started = Arc::new(Semaphore::new(0));
let resource_release = Arc::new(Semaphore::new(0));
let close_started = Arc::new(Semaphore::new(0));
let close_release = Arc::new(Semaphore::new(0));
let query: Arc<dyn Handler> = Arc::new(Held {
started: started.clone(),
release: release.clone(),
resource_started: resource_started.clone(),
resource_release: resource_release.clone(),
});
let mut native = NativeRegistry::default();
let closing = close_started.clone();
let closed = close_release.clone();
native
.insert(
"native.echo".into(),
Arc::new(move |_, _| {
let query = query.clone();
let closing = closing.clone();
let closed = closed.clone();
Box::pin(async move {
NativeActivation {
exports: Ok(BTreeMap::from([(
"echo".into(),
Published {
contract: contract(MethodMode::Query),
value: query,
},
)])),
owner: native_owner(move || {
let closing = closing.clone();
let closed = closed.clone();
Box::pin(async move {
closing.add_permits(1);
permit(&closed).await;
Ok(())
})
}),
}
})
}),
)
.unwrap();
let issuer = Issuer::default();
let app = Application::start_from_store(
store.clone(),
deployment,
native,
issuer.clone(),
Arc::new(Grant(issuer.clone())),
Some(maka_host_js::factory(Limits::default())),
)
.await
.unwrap();
assert_eq!(app.source(), Some(&source));
app.ready().await.unwrap();
let client = app.client();
let binding = client
.bind(client.activation(&id(2)).unwrap(), "echo".into())
.await
.unwrap()
.remove(0);
let observer = client
.start(
binding,
"echo".into(),
json!("held"),
scope(&issuer, true),
Attachments::new().into(),
)
.await
.unwrap();
permit(&started).await;
// A newer desired source can collect DB package bytes. Already accepted
// execution still owns its original bytes and storage domain.
let empty = Input {
environment: Environment {
location: ExecutionLocation::Host,
presentation: None,
},
instances: BTreeMap::new(),
recovery: None,
launch: None,
};
store
.commit_application(deployment, 1, empty, vec![], vec![])
.await
.unwrap();
drop(observer);
drop(app);
drop(client);
drop(store);
assert_eq!(
RootOwner::open(&root, &namespaces).err().unwrap().kind(),
std::io::ErrorKind::WouldBlock
);
release.add_permits(1);
permit(&resource_started).await;
assert_eq!(
RootOwner::open(&root, &namespaces).err().unwrap().kind(),
std::io::ErrorKind::WouldBlock
);
resource_release.add_permits(1);
permit(&close_started).await;
assert_eq!(
RootOwner::open(&root, &namespaces).err().unwrap().kind(),
std::io::ErrorKind::WouldBlock
);
close_release.add_permits(1);
let reopened = loop {
match RootOwner::open(&root, &namespaces) {
Ok(owner) => break owner,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
tokio::task::yield_now().await
}
Err(error) => panic!("{error}"),
}
};
let store = EventLog::for_root(Arc::new(reopened)).await.unwrap();
let loaded = store.load_application(deployment).await.unwrap().unwrap();
let (loaded_source, _, _, _) = loaded.into_parts();
assert_eq!(loaded_source.reference.state_domain, source.state_domain);
assert_eq!(loaded_source.reference.revision, 2);
store.close().await.unwrap();
})
.await
.unwrap();
}
#[tokio::test]
async fn persisted_preflight_error_runs_no_native_effect_and_releases_storage() {
let dir = tempfile::tempdir().unwrap();
let namespaces = RootNamespaces {
ownership: dir.path().join("owners"),
control: dir.path().join("control"),
};
let root = dir.path().join("root");
let owner = Arc::new(RootOwner::create(&root, &namespaces).unwrap());
let store = Arc::new(EventLog::for_root(owner).await.unwrap());
let provider = native_package();
let consumer = consumer();
let deployment = Uuid::new_v4();
store
.commit_application(
deployment,
0,
input(&provider, &consumer),
vec![],
vec![provider, consumer],
)
.await
.unwrap();
let issuer = Issuer::default();
let result = Application::start_from_store(
store.clone(),
deployment,
NativeRegistry::default(),
issuer.clone(),
Arc::new(Grant(issuer)),
Some(maka_host_js::factory(Limits::default())),
)
.await;
assert!(
result
.err()
.unwrap()
.contains("native implementation unavailable")
);
Arc::try_unwrap(store).ok().unwrap().close().await.unwrap();
assert!(RootOwner::open(&root, &namespaces).is_ok());
}
#[tokio::test]
async fn stored_failed_native_cleanup_keeps_the_actual_state_root_lease() {
let dir = tempfile::tempdir().unwrap();
let namespaces = RootNamespaces {
ownership: dir.path().join("owners"),
control: dir.path().join("control"),
};
let root = dir.path().join("root");
let owner = Arc::new(RootOwner::create(&root, &namespaces).unwrap());
let store = Arc::new(EventLog::for_root(owner).await.unwrap());
let provider = native_package();
let consumer = consumer();
let deployment = Uuid::new_v4();
store
.commit_application(
deployment,
0,
input(&provider, &consumer),
vec![],
vec![provider, consumer],
)
.await
.unwrap();
let mut native = NativeRegistry::default();
native
.insert(
"native.echo".into(),
Arc::new(|_, _| {
Box::pin(async {
NativeActivation {
exports: Err("exports rejected".into()),
owner: native_owner(|| {
Box::pin(async { Err("native cleanup unconfirmed".into()) })
}),
}
})
}),
)
.unwrap();
let issuer = Issuer::default();
let app = Application::start_from_store(
store.clone(),
deployment,
native,
issuer.clone(),
Arc::new(Grant(issuer)),
Some(maka_host_js::factory(Limits::default())),
)
.await
.unwrap();
assert!(app.ready().await.is_err());
assert!(
app.shutdown()
.await
.unwrap_err()
.contains("cleanup unconfirmed")
);
drop(app);
drop(store);
tokio::task::yield_now().await;
assert_eq!(
RootOwner::open(&root, &namespaces).err().unwrap().kind(),
std::io::ErrorKind::WouldBlock
);
}