Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Concurrent writes: optimistic concurrency (version + If-Match → 412) so a co-editor's save is never lost silently

クローズ
#561 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

関連するプルリクエストがすでにマージされています。

  • #562 @silverbackdan による — マージ済み

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
php, symfony
領域
api, backend, databases

調査の方向性

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 の本文から書いたものです。

説明

enhancement

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.at 17: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 response ETag.
  • A PATCH (and PUT/DELETE) with If-Match succeeds 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 / /move take part.
  • CORS: expose ETag and allow If-Match for 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 を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

components-web-app/api-components-bundle のほかの issue

components-web-app/api-components-bundle の issue をすべて見る

似ている issue

PHP の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。