Spartan: A ''Forking'' Java Program Launcher, Part 2
Welcome back! Toady we get a little more acquainted with Spartan by looking at its annotations and the APIs and data structures it uses.
Join the DZone community and get the full member experience.
Join For FreeIf you missed Part 1, you can check it out here.
Spartan Annotations
Supervisor main Method Service Entry Point
The method follows the standard call signature for the Java main
method program entry point, hence it is a static method. However, its owning class must derive from the spartan.SpartanBase
class and it must support a default constructor taking no arguments.
@SupervisorMain
public static void main(String[] args) {
...
}
Supervisor sub-command Method EntryPpoint
A supervisor sub-command method must be an instance method of the class which contains the service's main method entry point.
@SupervisorCommand("GENFIB")
public void generateFibonacciSequence(String[] args, PrintStream rspStream) {
...
}
The token name of the sub-command is provided as a text string argument to the annotation. It is a Spartan programming convention to denote the sub-command in upper case, but when being invoked from a shell command line it is treated in a case insensitive manner. It is okay to use an under score character but not the hyphen character in sub-command tokens.
A supervisor sub-command executes in the context of the supervisor process.
Code which is already executing in the supervisor process should not invoke supervisor sub-commands via the Spartan invokeCommand API - it should instead directly call the method. These supervisor sub-commands are intended to be invoked from an external source such as from a bash command line shell.
The first entry of the args
array will be the name of the sub-command that was invoked.
The PrintStream
argument is used to write output back to the invoker of the sub-command.
Worker Child Process sub-command Method Entry Point
A worker child process sub-command method is a static method; they can reside in any class.
@ChildWorkerCommand(cmd="ETL", jvmArgs={"-Xms128m", "-Xmx324m"})
public static void doEtlProcessing(String[] args, PrintStream rspStream) {
...
}
At a minimum, the cmd
annotation attribute must be supplied to give the sub-command token. Optionally, the jvmArgs
attribute can be present and is useful for establishing JVM options such as -Xms and -Xmx for minimum and maximum Java JVM heap memory.
A worker child process sub-command executes in the context of a newly spawned child process.
The first entry of the args
array will be the name of the sub-command that was invoked.
The PrintStream
argument is used to write output back to the invoker of the command.
A worker child process sub-command can be invoked from the supervisor process via a Spartan invokeCommand API; or they can be invoked from, say, a bash command line shell.
Some Spartan APIs and Class Data Structures
Spartan API for Invoking a Sub-Command
Here is an example of invoking a sub-command - the first argument is the name of the sub-command, which is followed by any command line arguments (provided as text strings - just as would be the case if invoked from a bash command line shell).
InvokeResponse rsp = Spartan.invokeCommand("ETL", "-run-forever");
The class of the returned object looks like so:
class InvokeResponse {
public final int childPID;
public final java.io.InputStream inStream;
public InvokeResponse(int childPID, java.io.InputStream inStream) {
this.childPID = childPID;
this.inStream = inStream;
}
}
The InputStream inStream
field is used by the invoker to read the output generated by the sub-command and to detect its termination (i.e., when the command's execution ceases for whatever reason - normal completion or even a fatally crashed child process). The childPID
is the same pid as used in the Linux operating system to represent the spawned child process.
These Spartan kill APIs can be used to terminate spawned children processes:
static void killSIGINT(int pid) throws KillProcessException;
static void killSIGKILL(int pid) throws KillProcessException;
static void killProcessGroupSIGINT(int pid) throws KillProcessException, KillProcessGroupException;
static void killProcessGroupSIGKILL(int pid) throws KillProcessException, KillProcessGroupException;
An invoked sub-command establishes a process group (dynamically). So killProcessGroupSIGINT|KILL
can be used to terminate multiple spawned process instances that are executing the same sub-command. The pid
of any one of the process instances can be passed to the call and the entire group will be terminated.
Spartan Interplay With Standard Linux Shell Commands
App support or operator staff could also use the Linux kill
command from a command line shell to cause a worker child process to terminate:
kill -TERM 31434
The status
supervisor sub-command can be used to produce a listing of active worker children processes:
$ ./spartan-cfg-ex status
spartan-cfg-ex: INFO: starting process 15968
*** timestamp *** | *** pid *** | *** command-line ***
2018-01-04T12:18:08.184 31434 "etl" "-run-forever" "some.json.gz"
1 child processes active
spartan-cfg-ex: INFO: process 15968 exiting normally
In this illustration, a Spartan example program, spartan-cfg-ex, has at some point started running as a Linux service daemon.
Now the Spartan executable is indeed called spartan, but a symbolic link spartan-cfg-ex references the Spartan executable, and the symbolic link name is the program name of the service (how Spartan binds to the Java program to be launched will be discussed in a latter section).
The service's supervisor process has spawned pid 31434 as a worker child process.
The program of the service can be accessed from the command line at any time, invoking the
status
sub-command, where the command's output is a listing of the active worker child processes.Process pid 15968 is where Spartan is running in client mode. Client mode handles invoking the sub-command
status
against the supervisor process; when the sub command completes then the client mode process 15968 goes away.
The point here is that a single executable program called spartan exists, it is a Java launcher program, but it is capable of being invoked under different context of execution, and if it is invoked with the -service
option, then it starts up a Java program running a supervisor process.
If it is invoked with a recognized sub command, then it behaves in client mode where it dispatches the sub command to be handled.
If it is a supervisor sub-command, then the supervisor process will handle the command and respond with any output. The client mode process prints that output to stdout
; if there is no redirection involved then it displays on the shell console.
If the sub-command is a worker child process command, Spartan sees to it that a child process is spawned to carry out the command. Once again the client mode process prints any output to stdout
.
We have already seen above that a supervisor process can use the Spartan.invokeCommand API call to cause a worker child process to be spawned. In that case, the supervisor process will be receiving the output of the child process. The spartan client mode process was active just long enough for getting the sub command recognized and spawned as a child process then it went away.
It is very easy to now do Java programming which makes use of spawned child processes. But it is also super easy to write shell scripts that invoke sub commands and do things with their output. Thus, Spartan brings the Java programming language fully into the embrace of true Linux platform power - service daemons, command line shell interaction, shell scripts, compatible use of process pids, Linux commands (ps, kill, top), Linux user account for controlling permissions access, etc., etc. Everything Linux aficionados do with Linux native programs written in C or C++, they can now easily do with Java programs. In a sense, Spartan makes Java a first class citizen of the Linux platform.
Published at DZone with permission of Roger Voss. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments