Home > Back-end >  Calling a Linux command via java.lang.Runtime.exec
Calling a Linux command via java.lang.Runtime.exec

Time:08-21

This is my code in Java:

 Runtime rt = Runtime.getRuntime();
 Process p = rt.exec("nohup java -jar /root/xxxx.jar &");

This command can be executed, I can see that the service is started, but I have not seen the nohup log, why is this?

CodePudding user response:

Short story

Your expectations are based on observations which are actually irrelevant to java, basically that ("redirecting output to nohup.out") does not work because processes spawned via java.lang.Runtime#exec are not bound to tty, thus nohup wrapper has no effect.

Long story

Q: Why do UNIX guys need to use nohup wrapper when they want to spawn long-running process/program?

When we are working from shell it's first three file descriptors (stdin, stdout and stderr) are bound to tty, for example:

]# ls -la /proc/self/fd/[0-2]
lrwx------ 1 root root 64 Aug 20 09:29 /proc/self/fd/0 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:29 /proc/self/fd/1 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:29 /proc/self/fd/2 -> /dev/pts/2

and any process spawned from shell inherits those three file descriptors, for example:

# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

...

# another shell
# 65681 - pid of ping
# 65654 - pid of shell (parent)

]# ps -ef | grep ping
root     65681 65654  0 09:31 pts/2    00:00:00 ping 8.8.8.8
root     65700 65684  0 09:32 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/65681/fd/[0-2]
lrwx------ 1 root root 64 Aug 20 09:32 /proc/65681/fd/0 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:32 /proc/65681/fd/1 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:32 /proc/65681/fd/2 -> /dev/pts/2

Q: what will happen if we leave our shell?

]# kill -9 65654
]# ls -la /proc/65681/fd/[0-2]
ls: cannot access /proc/65681/fd/[0-2]: No such file or directory
]# ps -ef | grep 65681
root     65738 65684  0 09:40 pts/3    00:00:00 grep --color=auto 65681

Operating system was need to close tty (we left shell), so OS sends SIGHUP signal to all processes which was using (bound to) that tty and closes tty

The purpose of nohup wrapper is following:

it inspects whether first three file descriptors (stdin, stdout and stderr) are bound to tty and if so it replaces them by /dev/null, nohup.out and nohup.out:

]# nohup ping 8.8.8.8
nohup: ignoring input and appending output to 'nohup.out'

...

# another shell
# 65767 - pid of ping
# 65752 - pid of shell (parent)

]# ps -ef | grep ping
root     65767 65752  0 09:44 pts/2    00:00:00 ping 8.8.8.8
root     65773 65684  0 09:45 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/65767/fd/[0-2]
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/1 -> /nohup.out
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/2 -> /nohup.out

now, trying to "leave" our shell:

]# kill -9 65752
]# ls -la /proc/65767/fd/[0-2]
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/1 -> /nohup.out
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/2 -> /nohup.out
]# ps -ef | grep ping
root     65767     1  0 09:44 ?        00:00:00 ping 8.8.8.8
root     65781 65684  0 09:46 pts/3    00:00:00 grep --color=auto ping

our long-running application is continuing running, everyone is happy - that is nohup wrapper magic.

In regard to your Q...

If your goal it to re-implement the same using Java you need to do the same in Java and do not rely on nohup wrapper, below are some examples:

Let's try using "naive" implementation like:

public class Test {

    public static void main(String[] args) throws Exception {
        Runtime rt = Runtime.getRuntime();
        rt.exec("ping 8.8.8.8");
        Thread.sleep(1_000_000_000);
    }
    
}
]# $JAVA_HOME/bin/javac Test.java 
]# $JAVA_HOME/bin/java -cp . Test 

# another shell
# 65890 - pid of ping
# 65873 - pid of java (parent)

]# ps -ef | grep ping
root     65890 65873  0 09:58 pts/2    00:00:00 ping 8.8.8.8
root     65895 65684  0 09:59 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/65890/fd/[0-2]
lr-x------ 1 root root 64 Aug 20 09:58 /proc/65890/fd/0 -> pipe:[651007]
l-wx------ 1 root root 64 Aug 20 09:58 /proc/65890/fd/1 -> pipe:[651008]
l-wx------ 1 root root 64 Aug 20 09:58 /proc/65890/fd/2 -> pipe:[651009]

now, instead of seeing file descriptors like "/dev/pts/xxx" we are seeing something weird like pipe:[xxx]. What is that? That is UNIX IPC - child process sends data to parent process via anonymous pipe. Anonymous pipes are not ttys, and that is why nohup wrapper won't have any effect, we may demonstrate that using shell (neither "nohup.out" in output nor "ignoring input and appending output to 'nohup.out'" message):

]# nohup ping 8.8.8.8 </dev/null 2>&1 | cat
^Z
[1]   Stopped                 nohup ping 8.8.8.8 < /dev/null 2>&1 | cat
]# ps -ef | grep ping
root     66840 65684  0 12:38 pts/3    00:00:00 ping 8.8.8.8
root     66845 65684  0 12:38 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/66840/fd/[0-2]
lr-x------ 1 root root 64 Aug 20 12:38 /proc/66840/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 12:38 /proc/66840/fd/1 -> pipe:[659814]
l-wx------ 1 root root 64 Aug 20 12:38 /proc/66840/fd/2 -> pipe:[659814]

What will happen if parent process gets exited? Actually, it depends on whether child process is sending data via anonymous pipe or not, but in the worst case child process will exit:

]# kill 65873
]# ls -la /proc/65890/fd/[0-2]
]# ps -ef | grep ping
root     66238 65684  0 10:31 pts/3    00:00:00 grep --color=auto ping

And the only reliable way to get the behaviour similar to nohup wrapper is to "re-implement" nohup wrapper on Java side:

import java.io.File;

public class Test {

    public static void main(String[] args) throws Exception {
        Process process = new ProcessBuilder("ping", "8.8.8.8")
                .directory(new File("/tmp"))
                .redirectInput(new File("/dev/null"))
                .redirectOutput(new File("/tmp/nohup.out"))
                .redirectError(new File("/tmp/nohup.out"))
                .start();
        Thread.sleep(1_000_000_000);
    }

}
]# ps -ef | grep ping
root     66394 66377  0 10:37 pts/2    00:00:00 ping 8.8.8.8
root     66398 65684  0 10:37 pts/3    00:00:00 grep --color=auto ping
]# kill 66377
]# ls -la /proc/66394/fd/[0-2]
lr-x------ 1 root root 64 Aug 20 10:37 /proc/66394/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 10:37 /proc/66394/fd/1 -> /tmp/nohup.out
l-wx------ 1 root root 64 Aug 20 10:37 /proc/66394/fd/2 -> /tmp/nohup.out
]# ps -ef | grep 66394
root     66394     1  0 10:37 pts/2    00:00:00 ping 8.8.8.8
root     66402 65684  0 10:38 pts/3    00:00:00 grep --color=auto 66394

Thus, the java implementation of yours:

nohup java -jar /root/xxxx.jar &

is:

Process process = new ProcessBuilder("java", "-jar", "/root/xxxx.jar")
        .redirectInput(new File("/dev/null"))
        .redirectOutput(new File("nohup.out"))
        .redirectError(new File("nohup.out"))
        .start();
  • Related