lilyball Posted June 11, 2014 Posted June 11, 2014 When a Workflow uses a Run Script phase, Alfred seems to wait for all spawned processes to exit before considering the workflow to be finished (which at the very least prevents running the same workflow a second time until the first invocation is considered done). The problem is that it's waiting for stuff it shouldn't. Specifically, daemonized processes (which is to say, processes that forked and abandoned the process group) are causing Alfred to continue waiting, even though the entire point of daemonizing is to detach from the controlling terminal (and in this case Alfred acts like the controlling terminal). The specific case I have here is launching MacVim. When launching Vim in GUI mode, it forks and abandons the process group, and the parent process dies. This is done explicitly so the terminal that launched it will return immediately to the command line instead of waiting on the process (it also allows the GUI Vim process to outlive the terminal). But this doesn't work in Alfred. Here's the spawned process log: 2014 Jun 10 17:12:32 71064 <430> 64b: /usr/local/Cellar/macvim/7.4-72/MacVim.app/Contents/MacOS/Vim -g -c setf test -c startinsert 2014 Jun 10 17:12:32 71101 <1> 64b: /usr/local/Cellar/macvim/7.4-72/MacVim.app/Contents/MacOS/Vim -f -g -c setf test -c startinsert That first process there, PID 71064, was spawned by Alfred (PID 430). It then forks and runs the second process, PID 71101, which as you can see has a PPID of 1, because it detached from the process group. 71064 then immediately exits (it's not visible in this log, but I verified it with `ps`). However, Alfred will continue the workflow to be running for as long as PID 71101 lives. If I turn on workflow debugging and add an `echo`, it doesn't print the output until I quit Vim. Similarly, if I try and invoke the workflow a second time, it doesn't actually run the script phase for the second invocation until I've quit the Vim process from the first one.
vitor Posted June 11, 2014 Posted June 11, 2014 I can confirm this. It was a wall while building some workflows, and had to work around it. Thank you kballard, for filing the bug report, I’ve been putting it off for quite a while.
Andrew Posted June 11, 2014 Posted June 11, 2014 It's unlikely that I'll be changing Alfred's behaviour as intrinsically, Alfred waits for a process to finish to gather the output from it. Sub daemon process behaviour is controlled by NSTask. I'm going to move this to noted, but for now, I'm not going to treat this as a bug (especially since there is a workaround).
lilyball Posted June 11, 2014 Author Posted June 11, 2014 Andrew, what's the workaround? Vitor hasn't responded, and I haven't come up with anything that I would reasonably expect to work given the observed behavior.
lilyball Posted June 11, 2014 Author Posted June 11, 2014 (edited) Andrew, I just built a quick test of NSTask behavior under these exact same circumstances, and `waitUntilExit` does indeed return as soon as the "outer" Vim process exits. I'm guessing you're actually waiting until the process stdout pipe has closed? I suppose that suggests the workaround: set stdout to /dev/null. But it also suggests that, once the task has terminated, Alfred should be able to just read any remaining information in the stdout/stderr pipes, and then immediately close them. Adopting this behavior will only affect workflows that spawn sub-processes and exit the parent process, and since doing that normally kills the subprocess as well, it means it really should only affect situations like MacVim where the process daemonizes itself. Sample code: import Foundation func runTask() { var task = NSTask() task.launchPath = "/Applications/MacVim.app/Contents/MacOS/Vim" task.arguments = ["-g"] println("Launching task...") task.launch() println("PID: \(task.processIdentifier)") println("Waiting for termination...") task.waitUntilExit() println("Exit status: \(task.terminationStatus)") CFRunLoopStop(CFRunLoopGetCurrent()) } CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes) { runTask() } CFRunLoopRun() Edited June 11, 2014 by kballard
Andrew Posted June 11, 2014 Posted June 11, 2014 Andrew, I just built a quick test of NSTask behavior under these exact same circumstances, and `waitUntilExit` does indeed return as soon as the "outer" Vim process exits. I'm guessing you're actually waiting until the process stdout pipe has closed? I suppose that suggests the workaround: set stdout to /dev/null. But it also suggests that, once the task has terminated, Alfred should be able to just read any remaining information in the stdout/stderr pipes, and then immediately close them. Adopting this behavior will only affect workflows that spawn sub-processes and exit the parent process, and since doing that normally kills the subprocess as well, it means it really should only affect situations like MacVim where the process daemonizes itself. Sample code: import Foundation func runTask() { var task = NSTask() task.launchPath = "/Applications/MacVim.app/Contents/MacOS/Vim" task.arguments = ["-g"] println("Launching task...") task.launch() println("PID: \(task.processIdentifier)") println("Waiting for termination...") task.waitUntilExit() println("Exit status: \(task.terminationStatus)") CFRunLoopStop(CFRunLoopGetCurrent()) } CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes) { runTask() } CFRunLoopRun() You are correct, Alfred doesn't use waitUntilExit, he waits until stdout pipe is closed. When I originally used waitUntilExit (Alfred v1), I was seeing hanging / performance issues which ultimately affected the perceptual speed of Alfred. I switched to stdout closing as a marker of being done and haven't seen a performance issue since. This may no longer be relevant in newer versions of OS X and whatnot, but as there isn't much of a call for this, I'm reluctant to make any change in this area. Cheers, Andrew
lilyball Posted June 11, 2014 Author Posted June 11, 2014 You are correct, Alfred doesn't use waitUntilExit, he waits until stdout pipe is closed. When I originally used waitUntilExit (Alfred v1), I was seeing hanging / performance issues which ultimately affected the perceptual speed of Alfred. I switched to stdout closing as a marker of being done and haven't seen a performance issue since. This may no longer be relevant in newer versions of OS X and whatnot, but as there isn't much of a call for this, I'm reluctant to make any change in this area. Cheers, Andrew You could try waiting until the pipe is closed, and also setting a terminationHandler on the NSTask. If the termination handler fires while the pipe is still open, you could then just drain any available data from the pipe and close it. That should leave the current performance intact (because if, as you suggest, the pipe closes before the task finishes terminating, then it should close before terminationHandler fires), but allow for handling cases like what I've described.
vitor Posted June 11, 2014 Posted June 11, 2014 I suppose that suggests the workaround: set stdout to /dev/null. Precisely. End your command with something like &>/dev/null, and you’re good to go. It does feel like a bit of a hack, though.
Andrew Posted June 11, 2014 Posted June 11, 2014 You could try waiting until the pipe is closed, and also setting a terminationHandler on the NSTask. If the termination handler fires while the pipe is still open, you could then just drain any available data from the pipe and close it. That should leave the current performance intact (because if, as you suggest, the pipe closes before the task finishes terminating, then it should close before terminationHandler fires), but allow for handling cases like what I've described. Interesting, thanks! I'll add a ticket to look into this / keep it in mind, but as we still support 10.6, the terminationHandler isn't currently a holistic solution... I could just make this a 10.7+ only fix but it may lead to unexpected or difficult to debug workflow behaviour for some workflow creators.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now