What happens
The host honors If-Match / If-None-Match correctly in the sequential case (stale If-Match PUT/DELETE → 412; If-None-Match: * on an existing resource → 412; If-None-Match GET → 304). But the check is check-then-write, not atomic: two concurrent PUTs carrying the same currently-valid If-Match ETag both return 2xx, and the loser is silently overwritten.
Both callers are told success; one write is gone. The same two PUTs run sequentially 412 correctly — this is purely a race between the precondition check and the write.
Measured
Witnessed live in a test (two overlapping conditional PUTs → 204/204, one body lost; sequential → 204/412 as expected):
Why it matters
Any client using ETags for optimistic concurrency (remoteStorage clients, DAV clients, the sparql UPDATE bridge, anything doing GET→transform→PUT-If-Match) can lose writes under load while believing the protocol protected it. This is the one of the plugin experiment's findings with real data-loss consequences, and it is unfixable from outside the host.
Suggested fix
Make the precondition check and the write atomic on the LDP write path — e.g. take a per-path lock (or serialize via a per-path promise queue) around stat→compare→write, or write-to-temp + atomic rename with the ETag re-checked under the lock.
What happens
The host honors
If-Match/If-None-Matchcorrectly in the sequential case (staleIf-MatchPUT/DELETE → 412;If-None-Match: *on an existing resource → 412;If-None-MatchGET → 304). But the check is check-then-write, not atomic: two concurrent PUTs carrying the same currently-validIf-MatchETag both return 2xx, and the loser is silently overwritten.Both callers are told success; one write is gone. The same two PUTs run sequentially 412 correctly — this is purely a race between the precondition check and the write.
Measured
Witnessed live in a test (two overlapping conditional PUTs → 204/204, one body lost; sequential → 204/412 as expected):
Why it matters
Any client using ETags for optimistic concurrency (remoteStorage clients, DAV clients, the sparql UPDATE bridge, anything doing GET→transform→PUT-If-Match) can lose writes under load while believing the protocol protected it. This is the one of the plugin experiment's findings with real data-loss consequences, and it is unfixable from outside the host.
Suggested fix
Make the precondition check and the write atomic on the LDP write path — e.g. take a per-path lock (or serialize via a per-path promise queue) around stat→compare→write, or write-to-temp + atomic rename with the ETag re-checked under the lock.