Good all the time of day.
Recently, on duty, it became necessary to write the background process for IBM AIX 5.2 in Java with a shell-controlling script.
Once the work is done, the documentation is up, the code is written, why not share it with the public? By this, let's get down to business.
')
1 demonization
In order to demonize an application, it is necessary to disconnect it from the terminal in which it starts up directly. To do this, follow these steps:
- Disconnect stdin from terminal
- Run the process in background'e by specifying the ampersand "&" at the end of the launch line
- Close stdin, stdout directly in the application (in our case, for Java, it will be System.in.close (); System.out.close ();)
Thus, the minimum startup line should look like this:
java daemon_app <& - &
where "<& -" will disable stdin;
"&" as it was said, will allow the application to be switched from foreground to background mode.
The daemon_app code should look something like this:
public class daemon_app
{
public static int main (String [] args)
{
try
{
daemonize ();
}
catch (throwable e)
{
System.err.println ("Startup failed." + E.getMessage ());
return 1;
}
doProcessing ();
return 0;
}
static private void daemonize () throws Exception
{
System.in.close ();
System.out.close ();
}
static private void doProcessing ()
{
// Do some processing
}
}
The daemonize method disables the application from stdin and stdout. The stderr stream remains one active, which we can use to log errors during the initialization stage.
The doProcessing method is designed to implement the main logic.
Further, we can use some kind of framework for logging, for example, log4j.
The next improvement is to change the launch string to intercept the data sent by our process to stderr. To do this, we modify the launch string as follows:
java daemon_app <& - 2> /var/log/daemon_app_error.log &
where “2> /var/log/daemon_app_error.log” redirects the output from stderr to the file /var/log/daemon_app_error.log.
If the reader does not know, then the following identifiers have input-output streams in the unix shell:
stdin - 0
stdout - 1
stderr - 2
2 Organization of interrupt signal processing
In the doProcessing method, we can organize an infinite loop with a precondition according to which the process will end. This condition can be SIGTERM sent from the operating system, for example, by kill -15 <pid>.
Code 15 (SIGTERM) is the same for both AIX, HP-UX and the usual Linux-based system.
The list of signals and their codes can be obtained using the command kill -l.
There are several ways to process signals in Java:
- Using sun.misc.Signal and sun.misc.SignalHandler followed by creating your own handler class that implements sun.misc.SignalHandler. More detailed information on this method can be found here (http://www.ibm.com/developerworks/ibm/library/i-signalhandling/)
- Use the Runtime.getRuntime (). AddShutdownHook method. (method signature: public void addShutdownHook (Thread hook))
The first method is not very good due to the use of classes from the sun.misc package. Why?
Here is what Sun
writes (http://java.sun.com/products/jdk/faq/faq-sun-packages.html) about package sun. *:
The sun. * Public packages.
A Java program that directly calls into the sun. * Java-compatible platforms. In fact, such a program is not guaranteed to work even in the future ...
The 2nd method involves passing to it as an argument a class inherited from Thread. This means that when SIGTERM is received, a new thread will be created, which will take the actions determined by the programmer to terminate the entire process.
Referring to the
documentation (http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#19152), what preconditions are needed to complete the Java program?
This is what happens when:
- All the threads that are not daemon threads (§2.19) terminate.
- It is a bylaw of the security manager.
Therefore:
- If we have non-running daemon threads, it is necessary to ensure their correct completion and join them to main using join. Daemon thread those for which setDaemon (true) was executed are not necessary to stop.
- Stop the thread main work, which means exit from the main method of the class being run.
You can simply do System.exit (), if you do not need to perform specific operations on the completion of the program, closing the resources used and active connections.
Modified code using Runtime.getRuntime (). AddShutdownHook to create an interrupt signal handler is shown below:
public class daemon_app
{
static private boolean shutdownFlag = false;
public static int main (String [] args)
{
try
{
daemonize ();
}
catch (throwable e)
{
System.err.println ("Startup failed." + E.getMessage ());
return 1;
}
registerShutdownHook ();
doProcessing ();
return 0;
}
static private void doProcessing ()
{
while (false == shutdownFlag)
{
// Do some processing
}
}
static public void setShutdownFlag () {shutdownFlag = true;}
private static void registerShutdownHook ()
{
Runtime.getRuntime (). AddShutdownHook (
new thread () {
public void run () {
daemon_app.setShutdownFlag ();
}
}
);
}
static private void daemonize () throws Exception
{
System.in.close ();
System.out.close ();
}
}
So, we have the registerShutdownHook method, which is called from main and registers a handler for the interrupt signal.
When receiving an interrupt signal, the static method setShutdownFlag is called, which changes the value of the static boolean property shutdownFlag to true, by the value of which the cycle is organized in the doProcessing method with a precondition.
3 Controlling script
So, the demon process is written. Now you need to create a script that controls its start, stop and state monitoring.
I will not describe the whole process of creating a shell control script; I will give only a few useful procedures.
An example of checking the environment variables necessary for starting / running a process
No argument procedure. We iterate over the necessary environment variables with the for loop. If there is no variable, output a warning. If at least one variable is not set, stop execution with error code 1.
check_env ()
{
exit_flag = 0
for env_var in JAVA_HOME ORACLE_HOME TUXEDO_HOME;
do
eval "env_value = \ $$ env_var"
if [-z "$ env_value"]
then
echo "ERROR: environment variable '$ env_var' is not set"
exit_flag = 1
fi
done
if [$ exit_flag -eq 1]
then
echo "Exiting. No process started"
exit 1
fi
}
Checking classpath elements
The argument of this procedure is a string with a classpath in which the elements are separated by a colon.
Replace the colon with a space character - as a result, we get the opportunity to check each element.
check_classpath ()
{
#Checking files in classpath are exists and readable
for resource in echo $ 1 | sed -e "s /: / / g" `;
do
if [$ {# resource} -gt 0] && [! -r $ resource] # if file is not readable
then
echo "WARNING: Resource '$ resource' included in CLASSPATH does not exist or not readable"
fi
done
}
Sample process start procedure
This procedure provides for the presence of set variables - PID_DIR, PROCESS_NAME, CPATH
Where
PID_DIR - directory for storing pid files
PROCESS_NAME - process name
CPATH - line with classpath
The process ID started in the background, can be defined using the shell variable "$!", Which is subsequently written to the pid file.
After that, there is a test for 5 seconds, or the process “did not fall” during the launch process. Every second there is a check with the help of ps -p process status.
launch_daemon ()
{
JVM_OPTS = ""
# Set max memory to 1G
JVM_OPTS = "$ JVM_OPTS -Xmx1G"
echo "Starting process \ c"
#Transfer it from terminal
$ JAVA_HOME / bin / java $ JVM_OPTS -cp $ CPATH daemon_app <& - 2> /var/log/$PROCESS_NAME.pid &
#Write pid to pid file
echo $! > $ PID_DIR / $ PROCESS_NAME.pid
if [$ {#!} -eq 0]
then
echo "... [failed]"
else # Checking for 5 seconds if process is alive
timer = 0
while [$ timer -lt 6]
do
echo ". \ c"
sleep 1
timer = $ timer + 1
done
if [`ps -p $! | wc -l` -gt 1]
then
echo "[started]"
exit 0
else
echo "[failed]"
exit 1
fi
fi
}
An example of the procedure to stop the process
The following implementation accepts 2 input arguments:
- process id
- absolute path to the pid file
The procedure sends the process SIGTERM and after 5 seconds of waiting, if the process is not stopped, sends it a SIGKILL, after which it deletes the pid file.
stop_daemon ()
{
echo "Stopping process \ c"
timer = 0
if [`ps -p $ 1 | wc -l` -eq 1]
then
echo "not running"
else
kill -TERM $ 1
while [`ps -p $ 1 | wc -l` -gt 1]
do
if [$ timer -gt 5]
then
kill -KILL $ 1
timer = 0
else
echo ". \ c"
sleep 1
fi
timer = $ timer + 1
done
echo "stopped"
fi
rm $ 2
}
Sample process status check procedure
Takes 1 argument - the absolute path to the pid file
Returns a value other than 0 if the process is running and running, 0 if the process is inactive.
check_running ()
{
if [-e $ 1]
then
fpid = `cat $ 1`
if [$ {# fpid} -gt 0]
then
lines = `ps -p $ fpid | wc -l`
echo $ (($ lines-1))
else
echo 0
fi
else
echo 0
fi
}
That's basically all I wanted to tell.
It is interesting to hear what everyone thinks about this topic.