Skip to content

Commit 21cedc9

Browse files
[MSHARED-617] StreamFeeder should flush OutputStream
[MSHARED-619] StreamFeeder silently ignores exceptions. [MSHARED-620] CommandLineCallable should defer starting threads until called. [MSHARED-621] CommandLineCallable should calculate process timeouts using 'System.nanoTime' instead of 'System.currentTimeMillis'. [MSHARED-622] CommandLineCallable silently ignores exceptions thrown from the stdin processor (StreemFeeder). o CommandLineCallable silently ignores exception thrown from the stdout/stderr processors (StreamPumper). o Bumped to next minor version due to addition of new public method in 'StreamFeeder'.
1 parent 0cd0352 commit 21cedc9

File tree

7 files changed

+292
-104
lines changed

7 files changed

+292
-104
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ limitations under the License.
2626
</parent>
2727

2828
<artifactId>plexus-utils</artifactId>
29-
<version>3.0.25-SNAPSHOT</version>
29+
<version>3.1.0-SNAPSHOT</version>
3030

3131
<name>Plexus Common Utilities</name>
3232
<description>A collection of various utility classes to ease working with strings, files, command lines, XML and

src/main/java/org/codehaus/plexus/util/cli/CommandLineUtils.java

Lines changed: 176 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818

1919
import java.io.IOException;
2020
import java.io.InputStream;
21-
import java.lang.reflect.Method;
2221
import java.util.Locale;
2322
import java.util.Map;
2423
import java.util.Properties;
2524
import java.util.StringTokenizer;
2625
import java.util.Vector;
26+
2727
import org.codehaus.plexus.util.Os;
2828
import org.codehaus.plexus.util.StringUtils;
2929

