Skip to content

Commit 133da6d

Browse files
committed
more stuff
1 parent 3028183 commit 133da6d

10 files changed

Lines changed: 215 additions & 118 deletions

File tree

indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordCompactionResource.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,19 @@ public Response updateClusterCompactionConfig(
122122
@Context HttpServletRequest req
123123
)
124124
{
125-
final AuditInfo auditInfo = AuthorizationUtils.buildAuditInfo(req);
126-
return ServletResourceUtils.buildUpdateResponse(
127-
() -> configManager.updateClusterCompactionConfig(
128-
updatePayload,
129-
DynamicConfigEtagHelper.getIfMatch(req),
130-
auditInfo
131-
)
132-
);
125+
try {
126+
final AuditInfo auditInfo = AuthorizationUtils.buildAuditInfo(req);
127+
return DynamicConfigEtagHelper.buildSetResultUpdateResponse(
128+
configManager.updateClusterCompactionConfig(
129+
updatePayload,
130+
DynamicConfigEtagHelper.getIfMatch(req),
131+
auditInfo
132+
)
133+
);
134+
}
135+
catch (DruidException e) {
136+
return ServletResourceUtils.buildErrorResponseFrom(e);
137+
}
133138
}
134139

135140
@GET

indexing-service/src/test/java/org/apache/druid/indexing/overlord/http/OverlordCompactionResourceTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.druid.audit.AuditManager;
2525
import org.apache.druid.client.coordinator.CoordinatorClient;
2626
import org.apache.druid.common.config.ConfigEtag;
27+
import org.apache.druid.common.config.ConfigManager;
2728
import org.apache.druid.error.DruidExceptionMatcher;
2829
import org.apache.druid.error.ErrorResponse;
2930
import org.apache.druid.indexer.CompactionEngine;
@@ -160,7 +161,7 @@ public void test_updateClusterConfig()
160161
EasyMock.anyObject(),
161162
EasyMock.anyObject()
162163
))
163-
.andReturn(true)
164+
.andReturn(ConfigManager.SetResult.ok())
164165
.once();
165166

166167
setupMockRequestForAudit();

