Fire & Forget Scripting
May 3rd, 2013It’s hard to believe FastScripts is just over ten years old. When I first started developing the app, one of my primary goals was to improve upon the relatively slow launch and execution time of scripts when using Apple’s own built-in script menu. At the time, my analysis suggested that the sluggishness was primarily due to the time spent loading the script from disk into memory, and in launching a separate helper app to carry out execution of the script.
To avoid this slowness, FastScripts adopted a strategy in which scripts are loaded into memory once, and kept ready to fire again at a moment’s notice. This, combined with support for global and application-specific keyboard shortcuts, opened a whole new world of opportunities for streamlining workflows with frequently reused scripts.
The approach comes with some drawbacks, however. For example, FastScripts can only run one script at a time. So if a script takes a long time to finish, it can be very frustrating to be locked out of the ability to run other scripts. Another issue is that occasional issues with Apple’s AppleScript support make it possible for a script to hang indefinitely or even crash. If this happens in the context of FastScripts running a script, then obviously it’s FastScripts itself that ends up hanging or crashing.
For a long time I have considered the possibility of changing FastScripts so that it runs scripts in a separate process. It already does this by necessity for shell scripts such as Ruby or Python, but I have clung to the belief that doing so for AppleScript would eliminate one of the app’s major advantages.
I use FastScripts myself to run a variety of extremely terse scripts. For example I have scripts that open favorite web sites, activate specific applications, or simply play and pause iTunes. It would be extremely frustrating if any of these scripts exhibited even a slight delay.
I am no longer convinced that the decade-old strategy of avoiding on-the-fly loading and running of scripts is providing any significant performance advantages. Macs have gotten faster in so many respects: faster RAM, faster disks, and faster and more numerous CPUs.
I have been experimenting myself with a version of FastScripts that runs every AppleScript in a separate process, and have experienced no noticeable delays. And now when I do occaionally run a script that takes several seconds, or even minutes to run, I can fire it off with FastScripts confident that it will be ready to immediately run another script.
If you are a FastScripts user and this sounds intriguing, the good news is you can try it out today. Just make sure you’re up to date with version 2.6.5 from the Mac App Store or direct from the FastScripts home page. Because I’m still tentative about the performance, by default FastScripts continues to run scripts with the old, in-process behavior. To enable the new separate-process functionality, just set this “secret” preference key in the Terminal:
defaults write com.red-sweater.FastScripts RSScriptingPreferSeparateProcesses -bool YES
If you do try this out and you also find performance is unaffected, please let me know in comments below. On the other hand, if you see a significant slowdown, that would be useful to know as well. And in case it’s not obvious, you can revert to FastScripts’s old behavior by re-running the line above with “NO” in place of “YES”. I will consider defaulting to separate-process execution in a future update, assuming performance is good across the board.
In adding this functionality to FastScripts, I also considered that performance might not be great for all users, but there would nonetheless be cases where running a separate process would be preferable. For example if you have one script that you know will always take a long time to complete, you might want FastScripts to only bother with a separate process for that script. You can instruct the app to run it in a separate process by implementing a custom AppleScript handler in the script itself:
on redSweaterScriptingPrefersSeparateProcess() return true end redSweaterScriptingPrefersSeparateProcess
Now when FastScripts runs the script, you won’t have to wait around for it to finish. Just fire and forget!