Skip to content

Commit 32eea5d

Browse files
committed
Fix ThreadSanitizer data races between bundle processing and async background writer closing in FileBasedSink and WriteFiles
1 parent aa8869c commit 32eea5d

3 files changed

Lines changed: 16 additions & 3 deletions

File tree

sdks/java/core/src/main/java/org/apache/beam/sdk/io/FileBasedSink.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.Objects;
4141
import java.util.Set;
4242
import java.util.UUID;
43+
import java.util.concurrent.atomic.AtomicBoolean;
4344
import org.apache.beam.sdk.coders.CannotProvideCoderException;
4445
import org.apache.beam.sdk.coders.Coder;
4546
import org.apache.beam.sdk.coders.CoderException;
@@ -925,13 +926,19 @@ public abstract static class Writer<DestinationT, OutputT> {
925926
*/
926927
private final @Nullable String mimeType;
927928

929+
private final AtomicBoolean readyToClose = new AtomicBoolean(false);
930+
928931
/** Construct a new {@link Writer} that will produce files of the given MIME type. */
929932
public Writer(WriteOperation<DestinationT, OutputT> writeOperation, String mimeType) {
930933
checkNotNull(writeOperation);
931934
this.writeOperation = writeOperation;
932935
this.mimeType = mimeType;
933936
}
934937

938+
void releaseForBackgroundClose() {
939+
readyToClose.set(true);
940+
}
941+
935942
/**
936943
* Called with the channel that a subclass will write its header, footer, and values to.
937944
* Subclasses should either keep a reference to the channel provided or create and keep a
@@ -1037,6 +1044,7 @@ private static void closeChannelAndThrow(
10371044
}
10381045

10391046
public final void cleanup() throws Exception {
1047+
readyToClose.get();
10401048
if (outputFile != null) {
10411049
LOG.info("Deleting temporary file {}", outputFile);
10421050
// outputFile may be null if open() was not called or failed.
@@ -1047,6 +1055,7 @@ public final void cleanup() throws Exception {
10471055

10481056
/** Closes the channel and returns the bundle result. */
10491057
public final void close() throws Exception {
1058+
readyToClose.get();
10501059
checkState(outputFile != null, "FileResult.close cannot be called with a null outputFile");
10511060
LOG.debug("Closing {}", outputFile);
10521061

sdks/java/core/src/main/java/org/apache/beam/sdk/io/WriteFiles.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,9 @@ public void processElement(
12721272
}
12731273

12741274
private void closeWriterInBackground(Writer<DestinationT, OutputT> writer) {
1275+
// Release memory barrier so background thread acquires all prior writes on Writer and
1276+
// channels.
1277+
writer.releaseForBackgroundClose();
12751278
// Close in parallel so flushing of buffered writes to files for many windows happens in
12761279
// parallel.
12771280
closeFutures.add(

sdks/java/core/src/test/java/org/apache/beam/sdk/io/TFRecordSchemaTransformProviderTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,10 @@ private void runTestWrite(String[] elems, String... base64) throws IOException {
434434
assertTrue("File should exist", tmpFile.exists());
435435
assertTrue("File should have content", tmpFile.length() > 0);
436436

437-
FileInputStream fis = new FileInputStream(tmpFile);
438-
String written = BaseEncoding.base64().encode(ByteStreams.toByteArray(fis));
439-
assertThat(written, is(in(base64)));
437+
try (FileInputStream fis = new FileInputStream(tmpFile)) {
438+
String written = BaseEncoding.base64().encode(ByteStreams.toByteArray(fis));
439+
assertThat(written, is(in(base64)));
440+
}
440441
}
441442

442443
@Test

0 commit comments

Comments
 (0)