-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Fail fast in artifact staging when storing an artifact fails #39367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,6 +40,7 @@ | |||||||
| import java.util.concurrent.ExecutorService; | ||||||||
| import java.util.concurrent.Executors; | ||||||||
| import java.util.concurrent.Future; | ||||||||
| import java.util.concurrent.TimeUnit; | ||||||||
| import org.apache.beam.model.jobmanagement.v1.ArtifactApi; | ||||||||
| import org.apache.beam.model.jobmanagement.v1.ArtifactStagingServiceGrpc; | ||||||||
| import org.apache.beam.model.pipeline.v1.RunnerApi; | ||||||||
|
|
@@ -223,15 +224,19 @@ public OverflowingSemaphore(int totalPermits) { | |||||||
| } | ||||||||
|
|
||||||||
| synchronized void aquire(int permits) throws Exception { | ||||||||
| while (usedPermits >= totalPermits) { | ||||||||
| if (exception != null) { | ||||||||
| throw exception; | ||||||||
| } | ||||||||
| while (exception == null && usedPermits >= totalPermits) { | ||||||||
| this.wait(); | ||||||||
| } | ||||||||
| checkException(); | ||||||||
| usedPermits += permits; | ||||||||
| } | ||||||||
|
|
||||||||
| synchronized void checkException() throws Exception { | ||||||||
| if (exception != null) { | ||||||||
| throw exception; | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| synchronized void release(int permits) { | ||||||||
| usedPermits -= permits; | ||||||||
| this.notifyAll(); | ||||||||
|
|
@@ -282,13 +287,16 @@ public RunnerApi.ArtifactInformation call() throws IOException { | |||||||
| .setTypeUrn(dest.getTypeUrn()) | ||||||||
| .setTypePayload(dest.getTypePayload()) | ||||||||
| .build(); | ||||||||
| } catch (IOException | InterruptedException exn) { | ||||||||
| } catch (Exception exn) { | ||||||||
| // As this thread will no longer be draining the queue, we don't want to get stuck writing | ||||||||
| // to it. | ||||||||
| // to it. This must happen for unchecked exceptions as well: getDestination can throw e.g. | ||||||||
| // InvalidPathException, and leaving the error unset would block the producer forever. | ||||||||
| totalPendingBytes.setException(exn); | ||||||||
| LOG.error("Exception staging artifacts", exn); | ||||||||
| if (exn instanceof IOException) { | ||||||||
| throw (IOException) exn; | ||||||||
| } else if (exn instanceof RuntimeException) { | ||||||||
| throw (RuntimeException) exn; | ||||||||
| } else { | ||||||||
| throw new RuntimeException(exn); | ||||||||
| } | ||||||||
|
|
@@ -409,10 +417,10 @@ public synchronized void onNext(ArtifactApi.ArtifactResponseWrapper responseWrap | |||||||
| ByteString chunk = responseWrapper.getGetArtifactResponse().getData(); | ||||||||
| if (chunk.size() > 0) { // Make sure we don't accidentally send the EOF value. | ||||||||
| totalPendingBytes.aquire(chunk.size()); | ||||||||
| currentOutput.put(chunk); | ||||||||
| putChunk(chunk); | ||||||||
| } | ||||||||
| if (responseWrapper.getIsLast()) { | ||||||||
| currentOutput.put(ByteString.EMPTY); // The EOF value. | ||||||||
| putChunk(ByteString.EMPTY); // The EOF value. | ||||||||
| if (pendingGets.isEmpty()) { | ||||||||
| resolveNextEnvironment(responseObserver); | ||||||||
| } else { | ||||||||
|
|
@@ -421,8 +429,16 @@ public synchronized void onNext(ArtifactApi.ArtifactResponseWrapper responseWrap | |||||||
| } | ||||||||
| } | ||||||||
| } catch (Exception exn) { | ||||||||
| LOG.error("Error submitting.", exn); | ||||||||
| onError(exn); | ||||||||
| // The write of a previous chunk failed; surface the failure to the client rather | ||||||||
| // than leaving the stream unterminated, which would make the client block forever. | ||||||||
| LOG.error("Error staging artifacts", exn); | ||||||||
| state = State.ERROR; | ||||||||
| stagingExecutor.shutdownNow(); | ||||||||
|
Comment on lines
+435
to
+436
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling Since the
Suggested change
|
||||||||
| responseObserver.onError( | ||||||||
| Status.INTERNAL | ||||||||
| .withDescription("Error staging artifacts: " + exn) | ||||||||
| .withCause(exn) | ||||||||
| .asException()); | ||||||||
| } | ||||||||
| break; | ||||||||
|
|
||||||||
|
|
@@ -433,6 +449,14 @@ public synchronized void onNext(ArtifactApi.ArtifactResponseWrapper responseWrap | |||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| private void putChunk(ByteString chunk) throws Exception { | ||||||||
| // The queue is drained by a StoreArtifact task. Don't block forever on a plain put if | ||||||||
| // that task has died with an error; poll so its exception can interrupt the wait. | ||||||||
| while (!currentOutput.offer(chunk, 1, TimeUnit.SECONDS)) { | ||||||||
| totalPendingBytes.checkException(); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| private void resolveNextEnvironment( | ||||||||
| StreamObserver<ArtifactApi.ArtifactRequestWrapper> responseObserver) { | ||||||||
| if (pendingResolves.isEmpty()) { | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When catching
Exception(which includesInterruptedException), the interrupted status of the thread is cleared. If the exception is anInterruptedException, we should restore the interrupted status by callingThread.currentThread().interrupt()before rethrowing or wrapping it, so that calling code or the thread pool can handle the interruption properly.