├── .circleci
└── config.yml
├── .classpath
├── .gitignore
├── .project
├── .travis.yml
├── CHANGES.md
├── LICENSE
├── README.md
├── appveyor.yml
├── benchmark
├── README.md
├── benchmark.sh
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── zaxxer
│ └── nuprocess
│ └── benchmark
│ └── NuProcessBenchmark.java
├── mvnvm.properties
├── pom.xml
├── shippable.yml
└── src
├── example
└── java
│ └── com
│ └── zaxxer
│ └── nuprocess
│ └── example
│ ├── NuSchool.java
│ ├── OldSchool.java
│ └── SshExample.java
├── main
└── java
│ └── com
│ └── zaxxer
│ └── nuprocess
│ ├── NuAbstractProcessHandler.java
│ ├── NuProcess.java
│ ├── NuProcessBuilder.java
│ ├── NuProcessFactory.java
│ ├── NuProcessHandler.java
│ ├── codec
│ ├── NuAbstractCharsetHandler.java
│ ├── NuCharsetDecoder.java
│ ├── NuCharsetDecoderHandler.java
│ ├── NuCharsetEncoder.java
│ └── NuCharsetEncoderHandler.java
│ ├── internal
│ ├── BaseEventProcessor.java
│ ├── BasePosixProcess.java
│ ├── Constants.java
│ ├── HexDumpElf.java
│ ├── IEventProcessor.java
│ ├── LibC.java
│ ├── LibJava10.java
│ ├── LibJava8.java
│ └── ReferenceCountedFileDescriptor.java
│ ├── linux
│ ├── EpollEvent.java
│ ├── LibEpoll.java
│ ├── LinProcessFactory.java
│ ├── LinuxProcess.java
│ └── ProcessEpoll.java
│ ├── osx
│ ├── LibKevent.java
│ ├── OsxProcess.java
│ ├── OsxProcessFactory.java
│ └── ProcessKqueue.java
│ ├── package.html
│ └── windows
│ ├── NuKernel32.java
│ ├── NuWinBase32.java
│ ├── NuWinNT.java
│ ├── ProcessCompletions.java
│ ├── WinProcessFactory.java
│ ├── WindowsCreateProcessEscape.java
│ └── WindowsProcess.java
└── test
└── java
└── com
└── zaxxer
└── nuprocess
├── CatTest.java
├── DirectWriteTest.java
├── EnvironmentTest.java
├── FastExitingProcessTest.java
├── InterruptTest.java
├── RunOnlyOnUnix.java
├── RunOnlyOnWindows.java
├── RunTest.java
├── ThreadedTest.java
├── cat.exe
├── internal
└── ConstantsTest.java
└── linux
└── EpollEventTest.java
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | jobs:
2 | build: # name of your job
3 | machine: true # executor type
4 | resource_class: brettwooldridge/ubuntu
5 |
6 | steps:
7 | # Commands run in a Linux virtual machine environment
8 | - checkout
9 | - run: mvn clean package
10 |
11 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /benchmark/target
2 | /target
3 | /.idea
4 | .settings/org.eclipse.core.resources.prefs
5 | .settings/org.eclipse.jdt.core.prefs
6 | .settings/org.eclipse.m2e.core.prefs
7 | *.iml
8 | *.ipr
9 | org.eclipse.pde.core.prefs
10 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | nuprocess
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.pde.PluginNature
21 | org.eclipse.jdt.core.javanature
22 | org.eclipse.m2e.core.maven2Nature
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | sudo: required
3 | dist: trusty
4 | cache:
5 | directories:
6 | - $HOME/.m2/repository
7 |
8 | os:
9 | - linux
10 | - osx
11 |
12 | matrix:
13 | include:
14 | - os: linux
15 | jdk:
16 | - openjdk8
17 | - openjdk11
18 | - oraclejdk8
19 | - oraclejdk11
20 | - os: osx
21 | osx_image: xcode8.3
22 | jdk:
23 | - oraclejdk8
24 | - os: osx
25 | osx_image: xcode10.1
26 | jdk:
27 | - oraclejdk11
28 |
29 | script:
30 | - mvn -Dmaven.javadoc.skip=true -e package -V
31 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | Changes in 3.0.0
2 | * Drop support for Java 7. Artifact is now Java 8+ only.
3 | * Fix race condition for fast exiting processes (#158).
4 | * Alter thread name from "ProcessQueue" to "NuProcessQueue".
5 |
6 | Changes in 2.0.6
7 |
8 | * Fix environment variable injection vulnerability. This is a follow-up from CVE-2022-39243 and does not
9 | have its own CVE assigned
10 |
11 | Changes in 2.0.5
12 |
13 | * Fix command line injection vulnerability (CVE-2022-39243) (#143)
14 |
15 | Changes in 2.0.4
16 |
17 | * Add `Automatic-Module-Name` to `MANIFEST.MF` to better support Java 9+ modules (#142)
18 |
19 | Changes in 2.0.3
20 |
21 | * Add handling for `EINTR` during `epoll_wait` and `ESRCH` from `kill` to prevent zombie processes (#125, #131)
22 | * Remove static `EpollEvent` queue to avoid blocking under heavy load, which can lead to leaking zombie processes if a blocked thread is interrupted (#138)
23 |
24 | Changes in 2.0.2
25 |
26 | * Ensure pipes are closed after failing to start a process on Linux, to prevent leaking descriptors (#120)
27 | * Reduce pipe buffer size on Windows to increase process throughput to `stdout` and `stderr` (#118)
28 | * Update JNA dependency to v5.8.0 to pick up support for the Apple M1 (#123)
29 |
30 | Changes in 2.0.1
31 |
32 | * Fix race condition between the `onStdinReady()` and `userWantsWrite()` methods that causes the state to
33 | become incorrect, resulting in failure to continue processing wanted `stdin` writes (#113)
34 |
35 | Changes in 2.0.0
36 |
37 | * Update JNA dependency to v5.5.0
38 | * Add ability to run processes synchronously (#104)
39 | * Handle detecting Java versions when non-numbers are present (#105)
40 |
41 | Changes in 1.2.6
42 |
43 | * Fix linkage issues with Azul JVMs. Special thanks to Bryan Turner for this pull request and extensive testing across
44 | a wide variety of JVMs. (#107)
45 |
46 | Changes in 1.2.5
47 |
48 | * Handle `SystemRoot` case-insensitively (Windows) (#103).
49 | * Compatibility changes for JNA 5.2.0 (#99).
50 |
51 | Changes in 1.2.4
52 |
53 | * Fix structure alignment (`ALIGN_GNUC`) on various platforms (#94).
54 | * Add support for JDK 10/11. (#92)
55 |
56 | Changes in 1.2.3
57 |
58 | * Resolve launch issue on Linux/macOS w/Zulu JVM.
59 |
60 | Changes in 1.2.2
61 |
62 | * Fix race condition seen (once) on Linux after moving to JVM internal API for process spawning.
63 |
64 | Changes in 1.2.1
65 |
66 | * On macOS, continue to use `posix_spawnp` directly, `POSIX_SPAWN_START_SUSPENDED` solves a lot of potential race conditions that we have to contend with on Linux.
67 |
68 | Changes in 1.2.0
69 |
70 | * Spawn processes on Linux and macOS using JVM internal method `Java_java_lang_UNIXProcess_forkAndExec`.
71 |
72 | Changes in 1.1.3
73 |
74 | * Add constructor with `Pointer` to `LibKevent.Kevent` (#82) that reduces reflection code executed in JNA.
75 | * Change to Linux `epoll` implementation that massively decreases the amount of memory used.
76 | * Optimize `epoll_wait` loop to avoid memory churn. (#80)
77 |
78 | Changes in 1.1.2
79 |
80 | * Fix issue on macOS where pipes would leak after many calls to `closeStdin`
81 |
82 | Changes in 1.1.1
83 |
84 | * Fixed issue where calling `writeStdin` with a buffer larger than 65kb would hang.
85 |
86 | Changes in 1.1.0
87 |
88 | * Expose new `NuProcess.getPID()` method on all platforms.
89 | * Fix wrong position and limit value on `inBuffer` caused by incorrect `inBuffer` clear.
90 | * Fix source of memory leak and `DirectWriteTest` failure on macOS; clear `wantsWrite`
91 | queue on process exit.
92 | * Remove `System.exit()` call from unit test.
93 |
94 | Changes in 1.0.4
95 |
96 | * Update OSGi manifest with correct exports.
97 | * Fix moar Windows argument escaping.
98 | * Introduce 'force' parameter to `NuProcess.closeStdin()`, and enqueue special
99 | tombstone marker into the `pendingWrites` queue for non-forced closes.
100 | * Remove use of `sun.misc.Unsafe`.
101 |
102 | Changes in 1.0.3
103 |
104 | * Remove dependency on `jna-platform`.
105 | * Handle fast-exiting processes on Linux.
106 | * Fix Windows argument escaping.
107 | * Handle race condition in named pipe creation on Windows.
108 |
109 | Changes in 1.0.2
110 |
111 | * Process `cwd` support for Mac, Linux, and Win32.
112 | * Remove `ThreadLocal` usage for macOS.
113 |
114 | Changes in 1.0.1
115 |
116 | * Performance improvements for macOS `kqueue` / `kevent`.
117 | * Fix issue where `stdout` and `stderr` are empty for quick exiting processes.
118 |
119 | Changes in 1.0.0
120 |
121 | * Genesis. See the git history for previous evolution.
122 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NuProcess
2 | =========
3 |
4 | [![][Build Status img]][Build Status]
5 | [![][Shippable Status img]][Shippable Status]
6 | [![][license img]][license]
7 | [![][Maven Central img]][Maven Central]
8 | [![][Javadocs img]][Javadocs]
9 |
10 | A low-overhead, non-blocking I/O, external Process execution implementation for Java. It is a replacement for
11 | ``java.lang.ProcessBuilder`` and ``java.lang.Process``.
12 |
13 | Have you ever been annoyed by the fact that whenever you spawn a process in Java you have to create two or three "pumper"
14 | threads (for every process) to pull data out of the ``stdout`` and ``stderr`` pipes and pump data into ``stdin``? If your
15 | code starts a lot of processes you can have dozens or hundreds of threads doing nothing but pumping data.
16 |
17 | NuProcess uses the JNA library to use platform-specific native APIs to achieve non-blocking I/O on the pipes between your
18 | Java process and the spawned processes:
19 |
20 | * Linux: uses epoll
21 | * MacOS X: uses kqueue/kevent
22 | * Windows: uses IO Completion Ports
23 |
24 | #### Maven
25 |
26 | com.zaxxer
27 | nuprocess
28 | 2.0.6
29 | compile
30 |
31 |
32 | :point_right: **Note: Versions 1.0.0 and above contain breaking API changes from 0.9.7.**
33 |
34 | #### It's mostly about the memory
35 | Speed-wise, there is not a significant difference between NuProcess and the standard Java Process class, even when running
36 | 500 concurrent processes. On some platforms such as MacOS X or Linux, NuProcess is 20% faster than ``java.lang.Process``
37 | for large numbers of processes.
38 |
39 | However, when it comes to memory there is a significant difference. The overhead of 500 threads, for example, is quite
40 | large compared to the one or few threads employed by NuProcess.
41 |
42 | Additionally, on unix-based platforms such as Linux, when creating a new process ``java.lang.Process`` uses a fork()/exec()
43 | operation. This requires a temporary copy of the Java process (the fork), before the exec is performed. When running
44 | tests on Linux, in order to spawn 500 processes required setting the JVM max. memory to 3Gb (-Xmx3g). NuProcess uses a
45 | variant of ``fork()`` called ``vfork()``, which does not impose this overhead. NuProcess can comfortably spawn 500 processes
46 | even when running the JVM with only 128Mb.
47 |
48 | #### Example
49 | Like the Java ``ProcessBuilder``, NuProcess offers ``NuProcessBuilder``, so building a process is fairly simple. Let's make a simple example where we use the Unix "cat" command. When launched with no parameters, *cat* reads from STDIN and echos the output to STDOUT. We're going to start the *cat* process, write *"Hello world!"* to its STDIN, and read the echoed reply from STDOUT and print it. Let's build and start the process.
50 | ```java
51 | NuProcessBuilder pb = new NuProcessBuilder(Arrays.asList("/bin/cat"));
52 | ProcessHandler handler = new ProcessHandler();
53 | pb.setProcessListener(handler);
54 | NuProcess process = pb.start();
55 | process.wantWrite();
56 | process.waitFor(0, TimeUnit.SECONDS); // when 0 is used for waitFor() the wait is infinite
57 | ```
58 | You'll notice the ``ProcessHandler`` in code above. This is a class you provide which receives callbacks from the process to handle input, output, termination, etc. And notice the ``wantWrite()`` call, this expresses that we have something we want to write to the process, so our ``ProcessHandler`` will be called back to perform the write. Here's what ``ProcessHandler`` looks like for our example:
59 | ```java
60 | class ProcessHandler extends NuAbstractProcessHandler {
61 | private NuProcess nuProcess;
62 |
63 | @Override
64 | public void onStart(NuProcess nuProcess) {
65 | this.nuProcess = nuProcess;
66 | }
67 |
68 | @Override
69 | public boolean onStdinReady(ByteBuffer buffer) {
70 | buffer.put("Hello world!".getBytes());
71 | buffer.flip();
72 | return false; // false means we have nothing else to write at this time
73 | }
74 |
75 | @Override
76 | public void onStdout(ByteBuffer buffer, boolean closed) {
77 | if (!closed) {
78 | byte[] bytes = new byte[buffer.remaining()];
79 | // You must update buffer.position() before returning (either implicitly,
80 | // like this, or explicitly) to indicate how many bytes your handler has consumed.
81 | buffer.get(bytes);
82 | System.out.println(new String(bytes));
83 |
84 | // For this example, we're done, so closing STDIN will cause the "cat" process to exit
85 | nuProcess.closeStdin(true);
86 | }
87 | }
88 | }
89 | ```
90 |
91 | #### Synchronous Operation
92 | NuProcess does allow you to perform synchronous *writes* to the *stdin* of the spawned process. Even though the writes are *synchronous* they are *non-blocking*; meaning the write returns immediately. In this model, you do not use ``NuProcess.wantWrite()`` and your ``onStdinReady()`` method will not be called. If you extend the ``NuAbstractProcessHandler`` you do not need to provide an implementation of ``onStdinReady()``. Use the ``NuProcess.writeStdin()`` method to write data to the process. This method will return immediately and writes are queued and occur in order. Read the JavaDoc for the ``NuProcess.writeStdin()`` method for cautions and caveats.
93 |
94 | In the synchronous model, the above example would look like this:
95 | ```java
96 | NuProcessBuilder pb = new NuProcessBuilder(Arrays.asList("/bin/cat"));
97 | ProcessHandler handler = new ProcessHandler();
98 | pb.setProcessListener(handler);
99 | NuProcess process = pb.start();
100 |
101 | ByteBuffer buffer = ByteBuffer.wrap("Hello, World!".getBytes());
102 | process.writeStdin(buffer);
103 |
104 | process.waitFor(0, TimeUnit.SECONDS); // when 0 is used for waitFor() the wait is infinite
105 | ```
106 |
107 | And the handler:
108 | ```java
109 | class ProcessHandler extends NuAbstractProcessHandler {
110 | private NuProcess nuProcess;
111 |
112 | @Override
113 | public void onStart(NuProcess nuProcess) {
114 | this.nuProcess = nuProcess;
115 | }
116 |
117 | public void onStdout(ByteBuffer buffer, boolean closed) {
118 | if (!closed) {
119 | byte[] bytes = new byte[buffer.remaining()];
120 | // You must update buffer.position() before returning (either implicitly,
121 | // like this, or explicitly) to indicate how many bytes your handler has consumed.
122 | buffer.get(bytes);
123 | System.out.println(new String(bytes));
124 |
125 | // For this example, we're done, so closing STDIN will cause the "cat" process to exit
126 | nuProcess.closeStdin(true);
127 | }
128 | }
129 | }
130 | ```
131 |
132 | #### JavaDocs
133 | You can read the [JavaDoc here](http://brettwooldridge.github.io/NuProcess/apidocs/index.html). Make sure you read and fully understand the JavaDoc for the ``NuProcessHandler`` interface as it is your primary contract with NuProcess.
134 |
135 | #### Settings
136 | These are settings that can be defined as System properties that control various behaviors of the NuProcess library. *You typically do not need to modify these.*
137 |
138 | ##### ``com.zaxxer.nuprocess.threads``
139 | This setting controls how many threads are used to handle the STDIN, STDOUT, STDERR streams of spawned processes. No
140 | matter how many processes are spawned, this setting will be the *maximum* number of threads used. Possible values are:
141 |
142 | * ``auto`` (default) - this sets the *maximum* number of threads to the number of CPU cores divided by 2.
143 | * ``cores`` - this sets the *maximum* number of threads to the number of CPU cores.
144 | * ```` - the sets the *maximum* number of threads to a specific number. Often ``1`` will provide good performance even for dozens of processes.
145 |
146 | The default is ``auto``, but in reality if your child processes are "bursty" in their output, rather than producing a
147 | constant stream of data, a single thread may provide equivalent performance even with hundreds of processes.
148 |
149 | ##### ``com.zaxxer.nuprocess.softExitDetection``
150 | On Linux and Windows there is no method by which you can be notified in an asynchronous manner that a child process has
151 | exited. Rather than polling all child processes constantly NuProcess uses what we call "Soft Exit Detection". When a
152 | child process exits, the OS automatically closes all of it's open file handles; which *is* something about which we can be
153 | notified. So, on Linux and Windows when NuProcess determines that both the STDOUT and STDERR streams have been closed
154 | in the child process, that child process is put into a "dead pool". The processes in the dead pool are polled to
155 | determine when they have truly exited and what their exit status was. See ``com.zaxxer.nuprocess.deadPoolPollMs``
156 |
157 | The default value for this property is ``true``. Setting this value to ``false`` will completely disable process exit
158 | detection, and the ``NuProcess.waitFor()" API __MUST__ be used. Failure to invoke this API on Linux will result in an
159 | ever-growing accumulation of "zombie" processes and eventually an inability to create new processes. There is very little
160 | reason to disable soft exit detection unless you have child process that itself closes the STDOUT and STDERR streams.
161 |
162 | ##### ``com.zaxxer.nuprocess.deadPoolPollMs``
163 | On Linux and Windows, when Soft Exit Detection is enabled (the default), this property controls how often the processes in
164 | the dead pool are polled for their exit status. The default value is 250ms, and the minimum value is 100ms.
165 |
166 | ##### ``com.zaxxer.nuprocess.lingerTimeMs``
167 | This property controls how long the processing thread(s) remains after the last executing child process has exited. In
168 | order to avoid the overhead of starting up another processing thread, if processes are frequently run it may be desirable
169 | for the processing thread to remain (linger) for some amount of time (default 2500ms).
170 |
171 | #### Related Projects
172 | Charles Duffy has developed a Clojure wrapper library [here](https://github.com/threatgrid/asynp).
173 | Julien Viet has developed a Vert.x 3 library [here](https://github.com/vietj/vertx-childprocess).
174 |
175 | #### Limitations
176 | The following limitations exist in NuProcess:
177 | * Currently only supports Linux, Windows, and MacOS X.
178 | * Java 7 and above
179 | * Linux support requires at least kernel version 2.6.17 or higher (kernels after June 2006)
180 |
181 | [Build Status]:https://circleci.com/gh/brettwooldridge/NuProcess`
182 | [Build Status img]:https://circleci.com/gh/brettwooldridge/NuProcess.svg?style=shield
183 |
184 | [Shippable Status]:https://app.shippable.com/github/brettwooldridge/NuProcess
185 | [Shippable Status img]:https://api.shippable.com/projects/5b4b6a3e6db3b807000a63d8/badge?branch=master
186 |
187 | [license]:LICENSE
188 | [license img]:https://img.shields.io/badge/license-Apache%202-blue.svg
189 |
190 | [Maven Central]:https://maven-badges.herokuapp.com/maven-central/com.zaxxer/nuprocess
191 | [Maven Central img]:https://maven-badges.herokuapp.com/maven-central/com.zaxxer/nuprocess/badge.svg
192 |
193 | [Javadocs]:http://javadoc.io/doc/com.zaxxer/nuprocess
194 | [Javadocs img]:http://javadoc.io/badge/com.zaxxer/nuprocess.svg
195 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 |
3 | environment:
4 | matrix:
5 | - JAVA_HOME: C:\Program Files\Java\jdk1.8.0
6 |
7 | os: Windows Server 2012
8 |
9 | install:
10 | - ps: |
11 | Add-Type -AssemblyName System.IO.Compression.FileSystem
12 | if (!(Test-Path -Path "C:\maven" )) {
13 | (new-object System.Net.WebClient).DownloadFile(
14 | 'http://www.us.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip',
15 | 'C:\maven-bin.zip'
16 | )
17 | [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\maven-bin.zip", "C:\maven")
18 | }
19 | $env:Path +=";C:\maven\apache-maven-3.3.9\bin;%JAVA_HOME%\bin"
20 |
21 | build_script:
22 | - mvn test -DargLine="-Djna.nosys=true"
23 |
--------------------------------------------------------------------------------
/benchmark/README.md:
--------------------------------------------------------------------------------
1 | ## NuProcess JMH Benchmark
2 |
3 | Prior to excuting the benchmark, make sure that the primary (NuProcess) library has been built and/or installed.
4 | ```cli
5 | mvn clean install
6 | ```
7 |
8 | Simple execution of the benchmark is as follows:
9 | ```cli
10 | cd ./benchmark
11 | ./benchmark.sh quick
12 | ```
13 |
14 | The following command line options are available when running the ``benchmark.sh`` script:
15 | ```cli
16 | benchmark.sh [clean] [-t ] [quick|medium|long|profile|debug]
17 | ```
18 |
19 | | Option | Description |
20 | |:-------:|:--------------------------------------------------------- |
21 | | clean | recompile the benchmark |
22 | | -t <threads> | execute JMH with the specified number of threads |
23 | | quick | a "quick" run of the benchmark; fewer iterations and forks |
24 | | medium | a "medium" run of the benchmark; more iterations and forks |
25 | | long | a "long" run of the benchmark; maximum iterations and forks |
26 | | profile | start JMH such as to allow attaching JProfiler (MacOS) |
27 | | debug | start JMH such as to allow the attachment of a debugger |
28 |
--------------------------------------------------------------------------------
/benchmark/benchmark.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | JAVA_OPTIONS="-server -XX:-RestrictContended -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms1096m -Xmx1096m"
4 | JMH_OPTIONS="-prof gc -prof hs_thr"
5 |
6 | if [[ "clean" == "$1" || ! -f "./target/microbenchmarks.jar" ]]; then
7 | mvn clean package
8 | shift
9 | fi
10 |
11 | JMH_THREADS="-t 4"
12 | if [[ "$2" == "-t" ]]; then
13 | JMH_THREADS="-t $3"
14 | set -- "$1" "${@:4}"
15 | fi
16 |
17 | if [[ ! -f "/tmp/random.dat" ]]; then
18 | echo "" ; echo "Generating 250MB random data file into /tmp/random.dat ... this might take 15-30s."
19 | dd bs=1048576 count=250 /tmp/random.dat
20 | fi
21 |
22 | if [[ "quick" == "$1" ]]; then
23 | java -jar ./target/microbenchmarks.jar -jvmArgs "$JAVA_OPTIONS" $JMH_OPTIONS -wi 3 -i 8 $JMH_THREADS -f 2 $2 $3 $4 $5 $6 $7 $8 $9
24 | elif [[ "medium" == "$1" ]]; then
25 | java -jar ./target/microbenchmarks.jar -jvmArgs "$JAVA_OPTIONS" $JMH_OPTIONS -wi 3 -f 8 -i 6 $JMH_THREADS $2 $3 $4 $5 $6 $7 $8 $9
26 | elif [[ "profile" == "$1" ]]; then
27 | java -server $JAVA_OPTIONS -agentpath:/Applications/JProfiler.app/Contents/Resources/app/bin/macos/libjprofilerti.jnilib=port=8849 -jar ./target/microbenchmarks.jar -r 5 -wi 3 -i 8 $JMH_THREADS -f 0 $2 $3 $4 $5 $6 $7 $8 $9
28 | elif [[ "debug" == "$1" ]]; then
29 | java -server $JAVA_OPTIONS -Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y -jar ./target/microbenchmarks.jar -r 5 -wi 3 -i 8 -t 8 -f 0 $2 $3 $4 $5 $6 $7 $8 $9
30 | else # "long"
31 | java -jar ./target/microbenchmarks.jar -jvmArgs "$JAVA_OPTIONS" $JMH_OPTIONS -wi 3 -i 15 $JMH_THREADS $1 $2 $3 $4 $5 $6 $7 $8 $9
32 | fi
33 |
--------------------------------------------------------------------------------
/benchmark/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.sonatype.oss
7 | oss-parent
8 | 7
9 |
10 |
11 |
12 | com.zaxxer
13 | nuprocess-benchmark
14 | 1.1.3-SNAPSHOT
15 |
16 | NuProcess Benchmarks
17 |
18 |
19 | UTF-8
20 |
21 | 1.19
22 |
23 |
24 |
25 |
26 | com.zaxxer
27 | nuprocess
28 | 2.1.0-SNAPSHOT
29 |
30 |
31 |
32 | org.openjdk.jmh
33 | jmh-core
34 | ${jmh.version}
35 |
36 |
37 |
38 |
39 |
40 |
41 | org.apache.maven.plugins
42 | maven-compiler-plugin
43 | 3.7.0
44 |
45 |
46 | org.openjdk.jmh.generators.BenchmarkProcessor
47 |
48 |
49 |
50 | org.openjdk.jmh
51 | jmh-generator-annprocess
52 | ${jmh.version}
53 |
54 |
55 | 1.8
56 | 1.8
57 |
58 |
59 |
60 |
61 | org.apache.maven.plugins
62 | maven-shade-plugin
63 | 2.3
64 |
65 |
66 | package
67 |
68 | shade
69 |
70 |
71 | microbenchmarks
72 |
73 |
75 | org.openjdk.jmh.Main
76 |
77 |
78 |
79 |
80 | *:*
81 |
82 | META-INF/services/javax.annotation.processing.Processor
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/zaxxer/nuprocess/benchmark/NuProcessBenchmark.java:
--------------------------------------------------------------------------------
1 | package com.zaxxer.nuprocess.benchmark;
2 |
3 | import com.zaxxer.nuprocess.NuAbstractProcessHandler;
4 | import com.zaxxer.nuprocess.NuProcess;
5 | import com.zaxxer.nuprocess.NuProcessBuilder;
6 | import org.openjdk.jmh.annotations.Benchmark;
7 | import org.openjdk.jmh.profile.GCProfiler;
8 | import org.openjdk.jmh.profile.HotspotThreadProfiler;
9 | import org.openjdk.jmh.runner.Runner;
10 | import org.openjdk.jmh.runner.options.Options;
11 | import org.openjdk.jmh.runner.options.OptionsBuilder;
12 |
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.nio.ByteBuffer;
16 | import java.nio.file.Files;
17 | import java.nio.file.Path;
18 | import java.nio.file.Paths;
19 | import java.util.concurrent.TimeUnit;
20 | import java.util.zip.CRC32;
21 |
22 | /**
23 | * A simple performance benchmark which uses {@code cat} to write a file to {@code stdout}.
24 | *
25 | * This benchmark requires a test file to use. One method to generate a test file is:
26 | *
30 | * The CRC32 for the file is also required to allow the benchmark to apply a spot check on the contents, after
31 | * they've been pumped through {@code cat}, to verify that the contents were not corrupted during processing. A
32 | * CRC32 checksum is used in preference to algorithms like SHA-256 because the speed of the hashing algorithm is
33 | * not the focus of this benchmark.
34 | *
35 | * After generating a test file, you can compute the CRC32 to update the benchmark by either running it once and
36 | * checking the value in the error message, or running:
37 | *
38 | * crc32 /tmp/random.dat
39 | *
40 | * That will display the CRC32 checksum in hex, which can then be converted to decimal to match {@code CRC32}'s
41 | * {@code getValue()}. (The calculator on most operating systems can convert hex to decimal.)
42 | */
43 | public class NuProcessBenchmark {
44 |
45 | private static final long TEST_CRC32;
46 | private static final String TEST_FILE = "/tmp/random.dat";
47 |
48 | static {
49 | final Path path = Paths.get(TEST_FILE);
50 | if (!Files.exists(path)) {
51 | throw new IllegalStateException(TEST_FILE + " does not exist.");
52 | }
53 |
54 | final CRC32 crc32 = new CRC32();
55 | final byte[] buf = new byte[8192];
56 | try (InputStream is = Files.newInputStream(path)) {
57 | do {
58 | int rc = is.read(buf);
59 | if (rc < 0) {
60 | break;
61 | }
62 | crc32.update(buf, 0, rc);
63 | } while (true);
64 | TEST_CRC32 = crc32.getValue();
65 | }
66 | catch (IOException e) {
67 | throw new RuntimeException(e);
68 | }
69 | }
70 |
71 | public static void main(String[] args) throws Exception {
72 | //System.setProperty("com.zaxxer.nuprocess.threads", "cores"); // set when running with forks(0) below
73 |
74 | Options options = new OptionsBuilder()
75 | .addProfiler(GCProfiler.class)
76 | .addProfiler(HotspotThreadProfiler.class)
77 | //.forks(0) // set when running with JProfiler to simplify profiling
78 | .forks(2)
79 | //.jvmArgsAppend("-Dcom.zaxxer.nuprocess.threads=cores") // to adjust the pump thread count
80 | .include(NuProcessBenchmark.class.getSimpleName())
81 | .measurementIterations(50)
82 | .threads(10)
83 | .timeUnit(TimeUnit.SECONDS)
84 | .warmupIterations(1)
85 | .build();
86 |
87 | new Runner(options).run();
88 | }
89 |
90 | @Benchmark
91 | public void cat() throws Exception {
92 | NuProcessBuilder builder = new NuProcessBuilder("cat", TEST_FILE);
93 | builder.setProcessListener(new NuAbstractProcessHandler() {
94 |
95 | private final CRC32 crc32 = new CRC32();
96 |
97 | @Override
98 | public void onExit(int statusCode) {
99 | long crc = crc32.getValue();
100 | if (crc != TEST_CRC32) {
101 | System.err.println("Incorrect CRC32 checksum (" + crc + "); file corruption?");
102 | }
103 | }
104 |
105 | @Override
106 | public void onStdout(ByteBuffer buffer, boolean closed) {
107 | // the contents of the file are run through CRC32 just to "do" something with them
108 | // note that update(ByteBuffer) requires Java 8. it's been selected because it doesn't
109 | // inflate the measured allocation rate by allocating buffers internally
110 | crc32.update(buffer);
111 | }
112 | });
113 |
114 | NuProcess process = builder.start();
115 |
116 | int exitCode = process.waitFor(5L, TimeUnit.MINUTES);
117 | if (exitCode == Integer.MIN_VALUE) {
118 | process.destroy(false);
119 |
120 | throw new AssertionError(process + " took longer than 5 minutes to complete");
121 | }
122 | if (exitCode != 0) {
123 | throw new AssertionError(process + " failed (Exit code: " + exitCode + ")");
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/mvnvm.properties:
--------------------------------------------------------------------------------
1 | mvn_version=3.9.6
2 |
--------------------------------------------------------------------------------
/shippable.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | build:
7 | ci:
8 | - mvn install
9 | #Create folders for test and code coverage
10 | - mkdir -p shippable/testresults
11 | - cp -r target/surefire-reports/* shippable/testresults
12 |
13 | runtime:
14 | nodePool: shippable_shared_aarch64
15 |
--------------------------------------------------------------------------------
/src/example/java/com/zaxxer/nuprocess/example/NuSchool.java:
--------------------------------------------------------------------------------
1 | package com.zaxxer.nuprocess.example;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.Arrays;
5 | import java.util.concurrent.TimeUnit;
6 | import java.util.zip.Adler32;
7 |
8 | import com.zaxxer.nuprocess.NuAbstractProcessHandler;
9 | import com.zaxxer.nuprocess.NuProcess;
10 | import com.zaxxer.nuprocess.NuProcessBuilder;
11 |
12 | /**
13 | * This class demonstrates how one might use the NuProcess classes to run 5000
14 | * processes (in batches of 500, for 10 iterations). It is used as kind of a
15 | * benchmark to compare to the conventional method of accomplishing the same
16 | * (see the OldSchool example).
17 | *
18 | * @author Brett Wooldridge
19 | */
20 | public class NuSchool
21 | {
22 | public static void main(String... args)
23 | {
24 | if (args.length < 1)
25 | {
26 | System.err.println("Usage: java com.zaxxer.nuprocess.example.NuSchool ");
27 | System.exit(0);
28 | }
29 |
30 | int PROCESSES = Integer.valueOf(args[0]);
31 |
32 | String command = "/bin/cat";
33 | if (System.getProperty("os.name").toLowerCase().contains("win"))
34 | {
35 | command = "src\\test\\java\\org\\nuprocess\\cat.exe";
36 | }
37 |
38 | long start = System.currentTimeMillis();
39 | long maxFreeMem = 0;
40 |
41 | NuProcessBuilder pb = new NuProcessBuilder(Arrays.asList(command));
42 | for (int times = 0; times < 10; times++)
43 | {
44 | NuProcess[] processes = new NuProcess[PROCESSES];
45 | LottaProcessHandler[] handlers = new LottaProcessHandler[PROCESSES];
46 |
47 | for (int i = 0; i < PROCESSES; i++)
48 | {
49 | handlers[i] = new LottaProcessHandler();
50 | pb.setProcessListener(handlers[i]);
51 | processes[i] = pb.start();
52 | }
53 |
54 | // Kick all of the processes to start going
55 | for (NuProcess process : processes)
56 | {
57 | process.wantWrite();
58 | }
59 |
60 | for (NuProcess process : processes)
61 | {
62 | try
63 | {
64 | maxFreeMem = Math.max(maxFreeMem, Runtime.getRuntime().freeMemory());
65 | process.waitFor(0, TimeUnit.SECONDS);
66 | }
67 | catch (InterruptedException e)
68 | {
69 | System.exit(-1);
70 | }
71 | }
72 |
73 | for (LottaProcessHandler handler : handlers)
74 | {
75 | if (handler.getAdler() != 4237270634l)
76 | {
77 | System.err.println("Adler32 mismatch between written and read");
78 | System.exit(-1);
79 | }
80 | else if (handler.getExitCode() != 0)
81 | {
82 | System.err.println("Exit code not zero (0)");
83 | System.exit(-1);
84 | }
85 | }
86 | }
87 |
88 | System.out.printf("Maximum memory used: %d\n", Runtime.getRuntime().totalMemory() - maxFreeMem);
89 | System.out.printf("Total execution time (ms): %d\n", (System.currentTimeMillis() - start));
90 | System.exit(0);
91 | }
92 |
93 | private static class LottaProcessHandler extends NuAbstractProcessHandler
94 | {
95 | private static final int WRITES = 100;
96 | private static final int LIMIT;
97 | private static final byte[] bytes;
98 |
99 | private NuProcess nuProcess;
100 | private int writes;
101 | private int size;
102 | private int exitCode;
103 |
104 | private Adler32 readAdler32 = new Adler32();
105 |
106 | static
107 | {
108 | // Create 600K of data.
109 | StringBuffer sb = new StringBuffer();
110 | for (int i = 0; i < 6000; i++)
111 | {
112 | sb.append("1234567890");
113 | }
114 | bytes = sb.toString().getBytes();
115 | LIMIT = WRITES * bytes.length;
116 | }
117 |
118 | @Override
119 | public void onStart(final NuProcess nuProcess)
120 | {
121 | this.nuProcess = nuProcess;
122 | }
123 |
124 | @Override
125 | public void onExit(int statusCode)
126 | {
127 | exitCode = statusCode;
128 | }
129 |
130 | @Override
131 | public void onStdout(ByteBuffer buffer, boolean closed)
132 | {
133 | size += buffer.remaining();
134 |
135 | byte[] bytes = new byte[buffer.remaining()];
136 | buffer.get(bytes);
137 | readAdler32.update(bytes);
138 |
139 | if (size == LIMIT || closed)
140 | {
141 | nuProcess.closeStdin(true);
142 | }
143 | }
144 |
145 | @Override
146 | public boolean onStdinReady(ByteBuffer buffer)
147 | {
148 | buffer.put(bytes);
149 | buffer.flip();
150 | return (++writes < WRITES);
151 | }
152 |
153 | int getExitCode()
154 | {
155 | return exitCode;
156 | }
157 |
158 | long getAdler()
159 | {
160 | return readAdler32.getValue();
161 | }
162 | };
163 | }
164 |
--------------------------------------------------------------------------------
/src/example/java/com/zaxxer/nuprocess/example/OldSchool.java:
--------------------------------------------------------------------------------
1 | package com.zaxxer.nuprocess.example;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.BufferedOutputStream;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 | import java.util.concurrent.CyclicBarrier;
8 | import java.util.concurrent.LinkedBlockingQueue;
9 | import java.util.concurrent.ThreadPoolExecutor;
10 | import java.util.concurrent.TimeUnit;
11 | import java.util.zip.Adler32;
12 |
13 | /**
14 | * This class demonstrates how one might use the conventional java.lang.Process
15 | * class to run 5000 processes (in batches of 500, for 10 iterations). It is
16 | * used as kind of a benchmark to compare to the NuProcess method of accomplishing
17 | * the same (see the NuSchool example).
18 | *
19 | * Execution notes:
20 | *
21 | * Linux (CentOS)
22 | * In order to run this test, java was run with the following parameters:
23 | * -Xmx2048m -Xss256k
24 | *
25 | * The following had to be added to /etc/security/limits.conf:
26 | * #domain type item value
27 | *
28 | * [user] soft nofile 4096
29 | * [user] hard nofile 8192
30 | * [user] soft nproc 4096
31 | * [user] soft nproc 4096
32 | * where [user] is the username of the executing user (you).
33 | *
34 | * The following change was made to /etc/security/limits.d/90-nproc.conf:
35 | * * soft nproc 1024
36 | * was changed to:
37 | * * soft nproc 8024
38 | *
39 | * @author Brett Wooldridge
40 | */
41 | public class OldSchool
42 | {
43 | private static volatile CyclicBarrier startBarrier;
44 |
45 | public static void main(String... args)
46 | {
47 | if (args.length < 1)
48 | {
49 | System.err.println("Usage: java com.zaxxer.nuprocess.example.OldSchool ");
50 | System.exit(0);
51 | }
52 |
53 | int PROCESSES = Integer.valueOf(args[0]);
54 |
55 | ThreadPoolExecutor outExecutor = new ThreadPoolExecutor(PROCESSES, PROCESSES, 10, TimeUnit.SECONDS, new LinkedBlockingQueue());
56 | ThreadPoolExecutor inExecutor = new ThreadPoolExecutor(PROCESSES, PROCESSES, 10, TimeUnit.SECONDS, new LinkedBlockingQueue());
57 |
58 | String command = "/bin/cat";
59 | if (System.getProperty("os.name").toLowerCase().contains("win"))
60 | {
61 | command = "src\\test\\java\\org\\nuprocess\\cat.exe";
62 | }
63 |
64 | ProcessBuilder pb = new ProcessBuilder(command);
65 | pb.redirectErrorStream(true);
66 |
67 | long start = System.currentTimeMillis();
68 | long maxFreeMem = 0;
69 |
70 | for (int times = 0; times < 10; times++)
71 | {
72 |
73 | Process[] processes = new Process[PROCESSES];
74 | InPumper[] inPumpers = new InPumper[PROCESSES];
75 | OutPumper[] outPumpers = new OutPumper[PROCESSES];
76 |
77 | startBarrier = new CyclicBarrier(PROCESSES);
78 | try
79 | {
80 | for (int i = 0; i < PROCESSES; i++)
81 | {
82 | Process process = pb.start();
83 | processes[i] = process;
84 |
85 | outPumpers[i] = new OutPumper(new BufferedInputStream(process.getInputStream(), 65536));
86 | inPumpers[i] = new InPumper(new BufferedOutputStream(process.getOutputStream(), 65536));
87 |
88 | outExecutor.execute(outPumpers[i]);
89 | inExecutor.execute(inPumpers[i]);
90 | }
91 |
92 | for (Process process : processes)
93 | {
94 | maxFreeMem = Math.max(maxFreeMem, Runtime.getRuntime().freeMemory());
95 | if (process.waitFor() != 0)
96 | {
97 | System.err.println("Exit code not zero (0)");
98 | System.exit(-1);
99 | }
100 | }
101 |
102 | for (OutPumper pumper : outPumpers)
103 | {
104 | if (pumper.getAdler() != 4237270634l)
105 | {
106 | System.err.println("Adler32 mismatch between written and read");
107 | System.exit(-1);
108 | }
109 | }
110 | }
111 | catch (Exception e)
112 | {
113 | e.printStackTrace(System.err);
114 | System.exit(-1);
115 | }
116 | }
117 |
118 | System.out.printf("Maximum memory used: %d\n", Runtime.getRuntime().totalMemory() - maxFreeMem);
119 | System.out.printf("Total execution time (ms): %d\n", (System.currentTimeMillis() - start));
120 | System.exit(0);
121 | }
122 |
123 | public static class InPumper implements Runnable
124 | {
125 | private static final byte[] bytes;
126 | private OutputStream outputStream;
127 |
128 | static
129 | {
130 | // Create 600K of data.
131 | StringBuffer sb = new StringBuffer();
132 | for (int i = 0; i < 6000; i++)
133 | {
134 | sb.append("1234567890");
135 | }
136 | bytes = sb.toString().getBytes();
137 | }
138 |
139 | public InPumper(OutputStream outputStream)
140 | {
141 | this.outputStream = outputStream;
142 | }
143 |
144 | @Override
145 | public void run()
146 | {
147 | try
148 | {
149 | startBarrier.await();
150 | for (int i = 0; i < 100; i++)
151 | {
152 | outputStream.write(bytes);
153 | }
154 |
155 | outputStream.close();
156 | }
157 | catch (Exception e)
158 | {
159 | System.err.println(e);
160 | return;
161 | }
162 | }
163 | }
164 |
165 | public static class OutPumper implements Runnable
166 | {
167 | private InputStream inputStream;
168 | private Adler32 readAdler32;
169 |
170 | OutPumper(InputStream inputStream)
171 | {
172 | this.inputStream = inputStream;
173 |
174 | this.readAdler32 = new Adler32();
175 | }
176 |
177 | @Override
178 | public void run()
179 | {
180 | try
181 | {
182 | byte[] buf = new byte[65536];
183 | while (true)
184 | {
185 | int rc = inputStream.read(buf);
186 | if (rc == -1)
187 | {
188 | break;
189 | }
190 |
191 | readAdler32.update(buf, 0, rc);
192 | }
193 |
194 | inputStream.close();
195 | }
196 | catch (Exception e)
197 | {
198 | System.err.println(e);
199 | return;
200 | }
201 | }
202 |
203 | long getAdler()
204 | {
205 | return readAdler32.getValue();
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/example/java/com/zaxxer/nuprocess/example/SshExample.java:
--------------------------------------------------------------------------------
1 | package com.zaxxer.nuprocess.example;
2 |
3 | import java.io.PrintWriter;
4 | import java.nio.ByteBuffer;
5 | import java.util.Arrays;
6 | import java.util.LinkedList;
7 | import java.util.concurrent.Semaphore;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | import com.zaxxer.nuprocess.NuAbstractProcessHandler;
11 | import com.zaxxer.nuprocess.NuProcess;
12 | import com.zaxxer.nuprocess.NuProcessBuilder;
13 |
14 | public class SshExample
15 | {
16 | private PrintWriter writer = new PrintWriter(System.out);
17 | private String host;
18 |
19 | public static void main(String[] args)
20 | {
21 | if (args.length < 1)
22 | {
23 | System.err.println("Usage: java com.zaxxer.nuprocess.example.SshExample ");
24 | System.exit(0);
25 | }
26 |
27 | SshExample ssh = new SshExample(args[0]);
28 | ssh.execute();
29 | }
30 |
31 | private SshExample(String host)
32 | {
33 | this.host = host;
34 | }
35 |
36 | private void execute()
37 | {
38 | NuProcessBuilder pb = new NuProcessBuilder(Arrays.asList("ssh", host));
39 | ProcessHandler processHandler = new ProcessHandler();
40 | pb.setProcessListener(processHandler);
41 | NuProcess np = pb.start();
42 |
43 | processHandler.write("cd");
44 | processHandler.write("ls -l");
45 | processHandler.write("exit");
46 |
47 | processHandler.awaitDisconnection();
48 | }
49 |
50 | class ProcessHandler extends NuAbstractProcessHandler
51 | {
52 | private NuProcess nuProcess;
53 | private LinkedList cmdList = new LinkedList();
54 | private Semaphore disconnected = new Semaphore(0);
55 |
56 | @Override
57 | public void onStart(NuProcess nuProcess)
58 | {
59 | this.nuProcess = nuProcess;
60 | }
61 |
62 | public void awaitDisconnection()
63 | {
64 | disconnected.acquireUninterruptibly(1);
65 | }
66 |
67 | public void clear()
68 | {
69 | cmdList.clear();
70 | }
71 |
72 | public void close()
73 | {
74 | nuProcess.destroy(false);
75 | }
76 |
77 | //Send key event
78 | public void sendKey(String ch)
79 | {
80 | clear();
81 | nuProcess.writeStdin(ByteBuffer.wrap(ch.getBytes()));
82 | }
83 |
84 | //Send command
85 | public void write(String stack)
86 | {
87 | cmdList.add(stack + "\n");
88 | //nuProcess.hasPendingWrites();
89 | nuProcess.wantWrite();
90 | }
91 |
92 | public synchronized Boolean isPending()
93 | {
94 | return nuProcess.hasPendingWrites();
95 | }
96 |
97 | @Override
98 | public boolean onStdinReady(ByteBuffer buffer)
99 | {
100 | if (!cmdList.isEmpty())
101 | {
102 | String cmd = cmdList.poll();
103 | buffer.put(cmd.getBytes());
104 | buffer.flip();
105 | }
106 |
107 | return !cmdList.isEmpty();
108 | }
109 |
110 | @Override
111 | public void onStdout(ByteBuffer buffer, boolean closed)
112 | {
113 | int remaining = buffer.remaining();
114 | byte[] bytes = new byte[remaining];
115 | buffer.get(bytes);
116 |
117 | writer.print(new String(bytes));
118 | writer.flush();
119 |
120 | if (closed)
121 | {
122 | disconnected.release();
123 | }
124 |
125 | // nuProcess.wantWrite();
126 | // We're done, so closing STDIN will cause the "cat" process to exit
127 | //nuProcess.closeStdin(true);
128 | }
129 |
130 | @Override
131 | public void onStderr(ByteBuffer buffer, boolean closed)
132 | {
133 | this.onStdout(buffer, false);
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/NuAbstractProcessHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | /**
22 | * Convenience base class for a {@link NuProcessHandler} with default methods
23 | * that can be overridden.
24 | *
25 | * @author Brett Wooldridge
26 | */
27 | public abstract class NuAbstractProcessHandler implements NuProcessHandler
28 | {
29 | /** {@inheritDoc} */
30 | @Override
31 | public void onPreStart(NuProcess nuProcess)
32 | {
33 | }
34 |
35 | /** {@inheritDoc} */
36 | @Override
37 | public void onStart(NuProcess nuProcess)
38 | {
39 | }
40 |
41 | /** {@inheritDoc} */
42 | @Override
43 | public void onExit(int statusCode)
44 | {
45 | }
46 |
47 | /** {@inheritDoc} */
48 | @Override
49 | public void onStdout(ByteBuffer buffer, boolean closed)
50 | {
51 | // Ensure we consume the entire buffer in case it's not used.
52 | buffer.position(buffer.limit());
53 | }
54 |
55 | /** {@inheritDoc} */
56 | @Override
57 | public void onStderr(ByteBuffer buffer, boolean closed)
58 | {
59 | // Ensure we consume the entire buffer in case it's not used.
60 | buffer.position(buffer.limit());
61 | }
62 |
63 | /** {@inheritDoc} */
64 | @Override
65 | public boolean onStdinReady(ByteBuffer buffer)
66 | {
67 | return false;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/NuProcess.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess;
18 |
19 | import java.nio.ByteBuffer;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | /**
23 | * The {@link NuProcessBuilder#start()} method creates a native process and
24 | * returns an instance of a platform-specific implementation of
25 | * {@link NuProcess} that can be used to control the process and obtain
26 | * information about it. The {@link NuProcess} interface provides methods for
27 | * expressing desire to perform output to the process, waiting for the process
28 | * to complete, checking the status of the process, and destroying (killing) the
29 | * process.
30 | *
31 | * The methods that create processes may not work well for special processes on
32 | * certain native platforms, such as native windowing processes, daemon
33 | * processes, Win16/DOS processes on Microsoft Windows, or shell scripts.
34 | *
35 | * See the {@link NuProcessHandler} interface for asynchronous callbacks related
36 | * to start, exit, and input/output notifications and operations for the
37 | * process.
38 | *
39 | * @author Brett Wooldridge
40 | */
41 | public interface NuProcess
42 | {
43 | int BUFFER_CAPACITY = 65536;
44 |
45 | /**
46 | * Waits for the process to exit in a blocking fashion. See
47 | * {@link NuProcessHandler#onExit} for the non-blocking method process exit
48 | * notification and exit code retrieval. If the process is terminated by the
49 | * {@link #destroy(boolean)} method, the exit code is non-deterministic.
50 | *
51 | * @param timeout a timeout value, 0 indicates an infinite wait
52 | * @param timeUnit the unit of time indicator for the timeout value
53 | * @return the exit code of the process, or {@code Integer.MIN_VALUE} if the
54 | * timeout is reached
55 | * @throws InterruptedException thrown if the thread is interrupted while
56 | * waiting
57 | */
58 | int waitFor(long timeout, TimeUnit timeUnit) throws InterruptedException;
59 |
60 | /**
61 | * Express a desire to write data to the STDIN stream of the process. Calling
62 | * this method will result in the {@link NuProcessHandler#onStdinReady}
63 | * callback method of the process handler being called when space is
64 | * available in the STDIN pipe.
65 | *
66 | * This method will throw a {@link IllegalStateException} if the
67 | * {@link #closeStdin} method has already been called.
68 | */
69 | void wantWrite();
70 |
71 | /**
72 | * Performs a "direct write" rather than expressing a desire to write using
73 | * {@link #wantWrite} and performing the write in a callback. Be careful
74 | * mixing this paradigm with the asynchronous paradigm imposed by
75 | * {@link #wantWrite()}.
76 | *
77 | * This method returns immediately and the write of the data occurs on the
78 | * asynchronous processing thread. You can perform multiple
79 | * {@code writeStdin()} calls without regard to the size of the interprocess
80 | * pipe; the writes will be queued and written asynchronously as space is
81 | * available in the pipe.
82 | *
83 | * Note that if the client process is not pulling data out of the pipe,
84 | * calling this method repeatedly will result is the accumulation of
85 | * unwritten ByteBuffers in the Java process and possibly an eventual out of
86 | * memory condition.
87 | *
88 | * Using a Direct ByteBuffer will provide performance improvements. Note that
89 | * NuProcess will not flip the buffer for you; after writing your data into
90 | * the {@code buffer} you must flip the buffer before returning from this
91 | * method.
92 | *
93 | * @param buffer the {@link ByteBuffer} to write to the STDIN stream of the
94 | * process
95 | */
96 | void writeStdin(ByteBuffer buffer);
97 |
98 | /**
99 | * This method is used to close the STDIN pipe between the Java process and
100 | * the spawned process.
101 | *
102 | * If {@code force} is {@code true}, the STDIN pipe is immediately closed regardless of
103 | * pending unwritten data, and even data that has been written into the pipe
104 | * will be immediately discarded.
105 | *
106 | * Otherwise, STDIN will be closed only after all pending writes
107 | * have completed.
108 | *
109 | * @param force if true is passed, STDIN will be immediately closed
110 | * even if there are pending writes; otherwise, it will be closed
111 | * after all pending writes are completed
112 | */
113 | void closeStdin(boolean force);
114 |
115 | /**
116 | * This method is most useful in combination with {@link #writeStdin}. This
117 | * method returns true if there is outstanding data to be written to the
118 | * stdin stream, and false if there are no pending writes.
119 | *
120 | * @return true if there are pending writes or STDIN is pending
121 | * close, false otherwise
122 | */
123 | boolean hasPendingWrites();
124 |
125 | /**
126 | * Terminates the process.
127 | *
128 | * If {@code force} is {@code false}, the process will be terminated
129 | * gracefully (i.e. its shutdown logic will be allowed to execute), assuming
130 | * the OS supports such behavior. Note that the process may not actually
131 | * terminate, as its cleanup logic may fail or it may choose to ignore the
132 | * termination request. If a guarantee of termination is required, call this
133 | * method with {@code force} equal to {@code true} instead.
134 | *
135 | * You can also call {@link #waitFor(long, TimeUnit)} after calling this
136 | * method with {@code force=false}, to give the process an opportunity to
137 | * terminate gracefully. If the timeout expires, you can then call this
138 | * method again with {@code force=true} to ensure the process is terminated.
139 | *
140 | * If {@code force} is true, the process is guaranteed to terminate, but
141 | * whether it is terminated gracefully or not is OS-dependent. Note that it
142 | * may take the OS a moment to terminate the process, so {@link #isRunning()}
143 | * may return true for a brief period after calling this method. You can use
144 | * {@link #waitFor(long, TimeUnit)} if you want to wait until the process has
145 | * actually been terminated.
146 | *
147 | * When this method is called with force, the exit code returned by
148 | * {@link #waitFor} or passed to the {@link NuProcessHandler#onExit} callback
149 | * method is non-deterministic.
150 | *
151 | * @param force if true is passed, the process will be forcibly
152 | * killed
153 | */
154 | void destroy(boolean force);
155 |
156 | /**
157 | * Tests whether or not the process is still running or has exited.
158 | *
159 | * @return true if the process is still running, false if it has exited
160 | */
161 | boolean isRunning();
162 |
163 | /**
164 | * Sets a new process handler for this {@link NuProcess} instance. This
165 | * method is only safe to call from within one of the callback methods of the
166 | * existing {@link NuProcessHandler}.
167 | *
168 | * @param processHandler the new {@link NuProcessHandler}
169 | */
170 | void setProcessHandler(NuProcessHandler processHandler);
171 |
172 | int getPID();
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/NuProcessBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess;
18 |
19 | import java.nio.file.Path;
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Map.Entry;
25 | import java.util.TreeMap;
26 |
27 | /**
28 | * This class is used to create operating system processes.
29 | *
30 | * Each {@code NuProcessBuilder} instance manages a collection of process
31 | * attributes. The {@link #start()} method creates a new {@link NuProcess}
32 | * instance with those attributes. The {@link #start()} method can be invoked
33 | * repeatedly from the same instance to create new subprocesses with identical
34 | * or related attributes.
35 | *
36 | * Each {@code NuProcessBuilder} manages these attributes:
37 | *
38 | *
a command, a list of strings which signifies the external
39 | * program file to be invoked and its arguments, if any. Which string lists
40 | * represent a valid operating system command is system-dependent. For example,
41 | * it is common for each conceptual argument to be an element in this list, but
42 | * there are operating systems where programs are expected to tokenize command
43 | * line strings themselves - on such a system a Java implementation might
44 | * require commands to contain exactly two elements.
45 | *
46 | *
an environment, which is a system-dependent mapping from
47 | * variables to values. The initial value is a copy of the environment of the
48 | * current process. See {@link System#getenv()}.
49 | *
50 | *
51 | * Modifying a process builder's attributes will affect processes subsequently
52 | * started by that object's {@link #start()} method, but will never affect
53 | * previously started processes or the Java process itself.
54 | *
55 | * @author Brett Wooldridge
56 | */
57 | public class NuProcessBuilder
58 | {
59 | private static final NuProcessFactory factory;
60 |
61 | private final List command;
62 | private final TreeMap environment;
63 | private Path cwd;
64 | private NuProcessHandler processListener;
65 |
66 | static {
67 | String factoryClassName = null;
68 | String osname = System.getProperty("os.name").toLowerCase();
69 | if (osname.contains("mac") || osname.contains("freebsd")) {
70 | factoryClassName = "com.zaxxer.nuprocess.osx.OsxProcessFactory";
71 | }
72 | else if (osname.contains("win")) {
73 | factoryClassName = "com.zaxxer.nuprocess.windows.WinProcessFactory";
74 | }
75 | else if (osname.contains("linux")) {
76 | factoryClassName = "com.zaxxer.nuprocess.linux.LinProcessFactory";
77 | }
78 | else if (osname.contains("sunos")) {
79 | factoryClassName = "com.zaxxer.nuprocess.solaris.SolProcessFactory";
80 | }
81 |
82 | if (factoryClassName == null) {
83 | throw new RuntimeException("Unsupported operating system: " + osname);
84 | }
85 |
86 | try {
87 | Class> forName = Class.forName(factoryClassName);
88 | factory = (NuProcessFactory) forName.newInstance();
89 | }
90 | catch (Exception e) {
91 | throw new RuntimeException(e);
92 | }
93 | }
94 |
95 | /**
96 | * Constructs a process builder with the specified operating system program
97 | * and arguments. This constructor makes a copy of the command list. Invokers
98 | * of this constructor must later call
99 | * {@link #setProcessListener(NuProcessHandler)} in order to set a
100 | * {@link NuProcessHandler} instance.
101 | *
102 | * @param commands a {@link List} of commands
103 | * @param environment The environment for the process
104 | */
105 | public NuProcessBuilder(List commands, Map environment)
106 | {
107 | if (commands == null || commands.isEmpty()) {
108 | throw new IllegalArgumentException("List of commands may not be null or empty");
109 | }
110 |
111 | this.environment = new TreeMap(environment);
112 | this.command = new ArrayList(commands);
113 | }
114 |
115 | /**
116 | * Constructs a process builder with the specified operating system program
117 | * and arguments. This constructor makes a copy of the command list. Invokers
118 | * of this constructor must later call
119 | * {@link #setProcessListener(NuProcessHandler)} in order to set a
120 | * {@link NuProcessHandler} instance.
121 | *
122 | * @param commands a {@link List} of commands
123 | */
124 | public NuProcessBuilder(List commands)
125 | {
126 | if (commands == null || commands.isEmpty()) {
127 | throw new IllegalArgumentException("List of commands may not be null or empty");
128 | }
129 |
130 | this.environment = new TreeMap(System.getenv());
131 | this.command = new ArrayList(commands);
132 | }
133 |
134 | /**
135 | * Constructs a process builder with the specified operating system program
136 | * and arguments. Invokers of this constructor must later call
137 | * {@link #setProcessListener(NuProcessHandler)} in order to set a
138 | * {@link NuProcessHandler} instance.
139 | *
140 | * @param commands a list of commands
141 | */
142 | public NuProcessBuilder(String... commands)
143 | {
144 | if (commands == null || commands.length == 0) {
145 | throw new IllegalArgumentException("List of commands may not be null or empty");
146 | }
147 |
148 | this.environment = new TreeMap(System.getenv());
149 | this.command = new ArrayList(Arrays.asList(commands));
150 | }
151 |
152 | /**
153 | * Constructs a process builder with the specified {@link NuProcessHandler}
154 | * and operating system program and arguments.
155 | *
156 | * @param nuProcessHandler a {@link NuProcessHandler} instance
157 | * @param commands a list of commands
158 | */
159 | public NuProcessBuilder(NuProcessHandler nuProcessHandler, String... commands)
160 | {
161 | this(commands);
162 |
163 | if (nuProcessHandler == null) {
164 | throw new IllegalArgumentException("A NuProcessListener must be specified");
165 | }
166 |
167 | this.processListener = nuProcessHandler;
168 | }
169 |
170 | /**
171 | * Constructs a process builder with the specified {@link NuProcessHandler}
172 | * and operating system program and arguments. This constructor makes a copy
173 | * of the command list.
174 | *
175 | * @param nuProcessHandler a {@link NuProcessHandler} instance
176 | * @param commands a {@link List} of commands
177 | */
178 | public NuProcessBuilder(NuProcessHandler nuProcessHandler, List commands)
179 | {
180 | this(commands);
181 |
182 | if (nuProcessHandler == null) {
183 | throw new IllegalArgumentException("A NuProcessListener must be specified");
184 | }
185 |
186 | this.processListener = nuProcessHandler;
187 | }
188 |
189 | /**
190 | * Get the {@link List} of commands that were used to construct this
191 | * {@link NuProcessBuilder}.
192 | *
193 | * @return a {@link List} of commands
194 | */
195 | public List command()
196 | {
197 | return command;
198 | }
199 |
200 | /**
201 | * Returns a string map view of this process builder's environment. Whenever
202 | * a process builder is created, the environment is initialized to a copy of
203 | * the current process environment. Subprocesses subsequently started by this
204 | * object's {@link #start()} method will use this map as their environment.
205 | *
206 | * The returned object may be modified using ordinary Map operations prior to
207 | * invoking the {@link #start()} method. The returned map is typically
208 | * case-sensitive on all platforms.
209 | *
210 | * @return This process builder's environment
211 | */
212 | public Map environment()
213 | {
214 | return environment;
215 | }
216 |
217 | /**
218 | * Set the {@link NuProcessHandler} instance that will be used for the next
219 | * and subsequent launch of a {@link NuProcess} when calling the
220 | * {@link #start()} method.
221 | *
222 | * @param listener a {@link NuProcessHandler} instance
223 | */
224 | public void setProcessListener(NuProcessHandler listener)
225 | {
226 | if (listener == null) {
227 | throw new IllegalArgumentException("A NuProcessListener must be specified");
228 | }
229 |
230 | this.processListener = listener;
231 | }
232 |
233 | /**
234 | * Set the {@link Path} to which the current working directory (cwd) of the
235 | * subsequent launch of a {@link NuProcess} will be set when calling the
236 | * {@link #start()} method.
237 | *
238 | * @param cwd a {@link Path} to use for the process's current working
239 | * directory, or {@code null} to disable setting the cwd of
240 | * subsequently launched proceses
241 | */
242 | public void setCwd(Path cwd)
243 | {
244 | this.cwd = cwd;
245 | }
246 |
247 | /**
248 | * Spawn the child process with the configured commands, environment, and
249 | * {@link NuProcessHandler}.
250 | *
251 | * @return a {@link NuProcess} instance or {@code null} if there is an
252 | * immediately detectable launch failure
253 | */
254 | public NuProcess start()
255 | {
256 | ensureNoNullCharacters(command);
257 | ensureListener();
258 | String[] env = prepareEnvironment();
259 |
260 | return factory.createProcess(command, env, processListener, cwd);
261 | }
262 |
263 | /**
264 | * Spawn the child process with the configured commands, environment, and {@link NuProcessHandler}
265 | * and wait for it to complete running.
266 | *
267 | * @since 1.3
268 | */
269 | public void run()
270 | {
271 | ensureNoNullCharacters(command);
272 | ensureListener();
273 | String[] env = prepareEnvironment();
274 |
275 | factory.runProcess(command, env, processListener, cwd);
276 | }
277 |
278 | private void ensureListener()
279 | {
280 | if (processListener == null) {
281 | throw new IllegalArgumentException("NuProcessHandler not specified");
282 | }
283 | }
284 |
285 | private void ensureNoNullCharacters(List commands) {
286 | for (String command : commands) {
287 | if (command.indexOf('\u0000') >= 0) {
288 | throw new IllegalArgumentException("Commands may not contain null characters");
289 | }
290 | }
291 | }
292 |
293 | private void ensureNoNullCharacters(String environment) {
294 | if (environment.indexOf('\u0000') >= 0) {
295 | throw new IllegalArgumentException("Environment may not contain null characters");
296 | }
297 | }
298 |
299 | private String[] prepareEnvironment()
300 | {
301 | String[] env = new String[environment.size()];
302 | int i = 0;
303 | for (Entry entrySet : environment.entrySet()) {
304 | String key = entrySet.getKey();
305 | String value = entrySet.getValue();
306 | ensureNoNullCharacters(key);
307 | ensureNoNullCharacters(value);
308 | env[i++] = key + "=" + value;
309 | }
310 |
311 | return env;
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/NuProcessFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess;
18 |
19 | import java.nio.file.Path;
20 | import java.util.List;
21 |
22 | /**
23 | * This is an internal class. Instances of this interface create and
24 | * start processes in a platform-specific fashion.
25 | *
26 | * @author Brett Wooldridge
27 | */
28 | public interface NuProcessFactory
29 | {
30 | NuProcess createProcess(List commands, String[] env, NuProcessHandler processListener, Path cwd);
31 |
32 | /**
33 | * Runs the process synchronously.
34 | *
35 | * Pumping is done on the calling thread, and this method will not return until the process has exited.
36 | *
37 | * @since 1.3
38 | */
39 | void runProcess(List commands, String[] env, NuProcessHandler processListener, Path cwd);
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/NuProcessHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | /**
22 | * Executors of a {@link NuProcess} must provide an implementation of this class
23 | * to the {@link ProcessBuilder} prior to calling {@link ProcessBuilder#start()}
24 | * . This interface provides the primary means of asynchronous interaction
25 | * between the running child process and your code.
26 | *
27 | * Methods specified by this interface will be invoked by the asynchronous
28 | * processing thread. It is up to the implementation of this interface to handle
29 | * the resulting state changes or process the data passed to it.
30 | *
31 | * Note that the processing thread that executes these callbacks on behalf of a
32 | * NuProcess is the same thread that handles processing for other instances of
33 | * NuProcess, and as a result you should never perform a blocking or
34 | * time-consuming operation on the callback thread. Doing so will block or
35 | * severely hinder the processing of other NuProcess instances. Any operation
36 | * that requires more than single-digit millisecond processing should be handed
37 | * off to another thread so that the processing thread can return to immediately
38 | * service other NuProcess instances.
39 | *
40 | * @author Brett Wooldridge
41 | */
42 | public interface NuProcessHandler
43 | {
44 | /**
45 | * This method is invoked when you call the {@link ProcessBuilder#start()}
46 | * method. This is an opportunity to store away the {@code NuProcess}
47 | * instance, possibly in your listener, so that it can be used for
48 | * interaction within other callbacks in this interface.
49 | *
50 | * Unlike the {@link #onStart(NuProcess)} method, this method is invoked
51 | * before the process is spawned, and is guaranteed to be invoked before any
52 | * other methods are called.
53 | *
54 | * @param nuProcess The {@link NuProcess} that is starting. Note that the
55 | * instance is not yet initialized, so it is not legal to call any of
56 | * its methods, and doing so will result in undefined behavior. If you
57 | * need to call any of the instance's methods, use
58 | * {@link #onStart(NuProcess)} instead.
59 | */
60 | void onPreStart(NuProcess nuProcess);
61 |
62 | /**
63 | * This method is invoked when you call the {@link ProcessBuilder#start()}
64 | * method. This is an opportunity to store away the {@code NuProcess}
65 | * instance, possibly in your listener, so that it can be used for
66 | * interaction within other callbacks in this interface.
67 | *
68 | * Note that this method is called at some point after the process is
69 | * spawned. It is possible for other methods (even {@link #onExit(int)}) to
70 | * be called first. If you need a guarantee that no other methods will be
71 | * called first, use {@link #onPreStart(NuProcess)} instead.
72 | *
73 | * @param nuProcess the {@link NuProcess} that is starting
74 | */
75 | void onStart(NuProcess nuProcess);
76 |
77 | /**
78 | * This method is invoked when the process exits. This method is also invoked
79 | * immediately in the case of a failure to launch the child process.
80 | *
81 | * There are two special values, besides ordinary process exit codes, that
82 | * may be passed to this method. A value of {@link Integer#MIN_VALUE}
83 | * indicates some kind of launch failure. A value of
84 | * {@link Integer#MAX_VALUE} indicates an unknown or expected failure mode.
85 | *
86 | * @param exitCode the exit code of the process, or a special value
87 | * indicating unexpected failures
88 | */
89 | void onExit(int exitCode);
90 |
91 | /**
92 | * This method is invoked when there is stdout data to process or an the
93 | * end-of-file (EOF) condition has been reached. In the case of EOF, the
94 | * {@code closed} parameter will be {@code true}; this is your signal that
95 | * EOF has been reached.
96 | *
97 | * You do not own the {@link ByteBuffer} provided to you. You should not
98 | * retain a reference to this buffer.
99 | *
100 | * Upon returning from this method, if any bytes are left in the buffer
101 | * (i.e., {@code buffer.hasRemaining()} returns {@code true}), then the
102 | * buffer will be {@link ByteBuffer#compact() compacted} after returning. Any
103 | * unused data will be kept at the start of the buffer and passed back to you
104 | * as part of the next invocation of this method (which might be when EOF is
105 | * reached and {@code closed} is {@code true}).
106 | *
107 | * Exceptions thrown out from your method will be ignored, but your method
108 | * should handle all exceptions itself.
109 | *
110 | * @param buffer a {@link ByteBuffer} containing received stdout data
111 | * @param closed {@code true} if EOF has been reached
112 | */
113 | void onStdout(ByteBuffer buffer, boolean closed);
114 |
115 | /**
116 | * This method is invoked when there is stderr data to process or an the
117 | * end-of-file (EOF) condition has been reached. In the case of EOF, the
118 | * {@code closed} parameter will be {@code true}; this is your signal that
119 | * EOF has been reached.
120 | *
121 | * You do not own the {@link ByteBuffer} provided to you. You should not
122 | * retain a reference to this buffer.
123 | *
124 | * Upon returning from this method, if any bytes are left in the buffer
125 | * (i.e., {@code buffer.hasRemaining()} returns {@code true}), then the
126 | * buffer will be {@link ByteBuffer#compact() compacted} after returning. Any
127 | * unused data will be kept at the start of the buffer and passed back to you
128 | * as part of the next invocation of this method (which might be when EOF is
129 | * reached and {@code closed} is {@code true}).
130 | *
131 | * Users wishing to merge stderr into stdout should simply delegate this
132 | * callback to {@link #onStdout(ByteBuffer, boolean)} when invoked, like so:
133 | *
134 | *
142 | * Notice that an EOF check is performed. If you merge streams in this way,
143 | * and you do not check for EOF here, then your
144 | * {@link #onStdout(ByteBuffer, boolean)} method will be called twice for an
145 | * EOF condition; once when the stdout stream closes, and once when the
146 | * stderr stream closes. If you check for EOF as above, your
147 | * {@link #onStdout(ByteBuffer, boolean)} method would only be called once
148 | * (for the close of stdout).
149 | *
150 | * Exceptions thrown out from your method will be ignored, but your method
151 | * should handle all exceptions itself.
152 | *
153 | * @param buffer a {@link ByteBuffer} containing received stderr data
154 | * @param closed {@code true} if EOF has been reached
155 | */
156 | void onStderr(ByteBuffer buffer, boolean closed);
157 |
158 | /**
159 | * This method is invoked after you have expressed a desire to write to stdin
160 | * by first calling {@link NuProcess#wantWrite()}. When this method is
161 | * invoked, your code should write data to be sent to the stdin of the child
162 | * process into the provided {@link ByteBuffer}. After writing data into the
163 | * {@code buffer} your code must {@link ByteBuffer#flip() flip} the
164 | * buffer before returning.
165 | *
166 | * If not all of the data needed to be written will fit in the provided
167 | * {@code buffer}, this method can return {@code true} to indicate a desire
168 | * to write more data. If there is no more data to be written at the time
169 | * this method is invoked, then {@code false} should be returned from this
170 | * method. It is always possible to call {@link NuProcess#wantWrite()} later
171 | * if data becomes available to be written.
172 | *
173 | * @param buffer a {@link ByteBuffer} into which your stdin-bound data should
174 | * be written
175 | * @return true if you have more data to write immediately, false otherwise
176 | */
177 | boolean onStdinReady(ByteBuffer buffer);
178 | }
179 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/codec/NuAbstractCharsetHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.codec;
18 |
19 | import com.zaxxer.nuprocess.NuProcess;
20 | import com.zaxxer.nuprocess.NuProcessHandler;
21 |
22 | import java.nio.ByteBuffer;
23 | import java.nio.CharBuffer;
24 | import java.nio.charset.Charset;
25 | import java.nio.charset.CharsetDecoder;
26 | import java.nio.charset.CharsetEncoder;
27 | import java.nio.charset.CoderResult;
28 |
29 | /**
30 | * Convenience implementation of {@link NuProcessHandler} which decodes stdin,
31 | * stdout, and stderr bytes to and from Java UTF-16 string data using a
32 | * {@link Charset} or separate {@link CharsetDecoder} and {@link CharsetEncoder
33 | * CharsetEncoders}.
34 | *
35 | * Subclass this and override any of {@link #onStdinCharsReady(CharBuffer)},
36 | * {@link #onStdoutChars(CharBuffer, boolean, CoderResult)}, and/or
37 | * {@link #onStderrChars(CharBuffer, boolean, CoderResult)} to process encoded
38 | * and decoded string data.
39 | *
40 | * You can also override any of the methods from {@link NuProcessHandler}) to
41 | * customize process handling behavior.
42 | *
43 | * @author Ben Hamilton
44 | */
45 | public abstract class NuAbstractCharsetHandler implements NuProcessHandler
46 | {
47 | private final NuCharsetEncoder stdinEncoder;
48 | private final NuCharsetDecoder stdoutDecoder;
49 | private final NuCharsetDecoder stderrDecoder;
50 |
51 | private class StdinEncoderHandler implements NuCharsetEncoderHandler
52 | {
53 | @Override
54 | public boolean onStdinReady(CharBuffer buffer)
55 | {
56 | return onStdinCharsReady(buffer);
57 | }
58 |
59 | @Override
60 | public void onEncoderError(CoderResult result)
61 | {
62 | onStdinEncoderError(result);
63 | }
64 | }
65 |
66 | private class StdoutDecoderHandler implements NuCharsetDecoderHandler
67 | {
68 | @Override
69 | public void onDecode(CharBuffer buffer, boolean closed, CoderResult decoderResult)
70 | {
71 | onStdoutChars(buffer, closed, decoderResult);
72 | }
73 | }
74 |
75 | private class StderrDecoderHandler implements NuCharsetDecoderHandler
76 | {
77 | @Override
78 | public void onDecode(CharBuffer buffer, boolean closed, CoderResult decoderResult)
79 | {
80 | onStderrChars(buffer, closed, decoderResult);
81 | }
82 | }
83 |
84 | /**
85 | * Constructor which encodes and decodes stdin, stdout, and stderr bytes
86 | * using the given {@link Charset}.
87 | *
88 | * @param charset The {@link Charset} with which to encode and decode stdin,
89 | * stdout, and stderr bytes
90 | */
91 | protected NuAbstractCharsetHandler(Charset charset)
92 | {
93 | this(charset.newEncoder(), charset.newDecoder(), charset.newDecoder());
94 | }
95 |
96 | /**
97 | * Constructor which encodes and decodes stdin, stdout, and stderr bytes
98 | * using specific {@link CharsetEncoder} and {@link CharsetDecoder
99 | * CharsetDecoders}, then invokes {@link #onStdinCharsReady(CharBuffer)},
100 | * {@link #onStdoutChars(CharBuffer, boolean, CoderResult)}, and
101 | * {@link #onStderrChars(CharBuffer, boolean, CoderResult)} to process the
102 | * encoded and decoded string data.
103 | *
104 | * @param stdinEncoder The {@link CharsetEncoder} with which to encode stdin
105 | * bytes
106 | * @param stdoutDecoder The {@link CharsetDecoder} with which to decode
107 | * stdout bytes
108 | * @param stderrDecoder The {@link CharsetDecoder} with which to decode
109 | * stderr bytes
110 | */
111 | protected NuAbstractCharsetHandler(CharsetEncoder stdinEncoder, CharsetDecoder stdoutDecoder, CharsetDecoder stderrDecoder)
112 | {
113 | this.stdinEncoder = new NuCharsetEncoder(new StdinEncoderHandler(), stdinEncoder);
114 | this.stdoutDecoder = new NuCharsetDecoder(new StdoutDecoderHandler(), stdoutDecoder);
115 | this.stderrDecoder = new NuCharsetDecoder(new StderrDecoderHandler(), stderrDecoder);
116 | }
117 |
118 | /**
119 | * Override this to provide Unicode Java string data to stdin.
120 | *
121 | * @param buffer The {@link CharBuffer} into which you should write string
122 | * data to be fed to stdin
123 | * @return {@code true} if you have more string data to feed to stdin
124 | */
125 | protected boolean onStdinCharsReady(CharBuffer buffer)
126 | {
127 | return false;
128 | }
129 |
130 | /**
131 | * Override this to handle errors encoding string data received from
132 | * {@link #onStdinCharsReady(CharBuffer)}.
133 | *
134 | * @param result The {@link CoderResult} indicating encoder error
135 | */
136 | protected void onStdinEncoderError(CoderResult result)
137 | {
138 | }
139 |
140 | /**
141 | * Override this to receive decoded Unicode Java string data read from
142 | * stdout.
143 | *
144 | * Make sure to set the {@link CharBuffer#position() position} of
145 | * {@code buffer} to indicate how much data you have read before returning.
146 | *
147 | * @param buffer The {@link CharBuffer} receiving Unicode string data.
148 | */
149 | protected void onStdoutChars(CharBuffer buffer, boolean closed, CoderResult coderResult)
150 | {
151 | // Consume the entire buffer by default.
152 | buffer.position(buffer.limit());
153 | }
154 |
155 | /**
156 | * Override this to receive decoded Unicode Java string data read from
157 | * stderr.
158 | *
159 | * Make sure to set the {@link CharBuffer#position() position} of
160 | * {@code buffer} to indicate how much data you have read before returning.
161 | *
162 | * @param buffer The {@link CharBuffer} receiving Unicode string data.
163 | */
164 | protected void onStderrChars(CharBuffer buffer, boolean closed, CoderResult coderResult)
165 | {
166 | // Consume the entire buffer by default.
167 | buffer.position(buffer.limit());
168 | }
169 |
170 | /** {@inheritDoc} */
171 | @Override
172 | public void onPreStart(NuProcess nuProcess)
173 | {
174 | }
175 |
176 | /** {@inheritDoc} */
177 | @Override
178 | public void onStart(NuProcess nuProcess)
179 | {
180 | }
181 |
182 | /** {@inheritDoc} */
183 | @Override
184 | public void onExit(int exitCode)
185 | {
186 | }
187 |
188 | /** {@inheritDoc} */
189 | @Override
190 | public final void onStdout(ByteBuffer buffer, boolean closed)
191 | {
192 | stdoutDecoder.onOutput(buffer, closed);
193 | }
194 |
195 | /** {@inheritDoc} */
196 | @Override
197 | public final void onStderr(ByteBuffer buffer, boolean closed)
198 | {
199 | stderrDecoder.onOutput(buffer, closed);
200 | }
201 |
202 | /** {@inheritDoc} */
203 | @Override
204 | public final boolean onStdinReady(ByteBuffer buffer)
205 | {
206 | return stdinEncoder.onStdinReady(buffer);
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/codec/NuCharsetDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.codec;
18 |
19 | import com.zaxxer.nuprocess.NuProcess;
20 | import com.zaxxer.nuprocess.NuProcessHandler;
21 |
22 | import java.nio.ByteBuffer;
23 | import java.nio.CharBuffer;
24 | import java.nio.charset.Charset;
25 | import java.nio.charset.CharsetDecoder;
26 | import java.nio.charset.CoderResult;
27 |
28 | /**
29 | * Implementation of {@link NuProcessHandler#onStdout(ByteBuffer, boolean)} or
30 | * {@link NuProcessHandler#onStderr(ByteBuffer, boolean)} which handles decoding
31 | * of stdout or stderr bytes to Java UTF-16 string data.
32 | *
33 | * Calls back into a {@link NuCharsetDecoderHandler} with decoded stdout or
34 | * stderr string data.
35 | *
36 | * This class is not intended to be subclassed.
37 | *
38 | * @author Ben Hamilton
39 | */
40 | public final class NuCharsetDecoder
41 | {
42 | private final NuCharsetDecoderHandler handler;
43 | private final CharsetDecoder decoder;
44 | private final CharBuffer charBuffer;
45 |
46 | /**
47 | * Creates a decoder which uses a single {@link Charset} to decode output
48 | * data.
49 | *
50 | * @param handler {@link NuCharsetDecoderHandler} called back with decoded
51 | * string data
52 | * @param charset {@link Charset} used to decode output data
53 | */
54 | public NuCharsetDecoder(NuCharsetDecoderHandler handler, Charset charset)
55 | {
56 | this(handler, charset.newDecoder());
57 | }
58 |
59 | /**
60 | * Creates a decoder which uses a {@link CharsetDecoder CharsetDecoders} to
61 | * decode output data.
62 | *
63 | * @param handler {@link NuCharsetDecoderHandler} called back with decoded
64 | * string data
65 | * @param decoder {@link CharsetDecoder} used to decode stdout bytes to
66 | * string data
67 | */
68 | public NuCharsetDecoder(NuCharsetDecoderHandler handler, CharsetDecoder decoder)
69 | {
70 | this.handler = handler;
71 | this.decoder = decoder;
72 | this.charBuffer = CharBuffer.allocate(NuProcess.BUFFER_CAPACITY);
73 | }
74 |
75 | /**
76 | * Implementation of {@link NuProcessHandler#onStdout(ByteBuffer, boolean)}
77 | * or {@link NuProcessHandler#onStderr(ByteBuffer, boolean)} which decodes
78 | * output data and forwards it to {@code handler}.
79 | *
80 | * @param buffer {@link ByteBuffer} which received bytes from stdout or
81 | * stderr
82 | * @param closed true if stdout or stderr was closed, false otherwise
83 | */
84 | public void onOutput(ByteBuffer buffer, boolean closed)
85 | {
86 | CoderResult coderResult = decoder.decode(buffer, charBuffer, /* endOfInput */ closed);
87 | charBuffer.flip();
88 | this.handler.onDecode(charBuffer, closed, coderResult);
89 | charBuffer.compact();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/codec/NuCharsetDecoderHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.codec;
18 |
19 | import com.zaxxer.nuprocess.NuProcessHandler;
20 |
21 | import java.nio.CharBuffer;
22 | import java.nio.charset.CoderResult;
23 |
24 | /**
25 | * Callbacks invoked by {@link NuCharsetDecoder} with decoded string data.
26 | *
27 | * @see NuProcessHandler
28 | */
29 | public interface NuCharsetDecoderHandler
30 | {
31 | /**
32 | * This method is invoked when there is decoded data to process or an the
33 | * end-of-file (EOF) condition has been reached. In the case of EOF, the
34 | * {@code closed} parameter will be {@code true}; this is your signal that
35 | * EOF has been reached.
36 | *
37 | * You do not own the {@link CharBuffer} provided to you. You should not
38 | * retain a reference to this buffer.
39 | *
40 | * Upon returning from this method, if any characters are left in the buffer
41 | * (i.e., {@code buffer.hasRemaining()} returns {@code true}), then the
42 | * buffer will be {@link CharBuffer#compact() compacted} after returning. Any
43 | * unused data will be kept at the start of the buffer and passed back to you
44 | * as part of the next invocation of this method (which might be when EOF is
45 | * reached and {@code closed} is {@code true}).
46 | *
47 | * Exceptions thrown out from your method will be ignored, but your method
48 | * should handle all exceptions itself.
49 | *
50 | * @param buffer a {@link CharBuffer} containing received and decoded data
51 | * @param closed {@code true} if EOF has been reached
52 | * @param decoderResult a {@link CoderResult} signifying whether an error was
53 | * encountered decoding stdout bytes
54 | */
55 | void onDecode(CharBuffer buffer, boolean closed, CoderResult decoderResult);
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/codec/NuCharsetEncoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.codec;
18 |
19 | import com.zaxxer.nuprocess.NuProcess;
20 | import com.zaxxer.nuprocess.NuProcessHandler;
21 |
22 | import java.nio.ByteBuffer;
23 | import java.nio.CharBuffer;
24 | import java.nio.charset.Charset;
25 | import java.nio.charset.CharsetEncoder;
26 | import java.nio.charset.CoderResult;
27 |
28 | /**
29 | * Implementation of {@link NuProcessHandler#onStdinReady(ByteBuffer)} which
30 | * handles encoding of stdin bytes to Java UTF-16 string data.
31 | *
32 | * Calls back into a {@link NuCharsetEncoderHandler} with a {@link CharBuffer}
33 | * whose contents will be encoded with a specified {@link Charset} or
34 | * {@link CharsetEncoder} then passed to the stdin of a process.
35 | *
36 | * This class is not intended to be subclassed.
37 | *
38 | * @author Ben Hamilton
39 | */
40 | public final class NuCharsetEncoder
41 | {
42 | private final NuCharsetEncoderHandler handler;
43 | private final CharsetEncoder encoder;
44 | private final CharBuffer charBuffer;
45 |
46 | /**
47 | * Creates an encoder which uses a single {@link Charset} to encode input
48 | * data.
49 | *
50 | * @param handler {@link NuCharsetEncoderHandler} called back with a string
51 | * buffer to be encoded and fed to stdin
52 | * @param charset {@link Charset} used to encode stdin data to bytes
53 | */
54 | public NuCharsetEncoder(NuCharsetEncoderHandler handler, Charset charset)
55 | {
56 | this(handler, charset.newEncoder());
57 | }
58 |
59 | /**
60 | * Creates an encoder which uses a {@link CharsetEncoder} to encode input
61 | * data.
62 | *
63 | * @param handler {@link NuCharsetEncoderHandler} called back with a string
64 | * buffer into which the caller writes string data to be written to
65 | * stdin
66 | * @param encoder {@link CharsetEncoder} used to encode stdin string data to
67 | * bytes
68 | */
69 | public NuCharsetEncoder(NuCharsetEncoderHandler handler, CharsetEncoder encoder)
70 | {
71 | this.handler = handler;
72 | this.encoder = encoder;
73 | this.charBuffer = CharBuffer.allocate(NuProcess.BUFFER_CAPACITY);
74 | }
75 |
76 | /**
77 | * Implementation of {@link NuProcessHandler#onStdinReady(ByteBuffer)} which
78 | * calls {@link handler} with a string buffer then encodes it to bytes and
79 | * feeds it to the process's stdin.
80 | *
81 | * @param buffer The {@link ByteBuffer} passed to
82 | * {@link NuProcessHandler#onStdinReady(ByteBuffer)}
83 | * @return true if more data needs to be passed to stdin, false otherwise
84 | */
85 | public boolean onStdinReady(ByteBuffer buffer)
86 | {
87 | // TODO: Should we avoid invoking onStdinReady() when it returned false previously?
88 | boolean endOfInput = !this.handler.onStdinReady(charBuffer);
89 | CoderResult encoderResult = encoder.encode(charBuffer, buffer, endOfInput);
90 | buffer.flip();
91 | charBuffer.compact();
92 | if (encoderResult.isError()) {
93 | this.handler.onEncoderError(encoderResult);
94 | }
95 | if (encoderResult.isOverflow()) {
96 | return true;
97 | }
98 | else if (endOfInput) {
99 | CoderResult flushResult = encoder.flush(buffer);
100 | return flushResult.isOverflow();
101 | }
102 | else {
103 | return true;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/codec/NuCharsetEncoderHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.codec;
18 |
19 | import java.nio.CharBuffer;
20 | import java.nio.charset.CoderResult;
21 |
22 | import com.zaxxer.nuprocess.NuProcess;
23 | import com.zaxxer.nuprocess.NuProcessHandler;
24 |
25 | /**
26 | * Callbacks invoked by {@link NuCharsetEncoder} with decoded string data.
27 | *
28 | * @see NuProcessHandler
29 | */
30 | public interface NuCharsetEncoderHandler
31 | {
32 | /**
33 | * This method is invoked after you have expressed a desire to write to stdin
34 | * by first calling {@link NuProcess#wantWrite()}. When this method is
35 | * invoked, your code should write data to be sent to the stdin of the child
36 | * process into the provided {@link CharBuffer}. After writing data into the
37 | * {@code buffer} your code must {@link CharBuffer#flip() flip} the
38 | * buffer before returning.
39 | *
40 | * If not all of the data needed to be written will fit in the provided
41 | * {@code buffer}, this method can return {@code true} to indicate a desire
42 | * to write more data. If there is no more data to be written at the time
43 | * this method is invoked, then {@code false} should be returned from this
44 | * method. It is always possible to call {@link NuProcess#wantWrite()} later
45 | * if data becomes available to be written.
46 | *
47 | * Note that this method can be invoked one more time after you return
48 | * {@code false}, in case the encoded {@link CharBuffer} did not fit inside a
49 | * byte buffer.
50 | *
51 | * @param buffer a {@link CharBuffer} into which your stdin-bound data should
52 | * be written
53 | * @return true if you have more data to write immediately, false otherwise
54 | */
55 | boolean onStdinReady(CharBuffer buffer);
56 |
57 | /**
58 | * This method is invoked immediately after {@link #onStdinReady(CharBuffer)}
59 | * returns if encoding the {@link CharBuffer} to bytes fails.
60 | *
61 | * @param result The {@link CoderResult} indicating the encoding failure
62 | */
63 | void onEncoderError(CoderResult result);
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/BaseEventProcessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.internal;
18 |
19 | import java.util.Collection;
20 | import java.util.Map;
21 | import java.util.concurrent.ConcurrentHashMap;
22 | import java.util.concurrent.CyclicBarrier;
23 | import java.util.concurrent.atomic.AtomicBoolean;
24 | import java.util.logging.Level;
25 | import java.util.logging.Logger;
26 |
27 | import com.sun.jna.ptr.IntByReference;
28 |
29 | /**
30 | * @author Brett Wooldridge
31 | */
32 | public abstract class BaseEventProcessor implements IEventProcessor
33 | {
34 | public static final int LINGER_TIME_MS;
35 |
36 | protected static final int DEADPOOL_POLL_INTERVAL;
37 | protected static final int LINGER_ITERATIONS;
38 |
39 | private static final Logger LOGGER = Logger.getLogger(BaseEventProcessor.class.getCanonicalName());
40 |
41 | private final int lingerIterations;
42 |
43 | protected Map pidToProcessMap;
44 | protected Map fildesToProcessMap;
45 |
46 | protected volatile boolean shutdown;
47 |
48 | private CyclicBarrier startBarrier;
49 | private AtomicBoolean isRunning;
50 |
51 | static {
52 | LINGER_TIME_MS = Math.max(1000, Integer.getInteger("com.zaxxer.nuprocess.lingerTimeMs", 2500));
53 |
54 | DEADPOOL_POLL_INTERVAL = Math.min(LINGER_TIME_MS, Math.max(100, Integer.getInteger("com.zaxxer.nuprocess.deadPoolPollMs", 250)));
55 |
56 | LINGER_ITERATIONS = LINGER_TIME_MS / DEADPOOL_POLL_INTERVAL;
57 | }
58 |
59 | public BaseEventProcessor()
60 | {
61 | this(LINGER_ITERATIONS);
62 | }
63 |
64 | public BaseEventProcessor(int lingerIterations)
65 | {
66 | this.lingerIterations = lingerIterations;
67 | pidToProcessMap = new ConcurrentHashMap();
68 | fildesToProcessMap = new ConcurrentHashMap();
69 | isRunning = new AtomicBoolean();
70 | }
71 |
72 | /**
73 | * The primary run loop of the event processor.
74 | */
75 | @Override
76 | public void run()
77 | {
78 | try {
79 | // If the process is running synchronously, startBarrier will be null
80 | if (startBarrier != null) {
81 | startBarrier.await();
82 | }
83 |
84 | int idleCount = 0;
85 | while (!isRunning.compareAndSet(idleCount > lingerIterations && pidToProcessMap.isEmpty(), false)) {
86 | idleCount = (!shutdown && process()) ? 0 : (idleCount + 1);
87 | }
88 | }
89 | catch (Exception e) {
90 | // TODO: how to handle this error?
91 | LOGGER.log(Level.WARNING, "Aborting processing loop after unexpected exception (" +
92 | pidToProcessMap.size() + " processes running)", e);
93 | isRunning.set(false);
94 | }
95 | finally {
96 | if (startBarrier == null) {
97 | // If the process is running synchronously, when the run loop ends give the subclass
98 | // an opportunity to close any descriptors it might have been using
99 | close();
100 | }
101 | }
102 | }
103 |
104 | /** {@inheritDoc} */
105 | @Override
106 | public CyclicBarrier getSpawnBarrier()
107 | {
108 | startBarrier = new CyclicBarrier(2);
109 | return startBarrier;
110 | }
111 |
112 | /** {@inheritDoc} */
113 | @Override
114 | public boolean checkAndSetRunning()
115 | {
116 | return isRunning.compareAndSet(false, true);
117 | }
118 |
119 | /** {@inheritDoc} */
120 | @Override
121 | public void shutdown()
122 | {
123 | shutdown = true;
124 | Collection processes = pidToProcessMap.values();
125 | IntByReference exitCode = new IntByReference();
126 | for (T process : processes) {
127 | LibC.kill(process.getPid(), LibC.SIGTERM);
128 | process.onExit(Integer.MAX_VALUE - 1);
129 | LibC.waitpid(process.getPid(), exitCode, LibC.WNOHANG);
130 | }
131 | }
132 |
133 | /**
134 | * Closes the processor, freeing up any resources (such as file descriptors) it was using.
135 | *
136 | * Note: This method is only called for processors that are used to pump synchronous processes.
137 | * Processors used to pump asynchronous processes are never closed; while their threads may be stopped,
138 | * when there are no processes to pump, they are restarted if a new asynchronous process is started and will
139 | * reuse the same resources.
140 | *
141 | * @since 1.3
142 | */
143 | protected abstract void close();
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/Constants.java:
--------------------------------------------------------------------------------
1 | package com.zaxxer.nuprocess.internal;
2 |
3 | public class Constants
4 | {
5 | public static final int NUMBER_OF_THREADS;
6 | public static final int JVM_MAJOR_VERSION;
7 | static final OperatingSystem OS;
8 |
9 | enum OperatingSystem
10 | {
11 | MAC,
12 | LINUX,
13 | SOLARIS
14 | }
15 |
16 | static int getJavaMajorVersion(String versionString) {
17 | String[] parts = versionString.split("\\.");
18 | // Make sure we handle versions like '11-ea' which ships with centos7
19 | int major = Integer.parseInt(parts[0].split("\\D")[0]);
20 | if (major == 1) {
21 | major = Integer.parseInt(parts[1].split("\\D")[0]);
22 | }
23 | return major;
24 | }
25 |
26 | static {
27 |
28 | JVM_MAJOR_VERSION = getJavaMajorVersion(System.getProperty("java.version"));
29 |
30 | final String osname = System.getProperty("os.name").toLowerCase();
31 | if (osname.contains("mac") || osname.contains("freebsd"))
32 | OS = OperatingSystem.MAC;
33 | else if (osname.contains("linux"))
34 | OS = OperatingSystem.LINUX;
35 | else
36 | OS = OperatingSystem.SOLARIS;
37 |
38 | final String threads = System.getProperty("com.zaxxer.nuprocess.threads", "auto");
39 | if ("auto".equals(threads)) {
40 | NUMBER_OF_THREADS = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
41 | }
42 | else if ("cores".equals(threads)) {
43 | NUMBER_OF_THREADS = Runtime.getRuntime().availableProcessors();
44 | }
45 | else {
46 | NUMBER_OF_THREADS = Math.max(1, Integer.parseInt(threads));
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/HexDumpElf.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.zaxxer.nuprocess.internal;
17 |
18 | import java.util.Formatter;
19 |
20 | /**
21 | * Hex Dump Elf
22 | *
23 | * xxxx: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ................
24 | */
25 | public final class HexDumpElf
26 | {
27 | private static final int MAX_VISIBLE = 127;
28 | private static final int MIN_VISIBLE = 31;
29 |
30 | private HexDumpElf()
31 | {
32 | // private constructor
33 | }
34 |
35 | /**
36 | * Dump an array of bytes in hexadecimal.
37 | *
38 | * @param displayOffset the display offset (left column)
39 | * @param data the byte array of data
40 | * @param offset the offset to start dumping in the byte array
41 | * @param len the length of data to dump
42 | * @return the dump string
43 | */
44 | public static String dump(final int displayOffset, final byte[] data, final int offset, final int len)
45 | {
46 | StringBuilder sb = new StringBuilder();
47 | Formatter formatter = new Formatter(sb);
48 | StringBuilder ascii = new StringBuilder();
49 |
50 | int dataNdx = offset;
51 | final int maxDataNdx = offset + len;
52 | final int lines = (len + 16) / 16;
53 | for (int i = 0; i < lines; i++) {
54 | ascii.append(" |");
55 | formatter.format("%08x ", displayOffset + (i * 16));
56 |
57 | for (int j = 0; j < 16; j++) {
58 | if (dataNdx < maxDataNdx) {
59 | byte b = data[dataNdx++];
60 | formatter.format("%02x ", b);
61 | ascii.append((b > MIN_VISIBLE && b < MAX_VISIBLE) ? (char) b : ' ');
62 | }
63 | else {
64 | sb.append(" ");
65 | }
66 |
67 | if (j == 7) {
68 | sb.append(' ');
69 | }
70 | }
71 |
72 | ascii.append('|');
73 | sb.append(ascii).append('\n');
74 | ascii.setLength(0);
75 | }
76 |
77 | formatter.close();
78 | return sb.toString();
79 | }
80 | }
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/IEventProcessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.internal;
18 |
19 | import java.util.concurrent.CyclicBarrier;
20 |
21 | /**
22 | * This class is internal.
23 | *
24 | * @author Brett Wooldridge
25 | *
26 | * @param a subclass of {@link BasePosixProcess}
27 | */
28 | public interface IEventProcessor extends Runnable
29 | {
30 | /**
31 | * Check whether the processor instance is currently running, and if not set
32 | * it to the running state.
33 | *
34 | * @return true if the processor was already running, false otherwise
35 | */
36 | boolean checkAndSetRunning();
37 |
38 | /**
39 | * Get the CyclicBarrier that this thread should join, along with the
40 | * NuProcess start thread that is starting this processor. Used to cause the
41 | * OsxProcess to wait until the processor is up and running before returning
42 | * from start() to the user.
43 | *
44 | * @return the CyclicBarrier to join to ensure the processor is running
45 | * before registering processes with it
46 | */
47 | CyclicBarrier getSpawnBarrier();
48 |
49 | /**
50 | * Register a process for handling by the event processor.
51 | *
52 | * @param process the process to register
53 | */
54 | void registerProcess(T process);
55 |
56 | /**
57 | * Queues read handling for the process's STDOUT and STDERR streams.
58 | *
59 | * Prior to 2.1, this was performed by {@link #registerProcess}, but it was moved to a separate method
60 | * to help avoid a race condition when starting a new process where soft exit detection could result in
61 | * a fast-running process exiting before start was called on its process handler.
62 | *
63 | * @param process the process from which STDOUT and STDERR should be read
64 | * @since 2.1
65 | */
66 | void queueRead(T process);
67 |
68 | /**
69 | * Express that the client desires to write data into the STDIN stream as
70 | * soon as possible.
71 | *
72 | * @param process the process that wants to write to STDIN
73 | */
74 | void queueWrite(T process);
75 |
76 | /**
77 | * Close the process's STDIN pipe.
78 | *
79 | * @param process the process whose STDIN pipe should be closed
80 | */
81 | void closeStdin(T process);
82 |
83 | /**
84 | * Called by the event-loop to process asynchronous I/O events.
85 | *
86 | * @return true if events were processed, false if an idle timeout occurred
87 | */
88 | boolean process();
89 |
90 | /**
91 | * Cleanly shutdown the processors and cleanup all resources.
92 | */
93 | void shutdown();
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/LibC.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.internal;
18 |
19 | import com.sun.jna.*;
20 | import com.sun.jna.ptr.IntByReference;
21 | import com.zaxxer.nuprocess.internal.Constants.OperatingSystem;
22 |
23 | import java.nio.ByteBuffer;
24 |
25 | import static com.zaxxer.nuprocess.internal.Constants.OS;
26 |
27 | @SuppressWarnings("WeakerAccess")
28 | public class LibC
29 | {
30 | static {
31 | if (OS == OperatingSystem.MAC) {
32 | O_NONBLOCK = 0x0004; // MacOS X, Freebsd
33 | }
34 | else {
35 | O_NONBLOCK = 2048; // Linux
36 | }
37 |
38 | Native.register(NativeLibrary.getProcess());
39 | }
40 |
41 | public static native int pipe(int[] fildes);
42 |
43 | public static native int posix_spawnattr_init(Pointer posix_spawnattr_t);
44 |
45 | public static native int posix_spawnattr_destroy(Pointer posix_spawnattr_t);
46 |
47 | public static native int posix_spawnattr_setflags(Pointer posix_spawnattr_t, short flags);
48 |
49 | public static native int posix_spawn_file_actions_init(Pointer posix_spawn_file_actions_t);
50 |
51 | public static native int posix_spawn_file_actions_destroy(Pointer posix_spawn_file_actions_t);
52 |
53 | public static native int posix_spawn_file_actions_addclose(Pointer actions, int filedes);
54 |
55 | public static native int posix_spawn_file_actions_adddup2(Pointer actions, int fildes, int newfildes);
56 |
57 | public static native int posix_spawnp(IntByReference restrict_pid, String restrict_path, Pointer file_actions,
58 | Pointer /*const posix_spawnattr_t*/ restrict_attrp, StringArray /*String[]*/ argv, Pointer /*String[]*/ envp);
59 |
60 | public static native int fcntl(int fildes, int cmd);
61 |
62 | public static native int fcntl(int fildes, int cmd, long argO);
63 |
64 | public static native int close(int fildes);
65 |
66 | public static native int write(int fildes, ByteBuffer buf, int nbyte);
67 |
68 | public static native int read(int fildes, ByteBuffer buf, int nbyte);
69 |
70 | public static native int getpid();
71 |
72 | public static native int kill(int pid, int sig);
73 |
74 | public static native int waitpid(int pid, IntByReference status, int options);
75 |
76 | public static native Pointer signal(int signal, Pointer func);
77 |
78 | public static native String getcwd(Pointer buf, int size);
79 |
80 | // from /usr/include/sys/syscall.h
81 | // We can't use JNA direct mapping for syscall(), since it takes varargs.
82 | public interface SyscallLibrary extends Library
83 | {
84 | public static final int SYS___pthread_chdir = 348;
85 |
86 | int syscall(int syscall_number, Object... args);
87 | }
88 |
89 | public static SyscallLibrary SYSCALL = (SyscallLibrary) Native.loadLibrary(Platform.C_LIBRARY_NAME, SyscallLibrary.class);
90 |
91 | public static final int F_GETFL = 3;
92 | public static final int F_SETFL = 4;
93 |
94 | public static final int O_NONBLOCK;
95 |
96 | // from /usr/include/asm-generic/errno-base.h
97 | public static final int ESRCH = 3; /* No such process */
98 | public static final int EINTR = 4; /* Interrupted system call */
99 | public static final int ECHILD = 10; /* No child processes */
100 |
101 | // from /usr/include/sys/wait.h
102 | public static final int WNOHANG = 0x00000001;
103 |
104 | // from /usr/include/sys/spawn.h
105 | public static final short POSIX_SPAWN_START_SUSPENDED = 0x0080;
106 | public static final short POSIX_SPAWN_CLOEXEC_DEFAULT = 0x4000;
107 |
108 | // From /usr/include/sys/signal.h
109 | public static final int SIGKILL = 9;
110 | public static final int SIGTERM = 15;
111 | public static final int SIGCONT = 19;
112 | public static final int SIGUSR2 = 31;
113 |
114 | public static final Pointer SIG_IGN = Pointer.createConstant(1);
115 |
116 | /* If WIFEXITED(STATUS), the low-order 8 bits of the status. */
117 | public static int WEXITSTATUS(int status)
118 | {
119 | return (((status) & 0xff00) >> 8);
120 | }
121 |
122 | /* If WIFSIGNALED(STATUS), the terminating signal. */
123 | public static int WTERMSIG(int status)
124 | {
125 | return ((status) & 0x7f);
126 | }
127 |
128 | /* If WIFSTOPPED(STATUS), the signal that stopped the child. */
129 | public static int WSTOPSIG(int status)
130 | {
131 | return WEXITSTATUS(status);
132 | }
133 |
134 | /* Nonzero if STATUS indicates normal termination. */
135 | public static boolean WIFEXITED(int status)
136 | {
137 | return ((status) & 0x7f) == 0;
138 | }
139 |
140 | /* Nonzero if STATUS indicates termination by a signal. */
141 | public static boolean WIFSIGNALED(int status)
142 | {
143 | return (((byte) (((status) & 0x7f) + 1) >> 1) > 0);
144 | }
145 |
146 | /* Nonzero if STATUS indicates the child is stopped. */
147 | public static boolean WIFSTOPPED(int status)
148 | {
149 | return WTERMSIG(status) != 0;
150 | }
151 |
152 | public static int W_EXITCODE(int ret, int sig)
153 | {
154 | return ((ret) << 8 | (sig));
155 | }
156 |
157 | public static int W_STOPCODE(int sig)
158 | {
159 | return ((sig) << 8 | 0x7f);
160 | }
161 |
162 | public interface SignalFunction extends Callback
163 | {
164 | void invoke(int signal);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/LibJava10.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.internal;
18 |
19 | import com.sun.jna.JNIEnv;
20 | import com.sun.jna.Library;
21 | import com.sun.jna.Native;
22 | import com.sun.jna.NativeLibrary;
23 |
24 | import java.io.IOException;
25 | import java.util.HashMap;
26 | import java.util.Map;
27 |
28 | import static com.zaxxer.nuprocess.internal.Constants.JVM_MAJOR_VERSION;
29 | import static com.zaxxer.nuprocess.internal.Constants.OS;
30 |
31 | public class LibJava10
32 | {
33 | static {
34 | Map options = new HashMap<>();
35 | options.put(Library.OPTION_ALLOW_OBJECTS, Boolean.TRUE);
36 |
37 | if (OS != Constants.OperatingSystem.MAC) {
38 | Native.register(NativeLibrary.getInstance("java", options));
39 | }
40 | else {
41 | Native.register(NativeLibrary.getProcess(options));
42 | }
43 |
44 | Java_java_lang_ProcessImpl_init(JNIEnv.CURRENT, BasePosixProcess.class);
45 | }
46 |
47 | public static native void Java_java_lang_ProcessImpl_init(JNIEnv jniEnv, Object clazz);
48 |
49 | /**
50 | * JNIEXPORT jint JNICALL
51 | * Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
52 | * jobject process,
53 | * jint mode,
54 | * jbyteArray helperpath,
55 | * jbyteArray prog,
56 | * jbyteArray argBlock, jint argc,
57 | * jbyteArray envBlock, jint envc,
58 | * jbyteArray dir,
59 | * jintArray std_fds,
60 | * jboolean redirectErrorStream)
61 | *
62 | * @return the PID of the process
63 | */
64 | public static native int Java_java_lang_ProcessImpl_forkAndExec(
65 | JNIEnv jniEnv,
66 | Object process,
67 | int mode,
68 | Object helperpath,
69 | Object prog,
70 | Object argBlock, int argc,
71 | Object envBlock, int envc,
72 | Object dir,
73 | Object fds,
74 | byte redirectErrorStream
75 | ) throws IOException;
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/LibJava8.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton, Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.internal;
18 |
19 | import com.sun.jna.JNIEnv;
20 | import com.sun.jna.Library;
21 | import com.sun.jna.Native;
22 | import com.sun.jna.NativeLibrary;
23 |
24 | import java.io.IOException;
25 | import java.util.HashMap;
26 | import java.util.Map;
27 |
28 | import static com.zaxxer.nuprocess.internal.Constants.OS;
29 |
30 | public class LibJava8
31 | {
32 | static {
33 | Map options = new HashMap<>();
34 | options.put(Library.OPTION_ALLOW_OBJECTS, Boolean.TRUE);
35 |
36 | if (OS != Constants.OperatingSystem.MAC) {
37 | Native.register(NativeLibrary.getInstance("java", options));
38 | }
39 | else {
40 | Native.register(NativeLibrary.getProcess(options));
41 | }
42 |
43 | Java_java_lang_UNIXProcess_init(JNIEnv.CURRENT, BasePosixProcess.class);
44 | }
45 |
46 | public static native void Java_java_lang_UNIXProcess_init(JNIEnv jniEnv, Object clazz);
47 |
48 | /**
49 | * JNIEXPORT jint JNICALL
50 | * Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
51 | * jobject process,
52 | * jint mode,
53 | * jbyteArray helperpath,
54 | * jbyteArray prog,
55 | * jbyteArray argBlock, jint argc,
56 | * jbyteArray envBlock, jint envc,
57 | * jbyteArray dir,
58 | * jintArray std_fds,
59 | * jboolean redirectErrorStream)
60 | *
61 | * @return the PID of the process
62 | */
63 | public static native int Java_java_lang_UNIXProcess_forkAndExec(
64 | JNIEnv jniEnv,
65 | Object process,
66 | int mode,
67 | Object helperpath,
68 | Object prog,
69 | Object argBlock, int argc,
70 | Object envBlock, int envc,
71 | Object dir,
72 | Object fds,
73 | byte redirectErrorStream
74 | ) throws IOException;
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/internal/ReferenceCountedFileDescriptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Ben Hamilton
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.internal;
18 |
19 | import com.sun.jna.LastErrorException;
20 |
21 | /**
22 | * Encapsulates a file descriptor plus a reference count to ensure close requests
23 | * only close the file descriptor once the last reference to the file descriptor
24 | * is released.
25 | *
26 | * If not explicitly closed, the file descriptor will be closed when
27 | * this object is finalized.
28 | */
29 | public class ReferenceCountedFileDescriptor {
30 | private int fd;
31 | private int fdRefCount;
32 | private boolean closePending;
33 |
34 | public ReferenceCountedFileDescriptor(int fd) {
35 | this.fd = fd;
36 | this.fdRefCount = 0;
37 | this.closePending = false;
38 | }
39 |
40 | protected void finalize() {
41 | close();
42 | }
43 |
44 | public synchronized int acquire() {
45 | fdRefCount++;
46 | return fd;
47 | }
48 |
49 | public synchronized void release() {
50 | fdRefCount--;
51 | if (fdRefCount == 0 && closePending && fd != -1) {
52 | doClose();
53 | }
54 | }
55 |
56 | public synchronized void close() {
57 | if (fd == -1 || closePending) {
58 | return;
59 | }
60 |
61 | if (fdRefCount == 0) {
62 | doClose();
63 | } else {
64 | // Another thread has the FD. We'll close it when they release the reference.
65 | closePending = true;
66 | }
67 | }
68 |
69 | private void doClose() {
70 | try {
71 | LibC.close(fd);
72 | fd = -1;
73 | } catch (LastErrorException e) {
74 | throw new RuntimeException(e);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxxer/nuprocess/linux/EpollEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Brett Wooldridge
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zaxxer.nuprocess.linux;
18 |
19 | import java.util.Arrays;
20 | import java.util.List;
21 |
22 | import com.sun.jna.*;
23 |
24 | class EpollEvent
25 | {
26 | private static final int eventsOffset;
27 | private static final int fdOffset;
28 | private static final int size;
29 |
30 | static {
31 | EpollEventPrototype event = new EpollEventPrototype();
32 | eventsOffset = event.getFieldOffset("events");
33 | fdOffset = event.getFieldOffset("data");
34 | size = event.size();
35 | }
36 |
37 | private final Pointer pointer;
38 |
39 | EpollEvent() {
40 | pointer = new Memory(size);
41 | }
42 |
43 | int getEvents() {
44 | return pointer.getInt(eventsOffset);
45 | }
46 |
47 | void setEvents(final int mask) {
48 | pointer.setInt(eventsOffset, mask);
49 | }
50 |
51 | void setFileDescriptor(final int fd) {
52 | pointer.setInt(fdOffset, fd);
53 | }
54 |
55 | int getFileDescriptor() {
56 | return pointer.getInt(fdOffset);
57 | }
58 |
59 | Pointer getPointer() {
60 | return pointer;
61 | }
62 |
63 | int size() {
64 | return size;
65 | }
66 |
67 | public static class EpollEventPrototype extends Structure
68 | {
69 | /*
70 | struct epoll_event
71 | {
72 | uint32_t events; // Epoll events
73 | epoll_data_t data; // User data variable
74 | } __EPOLL_PACKED;
75 |
76 | sizeof(struct epoll_event) is 12 on x86 and x86_64, but is 16 on other 64-bit platforms
77 | */
78 |
79 | public int events;
80 | public EpollData data;
81 |
82 | EpollEventPrototype() {
83 | super(detectAlignment());
84 |
85 | data = new EpollData();
86 | data.setType("fd");
87 | }
88 |
89 | int getFieldOffset(String field)
90 | {
91 | return fieldOffset(field);
92 | }
93 |
94 | @SuppressWarnings("rawtypes")
95 | @Override
96 | protected List getFieldOrder() {
97 | return Arrays.asList("events", "data");
98 | }
99 |
100 | /**
101 | * Uses the OS architecture to reproduce the following logic from the epoll header:
102 | *