Repository navigation
[#1160] Report what start-ds printed when the server fails to start, and dump the quicksetup test instance's logs on a failed build - #1169
Conversation
maximthomas
left a comment
There was a problem hiding this comment.
praise: The failure message now carries the cause, and the change sits exactly on the one throw that lost it.
getStartFailedMessagefalls back toINFO_ERROR_STARTING_SERVER_CODEwhen start-ds printed nothing, andsilentFailedStartKeepsTheExitCodeMessagepins that fallback.- The tail is bounded by
MAX_REPORTED_START_LINES, andfailedStartReportsOnlyTheLastLinespins the bound from both sides (L50Labsent,L51Lpresent); the description shows theremoveFirstmutant going red. - The drain joins run only on the
returnValue != 0branch, so a successful start keeps its timing.
issue (non-blocking): silentFailedStartKeepsTheExitCodeMessage compares the message with the English text of a translated key.
opendj-server-legacy/src/test/java/org/opends/quicksetup/util/ServerControllerStartFailureTest.java:106
INFO_ERROR_STARTING_SERVER_CODE is translated in quickSetup_de/es/fr/ja/ko/zh_CN/zh_TW.properties, and nothing in the pom argLine, the failsafe properties or the test harness pins user.language. Running the silent stand-in through startServer() with -Duser.language=fr, de or ja returns the translated text ("Erreur lors du démarrage de Directory Server. Code d'erreur : 1."), so the assertion fails on a developer JVM with any of those default locales. CI's English runners stay green.
import static org.opends.messages.QuickSetupMessages.INFO_ERROR_STARTING_SERVER_CODE;
assertEquals(startAndExpectFailure(), INFO_ERROR_STARTING_SERVER_CODE.get(1).toString());suggestion (non-blocking): No test pins the drain joins, but one stand-in script can.
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ServerController.java:406-407, opendj-server-legacy/src/test/java/org/opends/quicksetup/util/ServerControllerStartFailureTest.java:56
All three stand-ins print and exit at once, so the readers have emptied the pipes before waitFor() returns. With both joins deleted, each case stayed green 120 of 120 times (the test's own scripts and assertions, run through the real startServer(), macOS, JDK 26). The description says a test cannot force the readers to lag behind the exit. A background child can: it holds the inherited pipes after sh exits, so its line arrives after waitFor(). At the head the message contains that line in 3 of 3 runs. Without the joins the tail is empty and the bare "Error code: 1." is thrown, also 3 of 3.
@Test
public void failedStartReportsWhatIsPrintedAfterTheExit() throws Exception
{
if (isWindows())
{
throw new SkipException("needs a child process that outlives the start command");
}
writeStartCommand("#!/bin/sh", "(sleep 1; echo 'printed after the exit') &", "exit 1");
String message = startAndExpectFailure();
assertTrue(message.contains("printed after the exit"), message);
}Pin: the case above (plus import org.testng.SkipException;, as LauncherTest has) goes red when either join is deleted.
suggestion (non-blocking): On a localized install, a failed start that printed anything now reports in English.
opendj-server-legacy/src/messages/org/opends/messages/quickSetup.properties:345, opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ServerController.java:622
INFO_ERROR_STARTING_SERVER_CODE_OUTPUT exists only in the base bundle. Before this PR the same failure was reported through the translated INFO_ERROR_STARTING_SERVER_CODE. With -Duser.language=fr the head reports "Error Starting Directory Server. Error code: 1. Last lines of the start command output:", where the base reported the French header. Leaving the new text untranslated is normal here. The suggestion is only to keep the header that is already translated:
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(INFO_ERROR_STARTING_SERVER_CODE.get(returnValue));
mb.append(INFO_ERROR_STARTING_SERVER_OUTPUT.get(String.join(System.lineSeparator(), outputTail)));
return mb.toMessage();INFO_ERROR_STARTING_SERVER_OUTPUT=%nLast lines of the start command output:%n%sIf you take this, the contains("Error code: 1.") checks in the other two cases become locale-dependent too. Compare them with INFO_ERROR_STARTING_SERVER_CODE.get(1).toString() as well.
question (non-blocking): On a verbose or unsuppressed start, is it deliberate that the error repeats up to 100 lines the user has just seen?
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ServerController.java:622, :692
StartReader.run sends each start-ds line to application.notifyListeners unless output is suppressed, and the same lines then go into the exception. Three paths print them live and then again inside the error. The first is setup --verbose or a large LDIF import (Installer.java:371, startServer(!verbose)), where handleInstallationError prints them. The second is the non-quiet uninstall CLI (UninstallCliHelper.java:997, startServer(false)), through printErrorMessage. The third is the GUI uninstaller (Uninstaller.java:1413, startServer() = startServer(true, false)). This is cosmetic. If it is deliberate, so that the error stays self-contained, nothing needs to change. If not:
startServerViaAnotherProcess(suppressOutput || application == null);
private void startServerViaAnotherProcess(boolean reportOutput) throws IOException, InterruptedException, ApplicationException
throw new ApplicationException(ReturnCode.START_ERROR, reportOutput
? getStartFailedMessage(returnValue, outputTail)
: INFO_ERROR_STARTING_SERVER_CODE.get(returnValue), null);suggestion (non-blocking): No CI cell runs the .bat branches of the three cases.
opendj-server-legacy/src/test/java/org/opends/quicksetup/util/ServerControllerStartFailureTest.java:59-62, :80, :99
build.yml:106-110 adds -P precommit only when runner.os == 'Linux'. opendj-server-legacy/pom.xml:646-655 binds surefire to phase none, and failsafe is declared only in the precommit profile. So the Windows build-maven cells compile the class and run none of it. As a result, the @for /L %%i loop, @echo … 1>&2, the @exit /b 1 exit code reaching waitFor(), and the CRLF write have not run anywhere.
Pin: one run on a Windows host, mvn -Pprecommit verify -pl opendj-server-legacy -am -Dit.test=ServerControllerStartFailureTest. If that is not possible, change the Testing section to say the Windows branch is unrun.
note (non-blocking): Deviation from the description.
- PR description, Testing: "the Windows legs of this PR run it". No CI cell runs failsafe outside Linux (see the suggestion above).
6bcf9c9 to
b35afb7
Compare
|
Thanks for the review. All six points are taken in b35afb7. issue: the silent case compares with English text. Fixed: every expected message is built from suggestion: pin the drain joins. Added suggestion: keep the translated header. Done as you proposed. The message is the translated question: the repeated lines. They were not deliberate. The tail is now reported only for
suggestion / note: the Mutants, each red in exactly one case: no joins, no |
…ver fails to start, and dump the quicksetup test instance's logs on a failed build ServerController kept the start-ds output only in logger.info, so a failed start threw "Error Starting Directory Server. Error code: 1." with no cause. In the branch of start-ds that exits with 1, WaitForFileDelete --logFile has already printed the server.out of the server that stopped, so both StartReaders now keep the last 100 lines of stdout and stderr, and a non-zero exit waits for them to drain (5 s at most each) and reports those lines in the new INFO_ERROR_STARTING_SERVER_CODE_OUTPUT message. A start command that printed nothing keeps the old message. The "Dump the server logs of a failed test" step also prints the logs of the quicksetup tests' own instance under build/unit-tests/quicksetup/OpenDS. ServerControllerStartFailureTest runs a stand-in start command that prints and exits with 1. Fixes OpenIdentityPlatform#1160
…eport the start output only when no listener showed it, and pin the drain wait - The message is the translated INFO_ERROR_STARTING_SERVER_CODE followed by the new INFO_ERROR_STARTING_SERVER_OUTPUT; INFO_ERROR_STARTING_SERVER_CODE_OUTPUT is removed. - The tail is reported only for suppressOutput || application == null; otherwise the listeners have already shown every line, and the old message is kept. - New cases: a line printed after the start command exited, and the two listener cases; every expected message is built from the message key, not from its English text.
…when the start command exits, so the line printed after the exit is not cut off by the JDK drain
b35afb7 to
280676f
Compare
|
Fixed in 280676f. The branch is rebased onto master 92ea60a; the two earlier commits are unchanged. CI at b35afb7 failed The stand-in now prints a line and sleeps for 1 s before it starts the child and exits, so the stdout reader is blocked in echo 'printed before the exit'
sleep 1
(sleep 1; echo 'printed after the exit') &
exit 1It passes 20 of 20 in |
maximthomas
left a comment
There was a problem hiding this comment.
praise: The round-1 points are all fixed, and the Linux flake was diagnosed instead of retried.
ServerController.java:343reports the tail only when no listener saw it (suppressOutput || application == null), andgetStartFailedMessage(:618-630) keeps the translatedINFO_ERROR_STARTING_SERVER_CODEheader.- The
failedStartReportsWhatIsPrintedAfterTheExitstand-in (ServerControllerStartFailureTest.java:100-106) is built around the JDK'sProcessPipeInputStream.processExited()drain, and its comment says why the pause is there. - Every expected message comes from
INFO_ERROR_STARTING_SERVER_CODE.get(1).toString()(:154-157), so the cases do not depend on the locale.
Fixes #1160
Problem
ServerControllerTest#testStartServerfailed once on CI withError Starting Directory Server. Error code: 1.and nothing else. The cause could not be recovered:ServerController.startServerViaAnotherProcess()threwINFO_ERROR_STARTING_SERVER_CODEwith the exit code only. The twoStartReaders did read every linestart-dsprinted, but passed them only tologger.infoand to theapplicationlisteners (none in the test).target/package/opendj*/logs, not the quicksetup tests' own instance underbuild/unit-tests/quicksetup/OpenDS.start-dsexits with 1 in one branch only:WaitForFileDeletereturned 0, yet the second--checkStartabilitydid not report a running server (98). Before that,WaitForFileDelete --logFileprintslogs/server.outof the server it waits for to stdout. So the server's own account of why it stopped already reachesStartReader; it just never reached the exception.Change
ServerController: bothStartReaders append to one shared tail of the last 100 lines of stdout and stderr (guarded by its own monitor). On a non-zero exit, the controller waits up to 5 s for each reader to drain whatstart-dsprinted.waitFor()returns as soon as the process has exited, while the readers may still be behind on what is left in the pipes. A line that a child ofstart-dsprints after the exit arrives only if the reader is blocked inread()at that moment: otherwise the JDK drains what the pipe holds and closes it (ProcessPipeInputStream.processExited()). Then, if nobody has seen those lines, it appends them to the old message:The first line is the translated
INFO_ERROR_STARTING_SERVER_CODE; only the newINFO_ERROR_STARTING_SERVER_OUTPUTpart is untranslated.suppressOutputor without anapplication. Otherwise the readers have already shown every line to the listeners (setup --verbose, the uninstall CLI and GUI), and the error keeps the old message instead of repeating up to 100 lines..github/workflows/build.yml: the dump step also printsopendj-server-legacy/build/unit-tests/quicksetup/OpenDS/logs/{server.out,errors}.New
ServerControllerStartFailureTest: the installation is a bare temporary directory whose start command (bin/start-ds, orbat/start-ds.baton Windows) is a stand-in script that prints and exits with 1. Six cases:read()and the JDK waits for that read instead of closing the pipe;suppressOutput, the listener gets nothing and the message carries both lines.Every expected message is built from
INFO_ERROR_STARTING_SERVER_CODE, not from its English text, so the cases also pass under a translated default locale.This PR does not fix the flake itself. It makes the next occurrence name its cause, which item 3 of the issue waits for.
Testing
mvn -Pprecommit verify -pl opendj-server-legacy -am -Dit.test=ServerControllerStartFailureTest,ServerControllerTest: BUILD SUCCESS.ServerControllerStartFailureTest6/6,ServerControllerTest2/2.attach-javadocs(doclint) passed.ServerController(each one goes red in the case named for it):joins deleted:failedStartReportsWhatIsPrintedAfterTheExit, in every run. On Linux it also fails other cases from time to time, since without the joins the message can also miss lines printed before the exit (ineclipse-temurin:21-jdk, over 20 runs:failedStartReportsBothOutputStreams19,failedStartReportsOnlyTheLastLines11,failedStartShownToTheListenersKeepsTheExitCodeMessage9);removeFirstdropped (no tail bound):failedStartReportsOnlyTheLastLines;failedStartShownToTheListenersKeepsTheExitCodeMessage;failedStartHiddenFromTheListenersReportsBothOutputStreams.opendj.jar, whose bundle is the English base; the translations ship inlib/opendj_<lang>.jar. Withopendj_fr.jarandopendj_ja.jaradded to the classpath (as an IDE run withtarget/classeshas them), the new test passes under-Duser.language=en,frandja. The previous version of the test fails 2 cases underfrandjaagainst this code.build-maven (ubuntu-latest, 21)and(ubuntu-latest, 25), run 37187761859). Its stand-in exited at once, so the line of the child was lost whenever the reader was not yet blocked inread()at the exit. Ineclipse-temurin:21-jdkthat version failed in 5 of 20 runs, then in 0 of the next 30. The current version passes 20 of 20 ineclipse-temurin:21-jdk(and 30 of 30 in a second series) and ineclipse-temurin:25-jdk, and 20 of 20 on macOS with JDK 21. With both joins deleted, it fails in every run: 10 of 10 on each, and 20 of 20 in the second series on 21.-P precommitis set only whenrunner.os == 'Linux'), and I have no Windows host, so the.batstand-ins (@for /L,1>&2,@exit /b 1, CRLF) have not run anywhere.