TEZ-4725: Fix flaky tests in TestAMRecoveryAggregationBroadcast#520
TEZ-4725: Fix flaky tests in TestAMRecoveryAggregationBroadcast#520maheshrajus wants to merge 1 commit into
Conversation
|
🎊 +1 overall
This message was automatically generated. |
| TezCounters counters = runDAGAndVerify(dag, true, | ||
| Collections.singletonList(TABLE_SCAN)); |
There was a problem hiding this comment.
line break is not needed here
| private void waitForVertexSucceeded(DAGClient dagClient, String vertexName, | ||
| long timeoutMs) throws Exception { | ||
| long deadline = System.currentTimeMillis() + timeoutMs; | ||
| while (System.currentTimeMillis() < deadline) { |
There was a problem hiding this comment.
try to use a monothonic clock instead of System.currentTimeMillis()
| TezCounters counters = runDAGAndVerify(dag, true, | ||
| Arrays.asList(TABLE_SCAN, AGGREGATION)); |
There was a problem hiding this comment.
line break is not needed here
| waitForVertexSucceeded(dagClient, vertexName, | ||
| TimeUnit.SECONDS.toMillis(60)); |
There was a problem hiding this comment.
line break is not needed here
| if (state == VertexStatus.State.SUCCEEDED) { | ||
| return; | ||
| } | ||
| if (state == VertexStatus.State.FAILED | ||
| || state == VertexStatus.State.KILLED | ||
| || state == VertexStatus.State.ERROR) { | ||
| throw new AssertionError("Vertex " + vertexName | ||
| + " reached terminal non-success state: " + state); | ||
| } | ||
| } |
There was a problem hiding this comment.
what about a switch case here, something like
switch (lastState) {
case SUCCEEDED -> { return; }
case FAILED, KILLED, ERROR -> throw new AssertionError("Vertex " + vertexName
+ " reached terminal non-success state: " + lastState);
...
}
| // and retry rather than crash. | ||
| String amHost = appReport.getHost(); | ||
| int amRpcPort = appReport.getRpcPort(); | ||
| if (amHost == null || amHost.equals("N/A") || amRpcPort <= 0) { |
There was a problem hiding this comment.
I think this condition can be refactored to a separate method with appReport parameter and be reused across different codepaths
| // YARN-808 gap: when the AM container is first allocated YARN briefly | ||
| // reports state=RUNNING before the AM has registered with the RM or bound | ||
| // its RPC listener. During that window the ApplicationReport contains | ||
| // sentinel values that must not be passed to | ||
| // NetUtils.createSocketAddrForHost() (which would throw | ||
| // IllegalArgumentException: port out of range) or to RPC.getProxy(): | ||
| // host == null / "N/A" : RM has not received registerApplicationMaster() | ||
| // rpcPort == 0 : protobuf wire default | ||
| // rpcPort == -1 : container up but RPC listener not yet bound | ||
| // Returning null lets callers (waitForProxy, sendAMHeartbeat) back off | ||
| // and retry rather than crash. |
There was a problem hiding this comment.
this long comment can be put to a new method that contains the refactored condition below
except the "return null lets callers" part, which belongs to this method
| // Workaround: check the default strings/sentinels set by YARN. | ||
| // port == 0 : protobuf wire default, AM has not called | ||
| // registerApplicationMaster() yet. | ||
| // port == -1 : AM container is allocated (state=RUNNING) but the | ||
| // RPC listener has not been bound yet. |
There was a problem hiding this comment.
this comment can be put to the new method that contains the refactored condition below
| private DAGClientRPCImplWithFakeReport mockReportClient(TezConfiguration conf, | ||
| String host, int rpcPort) throws IOException { | ||
| ApplicationReport report = mock(ApplicationReport.class); | ||
| when(report.getYarnApplicationState()).thenReturn(YarnApplicationState.RUNNING); | ||
| when(report.getHost()).thenReturn(host); | ||
| when(report.getRpcPort()).thenReturn(rpcPort); | ||
| return new DAGClientRPCImplWithFakeReport(mockAppId, dagIdStr, conf, report); | ||
| } |
There was a problem hiding this comment.
it's strange that mocking and real classes are mixed: if there is a real DAGClientRPCImplWithFakeReport, it could leverage a real ApplicationReport, couldn't it?
Root causes and fixes:
TestAMRecoveryAggregationBroadcast.testMapJoinTemporalFailure (race condition)
Replace fixed Thread.sleep(10s) before AM kill with a deterministic
waitForVertexSucceeded() helper that polls DAGClient.getVertexStatus()
every 500ms (up to 60s) until the target vertices reach SUCCEEDED state.
Each test now waits for only the vertices it logically depends on before
killing the AM, ensuring the recovery log assertions always see the
expected counts.
Make OUT_PATH unique per test run (random suffix) to eliminate cross-test.
DAGClientRPCImpl / TezClientUtils: port out of range:-1 (YARN-808 gap)
YARN sets rpcPort=-1 when an AM container is allocated (state=RUNNING)
but the AM has not yet bound its RPC listener. The existing guard only
checked rpcPort==0 (protobuf default), so rpcPort==-1 reached
NetUtils.createSocketAddrForHost(), which threw
IllegalArgumentException: port out of range:-1.
Fix DAGClientRPCImpl.createAMProxyIfNeeded(): rpcPort == 0 → rpcPort <= 0.
Fix TezClientUtils.getAMProxy(FrameworkClient,...): add the same
rpcPort <= 0 guard