pub struct Driver {
pub store: Store,
pub http: Client,
pub owner: String,
pub lease: i64,
pub retry: RetryPolicy,
pub workers: usize,
pub registry: Arc<Registry>,
pub grpc: GrpcCaller,
pub workflows: Arc<WorkflowRegistry>,
/* private fields */
}Fields§
§store: Store§http: Client§owner: String§lease: i64租约时长(秒)。持租约的实例崩了,这么久之后别的实例接手
retry: RetryPolicy重试退避策略。默认 10s 起、300s 封顶,可用环境变量改
workers: usize并行推进的 worker 数
registry: Arc<Registry>进程内分支注册表。嵌入式模式用,纯 HTTP 部署时是空表
grpc: GrpcCallergRPC 分支调用器(带 channel 缓存)
workflows: Arc<WorkflowRegistry>workflow 函数注册表。跟 registry 一样,纯 HTTP 部署时是空表
Implementations§
Source§impl Driver
impl Driver
Sourcepub fn new(store: Store, owner: String) -> Self
pub fn new(store: Store, owner: String) -> Self
默认配置的推进器。分支超时 10s、租约 30s、退避 10s→300s。
想按环境变量配就用 Driver::from_env
Sourcepub fn from_env(store: Store, owner: String) -> Self
pub fn from_env(store: Store, owner: String) -> Self
按环境变量配置:
| 变量 | 默认 | 说明 |
|---|---|---|
DTMRS_BRANCH_TIMEOUT | 10 | 调一个分支最多等几秒 |
DTMRS_LEASE | 30 | 租约时长(秒) |
DTMRS_RETRY_INTERVAL | 10 | 首次重试间隔(秒) |
DTMRS_RETRY_MAX_INTERVAL | 300 | 退避上限(秒) |
DTMRS_WORKERS | 16 | 并行推进的 worker 数 |
存储连接池另有 DTMRS_DB_POOL(默认 32),跟 worker 数要一起调 ——
池子小于 worker 数时,多出来的 worker 只会排队等连接
非法值一律退回默认,绝不因为配置写错就让推进器起不来
pub fn with_config(store: Store, owner: String, cfg: DriverConfig) -> Self
Sourcepub fn http_timeout_secs(&self) -> u64
pub fn http_timeout_secs(&self) -> u64
当前的分支调用超时(秒),启动日志里打出来方便确认配置生效
Sourcepub fn with_registry(self, r: Arc<Registry>) -> Self
pub fn with_registry(self, r: Arc<Registry>) -> Self
挂上进程内分支注册表 —— 嵌入式模式的入口
Sourcepub fn with_workflows(self, w: Arc<WorkflowRegistry>) -> Self
pub fn with_workflows(self, w: Arc<WorkflowRegistry>) -> Self
挂上 workflow 函数注册表
Sourcepub async fn run_forever(self, tick: Duration)
pub async fn run_forever(self, tick: Duration)
常驻推进器。起 workers 个并行的抢占循环。
§为什么可以直接并行,不需要新的并发控制
每个 worker 都走 lock_one_due —— 那是一次原子抢占(SQL 靠带条件的
UPDATE,Redis 靠 Lua 脚本),抢到才推。所以进程内 N 个 worker
跟部署 N 个实例是完全相同的情形,而后者的正确性已经有测试钉死了
(两个实例并发不会重复推进 / redis_多实例并发不重复推进)。
换句话说:这里没有引入新的竞态,只是把「多实例才能用上的并行」 在单进程内也用上。
§为什么不是把 process() 内部并行
一笔事务内部的分支必须按序(SAGA 就是顺序语义),并行只能跨事务。
§⚠ 为什么必须用 JoinSet 而不是 Vec
调用方是 tokio::spawn(driver.run_forever(..)),靠 abort 这个外层
任务来停推进器(Embedded 的 Drop 就是这么干的)。
tokio::spawn 出来的子任务是游离的:外层被 abort 掉,它们照跑不误。
这个坑实测撞出来过:跨进程重启_事务不丢且已完成的步骤不重做 里
第一个「进程」析构后,它那些僵尸 worker 还在抢同一笔事务,
而它们的 handler 永远返回 Unknown —— 于是第二个「进程」怎么等都推不完。
JoinSet 被 drop 时会把里面所有任务一并 abort,正好是我们要的语义。
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for Driver
impl !UnwindSafe for Driver
impl Freeze for Driver
impl Send for Driver
impl Sync for Driver
impl Unpin for Driver
impl UnsafeUnpin for Driver
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request