Skip to content

Commit 791d6dd

Browse files
authored
Merge branch 'apache:master' into HDDS-15172
2 parents bf22cb8 + 0ded782 commit 791d6dd

30 files changed

Lines changed: 1510 additions & 190 deletions

File tree

hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingUtil.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
2727
import io.opentelemetry.context.Context;
2828
import io.opentelemetry.context.Scope;
29+
import io.opentelemetry.context.propagation.TextMapGetter;
2930
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
3031
import io.opentelemetry.sdk.OpenTelemetrySdk;
3132
import io.opentelemetry.sdk.resources.Resource;
@@ -36,6 +37,7 @@
3637
import java.util.Collections;
3738
import java.util.HashMap;
3839
import java.util.Map;
40+
import java.util.function.Function;
3941
import org.apache.hadoop.hdds.conf.ConfigurationSource;
4042
import org.apache.ratis.util.function.CheckedRunnable;
4143
import org.apache.ratis.util.function.CheckedSupplier;
@@ -387,4 +389,46 @@ private static Span buildSpan(String spanName) {
387389
return tracer.spanBuilder(spanName).setNoParent().startSpan();
388390
}
389391
}
392+
393+
/**
394+
* A TextMapGetter implementation to extract tracing info from getHeader.
395+
*/
396+
public static class HttpHeaderGetter implements TextMapGetter<Function<String, String>> {
397+
398+
@Override
399+
public Iterable<String> keys(Function<String, String> carrier) {
400+
// Not used during the extract call, so returning an empty list.
401+
return Collections.emptyList();
402+
}
403+
404+
@Override
405+
public String get(Function<String, String> carrier, String key) {
406+
return carrier == null ? null : carrier.apply(key);
407+
}
408+
}
409+
410+
public static TraceCloseable createActivatedSpanFromW3cHttpHeaders(
411+
String spanName, Function<String, String> getHeader, ConfigurationSource conf) {
412+
if (conf == null || !isTracingEnabled(conf)) {
413+
return () -> { };
414+
}
415+
416+
Context remote = W3CTraceContextPropagator.getInstance()
417+
.extract(Context.current(), getHeader, new HttpHeaderGetter());
418+
419+
if (!Span.fromContext(remote).getSpanContext().isValid()) {
420+
return createActivatedSpan(spanName);
421+
}
422+
423+
Span span = tracer.spanBuilder(spanName)
424+
.setParent(remote)
425+
.startSpan();
426+
427+
Scope scope = span.makeCurrent();
428+
429+
return () -> {
430+
scope.close();
431+
span.end();
432+
};
433+
}
390434
}

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public class ContainerSet implements Iterable<Container<?>> {
6363

6464
private static final Logger LOG = LoggerFactory.getLogger(ContainerSet.class);
6565

66+
/**
67+
* Max attempts to acquire {@link Container#writeLock()} while verifying this set's id → container mapping
68+
* is same (e.g. another thread may {@link #updateContainer} / DiskBalancer swap the instance).
69+
*/
70+
private static final int MAX_CONTAINER_MAP_SWAP_RETRIES = 5;
71+
6672
private final ConcurrentSkipListMap<Long, Container<?>> containerMap = new
6773
ConcurrentSkipListMap<>();
6874
private final ConcurrentSkipListSet<Long> missingContainerSet =
@@ -279,6 +285,54 @@ public Container<?> getContainer(long containerId) {
279285
return containerMap.get(containerId);
280286
}
281287

288+
/**
289+
* Returns the max retry for a container map swap while acquiring container lock.
290+
* @return max retry count
291+
*/
292+
public static int maxContainerMapSwapRetries() {
293+
return MAX_CONTAINER_MAP_SWAP_RETRIES;
294+
}
295+
296+
/**
297+
* Locks the container mapped to {@code containerId} for write, and verifies that the instance locked is still
298+
* the one stored in this set. If the mapping is swapped, unlocks and retries up to
299+
* {@link #maxContainerMapSwapRetries()} times, then returns {@code null}.
300+
*
301+
* @return the locked container, or {@code null} if the mapping could not be stabilized after all retries
302+
* @throws StorageContainerException with {@code CONTAINER_NOT_FOUND}
303+
*/
304+
@Nullable
305+
public Container<?> getContainerWithWriteLock(long containerId) throws StorageContainerException {
306+
for (int retry = 0; retry < MAX_CONTAINER_MAP_SWAP_RETRIES; retry++) {
307+
Container<?> candidate = getContainer(containerId);
308+
if (candidate == null) {
309+
throw new StorageContainerException(
310+
"Container " + containerId + " not found in ContainerSet.",
311+
ContainerProtos.Result.CONTAINER_NOT_FOUND);
312+
}
313+
candidate.writeLock();
314+
Container<?> current = getContainer(containerId);
315+
if (current == null) {
316+
candidate.writeUnlock();
317+
throw new StorageContainerException(
318+
"Container " + containerId + " not found in ContainerSet.",
319+
ContainerProtos.Result.CONTAINER_NOT_FOUND);
320+
}
321+
if (current != candidate) {
322+
candidate.writeUnlock();
323+
if (LOG.isDebugEnabled()) {
324+
LOG.debug("Container {} mapping changed during lock acquisition (attempt {}); retrying.",
325+
containerId, retry);
326+
}
327+
continue;
328+
}
329+
return candidate;
330+
}
331+
LOG.warn("Container {} mapping kept changing after {} attempts; giving up.",
332+
containerId, MAX_CONTAINER_MAP_SWAP_RETRIES);
333+
return null;
334+
}
335+
282336
/**
283337
* Removes container from both memory and database. This should be used when the containerData on disk has been
284338
* removed completely from the node.

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/DeleteBlocksCommandHandler.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -303,19 +303,31 @@ public DeleteBlockTransactionExecutionResult call() {
303303
if (keyValueContainer.
304304
writeLockTryLock(tryLockTimeoutMs, TimeUnit.MILLISECONDS)) {
305305
try {
306-
String schemaVersion = containerData
307-
.getSupportedSchemaVersionOrDefault();
308-
if (getSchemaHandlers().containsKey(schemaVersion)) {
309-
schemaHandlers.get(schemaVersion).handle(containerData, tx);
306+
// Re-fetch the container after acquiring the lock. DiskBalancer may have relocated
307+
// this container to a different disk while we waited — in that case, the container
308+
// object in ContainerSet has changed and containerData points to the old replica.
309+
Container<?> current = containerSet.getContainer(containerId);
310+
if (current == null || current.getContainerData() != containerData) {
311+
LOG.debug("DeleteBlocks: containerData for container {} is stale "
312+
+ ", Will retry on the new replica.",
313+
containerId);
314+
lockAcquisitionFailed = true;
315+
txResultBuilder.setContainerID(containerId).setSuccess(false);
310316
} else {
311-
throw new UnsupportedOperationException(
312-
"Only schema version 1,2,3 are supported.");
317+
String schemaVersion = containerData
318+
.getSupportedSchemaVersionOrDefault();
319+
if (getSchemaHandlers().containsKey(schemaVersion)) {
320+
schemaHandlers.get(schemaVersion).handle(containerData, tx);
321+
} else {
322+
throw new UnsupportedOperationException(
323+
"Only schema version 1,2,3 are supported.");
324+
}
325+
txResultBuilder.setContainerID(containerId)
326+
.setSuccess(true);
313327
}
314328
} finally {
315329
keyValueContainer.writeUnlock();
316330
}
317-
txResultBuilder.setContainerID(containerId)
318-
.setSuccess(true);
319331
} else {
320332
lockAcquisitionFailed = true;
321333
txResultBuilder.setContainerID(containerId)

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -512,19 +512,19 @@ public BackgroundTaskResult call() {
512512
return BackgroundTaskResult.EmptyTaskResult.newResult();
513513
}
514514

515-
// Double check container state before acquiring lock to start move process.
516-
// Container state may have changed after selection.
517-
State containerState = container.getContainerData().getState();
518-
if (!movableContainerStates.contains(containerState)) {
519-
LOG.warn("Container {} is in {} state, skipping move process.", containerId, containerState);
520-
postCall(false, startTime);
521-
return BackgroundTaskResult.EmptyTaskResult.newResult();
522-
}
523-
524515
// hold read lock on the container first, to avoid other threads to update the container state,
525516
// such as block deletion.
526517
container.readLock();
527518
try {
519+
// Double check container state after acquiring lock to start move process.
520+
// Container state may have changed after selection.
521+
State containerState = container.getContainerData().getState();
522+
if (!movableContainerStates.contains(containerState)) {
523+
LOG.warn("Container {} is in {} state, skipping move process.", containerId, containerState);
524+
moveSucceeded = false;
525+
return BackgroundTaskResult.EmptyTaskResult.newResult();
526+
}
527+
528528
// Step 1: Copy container to new Volume's tmp Dir
529529
diskBalancerTmpDir = getDiskBalancerTmpDir(destVolume)
530530
.resolve(String.valueOf(containerId));
@@ -580,6 +580,10 @@ public BackgroundTaskResult call() {
580580
// old caller can still hold the old Container object.
581581
ozoneContainer.getContainerSet().updateContainer(newContainer);
582582
destVolume.incrementUsedSpace(containerSize);
583+
584+
// Test injector: ContainerSet now references newContainer while this thread still holds
585+
// readLock on the old replica.
586+
pauseInjector();
583587
// Mark old container as DELETED and persist state.
584588
// markContainerForDelete require writeLock, so release readLock first
585589
container.readUnlock();
@@ -597,13 +601,7 @@ public BackgroundTaskResult call() {
597601
metrics.incrSuccessBytes(containerSize);
598602
totalBalancedBytes.addAndGet(containerSize);
599603
} catch (IOException e) {
600-
if (injector != null) {
601-
try {
602-
injector.pause();
603-
} catch (IOException ex) {
604-
// do nothing
605-
}
606-
}
604+
pauseInjector();
607605
moveSucceeded = false;
608606
LOG.warn("Failed to move container {}", containerId, e);
609607
if (diskBalancerTmpDir != null) {
@@ -861,6 +859,17 @@ public static void setInjector(FaultInjector instance) {
861859
injector = instance;
862860
}
863861

862+
// call FaultInjector#pause when an injector is registered; ignore IOException.
863+
private static void pauseInjector() {
864+
if (injector != null) {
865+
try {
866+
injector.pause();
867+
} catch (IOException ex) {
868+
// do nothing
869+
}
870+
}
871+
}
872+
864873
@VisibleForTesting
865874
public void setReplicaDeletionDelay(long durationMills) {
866875
this.replicaDeletionDelay = durationMills;

hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerVolumeCalculation.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,41 @@ public static List<VolumeFixedUsage> getVolumeUsages(MutableVolumeSet volumeSet,
6767
* @param volumes Immutable list of volumes
6868
* from each source volume during container moves
6969
* @return Ideal usage as a ratio (used space / total capacity)
70-
* @throws IllegalArgumentException if total capacity is zero
7170
*/
7271
public static double getIdealUsage(List<VolumeFixedUsage> volumes) {
72+
if (volumes == null || volumes.isEmpty()) {
73+
return 0.0;
74+
}
75+
7376
long totalCapacity = 0L, totalEffectiveUsed = 0L;
74-
77+
7578
for (VolumeFixedUsage volumeUsage : volumes) {
76-
totalCapacity += volumeUsage.getUsage().getCapacity();
77-
totalEffectiveUsed += volumeUsage.getEffectiveUsed();
79+
final long capacity = volumeUsage.getUsage().getCapacity();
80+
if (capacity < 0) {
81+
throw new IllegalArgumentException(
82+
"Negative capacity = " + capacity + ": " + volumeUsage.getVolume());
83+
}
84+
85+
final long effectiveUsed = volumeUsage.getEffectiveUsed();
86+
if (effectiveUsed < 0) {
87+
throw new IllegalArgumentException(
88+
"Negative effective used = " + effectiveUsed + ": "
89+
+ volumeUsage.getVolume());
90+
}
91+
if (effectiveUsed > capacity) {
92+
throw new IllegalArgumentException(
93+
"Effective used = " + effectiveUsed + " > capacity = "
94+
+ capacity + ": " + volumeUsage.getVolume());
95+
}
96+
97+
totalCapacity += capacity;
98+
totalEffectiveUsed += effectiveUsed;
7899
}
79-
100+
101+
if (totalCapacity == 0) {
102+
return 0.0;
103+
}
104+
80105
return ((double) (totalEffectiveUsed)) / totalCapacity;
81106
}
82107

0 commit comments

Comments
 (0)