-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: SQLNESS SLEEP <DURATION_STRING> (#67)
## Rationale Some test would require a certain amount of time to output final results(i.e. continous aggregate with some amount of delay) So it make sense for sqlness to have a `sleep` function, i.e. mysql&postgre sql both have sleep function, but it could be useful to have a sleep function in test client side anyway. ## Detailed Changes add a `-- SQLNESS SLEEP <Milliseconds>` to sleep for given time in milliseconds before executing query, which internally just spawn a new thread for sleeping when sleep is needed, this is for simplicity sake, and also to allow cross-runtime `async` sleep. The overhead of spawn a thread is deemed low since the query is sleeping anyway, the only reason why we can't just blocking sleep directly is because it's `async` up there so blocking a `async` task make a lot of trouble to any `async` runtime. ## Test Plan a simple test added for waiting given time
- Loading branch information
Showing
5 changed files
with
131 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright 2024 CeresDB Project Authors. Licensed under Apache-2.0. | ||
|
||
use std::pin::Pin; | ||
use std::task::Context; | ||
use std::time::{Duration, Instant}; | ||
|
||
use crate::error::Result; | ||
use crate::interceptor::{Interceptor, InterceptorFactory, InterceptorRef}; | ||
use crate::SqlnessError; | ||
|
||
pub const PREFIX: &str = "SLEEP"; | ||
|
||
/// Sleep for given duration before executing the query. | ||
/// | ||
/// # Example | ||
/// ``` sql | ||
/// -- SQLNESS SLEEP <Duration> | ||
/// SELECT 1; | ||
/// ``` | ||
/// | ||
/// valid duration format: | ||
/// - `1s` for 1 second | ||
/// - `1ms` for 1 millisecond | ||
/// - `1s500ms` for 1.5 seconds | ||
/// etc. See detailed format in [duration_str](https://docs.rs/duration-str/0.11.2/duration_str/) crate | ||
/// | ||
/// Note that this implementation is not accurate and may be affected by the system load. | ||
/// It is guaranteed that the sleep time is at least the given milliseconds, but the lag may be | ||
/// longer. | ||
#[derive(Debug)] | ||
pub struct SleepInterceptor { | ||
duration: Duration, | ||
} | ||
|
||
struct Sleep { | ||
now: Instant, | ||
duration: Duration, | ||
} | ||
impl core::future::Future for Sleep { | ||
type Output = (); | ||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll<Self::Output> { | ||
let elapsed = self.now.elapsed(); | ||
if elapsed < self.duration { | ||
let waker = cx.waker().clone(); | ||
// detach the thread and let it wake the waker later | ||
let remaining = self.duration.saturating_sub(elapsed); | ||
std::thread::spawn(move || { | ||
std::thread::sleep(remaining); | ||
waker.wake(); | ||
}); | ||
std::task::Poll::Pending | ||
} else { | ||
std::task::Poll::Ready(()) | ||
} | ||
} | ||
} | ||
|
||
#[async_trait::async_trait] | ||
impl Interceptor for SleepInterceptor { | ||
async fn before_execute_async( | ||
&self, | ||
_execute_query: &mut Vec<String>, | ||
_context: &mut crate::case::QueryContext, | ||
) { | ||
// impl a cross-runtime sleep | ||
Sleep { | ||
now: Instant::now(), | ||
duration: self.duration, | ||
} | ||
.await; | ||
} | ||
} | ||
|
||
pub struct SleepInterceptorFactory; | ||
|
||
impl InterceptorFactory for SleepInterceptorFactory { | ||
fn try_new(&self, ctx: &str) -> Result<InterceptorRef> { | ||
let duration = duration_str::parse(ctx).map_err(|e| SqlnessError::InvalidContext { | ||
prefix: PREFIX.to_string(), | ||
msg: format!("Failed to parse duration: {}", e), | ||
})?; | ||
Ok(Box::new(SleepInterceptor { duration })) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[tokio::test] | ||
async fn wait_1500ms() { | ||
let input = "1s500ms"; | ||
let interceptor = SleepInterceptorFactory {}.try_new(input).unwrap(); | ||
let now = Instant::now(); | ||
interceptor | ||
.before_execute_async(&mut vec![], &mut crate::QueryContext::default()) | ||
.await; | ||
let elasped = now.elapsed().as_millis() as u64; | ||
assert!(elasped >= 1500); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters