Skip to content

Commit fce2929

Browse files
committed
more stuff
1 parent 3028183 commit fce2929

4 files changed

Lines changed: 62 additions & 67 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static String compute(@Nullable byte[] bytes)
6565
* Wildcard {@code *} matches any existing value. A comma-separated list is
6666
* satisfied if any element matches.
6767
*/
68-
public static boolean matches(String ifMatchHeader, @Nullable byte[] currentBytes)
68+
public static boolean matches(@Nullable String ifMatchHeader, @Nullable byte[] currentBytes)
6969
{
7070
if (ifMatchHeader == null) {
7171
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
);

server/src/main/java/org/apache/druid/server/coordinator/CoordinatorConfigManager.java

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -97,25 +97,6 @@ public CoordinatorDynamicConfig convertBytesToDynamicConfig(@Nullable byte[] byt
9797
);
9898
}
9999

100-
public ConfigManager.SetResult setDynamicConfig(CoordinatorDynamicConfig config, AuditInfo auditInfo)
101-
{
102-
return setDynamicConfig(config, null, auditInfo);
103-
}
104-
105-
public ConfigManager.SetResult setDynamicConfig(
106-
CoordinatorDynamicConfig config,
107-
@Nullable String ifMatchEtag,
108-
AuditInfo auditInfo
109-
)
110-
{
111-
return jacksonConfigManager.setIfMatch(
112-
CoordinatorDynamicConfig.CONFIG_KEY,
113-
ifMatchEtag,
114-
config,
115-
auditInfo
116-
);
117-
}
118-
119100
public ConfigManager.SetResult updateDynamicConfig(
120101
UnaryOperator<CoordinatorDynamicConfig> operator,
121102
@Nullable String ifMatchEtag,
@@ -205,14 +186,22 @@ public ConfigManager.SetResult getAndUpdateCompactionConfig(
205186

206187
if (current.equals(updated)) {
207188
return ConfigManager.SetResult.ok();
208-
} else {
209-
return jacksonConfigManager.set(
210-
DruidCompactionConfig.CONFIG_KEY,
211-
currentBytes,
212-
updated,
213-
auditInfo
189+
}
190+
final ConfigManager.SetResult result = jacksonConfigManager.set(
191+
DruidCompactionConfig.CONFIG_KEY,
192+
currentBytes,
193+
updated,
194+
auditInfo
195+
);
196+
// Under If-Match, a lost CAS means a concurrent writer committed between our
197+
// read and write: the precondition no longer holds, so report it as such
198+
// rather than as a retryable failure (conditional writes must not auto-retry).
199+
if (ifMatchEtag != null && result.isRetryable()) {
200+
return ConfigManager.SetResult.preconditionFailed(
201+
new IllegalStateException("If-Match precondition failed (concurrent update) for compaction config")
214202
);
215203
}
204+
return result;
216205
}
217206

218207
public DruidCompactionConfig convertBytesToCompactionConfig(@Nullable byte[] bytes)
@@ -257,14 +246,6 @@ public boolean updateCompactionTaskSlots(
257246
return updateConfigHelper(operator, ifMatchEtag, auditInfo);
258247
}
259248

260-
public boolean updateClusterCompactionConfig(
261-
ClusterCompactionConfig config,
262-
AuditInfo auditInfo
263-
)
264-
{
265-
return updateClusterCompactionConfig(config, null, auditInfo);
266-
}
267-
268249
public boolean updateClusterCompactionConfig(
269250
ClusterCompactionConfig config,
270251
@Nullable String ifMatchEtag,
@@ -409,18 +390,14 @@ private boolean updateConfigHelper(
409390
return true;
410391
}
411392

412-
// With If-Match, a retryable CAS failure means a concurrent writer beat us
413-
// between the precondition check and the CAS — surface as 412 too.
414-
final boolean preconditionFailed = setResult.isPreconditionFailed()
415-
|| (ifMatchEtag != null && setResult.isRetryable());
416-
if (preconditionFailed) {
393+
// getAndUpdateCompactionConfig already converts a lost CAS under If-Match into
394+
// a precondition failure, so a retryable result here is only a genuine
395+
// (non-conditional) CAS conflict that exhausted its retries.
396+
if (setResult.isPreconditionFailed()) {
417397
log.info("If-Match precondition failed on compaction config update");
418-
final String message = setResult.isPreconditionFailed()
419-
? setResult.getException().getMessage()
420-
: "If-Match precondition failed (concurrent update) on compaction config";
421398
throw DruidException.forPersona(DruidException.Persona.USER)
422399
.ofCategory(DruidException.Category.PRECONDITION_FAILED)
423-
.build(message);
400+
.build(setResult.getException().getMessage());
424401
}
425402

426403
if (setResult.getException() instanceof NoSuchElementException) {

0 commit comments

Comments
 (0)