processing/src/main/java/org/apache/druid/common/config/ConfigEtag.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@
3232
public final class ConfigEtag
3333
{
3434
private static final int ETAG_HASH_BYTES = 16;
35+
private static final ThreadLocal<MessageDigest> SHA_256 = ThreadLocal.withInitial(() -> {
36+
try {
37+
return MessageDigest.getInstance("SHA-256");
38+
}
39+
catch (NoSuchAlgorithmException e) {
40+
throw new IllegalStateException("SHA-256 not available", e);
41+
}
42+
});
3543

3644
private ConfigEtag()
3745
{
@@ -48,24 +56,19 @@ public static String compute(@Nullable byte[] bytes)
4856
if (bytes == null) {
4957
return null;
5058
}
51-
try {
52-
MessageDigest md = MessageDigest.getInstance("SHA-256");
53-
byte[] full = md.digest(bytes);
54-
byte[] truncated = Arrays.copyOf(full, ETAG_HASH_BYTES);
55-
return "\"" + Base64.getUrlEncoder().withoutPadding().encodeToString(truncated) + "\"";
56-
}
57-
catch (NoSuchAlgorithmException e) {
58-
// SHA-256 is required by every JRE.
59-
throw new IllegalStateException("SHA-256 not available", e);
60-
}
59+
final MessageDigest md = SHA_256.get();
60+
md.reset();
61+
final byte[] full = md.digest(bytes);
62+
final byte[] truncated = Arrays.copyOf(full, ETAG_HASH_BYTES);
63+
return "\"" + Base64.getUrlEncoder().withoutPadding().encodeToString(truncated) + "\"";
6164
}
6265

6366
/**
6467
* Whether {@code ifMatchHeader} matches the ETag of {@code currentBytes}.
6568
* Wildcard {@code *} matches any existing value. A comma-separated list is
6669
* satisfied if any element matches.
6770
*/
68-
public static boolean matches(String ifMatchHeader, @Nullable byte[] currentBytes)
71+
public static boolean matches(@Nullable String ifMatchHeader, @Nullable byte[] currentBytes)
6972
{
7073
if (ifMatchHeader == null) {
7174
return true;

processing/src/main/java/org/apache/druid/common/config/ConfigManager.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,27 @@ private MetadataCASUpdate createMetadataCASUpdate(
283283

284284
public static class SetResult
285285
{
286-
private static final SetResult SUCCESS = new SetResult(null, false, false);
287-
private final Exception exception;
286+
/**
287+
* Outcome of a {@link #set} attempt. Mutually exclusive, so callers never
288+
* have to reconcile overlapping flags.
289+
*/
290+
private enum Status
291+
{
292+
/** The write committed. */
293+
OK,
294+
/** A concurrent writer won the CAS; the caller may re-read and retry. */
295+
RETRYABLE,
296+
/** A client-supplied precondition (e.g. {@code If-Match}) failed; maps to HTTP 412. */
297+
PRECONDITION_FAILED,
298+
/** A non-retryable failure (bad input, manager not started, etc.). */
299+
FAILURE
300+
}
288301

289-
private final boolean retryableException;
290-
private final boolean preconditionFailed;
302+
private static final SetResult SUCCESS = new SetResult(Status.OK, null);
303+
304+
private final Status status;
305+
@Nullable
306+
private final Exception exception;
291307

292308
public static SetResult ok()
293309
{
@@ -296,42 +312,42 @@ public static SetResult ok()
296312

297313
public static SetResult failure(Exception e)
298314
{
299-
return new SetResult(e, false, false);
315+
return new SetResult(Status.FAILURE, e);
300316
}
301317

302318
public static SetResult retryableFailure(Exception e)
303319
{
304-
return new SetResult(e, true, false);
320+
return new SetResult(Status.RETRYABLE, e);
305321
}
306322

307323
/** Client-supplied precondition (e.g. {@code If-Match}) failed; maps to HTTP 412. */
308324
public static SetResult preconditionFailed(Exception e)
309325
{
310-
return new SetResult(e, false, true);
326+
return new SetResult(Status.PRECONDITION_FAILED, e);
311327
}
312328

313-
private SetResult(@Nullable Exception exception, boolean retryableException, boolean preconditionFailed)
329+
private SetResult(Status status, @Nullable Exception exception)
314330
{
331+
this.status = status;
315332
this.exception = exception;
316-
this.retryableException = retryableException;
317-
this.preconditionFailed = preconditionFailed;
318333
}
319334

320335
public boolean isOk()
321336
{
322-
return exception == null;
337+
return status == Status.OK;
323338
}
324339

325340
public boolean isRetryable()
326341
{
327-
return retryableException;
342+
return status == Status.RETRYABLE;
328343
}
329344

330345
public boolean isPreconditionFailed()
331346
{
332-
return preconditionFailed;
347+
return status == Status.PRECONDITION_FAILED;
333348
}
334349

350+
@Nullable
335351
public Exception getException()
336352
{
337353
return exception;

processing/src/main/java/org/apache/druid/common/config/JacksonConfigManager.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,7 @@ public <T> SetResult setIfMatch(
177177
new IllegalStateException("If-Match precondition failed for key[" + key + "]")
178178
);
179179
}
180-
final SetResult result = set(key, currentBytes, newValue, auditInfo);
181-
// Retryable CAS failure here = concurrent writer between our read and CAS;
182-
// surface as precondition failed since the caller asked us to reject that.
183-
if (!result.isOk() && result.isRetryable()) {
184-
return SetResult.preconditionFailed(
185-
new IllegalStateException("If-Match precondition failed (concurrent update) for key[" + key + "]")
186-
);
187-
}
188-
return result;
180+
return casConflictAsPreconditionFailed(set(key, currentBytes, newValue, auditInfo), key);
189181
}
190182

191183
/**
@@ -225,8 +217,18 @@ public <T> SetResult setIfMatch(
225217
if (ifMatchEtag == null) {
226218
return set(key, newValue, auditInfo);
227219
}
228-
final SetResult result = set(key, currentBytes, newValue, auditInfo);
229-
if (!result.isOk() && result.isRetryable()) {
220+
return casConflictAsPreconditionFailed(set(key, currentBytes, newValue, auditInfo), key);
221+
}
222+
223+
/**
224+
* Maps a CAS-conflict ({@link SetResult#isRetryable() retryable}) outcome to a
225+
* precondition failure. A conditional write that loses the CAS means another
226+
* writer committed between our read and write, so the supplied {@code If-Match}
227+
* no longer describes the stored value — the caller must re-read and retry.
228+
*/
229+
private static SetResult casConflictAsPreconditionFailed(SetResult result, String key)
230+
{
231+
if (result.isRetryable()) {
230232
return SetResult.preconditionFailed(
231233
new IllegalStateException("If-Match precondition failed (concurrent update) for key[" + key + "]")
232234
);

processing/src/test/java/org/apache/druid/common/config/JacksonConfigManagerTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,35 @@ public void testSetIfMatchTransformPreconditionFailsWhenCompareAndSwapDisabled()
286286
Mockito.verify(mockAuditManager, Mockito.never()).doAudit(Mockito.any(AuditEntry.class));
287287
}
288288

289+
@Test
290+
public void testSetIfMatchCasConflictReturnsPreconditionFailed()
291+
{
292+
final String key = "key";
293+
final TestConfig val = new TestConfig("v", "s", 1);
294+
final AuditInfo auditInfo = new AuditInfo("a", "i", "c", "ip");
295+
final byte[] currentBytes = "current".getBytes(java.nio.charset.StandardCharsets.UTF_8);
296+
297+
Mockito.when(mockConfigManager.isCompareAndSwapEnabled()).thenReturn(true);
298+
Mockito.when(mockConfigManager.getCurrentBytes(key)).thenReturn(currentBytes);
299+
Mockito.when(mockConfigManager.set(
300+
Mockito.eq(key),
301+
Mockito.any(ConfigSerde.class),
302+
Mockito.eq(currentBytes),
303+
Mockito.eq(val)
304+
)).thenReturn(ConfigManager.SetResult.retryableFailure(new IllegalStateException("Config value has changed")));
305+
306+
final ConfigManager.SetResult result = jacksonConfigManager.setIfMatch(
307+
key,
308+
ConfigEtag.compute(currentBytes),
309+
val,
310+
auditInfo
311+
);
312+
313+
Assertions.assertFalse(result.isOk());
314+
Assertions.assertTrue(result.isPreconditionFailed());
315+
Assertions.assertFalse(result.isRetryable());
316+
}
317+
289318
@Test
290319
public void testConvertByteToConfigWithNullConfigInByte()
291320
{

0 commit comments

Comments
 (0)