Skip to content

Commit ddc6396

Browse files
crepererumCopilot
andcommitted
feat: epoch-based interruption
Closes #15. Co-authored-by: Copilot <[email protected]>
1 parent 6f09233 commit ddc6396

File tree

15 files changed

+728
-88
lines changed

15 files changed

+728
-88
lines changed

guests/evil/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod env;
1212
mod fs;
1313
mod root;
1414
mod runtime;
15+
mod spin;
1516

1617
/// Method that returns the root filesystem.
1718
type RootFn = Box<dyn Fn() -> Option<Vec<u8>>>;
@@ -60,6 +61,34 @@ impl Evil {
6061
root: Box::new(common::root_empty),
6162
udfs: Box::new(runtime::udfs),
6263
},
64+
"spin::root" => Self {
65+
root: Box::new(spin::root::root),
66+
udfs: Box::new(common::udfs_empty),
67+
},
68+
"spin::udf_invoke" => Self {
69+
root: Box::new(common::root_empty),
70+
udfs: Box::new(spin::udf_invoke::udfs),
71+
},
72+
"spin::udf_name" => Self {
73+
root: Box::new(common::root_empty),
74+
udfs: Box::new(spin::udf_name::udfs),
75+
},
76+
"spin::udf_return_type_exact" => Self {
77+
root: Box::new(common::root_empty),
78+
udfs: Box::new(spin::udf_return_type_exact::udfs),
79+
},
80+
"spin::udf_return_type_other" => Self {
81+
root: Box::new(common::root_empty),
82+
udfs: Box::new(spin::udf_return_type_other::udfs),
83+
},
84+
"spin::udf_signature" => Self {
85+
root: Box::new(common::root_empty),
86+
udfs: Box::new(spin::udf_signature::udfs),
87+
},
88+
"spin::udfs" => Self {
89+
root: Box::new(common::root_empty),
90+
udfs: Box::new(spin::udfs::udfs),
91+
},
6392
other => panic!("unknown evil: {other}"),
6493
}
6594
}

guests/evil/src/spin/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! Payloads that lock the CPU during various operations.
2+
3+
use std::hash::{DefaultHasher, Hash, Hasher};
4+
5+
pub(crate) mod root;
6+
pub(crate) mod udf_invoke;
7+
pub(crate) mod udf_name;
8+
pub(crate) mod udf_return_type_exact;
9+
pub(crate) mod udf_return_type_other;
10+
pub(crate) mod udf_signature;
11+
pub(crate) mod udfs;
12+
13+
/// Method that locks the CPU forever.
14+
fn spin() {
15+
// Use a non-predictable costly loop.
16+
//
17+
// Practically `u64::MAX` will never come to an end.
18+
let mut hasher = DefaultHasher::new();
19+
for i in 0..u64::MAX {
20+
i.hash(&mut hasher);
21+
}
22+
let result = hasher.finish();
23+
24+
// side-effect to prevent the compiler from removing our code
25+
println!("hash: {result}");
26+
}

guests/evil/src/spin/root.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Payload that spins while retrieving the root filesystem.
2+
use crate::spin::spin;
3+
4+
/// Return root file system.
5+
///
6+
/// This always returns [`None`] because the example does not need any files.
7+
pub(crate) fn root() -> Option<Vec<u8>> {
8+
spin();
9+
None
10+
}

