-
Notifications
You must be signed in to change notification settings - Fork 1
refactor!: replace run_lock with a PackageKit-style transaction system
#23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
eatradish
wants to merge
54
commits into
master
Choose a base branch
from
feat/transaction-system
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
5c3a491
refactor!: replace `run_lock` with a PackageKit-style transaction system
eatradish 624e4c6
refactor: update examples for the transaction system
eatradish 12efc5c
fix(transaction): restrict cancellation to transaction owner
eatradish 6168c35
fix(transaction): make cancel and dequeue mutually exclusive
eatradish 79b738a
fix(transaction): bound the transaction queue
eatradish 2dc4dfd
fix(examples): subscribe to ResultReport before invoking transactions
eatradish 5a629e0
fix(examples): filter ResultReport by transaction id
eatradish 67e5455
feat: tag every Status signal with its transaction
eatradish 0c65b8f
fix(transaction): move dequeue to running atomically
eatradish bab425d
refactor!: per-transaction D-Bus objects (PackageKit-style)
eatradish 9da1dfb
fix(transaction): emit Queued before the transaction can run or be ca…
eatradish 2c7c8e0
fix: reclaim dormant transaction objects; sender-lock operations
eatradish 3ea0e7d
fix: drain progress forwarder before emitting ResultReport
eatradish ebcb484
refactor: split server.rs into focused modules
eatradish f60d475
fix: reserve the live-transaction slot atomically with quota check
eatradish af73efe
style: rustfmt transaction_object.rs
eatradish 79281b6
fix: synchronize reaping with the start claim
eatradish d5e538e
fix(transaction): snapshot queued and running atomically
eatradish 5dee627
fix(transaction): remove canceled entries from the bounded queue
eatradish 943cfb7
fix(examples): preserve progress ordering when merging signal streams
eatradish cdcb320
fix(examples): surface TransactionState stream for terminal cancellation
eatradish 560e2eb
fix(examples): check UpdatesList status before unwrapping result
eatradish 43ac0eb
refactor(transaction): merge signals into single TransactionEvent stream
eatradish 87d4266
fix(transaction): claim transaction before awaiting polkit
eatradish d3b8445
fix(transaction): expire authorization-pending transaction claims
eatradish 08a277c
fix(transaction): abort expired authorization checks
eatradish 57568a6
fix(transaction): synchronize claim expiry removal with enqueue
eatradish 2e1a7bd
fix(transaction): emit failure Result when task panics
eatradish caf933e
fix(auth): cancel remote PolicyKit check on authorization timeout
eatradish c3b0a37
fix(transaction): cancel PolicyKit check when reclaiming abandoned cl…
eatradish 896279e
fix(transaction): capture transaction states inside synchronized snap…
eatradish 734231e
fix(transaction): carry progress payload in a field so scalars serialize
eatradish b055e22
fix(examples): return an error for failed apply reports
eatradish 309e09b
fix(transaction): reject oversized transaction arguments before enque…
eatradish 78f4a77
fix(examples): treat event-stream closure as an apply failure
eatradish e4d3bb3
fix(transaction): bound the number of transaction arguments
eatradish 5ea75c8
fix(transaction): cancel authorization-pending transactions
eatradish 1913bfe
fix(transaction): reset dormant timeout after claim rollback
eatradish 63d8728
fix(examples): fail when Finished arrives without a result
eatradish e6b170a
fix(transaction): match claim generation before enqueueing
eatradish 6e53ae2
fix(transaction): distinguish rollback success from a missing cancell…
eatradish 2e8c76b
fix(transaction): allocate a claim generation for every invocation
eatradish 30486d8
test(transaction): skip session-bus test when no bus is available
eatradish 0836b3a
docs(README): describe TUM delivery via the transaction flow
eatradish 010fc18
refactor(transaction): extract shared operation runners
eatradish 766235b
refactor(transaction): split into directory module with separate tests
eatradish 1ca412b
fix(transaction): distinguish enqueued transactions from pending claims
eatradish dcac0e5
fix(transaction): use non-panicking elapsed checks in the reaper
eatradish c0072ed
fmt and clippy
eatradish a7e44b4
docs(limit): update
eatradish e341935
docs: update
eatradish f455aaf
docs: update
eatradish 8eb560e
fmt
eatradish 05f3f72
fmt
eatradish File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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,229 @@ | ||
| //! amo 事务客户端共享封装(PackageKit 风格对象路径,单流事件协议)。 | ||
| //! | ||
| //! 每个事务是独立的 D-Bus 对象(`/io/aosc/Amo/Transaction/<id>`): | ||
| //! - `CreateTransaction()` 返回路径,此时事务是"休眠"的,不做任何工作 | ||
| //! - 客户端先订阅该路径上的 `TransactionEvent` 信号 | ||
| //! - 再调用操作方法(Refresh / ApplyChanges / Simulate / UpdatesList)开工 | ||
| //! | ||
| //! 信号按对象路径隔离,客户端只会收到自己这个事务的信号——不需要按 | ||
| //! transaction_id 过滤,也没有"先订阅再调用"的竞态(工作永远在订阅 | ||
| //! 之后才开始)。 | ||
| //! | ||
| //! 单流协议:进度、状态、结果都走同一个 `TransactionEvent` 信号,服务端 | ||
| //! 保证发射顺序(进度 → 结果 → 终态)。客户端只需消费一个有序流,无需 | ||
| //! 自行合并/排序/处理取消边界。 | ||
|
|
||
| use anyhow::bail; | ||
| use futures::StreamExt; | ||
| use serde::{Deserialize, Serialize}; | ||
| use zbus::{Connection, proxy, zvariant::OwnedObjectPath}; | ||
|
|
||
| /// 事务最终状态。 | ||
| #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] | ||
| pub enum TaskStatus { | ||
| Success, | ||
| Failed(String), | ||
| } | ||
|
|
||
| /// ResultReport 载荷(TransactionEvent 的 Result 变体)。 | ||
| #[derive(Deserialize, Debug)] | ||
| #[allow(dead_code)] | ||
| pub struct TransactionResult { | ||
| pub transaction_id: u64, | ||
| pub role: String, | ||
| pub status: TaskStatus, | ||
| pub result: Option<serde_json::Value>, | ||
| } | ||
|
|
||
| /// TransactionState 信号的 state 字段。 | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] | ||
| #[serde(rename_all = "snake_case")] | ||
| pub enum TxState { | ||
| Queued, | ||
| Running, | ||
| Finished, | ||
| Cancelled, | ||
| } | ||
|
|
||
| /// TransactionState 载荷(TransactionEvent 的 State 变体)。 | ||
| #[derive(Deserialize, Debug)] | ||
| #[allow(dead_code)] | ||
| pub struct TransactionStateEvent { | ||
| pub transaction_id: u64, | ||
| pub role: String, | ||
| pub state: TxState, | ||
| } | ||
|
|
||
| /// 单流 `TransactionEvent` 信号载荷:一个事务的全部事件。 | ||
| /// | ||
| /// 注意:不能叫 `TransactionEvent`——zbus 的 proxy 宏会为 | ||
| /// `transaction_event` 信号生成同名类型,会冲突。 | ||
| #[derive(Deserialize, Debug)] | ||
| #[serde(tag = "type", rename_all = "snake_case")] | ||
| pub enum EventEnvelope { | ||
| /// 一条进度(原 Status 载荷)。服务端用带 `payload` 字段的 struct | ||
| /// 变体承载任意 JSON(含标量,如 oma 事件的 `"Done"`)。 | ||
| Progress { | ||
| /// 进度载荷。 | ||
| payload: serde_json::Value, | ||
| }, | ||
| /// 事务状态变更。 | ||
| State(TransactionStateEvent), | ||
| /// 事务结束报告。 | ||
| Result(TransactionResult), | ||
| } | ||
|
|
||
| /// 主接口:创建事务对象。 | ||
| #[proxy( | ||
| interface = "io.aosc.Amo1", | ||
| default_service = "io.aosc.Amo", | ||
| default_path = "/io/aosc/Amo" | ||
| )] | ||
| pub trait Amo { | ||
| fn create_transaction(&self) -> zbus::Result<OwnedObjectPath>; | ||
| fn get_transaction_list(&self) -> zbus::Result<String>; | ||
| } | ||
|
|
||
| /// 事务对象接口:路径在创建时返回,客户端用 builder 指定。 | ||
| #[proxy( | ||
| interface = "io.aosc.Amo.Transaction", | ||
| default_service = "io.aosc.Amo", | ||
| default_path = "/io/aosc/Amo" | ||
| )] | ||
| pub trait AmoTransaction { | ||
| fn refresh(&self) -> zbus::Result<()>; | ||
| fn apply_changes( | ||
| &self, | ||
| install: Vec<&str>, | ||
| remove: Vec<&str>, | ||
| upgrade: bool, | ||
| ) -> zbus::Result<()>; | ||
| fn simulate(&self, install: Vec<&str>, remove: Vec<&str>, upgrade: bool) -> zbus::Result<()>; | ||
| fn updates_list(&self) -> zbus::Result<()>; | ||
| fn cancel(&self) -> zbus::Result<()>; | ||
| fn destroy(&self) -> zbus::Result<()>; | ||
|
|
||
| #[zbus(signal)] | ||
| fn transaction_event(&self, event: String) -> zbus::Result<()>; | ||
| } | ||
|
|
||
| /// 一个已创建、已订阅信号的事务句柄。必须先 `create()` 再调用操作 | ||
| /// 方法,信号才不丢(D-Bus 信号不重放)。 | ||
| pub struct Tx { | ||
| pub proxy: AmoTransactionProxy<'static>, | ||
| events: TransactionEventStream, | ||
| } | ||
|
|
||
| /// 事务客户端:负责连接并创建事务对象。 | ||
| pub struct TransactionClient { | ||
| connection: Connection, | ||
| main: AmoProxy<'static>, | ||
| } | ||
|
|
||
| impl TransactionClient { | ||
| pub async fn connect() -> zbus::Result<Self> { | ||
| let connection = Connection::system().await?; | ||
| let main = AmoProxy::new(&connection).await?; | ||
| Ok(Self { connection, main }) | ||
| } | ||
|
|
||
| /// 创建事务对象并订阅它的信号(在调用操作方法之前)。 | ||
| pub async fn create(&self) -> zbus::Result<Tx> { | ||
| let path = self.main.create_transaction().await?; | ||
| let proxy = AmoTransactionProxy::builder(&self.connection) | ||
| .path(path)? | ||
| .build() | ||
| .await?; | ||
| let events = proxy.receive_transaction_event().await?; | ||
| Ok(Tx { proxy, events }) | ||
| } | ||
| } | ||
|
|
||
| /// 合并后的订阅事件,只属于本事务(路径隔离)。 | ||
| #[allow(dead_code)] | ||
| pub enum TxEvent { | ||
| /// 一条进度(TransactionEvent::Progress 载荷)。 | ||
| Status(serde_json::Value), | ||
| /// 事务状态变更(TransactionEvent::State 载荷)。 | ||
| State(TxState), | ||
| /// 事务结束报告(TransactionEvent::Result 载荷)。 | ||
| Result(TransactionResult), | ||
| } | ||
|
|
||
| /// 处理一条状态事件:Cancelled 与 Finished 都是终态,直接报错。 | ||
| /// | ||
| /// 单流协议保证发射顺序(进度 → 结果 → 终态):Finished 到达意味着 | ||
| /// Result 已不可能还在路上(同一有序流)——若还没收到 Result,说明 | ||
| /// 服务端结果发射失败(emit_result 错误只记日志),客户端必须报错 | ||
| /// 而不是永远等待。 | ||
| pub fn check_terminal_state(state: TxState) -> anyhow::Result<()> { | ||
| match state { | ||
| TxState::Cancelled => bail!("transaction cancelled"), | ||
| TxState::Finished => bail!("transaction finished without result"), | ||
| TxState::Queued | TxState::Running => Ok(()), | ||
| } | ||
| } | ||
|
|
||
| impl Tx { | ||
| /// 下一条事件(进度、状态或结果);流关闭(连接断开)时返回 `None`。 | ||
| /// | ||
| /// 单流协议:服务端保证发射顺序(进度 → 结果 → 终态),这里只需 | ||
| /// 消费一个有序流,无需跨流合并/排序。 | ||
| pub async fn next_event(&mut self) -> anyhow::Result<Option<TxEvent>> { | ||
| let Some(signal) = self.events.next().await else { | ||
| return Ok(None); | ||
| }; | ||
| let event: EventEnvelope = serde_json::from_str(&signal.args()?.event)?; | ||
| Ok(Some(match event { | ||
| EventEnvelope::Progress { payload } => TxEvent::Status(payload), | ||
| EventEnvelope::State(state) => TxEvent::State(state.state), | ||
| EventEnvelope::Result(report) => TxEvent::Result(report), | ||
| })) | ||
| } | ||
|
|
||
| /// 等到事务最终结果(跳过进度事件)。 | ||
| #[allow(dead_code)] | ||
| pub async fn wait_result(&mut self) -> anyhow::Result<TransactionResult> { | ||
| loop { | ||
| match self.next_event().await? { | ||
| Some(TxEvent::Result(report)) => return Ok(report), | ||
| Some(TxEvent::Status(_)) => continue, | ||
| // 终态(Cancelled/Finished)到达即报错:单流协议保证 | ||
| // 顺序(进度 → 结果 → 终态),Finished 意味着 Result | ||
| // 已不可能在路上——服务端 emit_result 失败时只记日志, | ||
| // 继续等会挂死。 | ||
| Some(TxEvent::State(state)) => check_terminal_state(state)?, | ||
| None => bail!("result stream closed"), | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn finished_without_result_is_an_error() { | ||
| let err = check_terminal_state(TxState::Finished).unwrap_err(); | ||
| assert!( | ||
| err.to_string().contains("finished without result"), | ||
| "unexpected error: {err}" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn cancelled_is_an_error() { | ||
| let err = check_terminal_state(TxState::Cancelled).unwrap_err(); | ||
| assert!( | ||
| err.to_string().contains("cancelled"), | ||
| "unexpected error: {err}" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn intermediate_states_are_not_terminal() { | ||
| assert!(check_terminal_state(TxState::Queued).is_ok()); | ||
| assert!(check_terminal_state(TxState::Running).is_ok()); | ||
| } | ||
| } | ||
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
无用注释?