Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+296
View File
@@ -0,0 +1,296 @@
//! 阶段计时使用预借出的单连接;独立 Rust 仅共享表结构和返回字段,不调用 Dever 运行库。
//! 原有生成程序基准另外覆盖连接借还、业务事务和完整分派,不能用这里替代它。
mod native;
mod sql;
use super::*;
use deadpool_sqlite::rusqlite::Connection;
use dever_runtime::{database::Executor, job::Claim, task::Scope};
use std::{
hint::black_box,
time::{Duration, Instant},
};
#[derive(Clone, Copy, Debug)]
enum Phase {
Enqueue,
Duplicate,
Claim,
Complete,
Cycle,
}
async fn clear(executor: &mut Executor, scope: &Scope<'_>) {
executor
.execute(scope, "DELETE FROM _dever_job", vec![])
.await
.unwrap();
}
async fn measure_dever(
executor: &mut Executor,
scope: &Scope<'_>,
phase: Phase,
keys: &[String],
payload: &Json,
) -> (u128, u64) {
clear(executor, scope).await;
if matches!(phase, Phase::Duplicate | Phase::Claim | Phase::Complete) {
for key in keys {
let mut value = submission(key);
value.payload = Some(payload);
job::enqueue(executor, scope, value, 1000).await.unwrap();
}
}
let mut claims = Vec::new();
if matches!(phase, Phase::Complete) {
for _ in keys {
claims.push(
job::claim(executor, scope, 1000, 60000)
.await
.unwrap()
.unwrap(),
);
}
}
let start = Instant::now();
let mut checksum = 0;
for (index, key) in keys.iter().enumerate() {
match phase {
Phase::Enqueue | Phase::Duplicate => {
let mut value = submission(key);
value.payload = Some(payload);
checksum += black_box(
job::enqueue(executor, scope, value, 1000)
.await
.unwrap()
.into_text(),
)
.len() as u64;
}
Phase::Claim => {
let claim = job::claim(executor, scope, 1000, 60000)
.await
.unwrap()
.unwrap();
checksum += black_box(claim).id.len() as u64;
}
Phase::Complete => {
job::complete(executor, scope, &claims[index], 1000)
.await
.unwrap();
checksum += 1;
}
Phase::Cycle => {
let mut value = submission(key);
value.payload = Some(payload);
let id = job::enqueue(executor, scope, value, 1000)
.await
.unwrap()
.into_text();
let claim = job::claim(executor, scope, 1000, 60000)
.await
.unwrap()
.unwrap();
assert_eq!(id, claim.id);
job::complete(executor, scope, &claim, 1000).await.unwrap();
checksum += black_box(claim).id.len() as u64;
}
}
}
let elapsed = start.elapsed().as_nanos();
let state = state(phase);
let count = executor
.query(
scope,
"SELECT COUNT(*) FROM _dever_job WHERE state=?1",
vec![Parameter::Text(state.into())],
1,
|row| row.get::<_, i64>(0),
)
.await
.unwrap()[0];
assert_eq!(count, keys.len() as i64);
(elapsed, checksum)
}
fn state(phase: Phase) -> &'static str {
match phase {
Phase::Enqueue | Phase::Duplicate => "pending",
Phase::Claim => "running",
Phase::Complete | Phase::Cycle => "completed",
}
}
async fn measure_native(
native: &mut native::Native,
phase: Phase,
keys: &[String],
payload: &str,
) -> (u128, u64) {
native
.call(|connection, _| connection.execute("DELETE FROM _dever_job", []))
.await
.unwrap();
if matches!(phase, Phase::Duplicate | Phase::Claim | Phase::Complete) {
for key in keys {
native.enqueue(key, payload).await;
}
}
let mut claims: Vec<Claim> = Vec::new();
if matches!(phase, Phase::Complete) {
for _ in keys {
claims.push(native.claim().await.unwrap());
}
}
let start = Instant::now();
let mut checksum = 0;
for (index, key) in keys.iter().enumerate() {
match phase {
Phase::Enqueue | Phase::Duplicate => {
checksum += black_box(native.enqueue(key, payload).await).len() as u64
}
Phase::Claim => checksum += black_box(native.claim().await.unwrap()).id.len() as u64,
Phase::Complete => {
native.complete(&claims[index]).await;
checksum += 1;
}
Phase::Cycle => {
let id = native.enqueue(key, payload).await;
let claim = native.claim().await.unwrap();
assert_eq!(id, claim.id);
native.complete(&claim).await;
checksum += black_box(claim).id.len() as u64;
}
}
}
let elapsed = start.elapsed().as_nanos();
let count: i64 = native
.call(move |connection, _| {
connection.query_row(
"SELECT COUNT(*) FROM _dever_job WHERE state=?1",
[state(phase)],
|row| row.get(0),
)
})
.await
.unwrap();
assert_eq!(count, keys.len() as i64);
(elapsed, checksum)
}
#[test]
#[ignore = "release-only owned SQLite phase timing; invoke explicitly"]
fn independent_sqlite_phases() {
let directory = Directory::new();
let native_runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.max_blocking_threads(1)
.enable_all()
.build()
.unwrap();
let connection = Connection::open(directory.0.join("native.db")).unwrap();
connection.busy_timeout(Duration::from_secs(10)).unwrap();
connection
.execute_batch(
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA synchronous=NORMAL;",
)
.unwrap();
connection.execute_batch(job::schema::SQL.sqlite).unwrap();
let mut native = native::Native::new(connection);
let runtime = Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 16,
})
.unwrap();
let mut options = options();
options.max_connections = 1;
options.io_timeout_ms = 10000;
let database = runtime
.run(async |scope| Database::open(&directory.0, options, scope).await)
.unwrap()
.unwrap();
let mut executor = runtime
.run(async |scope| {
let mut executor = database.executor().await.unwrap();
executor
.schema(scope, job::schema::TABLE, job::schema::SQL)
.await
.unwrap();
executor
})
.unwrap();
println!("case,sample,implementation,iterations,nanoseconds,checksum");
for (name, phase, count, payload) in [
(
"enqueue",
Phase::Enqueue,
1000,
Json::parse("{\"number\":1}").unwrap(),
),
(
"enqueue_64k",
Phase::Enqueue,
64,
Json::parse(&format!("\"{}\"", "x".repeat(65536))).unwrap(),
),
(
"duplicate",
Phase::Duplicate,
1000,
Json::parse("{\"number\":1}").unwrap(),
),
(
"claim",
Phase::Claim,
256,
Json::parse("{\"number\":1}").unwrap(),
),
(
"complete",
Phase::Complete,
256,
Json::parse("{\"number\":1}").unwrap(),
),
(
"cycle",
Phase::Cycle,
256,
Json::parse("{\"number\":1}").unwrap(),
),
] {
let keys: Vec<_> = (0..count).map(|index| format!("key-{index:04}")).collect();
for sample in 0..16 {
let mut order = [("dever", true), ("rust", false), ("control", true)];
order.rotate_left(sample % 3);
let mut expected = None;
for (label, generated) in order {
let (elapsed, checksum) = if generated {
runtime
.run(async |scope| {
measure_dever(&mut executor, scope, phase, &keys, &payload).await
})
.unwrap()
} else {
native_runtime.block_on(measure_native(
&mut native,
phase,
&keys,
payload.as_str(),
))
};
if let Some(expected) = expected {
assert_eq!(checksum, expected);
}
expected = Some(checksum);
if sample > 0 {
println!("{name},{},{label},{count},{elapsed},{checksum}", sample - 1);
}
}
}
}
native_runtime.block_on(native.close());
drop(executor);
database.close();
}
+109
View File
@@ -0,0 +1,109 @@
//! 独立 Rust 对照仅使用 Tokio/rusqlite;连接移交、取消、期限和退出等待不调用 Dever。
use deadpool_sqlite::rusqlite::{Connection, Result};
use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::{Duration, Instant},
};
use tokio::sync::Semaphore;
use tokio_util::task::TaskTracker;
const TIMEOUT: Duration = Duration::from_secs(10);
struct Cancel(Arc<AtomicBool>);
impl Drop for Cancel {
fn drop(&mut self) {
self.0.store(true, Ordering::Release);
}
}
pub(super) struct Native {
connection: Option<Connection>,
workers: Arc<Semaphore>,
tracker: TaskTracker,
}
impl Native {
pub(super) fn new(connection: Connection) -> Self {
Self {
connection: Some(connection),
workers: Arc::new(Semaphore::new(1)),
tracker: TaskTracker::new(),
}
}
pub(super) async fn call<Value: Send + 'static>(
&mut self,
operation: impl FnOnce(&mut Connection, &dyn Fn() -> Result<()>) -> Result<Value>
+ Send
+ 'static,
) -> Result<Value> {
let mut connection = self.connection.take().unwrap();
let cancelled = Cancel(Arc::new(AtomicBool::new(false)));
let signal = cancelled.0.clone();
let deadline = Instant::now() + TIMEOUT;
let workers = self.workers.clone();
let tracker = self.tracker.clone();
let work = async move {
let worker = workers.acquire_owned().await.unwrap();
let tracking = tracker.token();
tokio::task::spawn_blocking(move || {
let _worker = worker;
let check = || {
if signal.load(Ordering::Acquire) || Instant::now() >= deadline {
Err(deadpool_sqlite::rusqlite::Error::InvalidQuery)
} else {
Ok(())
}
};
let outcome = (|| {
check()?;
let progress = signal.clone();
connection.progress_handler(
1000,
Some(move || {
progress.load(Ordering::Acquire) || Instant::now() >= deadline
}),
)?;
let result = operation(&mut connection, &check);
connection.progress_handler(0, None::<fn() -> bool>)?;
check()?;
result
})();
(connection, outcome, tracking)
})
.await
.unwrap()
};
let (connection, result, _tracking) = tokio::time::timeout(TIMEOUT, work).await.unwrap();
self.connection = Some(connection);
result
}
pub(super) async fn transaction<Value: Send + 'static>(
&mut self,
operation: impl FnOnce(&Connection) -> Result<Value> + Send + 'static,
) -> Result<Value> {
self.call(move |connection, check| {
let transaction = connection.transaction_with_behavior(
deadpool_sqlite::rusqlite::TransactionBehavior::Immediate,
)?;
check()?;
let result = operation(&transaction)?;
check()?;
transaction.commit()?;
Ok(result)
})
.await
}
pub(super) async fn close(self) {
self.tracker.close();
self.tracker.wait().await;
drop(self.connection);
}
}
+83
View File
@@ -0,0 +1,83 @@
//! 同一表合同的独立 SQL 实现;不复用生产队列函数,也不新增生产解析或调度层。
use super::native::Native;
use deadpool_sqlite::rusqlite::{OptionalExtension, params};
use dever_runtime::job::{Claim, Policy};
use std::{fmt::Write, time::Instant};
#[derive(serde::Serialize)]
struct Execution {
kind: &'static str,
tenant: Option<i64>,
}
pub(super) fn identifier() -> String {
let mut bytes = [0u8; 16];
getrandom::getrandom(&mut bytes).unwrap();
let mut value = String::with_capacity(32);
write!(value, "{:032x}", u128::from_be_bytes(bytes)).unwrap();
value
}
impl Native {
pub(super) async fn enqueue(&mut self, key: &str, payload: &str) -> String {
assert!(!key.is_empty() && key.len() <= 1024);
let id = identifier();
let (key, payload, target, schema) = (
key.to_owned(),
payload.to_owned(),
"demo.account.publish".to_owned(),
"schema-1".to_owned(),
);
let execution = serde_json::to_string(&Execution {
kind: "System",
tenant: None,
})
.unwrap();
self.transaction(move |connection| {
let changed = connection.prepare_cached("INSERT INTO _dever_job(id,target,payload,schema,execution,task_key,state,retries,timeout_ms,available_at,created_at,execution_tenant) VALUES (?1,?2,?3,?4,?5,?6,'pending',?7,?8,?9,?10,?11) ON CONFLICT(execution_tenant,target,task_key) WHERE state IN ('pending','running') DO NOTHING")?.execute(params![id,target,payload,schema,execution,key,2,50,1000,1000,0])?;
if changed == 1 {
Ok(id)
} else {
connection.prepare_cached("SELECT id FROM _dever_job WHERE execution_tenant=?1 AND target=?2 AND task_key=?3 AND state IN ('pending','running')")?.query_row(params![0,target,key], |row| row.get(0))
}
}).await.unwrap()
}
pub(super) async fn claim(&mut self) -> Option<Claim> {
let started = Instant::now();
let token = identifier();
self.transaction(move |connection| {
let now = 1000i64.checked_add(started.elapsed().as_millis().try_into().unwrap()).unwrap();
let lease_until = now.checked_add(60000).unwrap();
connection.prepare_cached("UPDATE _dever_job SET state=CASE WHEN attempt >= retries THEN 'dead' ELSE 'pending' END, token=NULL,lease_until=NULL,error='lease expired' WHERE id IN (SELECT id FROM _dever_job WHERE state='running' AND lease_until <= ?1 ORDER BY lease_until,id LIMIT 256)")?.execute([now])?;
let value = connection.prepare_cached(
"SELECT id,target,substr(payload,1,16777217),schema,substr(execution,1,16777217),retries,timeout_ms,attempt,execution_tenant FROM _dever_job
WHERE id=(SELECT id FROM (
SELECT * FROM (SELECT id,available_at FROM _dever_job WHERE state='pending' AND attempt < retries AND available_at <= ?1 ORDER BY available_at,id LIMIT 1)
UNION ALL
SELECT * FROM (SELECT id,available_at FROM _dever_job WHERE state='running' AND attempt < retries AND lease_until <= ?1 AND available_at <= ?1 ORDER BY available_at,id LIMIT 1)
) ORDER BY available_at,id LIMIT 1)"
)?.query_row([now], |row| Ok(Claim {
id: row.get(0)?, target: row.get(1)?, payload: row.get(2)?, schema: row.get(3)?, execution: row.get(4)?,
policy: Policy { retries: row.get(5)?, timeout_ms: row.get(6)? }, attempt: row.get::<_,i64>(7)? + 1,
token, lease_until, execution_tenant: row.get(8)?,
})).optional()?;
if let Some(value) = &value {
connection.prepare_cached("UPDATE _dever_job SET state='running',attempt=?1,token=?2,lease_until=?3,error=CASE WHEN state='running' THEN 'lease expired' ELSE error END WHERE id=?4")?.execute(params![value.attempt,value.token,lease_until,value.id])?;
}
Ok(value)
}).await.unwrap()
}
pub(super) async fn complete(&mut self, claim: &Claim) {
let (id, token) = (claim.id.clone(), claim.token.clone());
let started = Instant::now();
self.transaction(move |connection| {
let now = 1000i64.checked_add(started.elapsed().as_millis().try_into().unwrap()).unwrap();
let changed = connection.prepare_cached("UPDATE _dever_job SET state=?1,available_at=?2,error=?3,token=NULL,lease_until=NULL WHERE id=?4 AND state='running' AND token=?5 AND lease_until > ?6")?.execute(params!["completed",now,None::<&str>,id,token,now])?;
assert_eq!(changed, 1);
Ok(())
}).await.unwrap();
}
}