Created
October 12, 2010 02:59
-
-
Save atr000/621601 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask | |
compile with: | |
gcc -Wall -O3 -x objective-c -fobjc-exceptions -framework Foundation -o asynctask asynctask.m | |
./asynctask | |
./asynctask > asynctask-output.txt 2>&1 | |
for ((i=0; i < 20; i++)); do ./asynctask; done | |
open -e asynctask-output.txt | |
asynctask.m is based on and extends FRCommand.h and FRCommand.m by Torsten Curdt (Apache License, Version 2.0). | |
Being a GUI-less application (i.e. a Foundation-based command line tool), asynctask.m runs an NSRunLoop manually | |
to enable the use of asynchronous "waitForDataInBackgroundAndNotify" notifications. In addition, asynctask.m | |
uses pthread_create(3) and pthread_detach(3) for writing more than 64 KB to the stdin of an NSTask. | |
Source code: | |
- http://github.com/tcurdt/feedbackreporter (by Torsten Curdt; Apache License, Version 2.0) | |
- http://github.com/tcurdt/feedbackreporter/tree/master/Sources/Main/ | |
- http://github.com/tcurdt/feedbackreporter/blob/443300c21f6b7c5b70298edd861b4b2017a97b00/Sources/Main/FRCommand.h | |
- http://github.com/tcurdt/feedbackreporter/blob/443300c21f6b7c5b70298edd861b4b2017a97b00/Sources/Main/FRCommand.m | |
# create testfile.txt | |
jot -b "line" 500 > testfile.txt | |
jot -b "line" 20000 > testfile.txt | |
jot -b "line" 1000000 > testfile.txt | |
du -h testfile.txt | |
*/ | |
/* | |
* Copyright 2008, Torsten Curdt | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
//#import <Cocoa/Cocoa.h> | |
#import <Foundation/Foundation.h> | |
#import <unistd.h> | |
#import <pthread.h> | |
#import <stdio.h> | |
// ADDED begin | |
static int empty_data_count; | |
static int task_exit_code; | |
struct arg_struct { | |
NSData *inData; | |
NSFileHandle *inHandle; | |
}; | |
void *threadFunction(void *arguments) | |
{ | |
[[NSAutoreleasePool alloc] init]; | |
struct arg_struct *taskArgs = arguments; | |
[taskArgs -> inHandle writeData: taskArgs -> inData]; | |
[taskArgs -> inHandle closeFile]; | |
//[taskArgs -> inHandle release]; | |
//[taskArgs -> inData release]; | |
return nil; | |
} | |
// ADDED end | |
@interface FRCommand : NSObject { | |
NSTask *task; | |
NSString *path; | |
NSArray *args; | |
NSData *stdinData; // ADDED | |
NSMutableString *output; | |
NSMutableString *error; | |
BOOL terminated; | |
NSFileHandle *outFile; | |
NSFileHandle *errFile; | |
NSFileHandle *stdinHandle; // ADDED | |
} | |
-(NSData *) availableDataOrError: (NSFileHandle *)file; | |
- (id) initWithPath:(NSString*)path; | |
- (void) setArgs:(NSArray*)args; | |
- (void) setInput:(NSData*)stdinData; // ADDED | |
- (void) setError:(NSMutableString*)error; | |
- (void) setOutput:(NSMutableString*)output; | |
- (int) execute; | |
@end | |
//#import "FRCommand.h" | |
@implementation FRCommand | |
- (id) initWithPath:(NSString*)pPath | |
{ | |
self = [super init]; | |
if (self != nil) | |
{ | |
task = [[NSTask alloc] init]; | |
args = [NSArray array]; | |
path = pPath; | |
stdinData = nil; // ADDED | |
error = nil; | |
output = nil; | |
terminated = NO; | |
} | |
return self; | |
} | |
// ADDED | |
// For "availableDataOrError:" see: | |
// - "NSTasks, NSPipes, and deadlocks when reading...", | |
// http://dev.notoptimal.net/2007/04/nstasks-nspipes-and-deadlocks-when.html | |
// - "NSTask stealth bug in readDataOfLength!! :(", | |
// http://www.cocoabuilder.com/archive/cocoa/173348-nstask-stealth-bug-in-readdataoflength.html#173647 | |
-(NSData *) availableDataOrError: (NSFileHandle *)file { | |
for (;;) | |
{ | |
@try { | |
return [file availableData]; | |
} @catch (NSException *e) { | |
if ([[e name] isEqualToString:NSFileHandleOperationException]) | |
{ | |
if ([[e reason] isEqualToString: @"*** -[NSConcreteFileHandle availableData]: Interrupted system call"]) | |
{ | |
continue; | |
} | |
return nil; | |
} | |
@throw; | |
} | |
} // for | |
} | |
- (void) setArgs:(NSArray*)pArgs | |
{ | |
args = pArgs; | |
} | |
- (void) setInput:(NSData*)pInput // ADDED | |
{ | |
stdinData = pInput; | |
} | |
- (void) setError:(NSMutableString*)pError | |
{ | |
error = pError; | |
} | |
- (void) setOutput:(NSMutableString*)pOutput | |
{ | |
output = pOutput; | |
} | |
-(void) appendDataFrom:(NSFileHandle*)fileHandle to:(NSMutableString*)string | |
{ | |
NSData *data = [self availableDataOrError: fileHandle]; | |
//NSData *data = [fileHandle availableData]; | |
if ([data length]) { | |
NSString *s = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding: NSUTF8StringEncoding]; | |
//NSString *s = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding: NSASCIIStringEncoding]; | |
[output appendString:s]; | |
//NSLog(@"| %@", s); | |
[s release]; | |
//[fileHandle waitForDataInBackgroundAndNotify]; | |
}else{ | |
empty_data_count += 1; | |
if (empty_data_count > 10) | |
{ | |
//[task interrupt]; // failed to abort infinite NSRunLoop | |
//[task terminate]; // same | |
[[NSNotificationCenter defaultCenter] removeObserver:self]; // only way to abort infinite NSRunLoop ??? | |
} | |
} | |
[fileHandle waitForDataInBackgroundAndNotify]; | |
} | |
-(void) outData: (NSNotification *) notification | |
{ | |
NSFileHandle *fileHandle = (NSFileHandle*) [notification object]; | |
[self appendDataFrom:fileHandle to:output]; | |
[fileHandle waitForDataInBackgroundAndNotify]; | |
} | |
-(void) errData: (NSNotification *) notification | |
{ | |
NSFileHandle *fileHandle = (NSFileHandle*) [notification object]; | |
[self appendDataFrom:fileHandle to:output]; | |
[fileHandle waitForDataInBackgroundAndNotify]; | |
} | |
- (void) terminated: (NSNotification *)notification | |
{ | |
NSLog(@"Task terminated"); | |
[[NSNotificationCenter defaultCenter] removeObserver:self]; | |
terminated = YES; | |
} | |
- (int) execute | |
{ | |
empty_data_count = 0; | |
[task setLaunchPath:path]; | |
[task setArguments:args]; | |
NSPipe *outPipe = [NSPipe pipe]; | |
NSPipe *errPipe = [NSPipe pipe]; | |
[task setStandardOutput:outPipe]; | |
[task setStandardError:errPipe]; | |
[task setCurrentDirectoryPath:@"."]; // ADDED | |
//NSFileHandle *outFile = [outPipe fileHandleForReading]; // TROUBLEMAKER | |
//NSFileHandle *errFile = [errPipe fileHandleForReading]; | |
outFile = [outPipe fileHandleForReading]; | |
errFile = [errPipe fileHandleForReading]; | |
// ADDED | |
// create inPipe after outPipe & errPipe | |
NSPipe *inPipe = [NSPipe pipe]; | |
stdinHandle = [inPipe fileHandleForWriting]; | |
[task setStandardInput:inPipe]; | |
struct arg_struct taskArguments; | |
taskArguments.inData = stdinData; | |
taskArguments.inHandle = stdinHandle; | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(terminated:) | |
name:NSTaskDidTerminateNotification | |
object:task]; | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(outData:) | |
name:NSFileHandleDataAvailableNotification | |
object:outFile]; | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(errData:) | |
name:NSFileHandleDataAvailableNotification | |
object:errFile]; | |
[outFile waitForDataInBackgroundAndNotify]; | |
[errFile waitForDataInBackgroundAndNotify]; | |
[task launch]; | |
// ADDED | |
pthread_t thread = nil; | |
if (pthread_create(&thread, nil, (void *(*)(void *))threadFunction, (void *)&taskArguments) != 0) | |
{ | |
perror("pthread_create failed"); | |
return 1; | |
} | |
if (pthread_detach(thread) != 0) | |
{ | |
perror("pthread_detach failed"); | |
return 1; | |
} | |
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; | |
while(!terminated) | |
{ | |
//if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:100000]]) | |
if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) | |
{ | |
break; | |
} | |
[pool release]; | |
pool = [[NSAutoreleasePool alloc] init]; | |
} | |
[pool release]; | |
[self appendDataFrom:outFile to:output]; | |
[self appendDataFrom:errFile to:error]; | |
int result = [task terminationStatus]; | |
task_exit_code = result; | |
return result; | |
} | |
-(void)dealloc | |
{ | |
[task release]; | |
[super dealloc]; | |
} | |
@end | |
int main(int argc, const char *argv[]) | |
{ | |
NSAutoreleasePool *mainpool = [[NSAutoreleasePool alloc] init]; | |
// Test 1: write data to stdin of NSTask for subsequent cat(1) or tail(1) | |
NSString *testfile = @"testfile.txt"; | |
NSData *data = [NSData dataWithContentsOfFile: testfile]; | |
FRCommand *newTask = [[[FRCommand alloc] initWithPath: @"/bin/cat"] autorelease]; | |
//[newTask setArgs: [NSArray arrayWithObjects: @"-n", nil ]]; | |
[newTask setArgs: [NSArray arrayWithObjects: @"-u", @"-n", nil ]]; | |
//FRCommand *newTask = [[[FRCommand alloc] initWithPath: @"/usr/bin/tail"] autorelease]; | |
//[newTask setArgs: [NSArray arrayWithObjects: @"-n", @"10", nil ]]; | |
[newTask setInput: data]; | |
// Test 2: do not write to stdin | |
/* | |
FRCommand *newTask = [[[FRCommand alloc] initWithPath: @"/bin/ls"] autorelease]; | |
[newTask setArgs: [NSArray arrayWithObjects: @"-l", nil ]]; | |
*/ | |
//---------------------- | |
NSMutableString *stdoutString = [NSMutableString string]; | |
NSMutableString *stderrString = [NSMutableString string]; | |
[newTask setOutput: stdoutString]; | |
[newTask setError: stderrString]; | |
[newTask execute]; | |
NSLog(@"stdoutString:\n%@", stdoutString); | |
NSLog(@"stderrString:\n%@", stderrString); | |
[mainpool release]; | |
printf("\nempty_data_count: %i\n", empty_data_count); | |
printf("\ntask_exit_code: %i\n\n", task_exit_code); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment