8352533: Report useful IOExceptions when jspawnhelper fails

Reviewed-by: simonis, rriggs, stuefe
This commit is contained in:
Aleksey Shipilev 2025-05-14 09:05:49 +00:00
parent 86f39ab5bb
commit 5c73dfc28c
3 changed files with 100 additions and 26 deletions

View File

@ -317,12 +317,23 @@ releaseBytes(JNIEnv *env, jbyteArray arr, const char* parr)
(*env)->ReleaseByteArrayElements(env, arr, (jbyte*) parr, JNI_ABORT);
}
#define IOE_FORMAT "error=%d, %s"
#define IOE_FORMAT "%s, error: %d (%s) %s"
#define SPAWN_HELPER_INTERNAL_ERROR_MSG "\n" \
"Possible reasons:\n" \
" - Spawn helper ran into JDK version mismatch\n" \
" - Spawn helper ran into unexpected internal error\n" \
" - Spawn helper was terminated by another process\n" \
"Possible solutions:\n" \
" - Restart JVM, especially after in-place JDK updates\n" \
" - Check system logs for JDK-related errors\n" \
" - Re-install JDK to fix permission/versioning problems\n" \
" - Switch to legacy launch mechanism with -Djdk.lang.Process.launchMechanism=VFORK\n"
static void
throwIOException(JNIEnv *env, int errnum, const char *defaultDetail)
throwIOExceptionImpl(JNIEnv *env, int errnum, const char *externalDetail, const char *internalDetail)
{
const char *detail = defaultDetail;
const char *errorDetail;
char *errmsg;
size_t fmtsize;
char tmpbuf[1024];
@ -330,16 +341,22 @@ throwIOException(JNIEnv *env, int errnum, const char *defaultDetail)
if (errnum != 0) {
int ret = getErrorString(errnum, tmpbuf, sizeof(tmpbuf));
if (ret != EINVAL)
detail = tmpbuf;
if (ret != EINVAL) {
errorDetail = tmpbuf;
} else {
errorDetail = "unknown";
}
} else {
errorDetail = "none";
}
/* ASCII Decimal representation uses 2.4 times as many bits as binary. */
fmtsize = sizeof(IOE_FORMAT) + strlen(detail) + 3 * sizeof(errnum);
fmtsize = sizeof(IOE_FORMAT) + strlen(externalDetail) + 3 * sizeof(errnum) + strlen(errorDetail) + strlen(internalDetail) + 1;
errmsg = NEW(char, fmtsize);
if (errmsg == NULL)
return;
snprintf(errmsg, fmtsize, IOE_FORMAT, errnum, detail);
snprintf(errmsg, fmtsize, IOE_FORMAT, externalDetail, errnum, errorDetail, internalDetail);
s = JNU_NewStringPlatform(env, errmsg);
if (s != NULL) {
jobject x = JNU_NewObjectByName(env, "java/io/IOException",
@ -350,14 +367,38 @@ throwIOException(JNIEnv *env, int errnum, const char *defaultDetail)
free(errmsg);
}
/**
* Throws IOException that signifies an internal error, e.g. spawn helper failure.
*/
static void
throwInternalIOException(JNIEnv *env, int errnum, const char *externalDetail, int mode)
{
switch (mode) {
case MODE_POSIX_SPAWN:
throwIOExceptionImpl(env, errnum, externalDetail, SPAWN_HELPER_INTERNAL_ERROR_MSG);
break;
default:
throwIOExceptionImpl(env, errnum, externalDetail, "");
}
}
/**
* Throws IOException that signifies a normal error.
*/
static void
throwIOException(JNIEnv *env, int errnum, const char *externalDetail)
{
throwIOExceptionImpl(env, errnum, externalDetail, "");
}
/**
* Throws an IOException with a message composed from the result of waitpid status.
*/
static void throwExitCause(JNIEnv *env, int pid, int status) {
static void throwExitCause(JNIEnv *env, int pid, int status, int mode) {
char ebuf[128];
if (WIFEXITED(status)) {
snprintf(ebuf, sizeof ebuf,
"Failed to exec spawn helper: pid: %d, exit value: %d",
"Failed to exec spawn helper: pid: %d, exit code: %d",
pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
snprintf(ebuf, sizeof ebuf,
@ -368,7 +409,7 @@ static void throwExitCause(JNIEnv *env, int pid, int status) {
"Failed to exec spawn helper: pid: %d, status: 0x%08x",
pid, status);
}
throwIOException(env, 0, ebuf);
throwInternalIOException(env, 0, ebuf, mode);
}
#ifdef DEBUG_PROCESS
@ -691,7 +732,7 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
(fds[2] == -1 && pipe(err) < 0) ||
(pipe(childenv) < 0) ||
(pipe(fail) < 0)) {
throwIOException(env, errno, "Bad file descriptor");
throwInternalIOException(env, errno, "Bad file descriptor", c->mode);
goto Catch;
}
c->fds[0] = fds[0];
@ -725,13 +766,13 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
if (resultPid < 0) {
switch (c->mode) {
case MODE_VFORK:
throwIOException(env, errno, "vfork failed");
throwInternalIOException(env, errno, "vfork failed", c->mode);
break;
case MODE_FORK:
throwIOException(env, errno, "fork failed");
throwInternalIOException(env, errno, "fork failed", c->mode);
break;
case MODE_POSIX_SPAWN:
throwIOException(env, errno, "posix_spawn failed");
throwInternalIOException(env, errno, "posix_spawn failed", c->mode);
break;
}
goto Catch;
@ -745,20 +786,21 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
{
int tmpStatus = 0;
int p = waitpid(resultPid, &tmpStatus, 0);
throwExitCause(env, p, tmpStatus);
throwExitCause(env, p, tmpStatus, c->mode);
goto Catch;
}
case sizeof(errnum):
if (errnum != CHILD_IS_ALIVE) {
/* This can happen if the spawn helper encounters an error
* before or during the handshake with the parent. */
throwIOException(env, 0, "Bad code from spawn helper "
"(Failed to exec spawn helper)");
throwInternalIOException(env, 0,
"Bad code from spawn helper (Failed to exec spawn helper)",
c->mode);
goto Catch;
}
break;
default:
throwIOException(env, errno, "Read failed");
throwInternalIOException(env, errno, "Read failed", c->mode);
goto Catch;
}
}
@ -770,7 +812,7 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
throwIOException(env, errnum, "Exec failed");
goto Catch;
default:
throwIOException(env, errno, "Read failed");
throwInternalIOException(env, errno, "Read failed", c->mode);
goto Catch;
}

View File

@ -28,6 +28,7 @@
* 6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958
* 4947220 7018606 7034570 4244896 5049299 8003488 8054494 8058464
* 8067796 8224905 8263729 8265173 8272600 8231297 8282219 8285517
* 8352533
* @key intermittent
* @summary Basic tests for Process and Environment Variable code
* @modules java.base/java.lang:open
@ -86,6 +87,7 @@ public class Basic {
/* Used for regex String matching for long error messages */
static final String PERMISSION_DENIED_ERROR_MSG = "(Permission denied|error=13)";
static final String NO_SUCH_FILE_ERROR_MSG = "(No such file|error=2)";
static final String SPAWNHELPER_FAILURE_MSG = "(Possible reasons:)";
/**
* Returns the number of milliseconds since time given by
@ -318,7 +320,9 @@ public class Basic {
} catch (IOException e) {
String m = e.getMessage();
if (EnglishUnix.is() &&
! matches(m, PERMISSION_DENIED_ERROR_MSG))
!matches(m, PERMISSION_DENIED_ERROR_MSG))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
}
@ -428,7 +432,9 @@ public class Basic {
} catch (IOException e) {
String m = e.getMessage();
if (EnglishUnix.is() &&
! matches(m, NO_SUCH_FILE_ERROR_MSG))
!matches(m, NO_SUCH_FILE_ERROR_MSG))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
@ -441,7 +447,9 @@ public class Basic {
} catch (IOException e) {
String m = e.getMessage();
if (EnglishUnix.is() &&
! matches(m, NO_SUCH_FILE_ERROR_MSG))
!matches(m, NO_SUCH_FILE_ERROR_MSG))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
@ -1978,6 +1986,8 @@ public class Basic {
if (EnglishUnix.is() &&
! matches(m, NO_SUCH_FILE_ERROR_MSG))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
//----------------------------------------------------------------
@ -1995,6 +2005,8 @@ public class Basic {
|| (EnglishUnix.is() &&
! matches(m, NO_SUCH_FILE_ERROR_MSG)))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
//----------------------------------------------------------------
@ -2011,6 +2023,8 @@ public class Basic {
|| (EnglishUnix.is() &&
! matches(m, NO_SUCH_FILE_ERROR_MSG)))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
//----------------------------------------------------------------
@ -2069,8 +2083,9 @@ public class Basic {
op.f();
fail();
} catch (IOException expected) {
check(expected.getMessage()
.matches("[Ss]tream [Cc]losed"));
String m = expected.getMessage();
check(m.matches("[Ss]tream [Cc]losed"));
check(!matches(m, SPAWNHELPER_FAILURE_MSG));
}
}
}
@ -2122,10 +2137,14 @@ public class Basic {
}
equal(-1, r);
} catch (IOException ioe) {
if (!ioe.getMessage().equals("Stream closed")) {
String m = ioe.getMessage();
if (!m.equals("Stream closed")) {
// BufferedInputStream may throw IOE("Stream closed").
unexpected(ioe);
}
if (matches(m, SPAWNHELPER_FAILURE_MSG)) {
unexpected(ioe);
}
} catch (Throwable t) { unexpected(t); }}};
thread.start();
@ -2179,6 +2198,9 @@ public class Basic {
! (msg.matches(".*Bad file.*") ||
msg.matches(".*Stream closed.*")))
unexpected(e);
if (matches(msg, SPAWNHELPER_FAILURE_MSG)) {
unexpected(e);
}
}
catch (Throwable t) { unexpected(t); }}};
reader.setDaemon(true);
@ -2271,6 +2293,9 @@ public class Basic {
if (EnglishUnix.is() &&
! matches(m, PERMISSION_DENIED_ERROR_MSG))
unexpected(e);
if (matches(m, SPAWNHELPER_FAILURE_MSG)) {
unexpected(e);
}
} catch (Throwable t) { unexpected(t); }
new File("emptyCommand").delete();

View File

@ -49,14 +49,21 @@ public class JspawnhelperProtocol {
private static final String[] CMD = { "pwd" };
private static final String ENV_KEY = "JTREG_JSPAWNHELPER_PROTOCOL_TEST";
private static final String SPAWNHELPER_FAILURE_MSG = "Possible reasons:";
private static void parentCode(String arg) throws IOException, InterruptedException {
System.out.println("Recursively executing 'JspawnhelperProtocol " + arg + "'");
Process p = null;
try {
p = Runtime.getRuntime().exec(CMD);
} catch (Exception e) {
// Check that exception contains rich message on failure.
e.printStackTrace(System.out);
System.exit(ERROR);
if (e instanceof IOException && e.getMessage().contains(SPAWNHELPER_FAILURE_MSG)) {
System.exit(ERROR);
} else {
System.exit(ERROR + 3);
}
}
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
System.out.println("Child process timed out");