guests/evil/src/spin/udf_invoke.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! UDF that spins forever when [`ScalarUDFImpl::invoke_with_args`] is called.
2+
use std::sync::Arc;
3+
4+
use arrow::datatypes::DataType;
5+
use datafusion_common::{Result as DataFusionResult, ScalarValue};
6+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
7+
8+
use crate::spin::spin;
9+
10+
/// UDF that spins.
11+
#[derive(Debug, PartialEq, Eq, Hash)]
12+
struct SpinUdf {
13+
/// Signature of the UDF.
14+
///
15+
/// We store this here because [`ScalarUDFImpl::signature`] requires us to return a reference.
16+
signature: Signature,
17+
}
18+
19+
impl SpinUdf {
20+
/// Create new UDF.
21+
fn new() -> Self {
22+
Self {
23+
signature: Signature::uniform(0, vec![], Volatility::Immutable),
24+
}
25+
}
26+
}
27+
28+
impl ScalarUDFImpl for SpinUdf {
29+
fn as_any(&self) -> &dyn std::any::Any {
30+
self
31+
}
32+
33+
fn name(&self) -> &str {
34+
"spin"
35+
}
36+
37+
fn signature(&self) -> &Signature {
38+
&self.signature
39+
}
40+
41+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
42+
Ok(DataType::Null)
43+
}
44+
45+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
46+
spin();
47+
Ok(ColumnarValue::Scalar(ScalarValue::Null))
48+
}
49+
}
50+
51+
/// Returns our evil UDFs.
52+
///
53+
/// The passed `source` is ignored.
54+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
55+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
56+
Ok(vec![Arc::new(SpinUdf::new())])
57+
}

