Spartan: A ''Forking'' Java Program Launcher (Part 1)
Spartan: A ''Forking'' Java Program Launcher (Part 1)
Check out the first part of this series on Spartan, a new open source Java forking program that makes launching Java easier.
Join the DZone community and get the full member experience.Join For Free
Find vulnerabilities in your open source libraries in seconds. Get your 30 day free trial today!
Spartan, a GitHub-hosted, open source project, is a "forking" Java program launcher — Java supervisor processes can launch Java child worker processes (uses Linux fork and then instantiates JVM). Child programs run as same user identity/permissions capability — no authentication or special permissions necessary. Share the same Java code amongst parent supervisor and children processes. Annotations mark entry point methods.
From Wikipedia: Fork (system call)
The Java program launcher, Java on Posix platforms and Java.exe on Windows, has remained pretty much the same in functionality since the origin of the Java programming language. This default Java program launcher provides a uniform experience for starting the execution of Java programs on all supported platforms.
But after two decades, the facility for launching Java programs is due for a facelift.
Today the predominant use of the Java language is in writing back-end or middle-ware, server-based software, and in more recent times the interest has shifted to centering on implementing microservices. (Java is also popularly used for Android mobile device computing but is not relevant to the discussion here.) These days Linux (or Posix) operating systems rule in the realm of server-based software — this is principally because cloud computing data centers mostly rely on a Linux-based OS as the host for their VMs. Also, enterprise computing looking to homogenize development across on-premise data centers and cloud-based computing have gravitated toward Linux as a common denominator.
Containerization coupled with microservices are dominant trends in server software development and its subsequent deployment. Linux is the most prominent OS platform for containerization because the most widely used container implementations are based on Linux kernel features.
Java threads are problematic when writing high availability, self-healing software — whereas the process of modern operating systems represents a much more robust unit of program execution that can be killed with impunity and then the program relaunched (Java threads — well, not so much).
ssh-based command-line shell connections are a pervasive means of connecting to server host. The ability to interact with services software from a command line can be a nice enabler for both developers and support staff — particularly if the service can respond to subcommands initiated from the command line and do so with practically no special effort required by the programmers of the service. Also, having such command line interaction capability with a running service daemon that is not dependent on TCP sockets ensures a tighter default security posture.
In view of these factors, it is time to take a new look at the matter of launching Java programs. Why not devise a new kind of Java program launcher that is better fitted to the above landscape of contemporary computing?
And so such a program has been written (implemented in C++11) and it is called Spartan.
What is Spartan?
The Linux native program Spartan is an alternative Java program launcher. It is specifically intended to launch a Java program to run as a Linux service daemon - which means the Java program stays resident indefinitely until issued a command instructing it to exit.
The special command line option
-service is reserved by Spartan and instructs for the program to be run as a service daemon (yes, it overlaps with the Java JVM
-service option but we will see how JVM options are dealt with later).
A Spartan annotation is required to designate the main() method entry point for the service daemon to begin execution at.
A Spartan-based Java program will respond to two subcommands without a programmer doing anything special:
The method implementing
status subcommand can be easily overridden and customized (to display more in-depth status info).
A Spartan-launched program can also be terminated via Control-C (Posix SIGINT signal).
Additionally, Spartan annotations can be applied to methods that are designated as entry points for programmer-defined custom subcommands.
Supervisor Process and Worker Child Processes
Spartan annotations are also used to designate method entry points for worker child processes.
A subcommand can, therefore, be issued to invoke a worker child process from the command line (in addition to supervisor-specific sub commands already mentioned).
The Spartan annotations for supervisor subcommands and worker child process subcommands will specify the subcommand text token as an annotation attribute. That token is what is typed at the command line in order to execute it — along with any command line arguments that are passed to the subcommand. Consequently, Spartan-based services are very easy to script via Linux shell programs such as bash.
A supervisor process can oversee the execution of one or more worker child processes — it can supervise them, so to speak. This is a great way to go about programming robust, self-healing software.
Multiple worker child processes could also be established with an arity corresponding to the count of detected CPU cores — the supervisor process could oversee the dispensing of work to be done by these child processes, so yet another way to partition a program for parallel processing (JVM heap size is kept to more minimal level per each parallel processing activity; the GC of each child process will be less stressed and perhaps faster due to smaller scoped garbage collections).
Native Java serialization can be effortlessly used to communicate data between these processes — they're all running from the same Java code so the classes are the same, and thus no version compatibility issues to contend with. Java serialization is also the simplest way for the supervisor to convey one-time configuration to any worker child process.
The Linux security model of the supervisor is the same for the worker child processes.
A Spartan-based program by default does not use any TCP socket listeners - it is up to programmers to decide whether to introduce the use of any socket listeners, as determined by the domain requirements of their particular program.
Spartan enables straightforward multi-process programming for Java programmers - read on to see why Spartan exceeds the existing Java process-related APIs in the all crucial ease of use department.
Why Processes Instead of Only Threads?
Threading came about as an evolutionary step beyond the operating system process as a unit of execution — a thread is a more lightweight construct from a scheduler context switching perspective and thus multiple threads can be executed within a single process context (threads exist in the owning process address space). However, when designing software for high availability rigor, with self-healing capability, the thread is dismal by comparison to the process as it can too easily become catastrophically unstable and thereby render the entire Java JVM unstable.
The Java JVM runs in a single process and only has threading available by which to manage concurrent activities. When any one JVM thread becomes destabilized, the entire JVM is highly prone to being compromised, too. Java thread APIs destroy, stop, suspend, resume, these are all deprecated. Here is an excerpt from the deprecated Thread.stop API:
A Java thread cannot really be killed explicitly without a high potential for undesirable side effects. As such Java threads must be coded to terminate nicely under their own volition. If a thread becomes wedged, though, then the running service is out of luck — not much can be done for that situation. This is not the ideal for when tasked with creating software systems where high availability and self-recovery are paramount concerns.
However, it is more straightforward to write robust high availability software where one process watches over another process in which the real work is being done. This arrangement is referred to as a watchdog. The watchdog parent process health check monitors the subsidiary child process — if the child process encounters error conditions or even out-right crashes, then the parent watchdog can take self-healing remediation actions that eventually may restore operations to normal. A wedged process or a fatally crashed child process can be explicitly killed by the parent watchdog process (or if the child process terminated abruptly the parent can detect that). A new child process can then be launched by the parent watchdog process.
In such error handling situations, the cycle might continue for a while — say, if a database resource had gone offline for servicing but later was brought back online, in which case a worker child process then successfully gets a connection to the database resource and resumes operation.
Or the error condition may continue and require support staff remediation. In that event, the watchdog process remains in a stable condition to where it can be monitoring the number of retries before it decides to communicate alert status to support staff. As the error condition persists the watchdog can apply back-off heuristics and perhaps spam suppression of alerts.
A watchdog coded specifically to the needs of a service can finesse the understanding of possible error conditions that might arise much more so than generic mechanisms that are typical of monitoring software packages. Having application specific knowledge can be very helpful as to error context - when retry is warranted vs. an out-right fatal condition requiring alerting, etc.
A custom implemented watchdog is the ideal because it can provide the smartest set of reactions to potential error conditions that a particular service may encounter - because it was written by a software developer that has the highest domain knowledge about the service.
Process Forking - the Easiest and Best Way to Implement a Watchdog
A single program using process forking will start out executing as the parent watchdog process. After making the fork call, it will then continue executing as the parent process along one code pathway, and then as the child process along another code pathway. It is easy to thus share the programming use of the same data structures and functions between parent and child processes.
The marshaling of data between parent and child processes will be guaranteed to be version compatible (the exact same data structures are seen identically by both) so there is no need to worry with version checks in parent-child inter-process communication.
The child process inherits and executes with the same permissions as the parent process so it can automatically access all the same files and directories that the parent can access. By the same token, the child is likewise prohibited from unwarranted resource access just as the parent. Hence the security posture is simple and straightforward — the child process can be regarded in the same manner, security-wise, as the parent process. Because of this inheriting of user identity and permissions, it is not necessary to provide authentication credentials when initiating the child process via a fork call.
Well, that is all nice and wonderful for C, C++, or Rust programmers, but what does that have to do with Java programming?
A core purpose of the Spartan Java program launcher is to enable a manner of programming with Java that closely resembles process forking goodness. A Spartan programmer really does Java programming with processes but in a manner very much retaining the benefits just described.
Yes, Java already has APIs for launching other processes — but at a cost relative to fork call convenience and simplicity. Sometimes a major step forward in programming is a matter of making something much easier to do, with much reduction of the negative drawbacks, worries, concerns. Because of the latter, often times a facility — such as the Java API for launching processes — can go virtually unused. There is usually just too much of a hassle factor involved so programmers by and large just don't bother to go there.
Opinions expressed by DZone contributors are their own.