From 1bd0cc4152d846f418908981bc7786cb297e288c Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Fri, 14 Dec 2012 13:58:58 -0800 Subject: [PATCH] ChildProcess can handle commands that return large amounts of data. When a command returned a large amount of data, it was blocking on the stderr callback when `[fileHandle availableData]` was called. From what I can tell, this is because stderr was being called with a zero-length string. This was fixed when `[fileHandle availableData]` was moved to run inside the NSTask thread (instead of on the main thread). It now returns a zero-length string rather than blocking forever. An unresolved question is why stderr is being called with zero-length strings. --- native/v8_extensions/native.mm | 18 ++++++++++-------- spec/stdlib/child-process-spec.coffee | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/native/v8_extensions/native.mm b/native/v8_extensions/native.mm index b63f9a2f9..5fb4cb28f 100644 --- a/native/v8_extensions/native.mm +++ b/native/v8_extensions/native.mm @@ -406,15 +406,12 @@ bool Native::Execute(const CefString& name, [task setStandardError:stderr]; CefRefPtr context = CefV8Context::GetCurrentContext(); - void (^outputHandle)(NSFileHandle *fileHandle, CefRefPtr function) = nil; + void (^outputHandle)(NSString *contents, CefRefPtr function) = nil; void (^taskTerminatedHandle)() = nil; - outputHandle = ^(NSFileHandle *fileHandle, CefRefPtr function) { + outputHandle = ^(NSString *contents, CefRefPtr function) { context->Enter(); - NSData *data = [fileHandle availableData]; - NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - CefV8ValueList args; args.push_back(CefV8Value::CreateString(std::string([contents UTF8String], [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding]))); CefRefPtr retval = function->ExecuteFunction(function, args); @@ -423,7 +420,6 @@ bool Native::Execute(const CefString& name, throwException(context->GetGlobal(), function->GetException(), @"Error thrown in OutputHandle"); } - [contents release]; context->Exit(); }; @@ -457,18 +453,24 @@ bool Native::Execute(const CefString& name, CefRefPtr stdoutFunction = options->GetValue("stdout"); if (stdoutFunction->IsFunction()) { stdout.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) { + NSData *data = [fileHandle availableData]; + NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; dispatch_sync(dispatch_get_main_queue(), ^() { - outputHandle(fileHandle, stdoutFunction); + outputHandle(contents, stdoutFunction); }); + [contents release]; }; } CefRefPtr stderrFunction = options->GetValue("stderr"); if (stderrFunction->IsFunction()) { stderr.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) { + NSData *data = [fileHandle availableData]; + NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; dispatch_sync(dispatch_get_main_queue(), ^() { - outputHandle(fileHandle, stderrFunction); + outputHandle(contents, stderrFunction); }); + [contents release]; }; } diff --git a/spec/stdlib/child-process-spec.coffee b/spec/stdlib/child-process-spec.coffee index 762ea958c..985d814b4 100644 --- a/spec/stdlib/child-process-spec.coffee +++ b/spec/stdlib/child-process-spec.coffee @@ -104,3 +104,26 @@ describe 'Child Processes', -> ChildProcess.exec(cmd, options).fail (error) -> expect(error.exitStatus).toBe 2 expect(errorOutput).toBe "bad\n" + + describe "when a command returns a large amount of data (over 10k)", -> + originalTimeout = null + beforeEach -> + originalTimeout = jasmine.getEnv().defaultTimeoutInterval + jasmine.getEnv().defaultTimeoutInterval = 1000 + + afterEach -> + jasmine.getEnv().defaultTimeoutInterval = originalTimeout + + it "does not block indefinitally on stdout or stderr callbacks (regression)", -> + output = [] + + waitsForPromise -> + cmd = "for i in {1..20000}; do echo $RANDOM; done" + options = + stdout: (data) -> output.push(data) + stderr: (data) -> console.log data.length + + ChildProcess.exec(cmd, options) + + runs -> + expect(output.length).toBeGreaterThan 1 \ No newline at end of file