Skip to content

Commit d5236c8

Browse files
authored
Merge pull request #158 from bturner/bturner-fix-start-exit-race-for-fast-processes
Prevent onStart/onExit race for fast-exiting processes.
2 parents 805c861 + 7a0f88c commit d5236c8

File tree

8 files changed

+67
-9
lines changed

8 files changed

+67
-9
lines changed

benchmark/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<dependency>
2626
<groupId>com.zaxxer</groupId>
2727
<artifactId>nuprocess</artifactId>
28-
<version>2.0.8-SNAPSHOT</version>
28+
<version>2.1.0-SNAPSHOT</version>
2929
</dependency>
3030

3131
<dependency>

mvnvm.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
mvn_version=3.8.5
1+
mvn_version=3.9.6

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>com.zaxxer</groupId>
66
<artifactId>nuprocess</artifactId>
7-
<version>2.0.8-SNAPSHOT</version>
7+
<version>2.1.0-SNAPSHOT</version>
88
<packaging>bundle</packaging>
99

1010
<name>NuProcess</name>

src/main/java/com/zaxxer/nuprocess/internal/IEventProcessor.java

+12
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ public interface IEventProcessor<T extends BasePosixProcess> extends Runnable
5353
*/
5454
void registerProcess(T process);
5555

56+
/**
57+
* Queues read handling for the process's STDOUT and STDERR streams.
58+
* <p>
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+
5668
/**
5769
* Express that the client desires to write data into the STDIN stream as
5870
* soon as possible.

src/main/java/com/zaxxer/nuprocess/linux/LinuxProcess.java

+6
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,15 @@ public NuProcess start(List<String> command, String[] environment, Path cwd) {
7575

7676
afterStart();
7777

78+
// Registration must happen prior to calling NuProcessHandler.onStart to allow handlers
79+
// to call wantWrite (which calls myProcessor.queueWrite)
7880
registerProcess();
7981

8082
callStart();
83+
84+
// Queueing read handling for stdout and stderr happens after start has been called
85+
// to ensure fast-exiting processes don't call NuProcessHandler.onExit before onStart
86+
myProcessor.queueRead(this);
8187
}
8288
catch (Exception e) {
8389
// TODO remove from event processor pid map?

src/main/java/com/zaxxer/nuprocess/linux/ProcessEpoll.java

+27-3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class ProcessEpoll extends BaseEventProcessor<LinuxProcess>
5454
this.process = process;
5555

5656
registerProcess(process);
57+
queueRead(process);
5758
checkAndSetRunning();
5859
}
5960

@@ -95,6 +96,32 @@ public void registerProcess(LinuxProcess process)
9596
fildesToProcessMap.put(stdinFd, process);
9697
fildesToProcessMap.put(stdoutFd, process);
9798
fildesToProcessMap.put(stderrFd, process);
99+
}
100+
finally {
101+
if (stdinFd != Integer.MIN_VALUE) {
102+
process.getStdin().release();
103+
}
104+
if (stdoutFd != Integer.MIN_VALUE) {
105+
process.getStdout().release();
106+
}
107+
if (stderrFd != Integer.MIN_VALUE) {
108+
process.getStderr().release();
109+
}
110+
}
111+
}
112+
113+
@Override
114+
public void queueRead(LinuxProcess process)
115+
{
116+
if (shutdown) {
117+
return;
118+
}
119+
120+
int stdoutFd = Integer.MIN_VALUE;
121+
int stderrFd = Integer.MIN_VALUE;
122+
try {
123+
stdoutFd = process.getStdout().acquire();
124+
stderrFd = process.getStderr().acquire();
98125

99126
EpollEvent event = process.getEpollEvent();
100127
event.setEvents(LibEpoll.EPOLLIN);
@@ -114,9 +141,6 @@ public void registerProcess(LinuxProcess process)
114141
}
115142
}
116143
finally {
117-
if (stdinFd != Integer.MIN_VALUE) {
118-
process.getStdin().release();
119-
}
120144
if (stdoutFd != Integer.MIN_VALUE) {
121145
process.getStdout().release();
122146
}

src/main/java/com/zaxxer/nuprocess/osx/OsxProcess.java

+4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ public NuProcess start(List<String> command, String[] environment, Path cwd) {
6565

6666
afterStart();
6767

68+
// On macOS, registration can be immediately followed by queueing read handling for stdout
69+
// and stderr without risking a racy exit because processes are launched suspended and are
70+
// only resumed after NuProcessHandler.onStart is called
6871
registerProcess();
72+
myProcessor.queueRead(this);
6973

7074
callStart();
7175

src/main/java/com/zaxxer/nuprocess/osx/ProcessKqueue.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ final class ProcessKqueue extends BaseEventProcessor<OsxProcess>
6868
this(-1);
6969

7070
registerProcess(process);
71+
queueRead(process);
7172
checkAndSetRunning();
7273
}
7374

@@ -101,9 +102,18 @@ public void registerProcess(OsxProcess process)
101102
}
102103

103104
int pid = process.getPid();
104-
Pointer pidPointer = Pointer.createConstant(pid);
105-
106105
pidToProcessMap.put(pid, process);
106+
}
107+
108+
@Override
109+
public void queueRead(OsxProcess process)
110+
{
111+
if (shutdown) {
112+
return;
113+
}
114+
115+
int pid = process.getPid();
116+
Pointer pidPointer = Pointer.createConstant(pid);
107117

108118
Integer stdinFd = null;
109119
Integer stdoutFd = null;
@@ -112,6 +122,7 @@ public void registerProcess(OsxProcess process)
112122
stdinFd = process.getStdin().acquire();
113123
stdoutFd = process.getStdout().acquire();
114124
stderrFd = process.getStderr().acquire();
125+
115126
// We don't use the processEvents array here, since this method is not
116127
// called on the event processor thread.
117128
Kevent[] events = (Kevent[]) new Kevent().toArray(4);
@@ -125,7 +136,8 @@ public void registerProcess(OsxProcess process)
125136
events[3].EV_SET(stdinFd, Kevent.EVFILT_WRITE, Kevent.EV_ADD | Kevent.EV_DISABLE | Kevent.EV_RECEIPT, 0, 0L, pidPointer);
126137

127138
registerEvents(events, 4);
128-
} finally {
139+
}
140+
finally {
129141
if (stdinFd != null) {
130142
process.getStdin().release();
131143
}

0 commit comments

Comments
 (0)