Concurrent writes: optimistic concurrency (version + If-Match → 412) so a co-editor's save is never lost silently
メンテナーはふだん 1 日以内に返信
関連するプルリクエストがすでにマージされています。
- #562 @silverbackdan による — マージ済み
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
調査の方向性
Start by tracing LastWriteResolver and how writable resources are serialized and updated; the payload does not name source files or tests. Check how versions could cover drafts, published resources, publish, and order changes, then review ETag and CORS handling. Done means writes with stale If-Match return 412 with the current resource, while writes without it continue to work.
索引モデルが issue の本文から書いたものです。
説明
Two admins saving the same field of the same resource at the same moment lose one save silently. Found while porting the TipTap co-editing reference (components-web-app/cwa-nuxt-module#429) with @cwa/nuxt 2.0.0-alpha.28 and this bundle at 2.0.0-alpha.35.
Reproduced
One HtmlContent draft open in two tabs (each with its own X-CWA-Client-Id), both typing in the same editor:
- The two PATCHes went out 1 ms apart, and both returned 200.
- Each body was the tab's whole
html, built from what that tab had before the other's save, so the second commit replaced the first. Neither client can tell that its save was overwritten. - In one run the PATCH responses carried
lastWrite.at17:00:36.235 (A) and 17:00:36.219 (B), and the stored html was A's.
Why the module can't settle it alone
lastWrite.at is stamped the first time the resource is serialised in the request (LastWriteResolver), not in commit order. With two workers a few milliseconds apart, the save that commits last can carry the earlier time. The module drops an update older than its stored copy, so a tab can drop the update that actually won and stay out of step with the stored data. That happened on the template stack in 3/3 runs.
Proposal: optimistic concurrency on writes
- Every writable resource exposes a version that changes on each write. Doctrine's
#[ORM\Version]integer would do, or an opaque token. Serialise it, for example in_metadata, and send it as the responseETag. - A PATCH (and PUT/DELETE) with
If-Matchsucceeds only if the stored version still matches; otherwise 412 Precondition Failed, with the current resource in the body so the client does not need a second request. - Without
If-Match, writes behave as now, so non-realtime clients keep working. - The version on Mercure updates lets a client order updates by version instead of by
lastWrite.at. - To decide: whether the version covers draft and published as one resource (the module treats them as one for conflicts), and how a publish (a draft's PATCH that removes the draft) and the order PATCH /
/movetake part. - CORS: expose
ETagand allowIf-Matchfor cross-origin apps.
Module side, once released
The module sends If-Match from the stored version on each save. On a 412 it shows the field's existing conflict (Use theirs / Keep mine) with the body as theirs, and Keep mine re-sends with the new version. It orders Mercure updates by version.
- 主要言語
- PHP
- スター
- 32
- フォーク
- 8
- 平均マージ
- 56分
- マージ済み PR(30日)
- 164
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
components-web-app/api-components-bundle のほかの issue
-
難易度 5/5 1週間以上 初心者へのやさしさ 30/100
components-web-app/api-components-bundle#333 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
components-web-app/api-components-bundle#325 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 52/100
components-web-app/api-components-bundle#259 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
components-web-app/api-components-bundle#186 ·
メンテナーはふだん 1 日以内に返信
components-web-app/api-components-bundle の issue をすべて見る
似ている issue
-
[Type] Bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 3 日以内に返信
-
ai-handle
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
-
bug P2
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 1 日以内に返信
-
🔍 Triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
coollabsio/coolify#12363 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
Automattic/blocks-engine#3618 ·
メンテナーはふだん 1 日以内に返信