157 lines
4.9 KiB
Rust
157 lines
4.9 KiB
Rust
// 手写 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
|
|
}
|