Skip to content

Commit 888e9a2

Browse files
authored
Document read-after-write semantics for getRegister (#131522)
Clarifies in its documentation that `BlobContainer#getRegister` offers only read-after-write semantics rather than full linearizability, and adds comments to its callers justifying why this is still safe.
1 parent a692cbd commit 888e9a2

File tree

5 files changed

+28
-8
lines changed

5 files changed

+28
-8
lines changed

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobContainer.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,13 @@ void run(BytesReference expected, BytesReference updated, ActionListener<Optiona
830830

831831
.<Void>newForked(l -> ensureOtherUploadsComplete(uploadId, uploadIndex, currentUploads, l))
832832

833-
// Step 4: Read the current register value.
833+
// Step 4: Read the current register value. Note that getRegister only has read-after-write semantics but that's ok here as:
834+
// - all earlier uploads are now complete,
835+
// - our upload is not completing yet, and
836+
// - later uploads can only be completing if they have already aborted ours.
837+
// Thus if our operation ultimately succeeds then there cannot have been any concurrent writes in flight, so this read
838+
// cannot have observed a stale value, whereas if our operation ultimately fails then it doesn't matter what this read
839+
// observes.
834840

835841
.<OptionalBytesReference>andThen(l -> getRegister(purpose, rawKey, l))
836842

server/src/main/java/org/elasticsearch/common/blobstore/BlobContainer.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,10 @@ default void copyBlob(
304304

305305
/**
306306
* Atomically sets the value stored at the given key to {@code updated} if the {@code current value == expected}.
307-
* Keys not yet used start at initial value 0. Returns the current value (before it was updated).
307+
* If a key has not yet been used as a register, its initial value is an empty {@link BytesReference}.
308+
* <p>
309+
* This operation, together with {@link #compareAndSetRegister}, must have linearizable semantics: a collection of such operations must
310+
* act as if they operate serially, with each operation taking place at some instant in between its invocation and its completion.
308311
*
309312
* @param purpose The purpose of the operation
310313
* @param key key of the value to update
@@ -323,9 +326,12 @@ void compareAndExchangeRegister(
323326

324327
/**
325328
* Atomically sets the value stored at the given key to {@code updated} if the {@code current value == expected}.
326-
* Keys not yet used start at initial value 0.
329+
* If a key has not yet been used as a register, its initial value is an empty {@link BytesReference}.
330+
* <p>
331+
* This operation, together with {@link #compareAndExchangeRegister}, must have linearizable semantics: a collection of such operations
332+
* must act as if they operate serially, with each operation taking place at some instant in between its invocation and its completion.
327333
*
328-
* @param purpose
334+
* @param purpose The purpose of the operation
329335
* @param key key of the value to update
330336
* @param expected the expected value
331337
* @param updated the new value
@@ -350,7 +356,12 @@ default void compareAndSetRegister(
350356

351357
/**
352358
* Gets the value set by {@link #compareAndSetRegister} or {@link #compareAndExchangeRegister} for a given key.
353-
* If a key has not yet been used, the initial value is an empty {@link BytesReference}.
359+
* If a key has not yet been used as a register, its initial value is an empty {@link BytesReference}.
360+
* <p>
361+
* This operation has read-after-write consistency with respect to writes performed using {@link #compareAndExchangeRegister} and
362+
* {@link #compareAndSetRegister}, but does not guarantee full linearizability. In particular, a {@code getRegister} performed during
363+
* one of these write operations may return either the old or the new value, and a caller may therefore observe the old value
364+
* <i>after</i> observing the new value, as long as both such read operations take place before the write operation completes.
354365
*
355366
* @param purpose The purpose of the operation
356367
* @param key key of the value to get

server/src/main/java/org/elasticsearch/common/blobstore/support/BlobContainerUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ public static void ensureValidRegisterContent(BytesReference bytesReference) {
3333
}
3434

3535
/**
36-
* Many blob stores have consistent (linearizable/atomic) read semantics and in these casees it is safe to implement {@link
37-
* BlobContainer#getRegister} by simply reading the blob using this utility.
38-
*
36+
* Many blob stores have consistent read-after-write semantics and in these cases it is safe to implement
37+
* {@link BlobContainer#getRegister} by simply reading the blob using this utility.
38+
* <p>
3939
* NB it is not safe for the supplied stream to resume a partial downloads, because the resumed stream may see a different state from
4040
* the original.
4141
*/

x-pack/plugin/snapshot-repo-test-kit/src/main/java/org/elasticsearch/repositories/blobstore/testkit/analyze/ContendedRegisterAnalyzeAction.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ public void onFailure(Exception e) {
156156
};
157157

158158
if (request.getInitialRead() > request.getRequestCount()) {
159+
// This is just the initial read, so we can use getRegister() despite its weaker read-after-write semantics: all subsequent
160+
// operations of this action use compareAndExchangeRegister() and do not rely on this value being accurate.
159161
blobContainer.getRegister(OperationPurpose.REPOSITORY_ANALYSIS, registerName, initialValueListener.delegateFailure((l, r) -> {
160162
if (r.isPresent()) {
161163
l.onResponse(r);

x-pack/plugin/snapshot-repo-test-kit/src/main/java/org/elasticsearch/repositories/blobstore/testkit/analyze/RepositoryAnalyzeAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ private Runnable finalRegisterValueVerifier(String registerName, int expectedFin
651651
case 0 -> new CheckedConsumer<ActionListener<OptionalBytesReference>, Exception>() {
652652
@Override
653653
public void accept(ActionListener<OptionalBytesReference> listener) {
654+
// All register operations have completed by this point so getRegister is safe
654655
getBlobContainer().getRegister(OperationPurpose.REPOSITORY_ANALYSIS, registerName, listener);
655656
}
656657

0 commit comments

Comments
 (0)