diff --git a/src/java.base/unix/native/libjava/ProcessImpl_md.c b/src/java.base/unix/native/libjava/ProcessImpl_md.c index fc97eb187eb..264818360c2 100644 --- a/src/java.base/unix/native/libjava/ProcessImpl_md.c +++ b/src/java.base/unix/native/libjava/ProcessImpl_md.c @@ -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; } diff --git a/test/jdk/java/lang/ProcessBuilder/Basic.java b/test/jdk/java/lang/ProcessBuilder/Basic.java index b77c292766d..ed9a0f9fb70 100644 --- a/test/jdk/java/lang/ProcessBuilder/Basic.java +++ b/test/jdk/java/lang/ProcessBuilder/Basic.java @@ -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(); diff --git a/test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java b/test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java index d31905ccc63..0098bc531fd 100644 --- a/test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java +++ b/test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java @@ -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");