JDK-4388188 : Fork/exec fails after creating JVM using JNI from C (Linux)
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 1.3.0
  • Priority: P2
  • Status: Closed
  • Resolution: Not an Issue
  • OS: linux
  • CPU: x86
  • Submitted: 2000-11-10
  • Updated: 2000-11-28
  • Resolved: 2000-11-28
Description

Name: yyT116575			Date: 11/10/2000


9 Nov 2000, eval1127@eng -- see first two bugs (# 4373248 and 4366835) returned via:
http://search.java.sun.com/query.html?qt=%2Bfork+%2Blinux+&col=obug&qp=&qs=&qc=&pw=100%25&ws=0&qm=0&st=1&nh=10&lk=1&rf=0&rq=0
------------------------------------------
java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0)
Java HotSpot(TM) Client VM (build 1.3.0, mixed mode)


After using JNI to create the JVM from a C/C++ program under Linux, it is
no longer possible to fork/exec a new process from that main process.
It is not possible to spawn a process from C++ (using fork() + exec()),
nor is it possible to spawn a process from the Java code (using Runtime.exec()).
The attached program demonstrates the problem.

1. Test fork from C++
> forktest
This program first launches the JVM, and then calls fork() and exec().
The child process attempts to run a simple script (tick.sh) that loops
forever printing counter output to a file.
The child process is forked, but then freezes when calling exec.
The JVM dies, leaving a single defunct java process.
The stack trace of the child process is :

#0  0x404d8deb in __sigsuspend (set=0xbfffd8d8) at
../sysdeps/unix/sysv/linux/sigsuspend.c:48
#1  0x405cdc82 in __pthread_wait_for_restart_signal (self=0x405d5940) at
pthread.c:785
#2  0x405cd6f6 in __pthread_kill_other_threads_np () at restart.h:26
#3  0x4054bd6b in __execve (file=0x8048d9a "./tick.sh", argv=0xbfffdab4,
envp=0x808f770)
    at ../sysdeps/unix/sysv/linux/execve.c:39
#4  0x4054bf67 in execl (path=0x8048d9a "./tick.sh", arg=0x8048d92 "tick.sh") at
execl.c:74
#5  0x8048a59 in testCFork ()
#6  0x8048b3d in main ()
#7  0x404d29cb in __libc_start_main (main=0x8048b14 <main>, argc=1,
argv=0xbfffeb44, init=0x8048610 <_init>,
    fini=0x8048ccc <_fini>, rtld_fini=0x4000ae60 <_dl_fini>,
stack_end=0xbfffeb3c)
    at ../sysdeps/generic/libc-start.c:92

The forktest program runs OK if the JVM is not launched before the fork.



2. Test fork from Java

The java Spawn class executes the shell script by using Runtime.exec().
When run from C++ the Spawn class is created but appears to fail in the
exec, even though no exception is raised. ie.
> forktest java

This program runs ok when launched directly using the JVM. ie.
> java Spawn


The problem appeared on with Redhat Linux (kernel 2.2.14-12 & 2.2.16-3) using
either jdk1.2.2 or jdk1.3.
The problem did not occur on SPARC Solaris 7 with jdk1.3.


------------------------------------------------------

The forktest program was built using:
> javac Spawn.java
> gcc ForkTest.C    -I$JAVAHOME/include -I$JAVAHOME/include/linux    -L$JAVAHOME/jre/lib/i386    -L$JAVAHOME/jre/lib/i386/client    -L$JAVAHOME/jre/lib/i386/native_threads    -ljava -ljvm -lhpi -lstdc++ -lnsl -o forktest



//--------------------------- file ForkTest.C ---------------------------

#include <jni.h>
#include <iostream.h>
#include <unistd.h>
#include <stdlib.h>

// Create the Java virtual machine, and start a test object.
static JNIEnv* createJVM(void) {
    JavaVM* jvm;
    JNIEnv* env;

    JavaVMOption options[20];
    int numOptions = 0;
    options[numOptions++].optionString = "-Djava.class.path=.";

    JavaVMInitArgs args;
    args.version = JNI_VERSION_1_2;
    args.options = options;
    args.nOptions = (jint)numOptions;
    args.ignoreUnrecognized = 0;

    if (JNI_CreateJavaVM(&jvm, (void **)&env, &args)) {
        cerr << "Can't create JVM." << endl;
        exit(1);
    }
    cout << "JVM created." << endl;
    return env;
}

void testJavaFork(JNIEnv *env) {
    jclass clazz = env->FindClass("Spawn");
    if (env->ExceptionOccurred() || (clazz == 0)) {
        cerr << "Can't find Java class." << endl;
        exit(1);
    }
    jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
    if (env->ExceptionOccurred() || (init == 0)) {
        cerr << "Can't find Java method." << endl;
        exit(1);
    }
    jobject obj = env->NewObject(clazz, init);
    if (env->ExceptionOccurred() || (obj == 0)) {
        cerr << "Can't create Java object." << endl;
        exit(1);
    }
    cout << "Java object created." << endl;
}

void testCFork(void) {
    pid_t pid;
    if ((pid = fork()) == 0) {
        // Child process
        execl("./tick.sh", "tick.sh", (char *)0);
        cerr << "Error in C exec." << endl;
        _exit(1);
    }
    // Parent process
    cout << "Child PID = " << pid << endl;
}

int main(int argc, char **argv) {

    JNIEnv* env = createJVM();

    if (argc > 1) {
        testJavaFork(env);
    } else {
        testCFork();
    }

    int count = 0;
    while (1) {
        cout << "Parent " << ++count << endl;
        sleep(5);
    }
}

//--------------------------- file Spawn.java ---------------------------

import java.io.*;

public class Spawn implements Runnable {
    public static void main(String[] args){
        new Spawn();
    }

    /**
     * Use a new thread to wait for process termination
     */
    public Spawn() {
        new Thread(this).start();
    }

    /**
     * Spawn a process to run a ticker shell script.
     */
    public void run() {
        try {
            System.out.println("Spawning tick....");
            Process proc = Runtime.getRuntime().exec("tick.sh");
            System.out.println("Done spawn.");
            proc.waitFor();
            System.out.println("tick exit status = " + proc.exitValue());
        } catch (Exception e) {
            System.err.println("Java exec failed " + e.getMessage());
            return;
        }
    }
}

//--------------------------- file tick.sh ---------------------------
#!/bin/sh

echo "pid = $$" | tee log
count=0
while [ 1 ]
do
    count=`expr $count + 1`
    echo "Tick = $count" | tee -a log
    sleep 4
done
(Review ID: 112065) 
======================================================================

Comments
WORK AROUND Name: yyT116575 Date: 11/10/2000 None ======================================================================
11-06-2004

EVALUATION When invoking a JVM from C on linux the binary needs to be compiled with -lpthread. Then the program will continue to run. This is a difference between the linker on Solaris and the linker on linux calvin.austin@Eng 2000-11-27 If the top-level program isn't linked with libpthreads, the program ends up calling fork() inside glibc instead of the "thread-aware" fork() within pthreads. robert.lougher@Eng 2000-11-27
27-11-2000