diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index 7887fbce25063981062dfc72faf0d57a85decc81..5d274cbf0dbffcc09159c60d2236f292654979ff 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -2616,6 +2616,11 @@ public static boolean isAclEnabled(Configuration conf) { public static final int DEFAULT_CLUSTER_LEVEL_APPLICATION_PRIORITY = 0; + public static final String APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB = + YARN_PREFIX + "app.attempt.diagnostics.capacity.kb"; + + public static final int DEFAULT_APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB = 64; + @Private public static boolean isDistributedNodeLabelConfiguration(Configuration conf) { return DISTRIBUTED_NODELABEL_CONFIGURATION_TYPE.equals(conf.get( diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/pom.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/pom.xml index 57074448d6e773e2f4e31052a73ea158a92655aa..ab86e640e000b2504f63e583f084fd3df9f8c754 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/pom.xml +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/pom.xml @@ -108,6 +108,15 @@ org.apache.hadoop hadoop-annotations + + + junit + junit + test + org.mockito mockito-all @@ -129,11 +138,6 @@ protobuf-java - junit - junit - test - - org.bouncycastle bcprov-jdk16 test diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/BoundedAppender.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/BoundedAppender.java new file mode 100644 index 0000000000000000000000000000000000000000..5ad595f594e3477f55330f3e5529c6af05cf7856 --- /dev/null +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/BoundedAppender.java @@ -0,0 +1,266 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.yarn.util; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +import javax.annotation.concurrent.NotThreadSafe; +import java.io.Serializable; +import java.util.Queue; + +/** + * An {@link Appendable} implementation that considers its {@link #limit} as + * upper bound. While {@link #append(CharSequence) append}ing the lengths of the + * past input values are gathered in a {@link Queue queue}. + *

+ * When {@link #limit} would be reached on append, if possible, at head of + * {@link #pastInputValues past input values}, some of the {@link String}s are + * {@link StringBuilder#delete(int, int) delete}d first considering: + *

+ *

+ * An example: + * + *

+ * {
+ *   @code
+ *   // At the beginning it's an empty string
+ *   final Appendable shortAppender = new BoundedAppender(80);
+ *   // The whole message fits into limit
+ *   shortAppender.append(
+ *       "message1 this is a very long message but fitting into limit\n");
+ *   // The first message is truncated, the second not
+ *   shortAppender.append("message2 this is shorter than the previous one\n");
+ *   // The first message is deleted, the second truncated, the third preserved
+ *   shortAppender.append("message3 this is even shorter message, maybe.\n");
+ *   // The first two are deleted, the third one truncated, the last preserved
+ *   shortAppender.append("message4 the shortest one, yet the greatest :)");
+ *   // Current contents are like this:
+ *   // Diagnostic messages truncated, showing last (80) chars out of (199) in
+ *   // total:
+ *   // ...s is even shorter message, maybe.
+ *   // message4 the shortest one, yet the greatest :)
+ * }
+ * 
+ *

+ * Note that null values are {@link #append(CharSequence) append}ed + * just like in {@link StringBuilder#append(CharSequence) original + * implementation}. + *

+ * Note that this class is not thread safe. + */ +@InterfaceAudience.Private +@InterfaceStability.Unstable +@NotThreadSafe +public class BoundedAppender implements Appendable, Serializable, CharSequence { + private static final long serialVersionUID = 1L; + @VisibleForTesting + static final String TRUNCATED_MESSAGES_TEMPLATE = + "Diagnostic messages truncated, showing last " + + "(%d) chars out of (%d) in total:\n...%s"; + + private final int limit; + private int totalInputLength = 0; + private final StringBuilder pastInputValues = new StringBuilder(); + + /** + * Constructor to initialize to any {@link #limit}. + * + * @param limit upper bound of the character count + */ + public BoundedAppender(final int limit) { + Preconditions.checkArgument(limit > 0, "limit should be positive"); + + this.limit = limit; + } + + /** + * Appends the specified string to this BoundedAppender, considering + * {@link #limit} as upper bound in a way that former {@code append}ed whole + * strings are {@link StringBuilder#delete(int, int) delete}d in FIFO order. + *

+ * If {@code csq} is {@code null}, then the four characters {@code "null"} are + * appended. + * + * @param csq the {@link CharSequence} to be appended + * @return {@code this} + * @throws IllegalArgumentException if {@code csq} doesn't fit into + * {@code limit} + * @see Appendable#append(CharSequence) + */ + @Override + public BoundedAppender append(CharSequence csq) { + csq = ensureNotNull(csq); + + return append(csq, 0, csq.length()); + } + + /** + * Appends the specified string to this BoundedAppender, considering + * {@link #limit} as upper bound in a way that former {@code append}ed whole + * strings are {@link StringBuilder#delete(int, int) delete}d in FIFO order. + *

+ * If {@code csq} is {@code null}, then the four characters {@code "null"} are + * considered for appending. + *

+ * {@code start} and {@code end} indexes are applied to {@code csq} in the + * general meaning of {@see CharSequence#subSequence(start, end)}, with the + * only exception that we do not allow empty subsequences to be appended: + * {@code start < end} should be held in all circumstances. + * + * @param csq The character sequence from which a subsequence will be + * appended. If csq is null, then characters will + * be appended as if csq contained the four characters + * "null". + * @param start The index of the first character in the subsequence + * @param end end index, excluded + * @return {@code this} + * @throws IllegalArgumentException if {@code csq} doesn't fit into + * {@code limit}, or {@code end} is not more than {@code start} + * @throws IndexOutOfBoundsException if start or end are + * negative, start is greater than end, or + * end is greater than csq.length() + * @see Appendable#append(CharSequence, int, int) + */ + @Override + public BoundedAppender append(CharSequence csq, final int start, + final int end) { + csq = ensureNotNull(csq); + + final int inputLength = end - start; + Preconditions.checkPositionIndexes(start, end, csq.length()); + Preconditions.checkElementIndex(end - 1, csq.length()); + Preconditions.checkState(start < end, String + .format("end index (%d) must be before start index (%d)", end, start)); + + csq = checkAndCut(csq, start, end); + pastInputValues.append(csq); + totalInputLength += inputLength; + + return this; + } + + /** + * Appends the specified character to this BoundedAppender. + * + * @param c the character to append + * @return {@code this} + */ + @Override + public BoundedAppender append(final char c) { + return append(String.valueOf(c)); + } + + private CharSequence ensureNotNull(final CharSequence csq) { + if (csq == null) { + return "null"; + } + + return csq; + } + + private CharSequence checkAndCut(CharSequence csq, final int start, int end) { + final int inputLength = end - start; + if (shouldCutNew(inputLength)) { + csq = cutNew(csq, end); + cutPastAll(); + } else if (shouldCutPast(inputLength)) { + csq = csq.subSequence(start, end); + cutPastBy(lengthToCutPast(inputLength)); + } else { + csq = csq.subSequence(start, end); + } + + return csq; + } + + private boolean shouldCutNew(final int inputLength) { + return inputLength > limit; + } + + private CharSequence cutNew(final CharSequence csq, final int end) { + final int newStart = end - limit; + + Preconditions.checkElementIndex(newStart, csq.length(), + String.format("new start index (%d) should be withing length (%d)", + newStart, csq.length())); + + return csq.subSequence(newStart, end); + } + + private void cutPastAll() { + pastInputValues.delete(0, pastInputValues.length()); + } + + private boolean shouldCutPast(final int newInputLength) { + return lengthToCutPast(newInputLength) > 0; + } + + private int lengthToCutPast(final int newInputLength) { + return pastInputValues.length() + newInputLength - limit; + } + + private void cutPastBy(final int cutLength) { + Preconditions.checkArgument(cutLength > 0, "nothing to cut"); + Preconditions.checkState(pastInputValues.length() >= cutLength, + String.format("cannot cut %d, could cut at most %d", + pastInputValues.length(), cutLength)); + + pastInputValues.delete(0, cutLength); + } + + @Override + public int length() { + return pastInputValues.length(); + } + + @Override + public char charAt(final int index) { + return pastInputValues.charAt(index); + } + + @Override + public CharSequence subSequence(final int start, final int end) { + return pastInputValues.subSequence(start, end); + } + + @Override + public String toString() { + if (totalInputLength > pastInputValues.length()) { + final String truncatedMessages = + String.format(TRUNCATED_MESSAGES_TEMPLATE, pastInputValues.length(), + totalInputLength, pastInputValues.toString()); + return truncatedMessages; + } + + return pastInputValues.toString(); + } +} diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml index 1e929a8c5007e12d9d8c428880d33ea0e40390bf..68f370f5bf86f5f79d45e00a5cf282b69396b999 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml @@ -3014,4 +3014,19 @@ 3000 + + + Defines the capacity of the diagnostics message of an application + attempt, in kilobytes. + When using ZooKeeper to store application state behavior, it's + important to limit the size of the diagnostic messages to + prevent YARN from overwhelming ZooKeeper. In cases where + yarn.resourcemanager.state-store.max-completed-applications is set to + a large number, it may be desirable to reduce the value of this property + to limit the total data stored. + + yarn.app.attempt.diagnostics.capacity.kb + 64 + + diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/BoundedAppenderTest.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/BoundedAppenderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..607cadfd4f3bd90c97b468357c511691aae16db6 --- /dev/null +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/util/BoundedAppenderTest.java @@ -0,0 +1,206 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.yarn.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; + +/** + * Testing {@link BoundedAppender} behavior. + */ +public class BoundedAppenderTest { + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void initWithZeroLimitThrowsException() { + expected.expect(IllegalArgumentException.class); + expected.expectMessage("limit should be positive"); + + new BoundedAppender(0); + } + + @Test + public void nothingAppendedNothingRead() { + final BoundedAppender boundedAppender = new BoundedAppender(1); + + assertEquals("nothing appended, nothing read", "", + boundedAppender.toString()); + } + + @Test + public void nullAppendedNullStringRead() { + final BoundedAppender boundedAppender = new BoundedAppender(4); + boundedAppender.append(null); + + assertEquals("null appended, \"null\" read", "null", + boundedAppender.toString()); + } + + @Test + public void appendLastAboveLimitPreservesLastMessagePostfix() { + final BoundedAppender boundedAppender = new BoundedAppender(3); + + boundedAppender.append("ab"); + boundedAppender.append("cde"); + boundedAppender.append("fghij"); + + assertEquals( + "last value appended above limit postfix is read correctly", String + .format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 3, 10, "hij"), + boundedAppender.toString()); + } + + @Test + public void appendMiddleAboveLimitPreservesLastMessageAndMiddlePostfix() { + final BoundedAppender boundedAppender = new BoundedAppender(3); + + boundedAppender.append("ab"); + boundedAppender.append("cde"); + + assertEquals("last value appended above limit postfix is read correctly", + String.format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 3, 5, "cde"), + boundedAppender.toString()); + + boundedAppender.append("fg"); + + assertEquals( + "middle value appended above limit postfix and last value are " + + "read correctly", + String.format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 3, 7, "efg"), + boundedAppender.toString()); + + boundedAppender.append("hijkl"); + + assertEquals( + "last value appended above limit postfix is read correctly", String + .format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 3, 12, "jkl"), + boundedAppender.toString()); + } + + @Test + public void appendBelowLimitOnceValueIsReadCorrectly() { + final BoundedAppender boundedAppender = new BoundedAppender(2); + + boundedAppender.append("ab"); + + assertEquals("value appended is read correctly", "ab", + boundedAppender.toString()); + } + + @Test + public void appendValuesBelowLimitAreReadCorrectlyInFifoOrder() { + final BoundedAppender boundedAppender = new BoundedAppender(3); + + boundedAppender.append("ab"); + boundedAppender.append("cd"); + boundedAppender.append("e"); + boundedAppender.append("fg"); + + assertEquals("last values appended fitting limit are read correctly", + String.format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 3, 7, "efg"), + boundedAppender.toString()); + } + + @Test + public void appendValuesWithInvalidIndicesThrowsException() { + final BoundedAppender boundedAppender = new BoundedAppender(1); + + expected.expect(IndexOutOfBoundsException.class); + + boundedAppender.append("a", -1, 0); + + expected.expect(IllegalStateException.class); + + boundedAppender.append("a", 0, 0); + + expected.expect(IndexOutOfBoundsException.class); + + boundedAppender.append("a", 0, 2); + + expected.expect(IndexOutOfBoundsException.class); + + boundedAppender.append("a", 1, 1); + } + + @Test + public void nullAppendedWithValidIndicesNullRead() { + final BoundedAppender boundedAppender = new BoundedAppender(4); + + boundedAppender.append(null, 0, 4); + + assertEquals("null appended with valid indices, \"null\" read", "null", + boundedAppender.toString()); + } + + @Test + public void appendValueWithValidIndicesIsReadCorrectly() { + final BoundedAppender boundedAppender = new BoundedAppender(2); + + boundedAppender.append("abcd", 1, 3); + + assertEquals("value appended with valid indices is read correctly", "bc", + boundedAppender.toString()); + } + + @Test + public void appendValuesWithValidIndicesAreReadCorrectlyInFifoOrder() { + final BoundedAppender boundedAppender = new BoundedAppender(3); + + boundedAppender.append("abcd", 1, 3); + boundedAppender.append("efgh", 0, 2); + boundedAppender.append("i", 0, 1); + boundedAppender.append("jk", 0, 2); + + assertEquals( + "last values appended with valid parameters fitting limit are " + + "read correctly", + String.format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 3, 7, "ijk"), + boundedAppender.toString()); + } + + @Test + public void appendOneCharIsReadCorrectly() { + final BoundedAppender boundedAppender = new BoundedAppender(1); + + boundedAppender.append('a'); + + assertEquals("one char appended is read correctly", "a", + boundedAppender.toString()); + } + + @Test + public void appendMultipleCharsAreReadCorrectlyInFifoOrder() { + final BoundedAppender boundedAppender = new BoundedAppender(2); + + boundedAppender.append('a'); + boundedAppender.append('b'); + boundedAppender.append('c'); + boundedAppender.append('d'); + + assertEquals( + "multiple chars appended are read correctly, the ones deleted in " + + "FIFO order", + String.format(BoundedAppender.TRUNCATED_MESSAGES_TEMPLATE, 2, 4, "cd"), + boundedAppender.toString()); + } +} \ No newline at end of file diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java index ab84985edcbc2a6ee34de90ec9de2c1edfbfcf1d..279854ddaebe88c69eb1c9c5dcb2002d307afe81 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/rmapp/attempt/RMAppAttemptImpl.java @@ -109,6 +109,7 @@ import org.apache.hadoop.yarn.state.SingleArcTransition; import org.apache.hadoop.yarn.state.StateMachine; import org.apache.hadoop.yarn.state.StateMachineFactory; +import org.apache.hadoop.yarn.util.BoundedAppender; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; import com.google.common.annotations.VisibleForTesting; @@ -171,7 +172,7 @@ // Set to null initially. Will eventually get set // if an RMAppAttemptUnregistrationEvent occurs private FinalApplicationStatus finalStatus = null; - private final StringBuilder diagnostics = new StringBuilder(); + private final BoundedAppender diagnostics; private int amContainerExitStatus = ContainerExitStatus.INVALID; private Configuration conf; @@ -518,6 +519,42 @@ public RMAppAttemptImpl(ApplicationAttemptId appAttemptId, this.amReq = amReq; this.blacklistedNodesForAM = amBlacklistManager; + + final int diagnosticsLimitKB = getDiagnosticsLimitKBOrThrow(conf); + + if (LOG.isDebugEnabled()) { + LOG.debug(YarnConfiguration.APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB + " : " + + diagnosticsLimitKB); + } + + this.diagnostics = new BoundedAppender(diagnosticsLimitKB * 1024); + } + + private int getDiagnosticsLimitKBOrThrow(final Configuration configuration) { + try { + final int diagnosticsLimitKB = configuration.getInt( + YarnConfiguration.APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB, + YarnConfiguration.DEFAULT_APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB); + + if (diagnosticsLimitKB <= 0) { + final String message = + YarnConfiguration.APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB + " (" + + diagnosticsLimitKB + ") should be positive."; + LOG.error(message); + + throw new YarnRuntimeException(message); + } + + return diagnosticsLimitKB; + } catch (final NumberFormatException nfe) { + final String message = String.format( + "%s should be a positive integer. Exception message: %s", + YarnConfiguration.APP_ATTEMPT_DIAGNOSTICS_CAPACITY_KB, + nfe.getMessage()); + LOG.error(message); + + throw new YarnRuntimeException(message); + } } @Override @@ -926,8 +963,8 @@ public void recover(RMState state) { attemptState.getState())); } - diagnostics.append("Attempt recovered after RM restart"); - diagnostics.append(attemptState.getDiagnostics()); + appendToDiagnosticsSafely("Attempt recovered after RM restart"); + appendToDiagnosticsSafely(attemptState.getDiagnostics()); this.amContainerExitStatus = attemptState.getAMContainerExitStatus(); if (amContainerExitStatus == ContainerExitStatus.PREEMPTED) { this.attemptMetrics.setIsPreempted(); @@ -942,12 +979,21 @@ public void recover(RMState state) { this.startTime = attemptState.getStartTime(); this.finishTime = attemptState.getFinishTime(); this.attemptMetrics.updateAggregateAppResourceUsage( - attemptState.getMemorySeconds(),attemptState.getVcoreSeconds()); + attemptState.getMemorySeconds(), attemptState.getVcoreSeconds()); this.attemptMetrics.updateAggregatePreemptedAppResourceUsage( attemptState.getPreemptedMemorySeconds(), attemptState.getPreemptedVcoreSeconds()); } + private void appendToDiagnosticsSafely(final String diagnosticsMessage) { + try { + diagnostics.append(diagnosticsMessage); + } catch (final IndexOutOfBoundsException | IllegalStateException e) { + LOG.warn("There was a problem appending a diagnostics message to an RM " + + "app attempt. Here is the message: %s", e); + } + } + public void transferStateFromAttempt(RMAppAttempt attempt) { this.justFinishedContainers = attempt.getJustFinishedContainersReference(); this.finishedContainersSentToAM = @@ -1463,7 +1509,7 @@ public AttemptFailedTransition() { @Override public void transition(RMAppAttemptImpl appAttempt, RMAppAttemptEvent event) { if (event.getDiagnosticMsg() != null) { - appAttempt.diagnostics.append(event.getDiagnosticMsg()); + appAttempt.appendToDiagnosticsSafely(event.getDiagnosticMsg()); } super.transition(appAttempt, event); } @@ -1566,7 +1612,7 @@ public void transition(RMAppAttemptImpl appAttempt, RMAppAttemptEvent event) { // Use diagnostic from launcher - appAttempt.diagnostics.append(event.getDiagnosticMsg()); + appAttempt.appendToDiagnosticsSafely(event.getDiagnosticMsg()); // Tell the app, scheduler super.transition(appAttempt, event); @@ -1655,8 +1701,7 @@ public void transition(RMAppAttemptImpl appAttempt, private void setAMContainerCrashedDiagnosticsAndExitStatus( RMAppAttemptContainerFinishedEvent finishEvent) { ContainerStatus status = finishEvent.getContainerStatus(); - String diagnostics = getAMContainerCrashedDiagnostics(finishEvent); - this.diagnostics.append(diagnostics); + appendToDiagnosticsSafely(getAMContainerCrashedDiagnostics(finishEvent)); this.amContainerExitStatus = status.getExitStatus(); } @@ -1718,7 +1763,7 @@ public ExpiredTransition() { @Override public void transition(RMAppAttemptImpl appAttempt, RMAppAttemptEvent event) { - appAttempt.diagnostics.append(getAMExpiredDiagnostics(event)); + appAttempt.appendToDiagnosticsSafely(getAMExpiredDiagnostics(event)); super.transition(appAttempt, event); } } @@ -1740,7 +1785,7 @@ public UnexpectedAMRegisteredTransition() { @Override public void transition(RMAppAttemptImpl appAttempt, RMAppAttemptEvent event) { assert appAttempt.submissionContext.getUnmanagedAM(); - appAttempt.diagnostics.append(getUnexpectedAMRegisteredDiagnostics()); + appAttempt.appendToDiagnosticsSafely(getUnexpectedAMRegisteredDiagnostics()); super.transition(appAttempt, event); } @@ -1825,7 +1870,7 @@ private void updateInfoOnAMUnregister(RMAppAttemptEvent event) { progress = 1.0f; RMAppAttemptUnregistrationEvent unregisterEvent = (RMAppAttemptUnregistrationEvent) event; - diagnostics.append(unregisterEvent.getDiagnosticMsg()); + appendToDiagnosticsSafely(unregisterEvent.getDiagnosticMsg()); originalTrackingUrl = sanitizeTrackingUrl(unregisterEvent.getFinalTrackingUrl()); finalStatus = unregisterEvent.getFinalApplicationStatus(); }