@@ -33,9 +33,16 @@
3333
*/
3434
public abstract class CommandLineUtils
3535
{
36+
37+
/**
38+
* A {@code StreamConsumer} providing consumed lines as a {@code String}.
39+
*
40+
* @see #getOutput()
41+
*/
3642
public static class StringStreamConsumer
3743
implements StreamConsumer
3844
{
45+
3946
private StringBuffer string = new StringBuffer();
4047

4148
private String ls = System.getProperty( "line.separator" );
@@ -49,24 +56,18 @@ public String getOutput()
4956
{
5057
return string.toString();
5158
}
52-
}
53-
54-
private static class ProcessHook extends Thread {
55-
private final Process process;
56-
57-
private ProcessHook( Process process )
58-
{
59-
super("CommandlineUtils process shutdown hook");
60-
this.process = process;
61-
this.setContextClassLoader( null );
62-
}
6359

64-
public void run()
65-
{
66-
process.destroy();
67-
}
6860
}
6961

62+
/**
63+
* Number of milliseconds per second.
64+
*/
65+
private static final long MILLIS_PER_SECOND = 1000L;
66+
67+
/**
68+
* Number of nanoseconds per second.
69+
*/
70+
private static final long NANOS_PER_SECOND = 1000000000L;
7071

7172
public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr )
7273
throws CommandLineException
@@ -120,10 +121,11 @@ public static int executeCommandLine( Commandline cl, InputStream systemIn, Stre
120121
* @throws CommandLineException or CommandLineTimeOutException if time out occurs
121122
* @noinspection ThrowableResultOfMethodCallIgnored
122123
*/
123-
public static CommandLineCallable executeCommandLineAsCallable( final Commandline cl, final InputStream systemIn,
124-
final StreamConsumer systemOut,
125-
final StreamConsumer systemErr,
126-
final int timeoutInSeconds )
124+
public static CommandLineCallable executeCommandLineAsCallable( final Commandline cl,
125+
final InputStream systemIn,
126+
final StreamConsumer systemOut,
127+
final StreamConsumer systemErr,
128+
final int timeoutInSeconds )
127129
throws CommandLineException
128130
{
129131
if ( cl == null )
@@ -133,108 +135,215 @@ public static CommandLineCallable executeCommandLineAsCallable( final Commandlin
133135

134136
final Process p = cl.execute();
135137

136-
final StreamFeeder inputFeeder = systemIn != null ?
137-
new StreamFeeder( systemIn, p.getOutputStream() ) : null;
138-
139-
final StreamPumper outputPumper = new StreamPumper( p.getInputStream(), systemOut );
140-
141-
final StreamPumper errorPumper = new StreamPumper( p.getErrorStream(), systemErr );
142-
143-
if ( inputFeeder != null )
138+
final Thread processHook = new Thread()
144139
{
145-
inputFeeder.start();
146-
}
147140

148-
outputPumper.start();
141+
{
142+
this.setName( "CommandLineUtils process shutdown hook" );
143+
this.setContextClassLoader( null );
144+
}
149145

150-
errorPumper.start();
146+
@Override
147+
public void run()
148+
{
149+
p.destroy();
150+
}
151151

152-
final ProcessHook processHook = new ProcessHook( p );
152+
};
153153

154154
ShutdownHookUtils.addShutDownHook( processHook );
155155

156156
return new CommandLineCallable()
157157
{
158+
158159
public Integer call()
159160
throws CommandLineException
160161
{
162+
StreamFeeder inputFeeder = null;
163+
StreamPumper outputPumper = null;
164+
StreamPumper errorPumper = null;
165+
boolean success = false;
161166
try
162167
{
168+
if ( systemIn != null )
169+
{
170+
inputFeeder = new StreamFeeder( systemIn, p.getOutputStream() );
171+
inputFeeder.start();
172+
}
173+
174+
outputPumper = new StreamPumper( p.getInputStream(), systemOut );
175+
outputPumper.start();
176+
177+
errorPumper = new StreamPumper( p.getErrorStream(), systemErr );
178+
errorPumper.start();
179+
163180
int returnValue;
164181
if ( timeoutInSeconds <= 0 )
165182
{
166183
returnValue = p.waitFor();
167184
}
168185
else
169186
{
170-
long now = System.currentTimeMillis();
171-
long timeoutInMillis = 1000L * timeoutInSeconds;
172-
long finish = now + timeoutInMillis;
173-
while ( isAlive( p ) && ( System.currentTimeMillis() < finish ) )
187+
final long now = System.nanoTime();
188+
final long timeout = now + NANOS_PER_SECOND * timeoutInSeconds;
189+
190+
while ( isAlive( p ) && ( System.nanoTime() < timeout ) )
174191
{
175-
Thread.sleep( 10 );
192+
// The timeout is specified in seconds. Therefore we must not sleep longer than one second
193+
// but we should sleep as long as possible to reduce the number of iterations performed.
194+
Thread.sleep( MILLIS_PER_SECOND - 1L );
176195
}
196+
177197
if ( isAlive( p ) )
178198
{
179-
throw new InterruptedException( "Process timeout out after " + timeoutInSeconds + " seconds" );
199+
throw new InterruptedException( String.format( "Process timed out after %d seconds.",
200+
timeoutInSeconds ) );
180201
}
202+
181203
returnValue = p.exitValue();
182204
}
183205

184-
waitForAllPumpers( inputFeeder, outputPumper, errorPumper );
185-
186-
if ( outputPumper.getException() != null )
206+
// TODO Find out if waitUntilDone needs to be called using a try-finally construct. The method may throw an
207+
// InterruptedException so that calls to waitUntilDone may be skipped.
208+
// try
209+
// {
210+
// if ( inputFeeder != null )
211+
// {
212+
// inputFeeder.waitUntilDone();
213+
// }
214+
// }
215+
// finally
216+
// {
217+
// try
218+
// {
219+
// outputPumper.waitUntilDone();
220+
// }
221+
// finally
222+
// {
223+
// errorPumper.waitUntilDone();
224+
// }
225+
// }
226+
if ( inputFeeder != null )
187227
{
188-
throw new CommandLineException( "Error inside systemOut parser", outputPumper.getException() );
228+
inputFeeder.waitUntilDone();
189229
}
190230

191-
if ( errorPumper.getException() != null )
231+
outputPumper.waitUntilDone();
232+
errorPumper.waitUntilDone();
233+
234+
if ( inputFeeder != null )
192235
{
193-
throw new CommandLineException( "Error inside systemErr parser", errorPumper.getException() );
236+
inputFeeder.close();
237+
handleException( inputFeeder, "stdin" );
194238
}
195239

240+
outputPumper.close();
241+
handleException( outputPumper, "stdout" );
242+
243+
errorPumper.close();
244+
handleException( errorPumper, "stderr" );
245+
246+
success = true;
196247
return returnValue;
197248
}
198249
catch ( InterruptedException ex )
199250
{
200-
if ( inputFeeder != null )
201-
{
202-
inputFeeder.disable();
203-
}
204-
outputPumper.disable();
205-
errorPumper.disable();
206-
throw new CommandLineTimeOutException( "Error while executing external command, process killed.", ex );
251+
throw new CommandLineTimeOutException( "Error while executing external command, process killed.",
252+
ex );
253+
207254
}
208255
finally
209256
{
210-
ShutdownHookUtils.removeShutdownHook( processHook );
211-
212-
processHook.run();
213-
214257
if ( inputFeeder != null )
215258
{
216-
inputFeeder.close();
259+
inputFeeder.disable();
260+
}
261+
if ( outputPumper != null )
262+
{
263+
outputPumper.disable();
264+
}
265+
if ( errorPumper != null )
266+
{
267+
errorPumper.disable();
217268
}
218269

219-
outputPumper.close();
220-
221-
errorPumper.close();
270+
try
271+
{
272+
ShutdownHookUtils.removeShutdownHook( processHook );
273+
processHook.run();
274+
}
275+
finally
276+
{
277+
try
278+
{
279+
if ( inputFeeder != null )
280+
{
281+
inputFeeder.close();
282+
283+
if ( success )
284+
{
285+
success = false;
286+
handleException( inputFeeder, "stdin" );
287+
success = true; // Only reached when no exception has been thrown.
288+
}
289+
}
290+
}
291+
finally
292+
{
293+
try
294+
{
295+
if ( outputPumper != null )
296+
{
297+
outputPumper.close();
298+
299+
if ( success )
300+
{
301+
success = false;
302+
handleException( outputPumper, "stdout" );
303+
success = true; // Only reached when no exception has been thrown.
304+
}
305+
}
306+
}
307+
finally
308+
{
309+
if ( errorPumper != null )
310+
{
311+
errorPumper.close();
312+
313+
if ( success )
314+
{
315+
handleException( errorPumper, "stderr" );
316+
}
317+
}
318+
}
319+
}
320+
}
222321
}
223322
}
323+
224324
};
225325
}
226326

227-
private static void waitForAllPumpers( StreamFeeder inputFeeder, StreamPumper outputPumper,
228-
StreamPumper errorPumper )
229-
throws InterruptedException
327+
private static void handleException( final StreamPumper streamPumper, final String streamName )
328+
throws CommandLineException
230329
{
231-
if ( inputFeeder != null )
330+
if ( streamPumper.getException() != null )
232331
{
233-
inputFeeder.waitUntilDone();
332+
throw new CommandLineException( String.format( "Failure processing %s.", streamName ),
333+
streamPumper.getException() );
334+
234335
}
336+
}
235337

236-
outputPumper.waitUntilDone();
237-
errorPumper.waitUntilDone();
338+
private static void handleException( final StreamFeeder streamFeeder, final String streamName )
339+
throws CommandLineException
340+
{
341+
if ( streamFeeder.getException() != null )
342+
{
343+
throw new CommandLineException( String.format( "Failure processing %s.", streamName ),
344+
streamFeeder.getException() );
345+
346+
}
238347
}
239348

240349
/**

src/main/java/org/codehaus/plexus/util/cli/DefaultConsumer.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,24 @@
1616
* limitations under the License.
1717
*/
1818

19+
import java.io.IOException;
20+
1921
/**
2022
* @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
2123
* @version $Id$
2224
*/
2325
public class DefaultConsumer
2426
implements StreamConsumer
2527
{
26-
public void consumeLine( String line )
28+
29+
public void consumeLine( String line ) throws IOException
2730
{
2831
System.out.println( line );
32+
33+
if ( System.out.checkError() )
34+
{
35+
throw new IOException( String.format( "Failure printing line '%s' to stdout.", line ) );
36+
}
2937
}
38+
3039
}

src/main/java/org/codehaus/plexus/util/cli/StreamConsumer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5454
********************************************************************************/
5555

56+
import java.io.IOException;
57+
5658
/**
5759
* Works in concert with the StreamPumper class to
5860
* allow implementations to gain access to the lines being
@@ -69,6 +71,8 @@ public interface StreamConsumer
6971
{
7072
/**
7173
* Called when the StreamPumper pumps a line from the Stream.
74+
* @param line The line to be consumed.
75+
* @throws IOException if consuming {@code line} fails.
7276
*/
73-
public void consumeLine( String line );
77+
public void consumeLine( String line ) throws IOException;
7478
}

0 commit comments

Comments
 (0)