guests/evil/src/spin/udf_name.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! UDF that spins forever when [`ScalarUDFImpl::name`] is called.
2+
use std::sync::Arc;
3+
4+
use arrow::datatypes::DataType;
5+
use datafusion_common::{Result as DataFusionResult, ScalarValue};
6+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
7+
8+
use crate::spin::spin;
9+
10+
/// UDF that spins.
11+
#[derive(Debug, PartialEq, Eq, Hash)]
12+
struct SpinUdf {
13+
/// Signature of the UDF.
14+
///
15+
/// We store this here because [`ScalarUDFImpl::signature`] requires us to return a reference.
16+
signature: Signature,
17+
}
18+
19+
impl SpinUdf {
20+
/// Create new UDF.
21+
fn new() -> Self {
22+
Self {
23+
signature: Signature::uniform(0, vec![], Volatility::Immutable),
24+
}
25+
}
26+
}
27+
28+
impl ScalarUDFImpl for SpinUdf {
29+
fn as_any(&self) -> &dyn std::any::Any {
30+
self
31+
}
32+
33+
fn name(&self) -> &str {
34+
spin();
35+
"spin"
36+
}
37+
38+
fn signature(&self) -> &Signature {
39+
&self.signature
40+
}
41+
42+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
43+
Ok(DataType::Null)
44+
}
45+
46+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
47+
Ok(ColumnarValue::Scalar(ScalarValue::Null))
48+
}
49+
}
50+
51+
/// Returns our evil UDFs.
52+
///
53+
/// The passed `source` is ignored.
54+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
55+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
56+
Ok(vec![Arc::new(SpinUdf::new())])
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! UDF that spins forever when [`ScalarUDFImpl::return_type`] is called.
2+
use std::sync::Arc;
3+
4+
use arrow::datatypes::DataType;
5+
use datafusion_common::{Result as DataFusionResult, ScalarValue};
6+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
7+
8+
use crate::spin::spin;
9+
10+
/// UDF that spins.
11+
#[derive(Debug, PartialEq, Eq, Hash)]
12+
struct SpinUdf {
13+
/// Signature of the UDF.
14+
///
15+
/// We store this here because [`ScalarUDFImpl::signature`] requires us to return a reference.
16+
signature: Signature,
17+
}
18+
19+
impl SpinUdf {
20+
/// Create new UDF.
21+
fn new() -> Self {
22+
Self {
23+
signature: Signature::exact(vec![], Volatility::Immutable),
24+
}
25+
}
26+
}
27+
28+
impl ScalarUDFImpl for SpinUdf {
29+
fn as_any(&self) -> &dyn std::any::Any {
30+
self
31+
}
32+
33+
fn name(&self) -> &str {
34+
"spin"
35+
}
36+
37+
fn signature(&self) -> &Signature {
38+
&self.signature
39+
}
40+
41+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
42+
spin();
43+
Ok(DataType::Null)
44+
}
45+
46+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
47+
Ok(ColumnarValue::Scalar(ScalarValue::Null))
48+
}
49+
}
50+
51+
/// Returns our evil UDFs.
52+
///
53+
/// The passed `source` is ignored.
54+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
55+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
56+
Ok(vec![Arc::new(SpinUdf::new())])
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! UDF that spins forever when [`ScalarUDFImpl::return_type`] is called.
2+
use std::sync::Arc;
3+
4+
use arrow::datatypes::DataType;
5+
use datafusion_common::{Result as DataFusionResult, ScalarValue};
6+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
7+
8+
use crate::spin::spin;
9+
10+
/// UDF that spins.
11+
#[derive(Debug, PartialEq, Eq, Hash)]
12+
struct SpinUdf {
13+
/// Signature of the UDF.
14+
///
15+
/// We store this here because [`ScalarUDFImpl::signature`] requires us to return a reference.
16+
signature: Signature,
17+
}
18+
19+
impl SpinUdf {
20+
/// Create new UDF.
21+
fn new() -> Self {
22+
Self {
23+
signature: Signature::uniform(0, vec![], Volatility::Immutable),
24+
}
25+
}
26+
}
27+
28+
impl ScalarUDFImpl for SpinUdf {
29+
fn as_any(&self) -> &dyn std::any::Any {
30+
self
31+
}
32+
33+
fn name(&self) -> &str {
34+
"spin"
35+
}
36+
37+
fn signature(&self) -> &Signature {
38+
&self.signature
39+
}
40+
41+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
42+
spin();
43+
Ok(DataType::Null)
44+
}
45+
46+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
47+
Ok(ColumnarValue::Scalar(ScalarValue::Null))
48+
}
49+
}
50+
51+
/// Returns our evil UDFs.
52+
///
53+
/// The passed `source` is ignored.
54+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
55+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
56+
Ok(vec![Arc::new(SpinUdf::new())])
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! UDF that spins forever when [`ScalarUDFImpl::signature`] is called.
2+
use std::sync::Arc;
3+
4+
use arrow::datatypes::DataType;
5+
use datafusion_common::{Result as DataFusionResult, ScalarValue};
6+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
7+
8+
use crate::spin::spin;
9+
10+
/// UDF that spins.
11+
#[derive(Debug, PartialEq, Eq, Hash)]
12+
struct SpinUdf {
13+
/// Signature of the UDF.
14+
///
15+
/// We store this here because [`ScalarUDFImpl::signature`] requires us to return a reference.
16+
signature: Signature,
17+
}
18+
19+
impl SpinUdf {
20+
/// Create new UDF.
21+
fn new() -> Self {
22+
Self {
23+
signature: Signature::uniform(0, vec![], Volatility::Immutable),
24+
}
25+
}
26+
}
27+
28+
impl ScalarUDFImpl for SpinUdf {
29+
fn as_any(&self) -> &dyn std::any::Any {
30+
self
31+
}
32+
33+
fn name(&self) -> &str {
34+
"spin"
35+
}
36+
37+
fn signature(&self) -> &Signature {
38+
spin();
39+
&self.signature
40+
}
41+
42+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
43+
Ok(DataType::Null)
44+
}
45+
46+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
47+
Ok(ColumnarValue::Scalar(ScalarValue::Null))
48+
}
49+
}
50+
51+
/// Returns our evil UDFs.
52+
///
53+
/// The passed `source` is ignored.
54+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
55+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
56+
Ok(vec![Arc::new(SpinUdf::new())])
57+
}

guests/evil/src/spin/udfs.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Payload that spins while retrieving the list of UDFs.
2+
use std::sync::Arc;
3+
4+
use datafusion_common::Result as DataFusionResult;
5+
use datafusion_expr::ScalarUDFImpl;
6+
7+
use crate::spin::spin;
8+
9+
/// Returns our evil UDFs.
10+
///
11+
/// The passed `source` is ignored.
12+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
13+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
14+
spin();
15+
Ok(vec![])
16+
}

0 commit comments

Comments
 (0)