Curl Through Nstask Not Terminating If a Pipe Is Present

cURL through NSTask not terminating if a pipe is present

Expanding a little on @Hod's answer: The standard output of the launched
process is redirected to a pipe, but your program never reads from the
other pipe end. A pipe has a limited buffer, see for example
How big is the pipe buffer?
which explains that the pipe buffer size on macOS is (at most) 64KB.

If the pipe buffer is full then the launched process cannot write on it
anymore. If the process uses blocking I/O then a write() to the pipe will block until until at least one byte can be written. That does
never happen in your case, so the process hangs and does not terminate.

The problem can occur only if the amount written to standard output
exceeds the pipe buffer size, which explains why it happens only with certain URLs and not with others.

As a solution, you can read from the pipe, e.g. with

let data = pipe.fileHandleForReading.readDataToEndOfFile()

before waiting for the process to terminate. Another option is to
use asynchronous reading, e.g. with the code from Real time NSTask output to NSTextView with Swift:

pipe.fileHandleForReading.readabilityHandler = { fh in
let data = fh.availableData
// process data ...
}

That would also allow to read both standard output and standard error
from a process via pipes without blocking.

Real time NSTask output to NSTextView with Swift

(See Patrick F.'s answer for an update to Swift 3/4.)

You can read asynchronously from a pipe, using notifications.
Here is a simple example demonstrating how it works, hopefully that
helps you to get started:

let task = NSTask()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "echo 1 ; sleep 1 ; echo 2 ; sleep 1 ; echo 3 ; sleep 1 ; echo 4"]

let pipe = NSPipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.waitForDataInBackgroundAndNotify()

var obs1 : NSObjectProtocol!
obs1 = NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification,
object: outHandle, queue: nil) { notification -> Void in
let data = outHandle.availableData
if data.length > 0 {
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) {
print("got output: \(str)")
}
outHandle.waitForDataInBackgroundAndNotify()
} else {
print("EOF on stdout from process")
NSNotificationCenter.defaultCenter().removeObserver(obs1)
}
}

var obs2 : NSObjectProtocol!
obs2 = NSNotificationCenter.defaultCenter().addObserverForName(NSTaskDidTerminateNotification,
object: task, queue: nil) { notification -> Void in
print("terminated")
NSNotificationCenter.defaultCenter().removeObserver(obs2)
}

task.launch()

Instead of print("got output: \(str)") you can append the received
string to your text view.

The above code assumes that a runloop is active (which is the case
in a default Cocoa application).

NSTask hangs on readDataToEndOfFile

It's because you have never actually launched your task. You call

[task launchPath];

That just returns the task's path as a string, it doesn't actually launch the task. You want

[task launch];

Clang NSTask with streams

It is not clear to me what your problem is or what you've tried. However, if you are going to read the output from a pipe on your main thread using notifications and wish to also write to a pipe one option is to write to the pipe in another thread. The code below, based on your code, does this using GCD. For simplicity in this example the binary is deposited in /tmp:

// send a simple program to clang using a GCD task
- (void)provideStdin:(NSFileHandle *)stdinHandle
{
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
[stdinHandle writeData:[@"int main(int argc, char **argv)\n" dataUsingEncoding:NSUTF8StringEncoding]];
[stdinHandle writeData:[@"{\n" dataUsingEncoding:NSUTF8StringEncoding]];
[stdinHandle writeData:[@" write(1, \"hello\\n\", 6);\n" dataUsingEncoding:NSUTF8StringEncoding]];
[stdinHandle writeData:[@"}\n" dataUsingEncoding:NSUTF8StringEncoding]];
[stdinHandle closeFile]; // sent the code, close the file (pipe in this case)
});
}

// read the output from clang and dump to console
- (void) getData:(NSNotification *)notifcation
{
NSData *dataRead = [[notifcation userInfo] objectForKey:NSFileHandleNotificationDataItem];
NSString *textRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding];
NSLog(@"read %3ld: %@", (long)[textRead length], textRead);
}

// invoke clang using an NSTask, reading output via notifications
// and providing input via an async GCD task
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTask *task = [NSTask new];

NSPipe *outputPipe = [NSPipe new];
[task setStandardOutput:outputPipe];
[task setStandardError:outputPipe];
NSFileHandle *outputHandle = [outputPipe fileHandleForReading];

NSPipe* inPipe = [NSPipe pipe];
[task setStandardInput:inPipe];

[task setLaunchPath:@"/usr/bin/clang"];

[task setArguments:[NSArray arrayWithObjects:@"-o", @"/tmp/clang.out", @"-xc",@"-",nil]];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(getData:)
name:NSFileHandleReadCompletionNotification
object:outputHandle];

[outputHandle readInBackgroundAndNotify];

[task launch];
[self provideStdin:[inPipe fileHandleForWriting]];
}

Execute a terminal command from a Cocoa app

You can use NSTask. Here's an example that would run '/usr/bin/grep foo bar.txt'.

int pid = [[NSProcessInfo processInfo] processIdentifier];
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;

NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/grep";
task.arguments = @[@"foo", @"bar.txt"];
task.standardOutput = pipe;

[task launch];

NSData *data = [file readDataToEndOfFile];
[file closeFile];

NSString *grepOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (@"grep returned:\n%@", grepOutput);

NSPipe and NSFileHandle are used to redirect the standard output of the task.

For more detailed information on interacting with the operating system from within your Objective-C application, you can see this document on Apple's Development Center: Interacting with the Operating System.

Edit: Included fix for NSLog problem

If you are using NSTask to run a command-line utility via bash, then you need to include this magic line to keep NSLog working:

//The magic line that keeps your log where it belongs
task.standardOutput = pipe;

An explanation is here: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

Problem with using IN in sql

Are there any NULLs in mz_t_NaznExec.p_Services? "NOT IN" will always fail if there is even one NULL in the resultset.



Related Topics



Leave a reply



Submit