Skip to content

Commit 4a3662c

Browse files
committed
[HUDI-2875] allow memory executor exit gracefully. And fix concurrent call of mergehandle
1 parent 19689ab commit 4a3662c

12 files changed

Lines changed: 75 additions & 34 deletions

File tree

hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieConcatHandle.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.apache.log4j.LogManager;
3535
import org.apache.log4j.Logger;
3636

37+
import javax.annotation.concurrent.NotThreadSafe;
38+
3739
import java.io.IOException;
3840
import java.util.Collections;
3941
import java.util.Iterator;
@@ -65,6 +67,7 @@
6567
* Users should ensure there are no duplicates when "insert" operation is used and if the respective config is enabled. So, above scenario should not
6668
* happen and every batch should have new records to be inserted. Above example is for illustration purposes only.
6769
*/
70+
@NotThreadSafe
6871
public class HoodieConcatHandle<T extends HoodieRecordPayload, I, K, O> extends HoodieMergeHandle<T, I, K, O> {
6972

7073
private static final Logger LOG = LogManager.getLogger(HoodieConcatHandle.class);

hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieCreateHandle.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,15 @@
4242
import org.apache.log4j.LogManager;
4343
import org.apache.log4j.Logger;
4444

45+
import javax.annotation.concurrent.NotThreadSafe;
46+
4547
import java.io.IOException;
4648
import java.util.Collections;
4749
import java.util.Iterator;
4850
import java.util.List;
4951
import java.util.Map;
5052

53+
@NotThreadSafe
5154
public class HoodieCreateHandle<T extends HoodieRecordPayload, I, K, O> extends HoodieWriteHandle<T, I, K, O> {
5255

5356
private static final Logger LOG = LogManager.getLogger(HoodieCreateHandle.class);

hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieMergeHandle.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
import org.apache.log4j.LogManager;
5454
import org.apache.log4j.Logger;
5555

56+
import javax.annotation.concurrent.NotThreadSafe;
57+
5658
import java.io.IOException;
5759
import java.util.Collections;
5860
import java.util.HashSet;
@@ -92,6 +94,7 @@
9294
*
9395
* </p>
9496
*/
97+
@NotThreadSafe
9598
public class HoodieMergeHandle<T extends HoodieRecordPayload, I, K, O> extends HoodieWriteHandle<T, I, K, O> {
9699

97100
private static final Logger LOG = LogManager.getLogger(HoodieMergeHandle.class);

hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieSortedMergeHandle.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
import org.apache.avro.generic.GenericRecord;
3434

35+
import javax.annotation.concurrent.NotThreadSafe;
36+
3537
import java.io.IOException;
3638
import java.util.Iterator;
3739
import java.util.List;
@@ -45,6 +47,7 @@
4547
* The implementation performs a merge-sort by comparing the key of the record being written to the list of
4648
* keys in newRecordKeys (sorted in-memory).
4749
*/
50+
@NotThreadSafe
4851
public class HoodieSortedMergeHandle<T extends HoodieRecordPayload, I, K, O> extends HoodieMergeHandle<T, I, K, O> {
4952

5053
private Queue<String> newRecordKeysSorted = new PriorityQueue<>();

hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieUnboundedCreateHandle.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@
2828
import org.apache.log4j.LogManager;
2929
import org.apache.log4j.Logger;
3030

31+
import javax.annotation.concurrent.NotThreadSafe;
32+
3133
/**
3234
* A HoodieCreateHandle which writes all data into a single file.
3335
* <p>
3436
* Please use this with caution. This can end up creating very large files if not used correctly.
3537
*/
38+
@NotThreadSafe
3639
public class HoodieUnboundedCreateHandle<T extends HoodieRecordPayload, I, K, O> extends HoodieCreateHandle<T, I, K, O> {
3740

3841
private static final Logger LOG = LogManager.getLogger(HoodieUnboundedCreateHandle.class);

hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieParquetWriter.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,19 @@
3131
import org.apache.parquet.hadoop.ParquetFileWriter;
3232
import org.apache.parquet.hadoop.ParquetWriter;
3333

34+
import javax.annotation.concurrent.NotThreadSafe;
35+
3436
import java.io.IOException;
37+
import java.util.ConcurrentModificationException;
3538
import java.util.concurrent.atomic.AtomicLong;
3639

3740
/**
3841
* HoodieParquetWriter extends the ParquetWriter to help limit the size of underlying file. Provides a way to check if
3942
* the current file can take more records with the <code>canWrite()</code>
43+
*
44+
* ATTENTION: HoodieParquetWriter is not thread safe and developer should take care of the order of write and close
4045
*/
46+
@NotThreadSafe
4147
public class HoodieParquetWriter<T extends HoodieRecordPayload, R extends IndexedRecord>
4248
extends ParquetWriter<IndexedRecord> implements HoodieFileWriter<R> {
4349

@@ -84,7 +90,7 @@ public HoodieParquetWriter(String instantTime,
8490
}
8591

8692
@Override
87-
public synchronized void writeAvroWithMetadata(R avroRecord, HoodieRecord record) throws IOException {
93+
public void writeAvroWithMetadata(R avroRecord, HoodieRecord record) throws IOException, ConcurrentModificationException {
8894
if (populateMetaFields) {
8995
prepRecordWithMetadata(avroRecord, record, instantTime,
9096
taskContextSupplier.getPartitionIdSupplier().get(), recordIndex, file.getName());
@@ -101,15 +107,15 @@ public boolean canWrite() {
101107
}
102108

103109
@Override
104-
public synchronized void writeAvro(String key, IndexedRecord object) throws IOException {
110+
public void writeAvro(String key, IndexedRecord object) throws IOException, ConcurrentModificationException {
105111
super.write(object);
106112
if (populateMetaFields) {
107113
writeSupport.add(key);
108114
}
109115
}
110116

111117
@Override
112-
public synchronized void close() throws IOException {
118+
public void close() throws IOException, ConcurrentModificationException {
113119
super.close();
114120
}
115121

hudi-client/hudi-flink-client/src/main/java/org/apache/hudi/table/action/commit/FlinkMergeHelper.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void runMerge(HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List
7878
readSchema = mergeHandle.getWriterSchemaWithMetaFields();
7979
}
8080

81-
BoundedInMemoryExecutor<GenericRecord, GenericRecord, Void> wrapper = null;
81+
BoundedInMemoryExecutor<GenericRecord, GenericRecord, Void> executor = null;
8282
Configuration cfgForHoodieFile = new Configuration(table.getHadoopConf());
8383
HoodieFileReader<GenericRecord> reader = HoodieFileReaderFactory.<GenericRecord>getFileReader(cfgForHoodieFile, mergeHandle.getOldFilePath());
8484
try {
@@ -91,23 +91,23 @@ public void runMerge(HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List
9191

9292
ThreadLocal<BinaryEncoder> encoderCache = new ThreadLocal<>();
9393
ThreadLocal<BinaryDecoder> decoderCache = new ThreadLocal<>();
94-
wrapper = new BoundedInMemoryExecutor<>(table.getConfig().getWriteBufferLimitBytes(), new IteratorBasedQueueProducer<>(readerIterator),
94+
executor = new BoundedInMemoryExecutor<>(table.getConfig().getWriteBufferLimitBytes(), new IteratorBasedQueueProducer<>(readerIterator),
9595
Option.of(new UpdateHandler(mergeHandle)), record -> {
9696
if (!externalSchemaTransformation) {
9797
return record;
9898
}
9999
return transformRecordBasedOnNewSchema(gReader, gWriter, encoderCache, decoderCache, (GenericRecord) record);
100100
});
101-
wrapper.execute();
101+
executor.execute();
102102
} catch (Exception e) {
103103
throw new HoodieException(e);
104104
} finally {
105105
if (reader != null) {
106106
reader.close();
107107
}
108108
mergeHandle.close();
109-
if (null != wrapper) {
110-
wrapper.shutdownNow();
109+
if (null != executor) {
110+
executor.shutdownNow();
111111
}
112112
}
113113
}

hudi-client/hudi-java-client/src/main/java/org/apache/hudi/table/action/commit/JavaMergeHelper.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public void runMerge(HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List
7979
readSchema = mergeHandle.getWriterSchemaWithMetaFields();
8080
}
8181

82-
BoundedInMemoryExecutor<GenericRecord, GenericRecord, Void> wrapper = null;
82+
BoundedInMemoryExecutor<GenericRecord, GenericRecord, Void> executor = null;
8383
HoodieFileReader<GenericRecord> reader = HoodieFileReaderFactory.<GenericRecord>getFileReader(cfgForHoodieFile, mergeHandle.getOldFilePath());
8484
try {
8585
final Iterator<GenericRecord> readerIterator;
@@ -91,24 +91,27 @@ public void runMerge(HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List
9191

9292
ThreadLocal<BinaryEncoder> encoderCache = new ThreadLocal<>();
9393
ThreadLocal<BinaryDecoder> decoderCache = new ThreadLocal<>();
94-
wrapper = new BoundedInMemoryExecutor<>(table.getConfig().getWriteBufferLimitBytes(), new IteratorBasedQueueProducer<>(readerIterator),
94+
executor = new BoundedInMemoryExecutor<>(table.getConfig().getWriteBufferLimitBytes(), new IteratorBasedQueueProducer<>(readerIterator),
9595
Option.of(new UpdateHandler(mergeHandle)), record -> {
9696
if (!externalSchemaTransformation) {
9797
return record;
9898
}
9999
return transformRecordBasedOnNewSchema(gReader, gWriter, encoderCache, decoderCache, (GenericRecord) record);
100100
});
101-
wrapper.execute();
101+
executor.execute();
102102
} catch (Exception e) {
103103
throw new HoodieException(e);
104104
} finally {
105+
// HUDI-2875: mergeHandle is not thread safe, we should totally terminate record inputting
106+
// and executor firstly and then close mergeHandle.
105107
if (reader != null) {
106108
reader.close();
107109
}
108-
mergeHandle.close();
109-
if (null != wrapper) {
110-
wrapper.shutdownNow();
110+
if (null != executor) {
111+
executor.shutdownNow();
112+
executor.awaitTermination();
111113
}
114+
mergeHandle.close();
112115
}
113116
}
114117

hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/table/action/bootstrap/OrcBootstrapMetadataHandler.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ Schema getAvroSchema(Path sourceFilePath) throws IOException {
6363
@Override
6464
void executeBootstrap(HoodieBootstrapHandle<?, ?, ?, ?> bootstrapHandle, Path sourceFilePath, KeyGeneratorInterface keyGenerator,
6565
String partitionPath, Schema avroSchema) throws Exception {
66-
BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void> wrapper = null;
66+
BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void> executor = null;
6767
Reader orcReader = OrcFile.createReader(sourceFilePath, OrcFile.readerOptions(table.getHadoopConf()));
6868
TypeDescription orcSchema = orcReader.getSchema();
6969
try (RecordReader reader = orcReader.rows(new Reader.Options(table.getHadoopConf()).schema(orcSchema))) {
70-
wrapper = new BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void>(config.getWriteBufferLimitBytes(),
70+
executor = new BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void>(config.getWriteBufferLimitBytes(),
7171
new OrcReaderIterator(reader, avroSchema, orcSchema), new BootstrapRecordConsumer(bootstrapHandle), inp -> {
7272
String recKey = keyGenerator.getKey(inp).getRecordKey();
7373
GenericRecord gr = new GenericData.Record(HoodieAvroUtils.RECORD_KEY_SCHEMA);
@@ -76,13 +76,13 @@ void executeBootstrap(HoodieBootstrapHandle<?, ?, ?, ?> bootstrapHandle, Path so
7676
HoodieRecord rec = new HoodieAvroRecord(new HoodieKey(recKey, partitionPath), payload);
7777
return rec;
7878
}, table.getPreExecuteRunnable());
79-
wrapper.execute();
79+
executor.execute();
8080
} catch (Exception e) {
8181
throw new HoodieException(e);
8282
} finally {
8383
bootstrapHandle.close();
84-
if (null != wrapper) {
85-
wrapper.shutdownNow();
84+
if (null != executor) {
85+
executor.shutdownNow();
8686
}
8787
}
8888
}

hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/table/action/bootstrap/ParquetBootstrapMetadataHandler.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ Schema getAvroSchema(Path sourceFilePath) throws IOException {
6767
@Override
6868
void executeBootstrap(HoodieBootstrapHandle<?, ?, ?, ?> bootstrapHandle,
6969
Path sourceFilePath, KeyGeneratorInterface keyGenerator, String partitionPath, Schema avroSchema) throws Exception {
70-
BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void> wrapper = null;
70+
BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void> executor = null;
71+
ParquetReader<IndexedRecord> reader =
72+
AvroParquetReader.<IndexedRecord>builder(sourceFilePath).withConf(table.getHadoopConf()).build();
7173
try {
72-
ParquetReader<IndexedRecord> reader =
73-
AvroParquetReader.<IndexedRecord>builder(sourceFilePath).withConf(table.getHadoopConf()).build();
74-
wrapper = new BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void>(config.getWriteBufferLimitBytes(),
74+
executor = new BoundedInMemoryExecutor<GenericRecord, HoodieRecord, Void>(config.getWriteBufferLimitBytes(),
7575
new ParquetReaderIterator(reader), new BootstrapRecordConsumer(bootstrapHandle), inp -> {
7676
String recKey = keyGenerator.getKey(inp).getRecordKey();
7777
GenericRecord gr = new GenericData.Record(HoodieAvroUtils.RECORD_KEY_SCHEMA);
@@ -80,14 +80,15 @@ void executeBootstrap(HoodieBootstrapHandle<?, ?, ?, ?> bootstrapHandle,
8080
HoodieRecord rec = new HoodieAvroRecord(new HoodieKey(recKey, partitionPath), payload);
8181
return rec;
8282
}, table.getPreExecuteRunnable());
83-
wrapper.execute();
83+
executor.execute();
8484
} catch (Exception e) {
8585
throw new HoodieException(e);
8686
} finally {
87-
bootstrapHandle.close();
88-
if (null != wrapper) {
89-
wrapper.shutdownNow();
87+
reader.close();
88+
if (null != executor) {
89+
executor.shutdownNow();
9090
}
91+
bootstrapHandle.close();
9192
}
9293
}
9394
}

0 commit comments

Comments
 (0)