[squeak-dev] OSProcess streaming and early termination

David T. Lewis lewis at mail.msen.com
Sat Dec 8 02:10:13 UTC 2012


Hi Bert,

On Fri, Dec 07, 2012 at 04:54:31PM +0100, Bert Freudenberg wrote:
> Hi David, folks,
> 
> I need to read output from an external command which is potentially too large to fit in memory. So I want to read from the pipe, and possibly have to terminate early.
> 
> Here is what I have so far - "od" is an example only of course, but I need to be able to use arg arrays and a working dir and it illustrates the problem:
> 
> | process1 process2 |
> process1 := PipeableOSProcess new: '/usr/bin/od'
> 	arguments: {'-v'. '-t'. 'x1'. (Smalltalk imageName copyAfterLast: $/) asVmPathName}
> 	environment: nil descriptors: nil
> 	workingDir: Smalltalk imagePath asVmPathName
> 	errorPipelineStream: nil.
> process2 := ExpressionEvaluator block: [:stdin | stdin next: 1000].
> process2 pipeToInput: process1 pipeFromOutput.
> process1 value.
> process2 value.
> process2 succeeded
> 	ifFalse: [process2 errorUpToEnd]
> 	ifTrue: [process2 output]
> 
> This does get me the first 1000 bytes od the "od" output. 
> 
> However, this seems like more hoops than necessary to jump through - I have to set up the processes first, then pipe them, then execute them, only then can I access the output. Finding the right sequence required reading a lot of code and guessing. Is there a more convenient way? I tried "|" but it only wants a string argument, not an ExpressionEvaluator object.
> 
> Secondly, even though the external process should be gone after reading 1000 chars, it appears that it is still running. Do I manually have to kill it? I tried #closePipes but that does an upToEnd which in this case is counterproductive because it churns through a Gigabyte of data.
> 

I think this will work:

  cmd := 'od -v -t x1 ', (Smalltalk imageName copyAfterLast: $/) asVmPathName.
  pipeline := ProxyPipeline command: cmd.
  data := pipeline next: 1000.
  pipeline closePipes.
  data inspect

Your example exposed a problem in BufferedAsyncFileReadStream which was
reading all available data from a stream regardless of whether anybody was
consuming the data, so eventually the system gets a low memory warning. I added
a check to prevent this, so please do another update your OSProcess from
SqueakMap.

ProxyPipeline should have a better name. It made sense when I wrote it as a
support class for CommandShell, but it turns out that nobody uses CommandShell
and lots of people want to be able to evaluate a command line with some shell
syntax support. So maybe it should be a CommandPipeline or a ShellCommandLine
or something like that.


> Thirdly, how do I find out about errors in the external process? E.g. if I misspell the command there is nothing in its stderr, it all seems to fail silently.
> 

A PipeJunction has a pipeToInput, a pipeFromOutput, and an errorPipelineStream.
A command pipeline works by connecting the pipeFromOutput (aka stdout) from one
process proxy to the pipeToInput (aka stdin) of the next. The error output
(aka stderr) of a proxy is accumulated in the shared errorPipelineStream.
A ProxyPipeline behaves like a PipeJunction, with the stderr of all proxies
accumulated in a shared errorPipelineStream.

The stderr output of a command pipeline is in the errorPipelineStream, and is
accessed with #errorUpToEnd or #errorUpToEndOfFile.

Exit status of the external processes can be tested from the process proxies,
and testing methods such as ProxyPipeline>>succeeded give overall status.

Thus:

  cmd := 'foo -v -t x1 ', (Smalltalk imageName copyAfterLast: $/) asVmPathName.
  pipeline := ProxyPipeline command: cmd.
  pipeline succeeded ==> false
  pipeline first exitStatus ==> #fail
  pipeline errorUpToEndOfFile 'sqsh: foo: command not found
'

Dave

> Or maybe I'm going about this in a completely wrong way? I could not find an example anywhere in OSProcess that would pipe command output into Smalltalk code.
> 
> Help appreciated :)
> 
> - Bert -
> 
>


More information about the Squeak-dev mailing list