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

+37
View File
@@ -0,0 +1,37 @@
//! 固定 payload 指纹来自本基准的公开记录合同;不把生成函数作为 Rust 参考实现。
use dever::compiler::model::Program;
use sha2::{Digest, Sha256};
use std::path::Path;
pub fn run(core: &Path) {
crate::compare(
core,
"job-performance",
"jobs",
&[
include_str!("../auth/shared.rs"),
include_str!("reference.rs"),
include_str!("measure.rs"),
include_str!("checks.rs"),
]
.concat(),
&[("GENERATED_SUBMIT", "bench.queue.app.submit")],
&[],
);
}
pub fn metadata(program: &Program, reference: &str) -> String {
assert_eq!(program.jobs.len(), 1);
assert_eq!(program.models.len(), 1);
assert!(!program.has_roles());
assert_eq!(program.jobs[0].name, "bench.queue.job.publish");
let schema = format!(
"{:x}",
Sha256::digest(b"bench.queue.Payload{name:Text,amount:Int}")
);
reference
.replace("JOB_BINDING", &program.models.len().to_string())
.replace("RESULT_MODEL", &program.models[0].definition.to_string())
.replace("JOB_SCHEMA", &format!("{schema:?}"))
}
+131
View File
@@ -0,0 +1,131 @@
// 独立 SQL 校验持久队列和业务结果,不能只用函数返回值或随机 ID 长度认定正确。
async fn clear(scope: &Scope<'_>, registry: &Arc<Registry>) {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
executor
.execute(scope, "DELETE FROM _dever_job", vec![])
.await
.unwrap();
executor
.execute(scope, "DELETE FROM bench_queue_model", vec![])
.await
.unwrap();
}
async fn check_rows(scope: &Scope<'_>, registry: &Arc<Registry>, inputs: &[JobInput], handled: bool) {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
let rows = executor.query(scope,
"SELECT id,target,payload,schema,execution,task_key,state,attempt,retries,timeout_ms,token,lease_until,available_at,created_at FROM _dever_job ORDER BY task_key",
vec![], inputs.len() + 1, |row| Ok((
row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?,
row.get::<_, String>(3)?, row.get::<_, String>(4)?, row.get::<_, String>(5)?,
row.get::<_, String>(6)?, row.get::<_, i64>(7)?, row.get::<_, i64>(8)?, row.get::<_, i64>(9)?,
row.get::<_, Option<String>>(10)?, row.get::<_, Option<i64>>(11)?, row.get::<_, i64>(12)?, row.get::<_, i64>(13)?,
))).await.unwrap();
assert_eq!(rows.len(), inputs.len());
let mut unique = std::collections::BTreeSet::new();
for (row, input) in rows.iter().zip(inputs) {
assert!(unique.insert(&row.0));
assert_eq!(row.0.len(), 32);
assert_eq!(row.1, TARGET);
let payload: RustPayload = dever_runtime::wire::decode(&row.2).unwrap();
assert_eq!(payload.name, input.name);
assert_eq!(payload.amount, input.amount);
assert_eq!(row.3, SCHEMA);
assert!(matches!(
dever_runtime::wire::decode::<Execution>(&row.4).unwrap(),
Execution::System { tenant: None }
));
assert_eq!(row.5, input.key);
assert_eq!(row.6, if handled { "completed" } else { "pending" });
assert_eq!((row.7, row.8, row.9), (i64::from(handled), 2, 1000));
assert!(row.10.is_none() && row.11.is_none());
assert!(row.12 >= row.13 && row.13 > 0);
}
let results = executor
.query(
scope,
"SELECT name,amount FROM bench_queue_model ORDER BY name",
vec![],
inputs.len() + 1,
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)),
)
.await
.unwrap();
let expected = if handled {
inputs
.iter()
.map(|input| (input.name.clone(), input.amount * 3 + 7))
.collect::<Vec<_>>()
} else {
vec![]
};
assert_eq!(results, expected);
}
fn check_jobs(runtime: &Runtime, registry: &Arc<Registry>) {
runtime
.run(async |scope| {
for generated in [true, false] {
clear(scope, registry).await;
let inputs = inputs(2);
for input in &inputs {
let id = submit(generated, input, scope, registry).await;
assert_eq!(submit(generated, input, scope, registry).await, id);
}
check_rows(scope, registry, &inputs, false).await;
for claim in claims(scope, registry, 2).await {
execute(generated, claim, scope, registry).await.unwrap();
}
check_rows(scope, registry, &inputs, true).await;
clear(scope, registry).await;
let input = JobInput {
name: "rollback".into(),
amount: -4,
key: "rollback".into(),
};
submit(generated, &input, scope, registry).await;
let claim = claims(scope, registry, 1).await.pop().unwrap();
assert!(matches!(
execute(generated, claim, scope, registry).await,
Err(JobFailure::Handler)
));
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
assert_eq!(
executor
.query(
scope,
"SELECT COUNT(*) FROM bench_queue_model",
vec![],
1,
|row| Ok(row.get::<_, i64>(0)?)
)
.await
.unwrap(),
[0]
);
assert_eq!(
executor
.query(scope, "SELECT state FROM _dever_job", vec![], 1, |row| Ok(
row.get::<_, String>(0)?
))
.await
.unwrap(),
["running"]
);
drop(database);
clear(scope, registry).await;
submit(generated, &inputs[0], scope, registry).await;
let claim = claims(scope, registry, 1).await.pop().unwrap();
let mut claim = Arc::try_unwrap(claim).unwrap();
claim.payload = r#"["array-is-not-a-record",1]"#.into();
assert!(matches!(
execute(generated, Arc::new(claim), scope, registry).await,
Err(JobFailure::Blocked)
));
}
})
.unwrap();
}
+267
View File
@@ -0,0 +1,267 @@
// 入队计时含具体编码和外层提交;执行计时含真实生成分派、任务作用域、解码、业务写入和确认。
// 单阶段对照把 claim 放在计时外;完整循环另计入入队、claim、执行和确认。
struct JobInput {
name: String,
amount: i64,
key: String,
}
fn inputs(count: usize) -> Vec<JobInput> {
(0..count)
.map(|index| JobInput {
name: format!("job-{index:04} 中文\""),
amount: index as i64 % 997,
key: format!("key-{index:04}"),
})
.collect()
}
async fn submit(
generated: bool,
input: &JobInput,
scope: &Scope<'_>,
registry: &Arc<Registry>,
) -> String {
let mut database = DbContext::new(registry.clone());
let owner = database.enter();
let execution = Execution::System { tenant: None };
let id = if generated {
GENERATED_SUBMIT(
&input.name,
input.amount,
&input.key,
scope,
&mut database,
&execution,
)
.await
.unwrap()
} else {
rust_submit(
&input.name,
input.amount,
&input.key,
scope,
&mut database,
&execution,
)
.await
.unwrap()
};
database.finish(scope, owner, true).await.unwrap();
id.into_text()
}
async fn execute(
generated: bool,
claim: Arc<Claim>,
scope: &Scope<'_>,
registry: &Arc<Registry>,
) -> Result<(), JobFailure> {
let registry = registry.clone();
let timeout = claim.policy.timeout_ms;
let outcome = if generated {
scope
.spawn(move |child| job_execute(JOB_BINDING, None, claim, child, registry))
.unwrap()
.timeout(timeout)
.await
.unwrap()
} else {
scope
.spawn(move |child| rust_execute(JOB_BINDING, claim, child, registry))
.unwrap()
.timeout(timeout)
.await
.unwrap()
};
match outcome {
dever_runtime::task::Timed::Ready(value) => value,
_ => panic!("benchmark handler timed out"),
}
}
async fn claims(scope: &Scope<'_>, registry: &Arc<Registry>, count: usize) -> Vec<Arc<Claim>> {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
let mut claims = Vec::with_capacity(count);
for _ in 0..count {
let claim = job::claim(
executor,
scope,
dever_runtime::database::now().unwrap(),
60000,
)
.await
.unwrap()
.unwrap();
assert_eq!(claim.schema, SCHEMA);
assert_eq!(claim.target, TARGET);
assert_eq!(claim.attempt, 1);
claims.push(Arc::new(claim));
}
assert!(
job::claim(
executor,
scope,
dever_runtime::database::now().unwrap(),
60000
)
.await
.unwrap()
.is_none()
);
claims
}
fn measure_jobs(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
handler: bool,
inputs: Vec<JobInput>,
) -> (u128, u64) {
runtime
.run(async |scope| {
clear(scope, registry).await;
let claims = if handler {
for input in &inputs {
black_box(submit(false, input, scope, registry).await);
}
claims(scope, registry, inputs.len()).await
} else {
Vec::new()
};
let mut checksum = 0u64;
let start = Instant::now();
if handler {
for claim in claims {
execute(generated, black_box(claim), scope, registry)
.await
.unwrap();
checksum += black_box(1);
}
} else {
for input in &inputs {
let id = black_box(submit(generated, black_box(input), scope, registry).await);
checksum += id.len() as u64 + input.name.len() as u64 + input.amount as u64;
}
}
let elapsed = start.elapsed().as_nanos();
check_rows(scope, registry, &inputs, handler).await;
(elapsed, checksum)
})
.unwrap()
}
fn main() {
let (directory, path) = directory("jobs", r#""runtime":{"mode":"worker"}"#);
let runtime = runtime();
let registry = runtime
.run(async |scope| database_registry(&path, scope).await)
.unwrap()
.unwrap();
check_jobs(&runtime, &registry);
header();
for (name, handler) in [("job_enqueue_commit", false), ("job_handler_commit", true)] {
samples(name, |generated, count| {
measure_jobs(&runtime, &registry, generated, handler, inputs(count))
});
}
samples("job_enqueue_large_payload_commit", |generated, count| {
let mut inputs = inputs(count.div_ceil(64));
for input in &mut inputs {
input.name.push_str(&"a".repeat(64 * 1024));
}
measure_jobs(&runtime, &registry, generated, false, inputs)
});
samples("job_submit_claim_complete", |generated, count| {
// 完整循环的每个样本包含 250 项实际任务;初始化及独立 SQL 核验不计时。
measure_cycle(&runtime, &registry, generated, count.div_ceil(8), false)
});
samples("job_backlog_claim_complete", |generated, count| {
measure_cycle(&runtime, &registry, generated, count.div_ceil(8), true)
});
samples("job_enqueue_duplicate_commit", |generated, count| {
measure_duplicate(&runtime, &registry, generated, inputs(count))
});
drop(registry);
drop(runtime);
drop(directory);
}
fn measure_duplicate(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
inputs: Vec<JobInput>,
) -> (u128, u64) {
runtime
.run(async |scope| {
clear(scope, registry).await;
let mut expected = Vec::with_capacity(inputs.len());
for input in &inputs {
expected.push(submit(false, input, scope, registry).await);
}
let mut actual = Vec::with_capacity(inputs.len());
let mut checksum = 0u64;
let start = Instant::now();
for input in &inputs {
let id = submit(generated, black_box(input), scope, registry).await;
checksum += black_box(id.len() as u64);
actual.push(id);
}
let elapsed = start.elapsed().as_nanos();
assert_eq!(actual, expected);
check_rows(scope, registry, &inputs, false).await;
(elapsed, checksum)
})
.unwrap()
}
fn measure_cycle(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
count: usize,
backlog: bool,
) -> (u128, u64) {
let inputs = inputs(count);
runtime
.run(async |scope| {
clear(scope, registry).await;
if backlog {
for input in &inputs {
submit(generated, input, scope, registry).await;
}
}
let start = Instant::now();
let mut checksum = 0u64;
for input in &inputs {
if !backlog {
submit(generated, black_box(input), scope, registry).await;
}
let claim = {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
job::claim(
executor,
scope,
dever_runtime::database::now().unwrap(),
60000,
)
.await
.unwrap()
.unwrap()
};
execute(generated, Arc::new(claim), scope, registry)
.await
.unwrap();
checksum += black_box(1);
}
let elapsed = start.elapsed().as_nanos();
check_rows(scope, registry, &inputs, true).await;
(elapsed, checksum)
})
.unwrap()
}
+156
View File
@@ -0,0 +1,156 @@
// 手写 Rust 重新实现 payload 编码、业务计算、行解码和事务边界;仅复用同一队列和数据库设施。
use dever_runtime::{
job::{self, Claim, Execution, Failure as JobFailure},
wire::serde::{Deserialize, Serialize},
};
const SCHEMA: &str = JOB_SCHEMA;
const TARGET: &str = "bench.queue.job.publish";
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct RustPayload {
name: String,
amount: i64,
}
impl<'input> Deserialize<'input> for RustPayload {
fn deserialize<Decoder: dever_runtime::wire::serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Fields {
name: String,
amount: i64,
}
// 与 Dever record 一样只接受对象,不能用 serde 默认的数组兼容路径降低校验成本。
let fields = dever_runtime::wire::object::<Fields, Decoder>(decoder)?;
Ok(Self {
name: fields.name,
amount: fields.amount,
})
}
}
struct RustResult {
_id: dever_runtime::database::Id<RESULT_MODEL>,
_created_at: dever_runtime::time::calendar::DateTime,
_name: String,
amount: i64,
}
async fn rust_submit(
name: &str,
amount: i64,
key: &str,
scope: &Scope<'_>,
database: &mut DbContext,
execution: &Execution,
) -> Result<dever_runtime::value::id::Id, dever_runtime::database::Error> {
let payload = dever_runtime::wire::json::Json::encode(&RustPayload {
name: name.to_owned(),
amount,
})
.map_err(|_| {
dever_runtime::database::Error::new(
dever_runtime::database::Kind::InvalidData,
"invalid Job payload",
)
})?;
let now = dever_runtime::database::now()?;
let executor = database.on(JOB_BINDING, scope).await?;
job::enqueue(
executor,
scope,
job::Submission {
target: TARGET,
payload: Some(&payload),
schema: SCHEMA,
execution,
key,
policy: job::Policy {
retries: 2,
timeout_ms: 1000,
},
at: now,
},
now,
)
.await
}
async fn rust_apply(
payload: RustPayload,
scope: &Scope<'_>,
database: &mut DbContext,
) -> Result<(), JobFailure> {
let amount = payload
.amount
.checked_mul(3)
.and_then(|value| value.checked_add(7))
.ok_or(JobFailure::Handler)?;
let parameters = vec![
Parameter::Text(payload.name),
Parameter::Integer(amount),
Parameter::Integer(dever_runtime::database::now().map_err(|_| JobFailure::Handler)?),
];
let executor = database
.on(0, scope)
.await
.map_err(|_| JobFailure::Handler)?;
let saved = executor.query(scope,
"INSERT INTO \"bench_queue_model\" (\"name\",\"amount\",\"created_at\") VALUES (?1,?2,?3) RETURNING \"id\",\"created_at\",\"name\",\"amount\"",
parameters, 1, |row| Ok(RustResult {
_id: dever_runtime::database::Id(row.get(0)?),
_created_at: dever_runtime::time::calendar::DateTime::from_millis(row.get::<_, i64>(1)?).map_err(|_| dever_runtime::database::Error::new(dever_runtime::database::Kind::InvalidData, "invalid stored DateTime"))?,
_name: row.get(2)?, amount: row.get(3)?,
})
).await.map_err(|_| JobFailure::Handler)?.pop().ok_or(JobFailure::Handler)?;
if saved.amount < 0 {
return Err(JobFailure::Handler);
}
Ok(())
}
async fn rust_execute(
binding: usize,
claim: Arc<Claim>,
scope: Scope<'static>,
registry: Arc<Registry>,
) -> Result<(), JobFailure> {
let execution: Execution =
dever_runtime::wire::decode(&claim.execution).map_err(|_| JobFailure::Blocked)?;
if claim.target != TARGET || claim.schema != SCHEMA || !registry.same_pool(binding, JOB_BINDING)
{
return Err(JobFailure::Blocked);
}
if !matches!(execution, Execution::System { tenant: None }) || claim.execution_tenant != 0 {
return Err(JobFailure::Blocked);
}
let payload: RustPayload =
dever_runtime::wire::decode(&claim.payload).map_err(|_| JobFailure::Blocked)?;
let mut database = DbContext::new(registry);
let owner = database.enter();
let outcome = async {
rust_apply(payload, &scope, &mut database).await?;
let executor = database
.on(JOB_BINDING, &scope)
.await
.map_err(|_| JobFailure::Handler)?;
job::complete(
executor,
&scope,
&claim,
dever_runtime::database::now().map_err(|_| JobFailure::Handler)?,
)
.await
.map_err(|_| JobFailure::Handler)
}
.await;
database
.finish(&scope, owner, outcome.is_ok())
.await
.map_err(|_| JobFailure::Handler)?;
outcome
}