mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 23:20:18 +00:00
Improve scope error handling (#251)
This commit is contained in:
parent
7e0c87d72a
commit
3f510e1581
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -13,7 +13,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "air"
|
name = "air"
|
||||||
version = "0.23.0"
|
version = "0.24.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"air-execution-info-collector",
|
"air-execution-info-collector",
|
||||||
"air-interpreter-data",
|
"air-interpreter-data",
|
||||||
@ -52,7 +52,7 @@ version = "0.1.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "air-interpreter"
|
name = "air-interpreter"
|
||||||
version = "0.23.0"
|
version = "0.24.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"air",
|
"air",
|
||||||
"air-log-targets",
|
"air-log-targets",
|
||||||
|
@ -43,8 +43,9 @@ impl<'i> super::ExecutableInstruction<'i> for Next<'i> {
|
|||||||
maybe_meet_iteration_start(self, fold_state, trace_ctx)?;
|
maybe_meet_iteration_start(self, fold_state, trace_ctx)?;
|
||||||
exec_ctx.scalars.meet_next_before();
|
exec_ctx.scalars.meet_next_before();
|
||||||
|
|
||||||
next_instr.execute(exec_ctx, trace_ctx)?;
|
let result = next_instr.execute(exec_ctx, trace_ctx);
|
||||||
exec_ctx.scalars.meet_next_after();
|
exec_ctx.scalars.meet_next_after();
|
||||||
|
result?;
|
||||||
|
|
||||||
// get the same fold state again because of borrow checker
|
// get the same fold state again because of borrow checker
|
||||||
let fold_state = exec_ctx.scalars.get_iterable_mut(iterator_name)?;
|
let fold_state = exec_ctx.scalars.get_iterable_mut(iterator_name)?;
|
||||||
|
@ -26,6 +26,9 @@ use std::collections::HashMap;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Depth of a global scope.
|
||||||
|
const GLOBAL_DEPTH: usize = 0;
|
||||||
|
|
||||||
// TODO: move this code snippet to documentation when it's ready
|
// TODO: move this code snippet to documentation when it's ready
|
||||||
|
|
||||||
/// There are two scopes for variable scalars in AIR: global and local. A local scope
|
/// There are two scopes for variable scalars in AIR: global and local. A local scope
|
||||||
@ -65,7 +68,6 @@ use std::rc::Rc;
|
|||||||
///
|
///
|
||||||
/// Although there could be only one iterable value for a fold block, because of CRDT rules.
|
/// Although there could be only one iterable value for a fold block, because of CRDT rules.
|
||||||
/// This struct is intended to provide abilities to work with scalars as it was described.
|
/// This struct is intended to provide abilities to work with scalars as it was described.
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct Scalars<'i> {
|
pub(crate) struct Scalars<'i> {
|
||||||
// TODO: use Rc<String> to avoid copying
|
// TODO: use Rc<String> to avoid copying
|
||||||
/// Terminology used here (mainly to resolve concerns re difference between scalars and values):
|
/// Terminology used here (mainly to resolve concerns re difference between scalars and values):
|
||||||
@ -91,7 +93,7 @@ pub(crate) struct Scalars<'i> {
|
|||||||
/// They are needed for careful isolation of scopes produced by iterations in fold blocks,
|
/// They are needed for careful isolation of scopes produced by iterations in fold blocks,
|
||||||
/// precisely to limit access of non iterable variables defined on one depths to ones
|
/// precisely to limit access of non iterable variables defined on one depths to ones
|
||||||
/// defined on another.
|
/// defined on another.
|
||||||
pub(crate) invalidated_depths: HashSet<usize>,
|
pub(crate) allowed_depths: HashSet<usize>,
|
||||||
|
|
||||||
pub(crate) iterable_variables: HashMap<String, FoldState<'i>>,
|
pub(crate) iterable_variables: HashMap<String, FoldState<'i>>,
|
||||||
|
|
||||||
@ -120,6 +122,17 @@ impl SparseCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'i> Scalars<'i> {
|
impl<'i> Scalars<'i> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let allowed_depths = maplit::hashset! { GLOBAL_DEPTH };
|
||||||
|
|
||||||
|
Self {
|
||||||
|
non_iterable_variables: HashMap::new(),
|
||||||
|
allowed_depths,
|
||||||
|
iterable_variables: HashMap::new(),
|
||||||
|
current_depth: GLOBAL_DEPTH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if there was a previous value for the provided key on the same
|
/// Returns true if there was a previous value for the provided key on the same
|
||||||
/// fold block.
|
/// fold block.
|
||||||
pub(crate) fn set_value(&mut self, name: impl Into<String>, value: ValueAggregate) -> ExecutionResult<bool> {
|
pub(crate) fn set_value(&mut self, name: impl Into<String>, value: ValueAggregate) -> ExecutionResult<bool> {
|
||||||
@ -180,9 +193,9 @@ impl<'i> Scalars<'i> {
|
|||||||
.get(name)
|
.get(name)
|
||||||
.and_then(|values| {
|
.and_then(|values| {
|
||||||
let last_cell = values.last();
|
let last_cell = values.last();
|
||||||
let value_not_invalidated = !self.invalidated_depths.contains(&last_cell.depth);
|
let depth_allowed = self.allowed_depths.contains(&last_cell.depth);
|
||||||
|
|
||||||
if value_not_invalidated {
|
if depth_allowed {
|
||||||
Some(last_cell.value.as_ref())
|
Some(last_cell.value.as_ref())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -212,22 +225,27 @@ impl<'i> Scalars<'i> {
|
|||||||
|
|
||||||
pub(crate) fn meet_fold_start(&mut self) {
|
pub(crate) fn meet_fold_start(&mut self) {
|
||||||
self.current_depth += 1;
|
self.current_depth += 1;
|
||||||
|
self.allowed_depths.insert(self.current_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// meet next before recursion
|
// meet next before recursion
|
||||||
pub(crate) fn meet_next_before(&mut self) {
|
pub(crate) fn meet_next_before(&mut self) {
|
||||||
self.invalidated_depths.insert(self.current_depth);
|
self.allowed_depths.remove(&self.current_depth);
|
||||||
self.current_depth += 1;
|
self.current_depth += 1;
|
||||||
|
self.allowed_depths.insert(self.current_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// meet next after recursion
|
// meet next after recursion
|
||||||
pub(crate) fn meet_next_after(&mut self) {
|
pub(crate) fn meet_next_after(&mut self) {
|
||||||
|
self.allowed_depths.remove(&self.current_depth);
|
||||||
self.current_depth -= 1;
|
self.current_depth -= 1;
|
||||||
self.invalidated_depths.remove(&self.current_depth);
|
self.allowed_depths.insert(self.current_depth);
|
||||||
|
|
||||||
self.cleanup_obsolete_values();
|
self.cleanup_obsolete_values();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn meet_fold_end(&mut self) {
|
pub(crate) fn meet_fold_end(&mut self) {
|
||||||
|
self.allowed_depths.remove(&self.current_depth);
|
||||||
self.current_depth -= 1;
|
self.current_depth -= 1;
|
||||||
self.cleanup_obsolete_values();
|
self.cleanup_obsolete_values();
|
||||||
}
|
}
|
||||||
@ -314,8 +332,8 @@ impl<'i> Scalars<'i> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_global_value(current_scope_depth: usize) -> bool {
|
fn is_global_value(value_depth: usize) -> bool {
|
||||||
current_scope_depth == 0
|
value_depth == GLOBAL_DEPTH
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_value_obsolete(value_depth: usize, current_scope_depth: usize) -> bool {
|
fn is_value_obsolete(value_depth: usize, current_scope_depth: usize) -> bool {
|
||||||
@ -324,6 +342,12 @@ fn is_value_obsolete(value_depth: usize, current_scope_depth: usize) -> bool {
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
impl Default for Scalars<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Scalars::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'i> fmt::Display for Scalars<'i> {
|
impl<'i> fmt::Display for Scalars<'i> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
writeln!(f, "fold_block_id: {}", self.current_depth)?;
|
writeln!(f, "fold_block_id: {}", self.current_depth)?;
|
||||||
|
@ -15,3 +15,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
mod scalars_scope;
|
mod scalars_scope;
|
||||||
|
mod scopes_behaviour_with_errors;
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Fluence Labs Limited
|
||||||
|
*
|
||||||
|
* Licensed 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 air_test_utils::prelude::*;
|
||||||
|
|
||||||
|
use fstrings::f;
|
||||||
|
use fstrings::format_args_f;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_that_scalar_is_visible_only_inside_fold_block() {
|
||||||
|
let variable_setter_peer_id = "variable_setter_peer_id";
|
||||||
|
let mut variable_setter_vm = create_avm(set_variable_call_service(json!([1, 2, 3])), variable_setter_peer_id);
|
||||||
|
|
||||||
|
let fallible_peer_id = "fallible_peer_id";
|
||||||
|
let mut fallible_peer_vm = create_avm(fallible_call_service("fail"), fallible_peer_id);
|
||||||
|
|
||||||
|
let variable_receiver_peer_id = "variable_receiver_peer_id";
|
||||||
|
let mut variable_receiver_peer_vm = create_avm(echo_call_service(), variable_receiver_peer_id);
|
||||||
|
|
||||||
|
let script = f!(r#"
|
||||||
|
(seq
|
||||||
|
(call "{variable_setter_peer_id}" ("" "") ["iterable_1"] iterable_1)
|
||||||
|
(xor
|
||||||
|
(fold iterable_1 iterator_1
|
||||||
|
(seq
|
||||||
|
(call "{variable_setter_peer_id}" ("" "") [] scalar)
|
||||||
|
(seq
|
||||||
|
(next iterator_1)
|
||||||
|
(call "{fallible_peer_id}" ("fail" "") [] scalar)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call "{variable_receiver_peer_id}" ("" "") [scalar])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
|
||||||
|
let result = checked_call_vm!(variable_setter_vm, <_>::default(), &script, "", "");
|
||||||
|
let result = checked_call_vm!(fallible_peer_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let result = checked_call_vm!(variable_receiver_peer_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let actual_trace = trace_from_result(&result);
|
||||||
|
|
||||||
|
let expected_trace = vec![
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::service_failed(1, "failed result from fallible_call_service"),
|
||||||
|
executed_state::request_sent_by(fallible_peer_id),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(actual_trace, expected_trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scopes_check_that_scalar_not_overwritten_by_fold_end() {
|
||||||
|
let variable_setter_peer_id = "variable_setter_peer_id";
|
||||||
|
let mut variable_setter_vm = create_avm(set_variable_call_service(json!([1, 2, 3])), variable_setter_peer_id);
|
||||||
|
|
||||||
|
let fallible_peer_id = "fallible_peer_id";
|
||||||
|
let mut fallible_peer_vm = create_avm(fallible_call_service("fail"), fallible_peer_id);
|
||||||
|
|
||||||
|
let variable_receiver_peer_id = "variable_receiver_peer_id";
|
||||||
|
let mut variable_receiver_peer_vm = create_avm(echo_call_service(), variable_receiver_peer_id);
|
||||||
|
|
||||||
|
let script = f!(r#"
|
||||||
|
(seq
|
||||||
|
(seq
|
||||||
|
(call "{variable_setter_peer_id}" ("" "") ["iterable_1"] iterable_1)
|
||||||
|
(call "{variable_setter_peer_id}" ("" "") ["scalar"] scalar)
|
||||||
|
)
|
||||||
|
(xor
|
||||||
|
(fold iterable_1 iterator_1
|
||||||
|
(seq
|
||||||
|
(call "{variable_setter_peer_id}" ("" "") [] scalar)
|
||||||
|
(seq
|
||||||
|
(next iterator_1)
|
||||||
|
(call "{fallible_peer_id}" ("fail" "") [] scalar)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call "{variable_receiver_peer_id}" ("" "") [scalar])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
|
||||||
|
let result = checked_call_vm!(variable_setter_vm, <_>::default(), &script, "", "");
|
||||||
|
let result = checked_call_vm!(fallible_peer_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let result = checked_call_vm!(variable_receiver_peer_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let actual_trace = trace_from_result(&result);
|
||||||
|
|
||||||
|
let expected_trace = vec![
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
executed_state::service_failed(1, "failed result from fallible_call_service"),
|
||||||
|
executed_state::scalar(json!([1, 2, 3])),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(actual_trace, expected_trace);
|
||||||
|
}
|
@ -224,9 +224,9 @@ fn new_in_fold_with_ap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_with_errors() {
|
fn new_with_streams_with_errors() {
|
||||||
let faillible_peer_id = "failible_peer_id";
|
let fallible_peer_id = "fallible_peer_id";
|
||||||
let mut faillible_vm = create_avm(fallible_call_service("service_id_1"), faillible_peer_id);
|
let mut fallible_vm = create_avm(fallible_call_service("service_id_1"), fallible_peer_id);
|
||||||
|
|
||||||
let local_peer_id = "local_peer_id";
|
let local_peer_id = "local_peer_id";
|
||||||
let mut vm = create_avm(echo_call_service(), local_peer_id);
|
let mut vm = create_avm(echo_call_service(), local_peer_id);
|
||||||
@ -239,7 +239,7 @@ fn new_with_errors() {
|
|||||||
(new $restricted_stream_2
|
(new $restricted_stream_2
|
||||||
(seq
|
(seq
|
||||||
(call "{local_peer_id}" ("" "") [2] $restricted_stream_2) ;; should have generation 1 in a data
|
(call "{local_peer_id}" ("" "") [2] $restricted_stream_2) ;; should have generation 1 in a data
|
||||||
(call "{faillible_peer_id}" ("service_id_1" "local_fn_name") [] result)
|
(call "{fallible_peer_id}" ("service_id_1" "local_fn_name") [] result)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(call "{local_peer_id}" ("" "") [2] restricted_stream_1) ;; should have generation 0 in a data
|
(call "{local_peer_id}" ("" "") [2] restricted_stream_1) ;; should have generation 0 in a data
|
||||||
@ -248,7 +248,7 @@ fn new_with_errors() {
|
|||||||
)"#);
|
)"#);
|
||||||
|
|
||||||
let result = checked_call_vm!(vm, <_>::default(), &script, "", "");
|
let result = checked_call_vm!(vm, <_>::default(), &script, "", "");
|
||||||
let result = call_vm!(faillible_vm, <_>::default(), script, "", result.data);
|
let result = call_vm!(fallible_vm, <_>::default(), script, "", result.data);
|
||||||
|
|
||||||
let actual_trace = trace_from_result(&result);
|
let actual_trace = trace_from_result(&result);
|
||||||
let expected_trace = vec![
|
let expected_trace = vec![
|
||||||
@ -278,6 +278,62 @@ fn new_with_errors() {
|
|||||||
assert_eq!(actual_global_streams, expected_global_streams);
|
assert_eq!(actual_global_streams, expected_global_streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_with_scalars_with_errors() {
|
||||||
|
let set_variable_peer_id = "set_variable_peer_id";
|
||||||
|
let variables_mapping = maplit::hashmap! {
|
||||||
|
"global".to_string() => json!(1),
|
||||||
|
"scoped".to_string() => json!(2),
|
||||||
|
};
|
||||||
|
let mut set_variable_vm = create_avm(
|
||||||
|
set_variables_call_service(variables_mapping, VariableOptionSource::Argument(0)),
|
||||||
|
set_variable_peer_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
let variable_receiver_peer_id = "variable_receiver_peer_id";
|
||||||
|
let mut variable_receiver_vm = create_avm(echo_call_service(), variable_receiver_peer_id);
|
||||||
|
|
||||||
|
let fallible_peer_id = "fallible_peer_id";
|
||||||
|
let fallible_service_id = "fallible_service_id";
|
||||||
|
let mut fallible_peer_vm = create_avm(fallible_call_service(fallible_service_id), fallible_peer_id);
|
||||||
|
|
||||||
|
let script = f!(r#"
|
||||||
|
(seq
|
||||||
|
(seq
|
||||||
|
(call "{set_variable_peer_id}" ("" "") ["global"] scalar)
|
||||||
|
(xor
|
||||||
|
(new scalar
|
||||||
|
(seq
|
||||||
|
(call "{set_variable_peer_id}" ("" "") ["scoped"] scalar)
|
||||||
|
(seq
|
||||||
|
(call "{variable_receiver_peer_id}" ("" "") [scalar])
|
||||||
|
(call "{fallible_peer_id}" ("{fallible_service_id}" "") [])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call "{variable_receiver_peer_id}" ("" "") [scalar])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call "{variable_receiver_peer_id}" ("" "") [scalar])
|
||||||
|
)"#);
|
||||||
|
|
||||||
|
let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", "");
|
||||||
|
let result = checked_call_vm!(variable_receiver_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let result = checked_call_vm!(fallible_peer_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let result = checked_call_vm!(variable_receiver_vm, <_>::default(), &script, "", result.data);
|
||||||
|
let actual_trace = trace_from_result(&result);
|
||||||
|
|
||||||
|
let expected_trace = vec![
|
||||||
|
executed_state::scalar_number(1),
|
||||||
|
executed_state::scalar_number(2),
|
||||||
|
executed_state::scalar_number(2),
|
||||||
|
executed_state::service_failed(1, r#"failed result from fallible_call_service"#),
|
||||||
|
executed_state::scalar_number(1),
|
||||||
|
executed_state::scalar_number(1),
|
||||||
|
];
|
||||||
|
assert_eq!(actual_trace, expected_trace);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_with_global_scalars() {
|
fn new_with_global_scalars() {
|
||||||
let set_variable_peer_id = "set_variable_peer_id";
|
let set_variable_peer_id = "set_variable_peer_id";
|
||||||
|
Loading…
Reference in New Issue
Block a user