├── .gitignore ├── README-snowleopard ├── README-textmate ├── README.markdown ├── Rakefile ├── TODO ├── bin ├── fautotest ├── fconsole ├── fcucumber ├── frake ├── fruby └── snailgun ├── lib └── snailgun │ └── server.rb ├── ruby-1.9.2-p0.patch ├── snailgun.gemspec └── textmate.patch /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.orig 3 | *.gem 4 | tmp 5 | doc/rdoc 6 | coverage 7 | -------------------------------------------------------------------------------- /README-snowleopard: -------------------------------------------------------------------------------- 1 | If you use ruby-1.9.2-p0 on Snow Leopard snailgun won't work out of the Box! 2 | 3 | I have included a small patch to the ruby-sources that make it work: 4 | 5 | (this is assuming you use rvm) 6 | 7 | cd $HOME/.rvm/src/ruby-1.9.2-p0/ext/socket 8 | patch -p1 < wherever_snailgun_was_installed/ruby-1.9.2-p0.patch 9 | ruby extconf.rb 10 | make clean 11 | make clean install 12 | 13 | Patch was taken from http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=29242 14 | 15 | thieso@gmail.com 20101024 16 | -------------------------------------------------------------------------------- /README-textmate: -------------------------------------------------------------------------------- 1 | To get ultra fast CMD-R results in textmate you need to patch the run_script.rb that comes with textmate: 2 | 3 | cd /Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby.tmbundle/Support/RubyMate/ 4 | 5 | patch -p0 < wherever_snailgun_was_installed/textmate.patch 6 | 7 | To run test in never before seen speed simply cd into your rails-app (tested with rails 3) and say: 8 | 9 | > snailgun 10 | 11 | the output should read something like: 12 | Now entering subshell for RAILS_ENV=test. Use 'exit' to terminate snailgun 13 | Server starting for RAILS_ENV=test 14 | .. some seconds later ... 15 | Server ready for RAILS_ENV=test 16 | 17 | Now you can either use fruby instead of ruby to start tests "by hand": 18 | 19 | fruby -Itest test/unit/your_test.rb 20 | 21 | or - in TextMate - simply navigate to your_test.rb and hit CMD-R 22 | 23 | enjoy! 24 | 25 | thieso@gmail.com 20101024 at #rchh 26 | 27 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Snailgun 2 | ======== 3 | 4 | Snailgun accelerates the startup of Ruby applications which require large 5 | numbers of libraries. It does this by preparing a Ruby process with your 6 | chosen libraries preloaded, and then forking that process whenever a new 7 | command-line Ruby interpreter is required. 8 | 9 | Installation 10 | ------------ 11 | 12 | sudo gem install snailgun 13 | 14 | Or for the latest code, `git clone git://github.com/candlerb/snailgun.git` 15 | and put the bin directory into your PATH. 16 | 17 | Case 1: standalone 18 | ------------------ 19 | 20 | # WITHOUT SNAILGUN 21 | $ time ruby -rubygems -e 'require "active_support"' -e 'puts "".blank?' 22 | true 23 | 24 | real 0m2.123s 25 | user 0m1.424s 26 | sys 0m0.168s 27 | 28 | # WITH SNAILGUN 29 | $ snailgun -rubygems -ractive_support 30 | Snailgun starting on /home/brian/.snailgun/14781 - 'exit' to end 31 | $ time fruby -e 'puts "".blank?' 32 | true 33 | 34 | real 0m0.064s 35 | user 0m0.020s 36 | sys 0m0.004s 37 | 38 | $ exit 39 | logout 40 | Snailgun ended 41 | $ 42 | 43 | Case 2: Rails app 44 | ----------------- 45 | 46 | When using Rails or Merb, snailgun will start a process preloaded for the 47 | `test` environment only unless told otherwise. 48 | 49 | You need to edit `config/environments/test.rb` and set 50 | `config.cache_classes = false`. This is so that your application classes 51 | are loaded each time you run a test, rather than being preloaded into 52 | the test environment. 53 | 54 | Snailgun will take several seconds to be ready to process requests. Start 55 | with `snailgun -v` if you wish to be notified when it is ready. 56 | 57 | $ rails testapp 58 | $ cd testapp 59 | $ vi config/environments/test.rb 60 | ... set config.cache_classes = false 61 | $ snailgun -I test 62 | Now entering subshell. Use 'exit' to terminate snailgun 63 | 64 | $ time RAILS_ENV=test fruby script/runner 'puts 1+2' 65 | 3 66 | 67 | real 0m0.169s 68 | user 0m0.040s 69 | sys 0m0.008s 70 | 71 | # To run your test suite 72 | $ frake test # or frake spec 73 | 74 | Your preloaded process will remain around until you type `exit` to terminate 75 | it. 76 | 77 | Note that any attempt by `fruby` or `frake` to perform an action in an 78 | environment other than 'test' will fail. See below for how to run multiple 79 | snailgun environments. 80 | 81 | Merb support has been contributed (using MERB_ENV), but it is untested by 82 | me. 83 | 84 | Case 3: Rails with multiple environments 85 | ---------------------------------------- 86 | 87 | After reading the warnings below, you may choose to start multiple snailgun 88 | processes each configured for a different environment, as follows: 89 | 90 | $ snailgun --rails test,development 91 | 92 | This gives the potential for faster startup of rake tasks which involve 93 | the development environment (such as migrations) and the console. The 94 | utility `fconsole` is provided for this. 95 | 96 | However, beware that frake and fruby need to decide which of the preloaded 97 | environments to dispatch the command to. The safest way is to force the 98 | correct one explicitly: 99 | 100 | RAILS_ENV=test frake test:units 101 | RAILS_ENV=development fruby script/server 102 | RAILS_ENV=test fruby script/runner 'puts "".blank?' 103 | 104 | If you do not specify the environment, then a simple heuristic is used: 105 | 106 | * `fruby` always defaults to the 'development' environment. 107 | 108 | * `frake` honours any `RAILS_ENV=xxx` setting on the command line. If 109 | missing, `frake` defaults to the 'test' environment if no args are given or 110 | if an arg containing the word 'test' or 'spec' is given; otherwise it falls 111 | back to the 'development' environment. 112 | 113 | WARNING: The decision as to which of the preloaded environments to use is 114 | made *before* actually running the command. If the wrong choice is made, it 115 | can lead to problems. 116 | 117 | In the worst case, you may have a 'test'-type task, but find that it is 118 | wrongly dispatched to your 'development' environment - and possibly ends up 119 | blowing away your development database. This actually happened to me while 120 | developing snailgun. SO IF YOUR DEVELOPMENT DATABASE CONTAINS USEFUL DATA, 121 | KEEP IT BACKED UP. 122 | 123 | If you run test files individually, it is especially critical that you set 124 | the correct environment. e.g. 125 | 126 | RAILS_ENV=test fruby -Ilib -Itest test/unit/some_test.rb 127 | 128 | Case 4: Rails with cucumber 129 | --------------------------- 130 | 131 | Cucumber creates its own Rails environment called "cucumber", so you can 132 | setup snailgun like this: 133 | 134 | $ snailgun --rails test,cucumber 135 | 136 | Then use `frake cucumber` to exercise the features. frake selects the 137 | "cucumber" environment if run with "cucumber" as an argument. 138 | 139 | NOTE: to make your model classes be loaded on each run you need to set 140 | `config.cache_classes = false` in `config/environments/cucumber.rb`. 141 | Cucumber will give a big warning saying that this is known to be a 142 | problem with transactional fixtures. I don't use transactional fixtures 143 | so this isn't a problem for me. 144 | 145 | For a substantial performance boost, remove `:lib=>false` lines from 146 | `config/environments/cucumber.rb` so that cucumber, webrat, nokogiri etc 147 | are preloaded. 148 | 149 | Smaller performance boosts can be had from further preloading. For example, 150 | cucumber makes use of some rspec libraries for diffing even if you're not 151 | using rspec, so you can preload those. Add something like this to the end of 152 | `config/environments/cucumber.rb` 153 | 154 | begin 155 | require 'spec/expectations' 156 | require 'spec/runner/differs/default' 157 | rescue LoadError 158 | end 159 | require 'test_help' 160 | require 'test/unit/testresult' 161 | require 'active_support/secure_random' 162 | require 'active_support/time_with_zone' 163 | 164 | autotest 165 | -------- 166 | 167 | There is some simple support for autotest (from the ZenTest package). 168 | Just type `fautotest` instead of `autotest` after snailgun has been started. 169 | This hasn't been tested for a while. 170 | 171 | Bypassing rubygems 172 | ------------------ 173 | 174 | You can get noticeably faster startup if you don't use rubygems to invoke 175 | the programs. To do this, you can add the binary directory directly into 176 | the front of your PATH, e.g. for Ubuntu 177 | 178 | PATH=/var/lib/gems/1.8/gems/snailgun-1.0.3/bin:$PATH 179 | 180 | Alternatively, create a file called `fruby` somewhere early on in your PATH 181 | (e.g. under `$HOME/bin`), like this: 182 | 183 | #!/usr/bin/env ruby 184 | load '/path/to/the/real/fruby' 185 | 186 | Repeat for `frake` etc. 187 | 188 | Other bugs and limitations 189 | -------------------------- 190 | Only works with Linux/BSD systems, due to use of passing open file 191 | descriptors across a socket. 192 | 193 | Ctrl-C doesn't terminate frake processes. 194 | 195 | `fruby script/console` doesn't give any speedup, because script/console uses 196 | exec to invoke irb. Use the supplied `fconsole` instead. 197 | 198 | The environment is not currently passed across the socket to the ruby 199 | process. This means it's not usable as a fast CGI replacement. 200 | 201 | In Rails, you need to beware that any changes to your `config/environment*` 202 | will not be reflected until you stop and restart snailgun. 203 | 204 | Licence 205 | ------- 206 | This code is released under the same licence as Ruby itself. 207 | 208 | Author 209 | ------ 210 | Brian Candler 211 | 212 | Credits: 213 | 214 | * Jan X 215 | * George Ogata 216 | * Niklas Hofer 217 | * Thies C. Arntzen 218 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/clean' 3 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/candlerb/snailgun/f9b5abd5d438b95354a5d528c18a28a1c84fc568/TODO -------------------------------------------------------------------------------- /bin/fautotest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Copyright (C) Brian Candler 2009. Released under the Ruby licence. 3 | 4 | # shortcut for: RAILS_ENV=test fruby /path/to/autotest 5 | 6 | ENV["RAILS_ENV"] = "test" 7 | Test::Unit.run = true if defined?(Test::Unit) && Test::Unit.respond_to?(:run=) 8 | ARGV[0,0] = ["-e","gem 'ZenTest'","-e","load Gem.bin_path('ZenTest', 'autotest')","--"] 9 | load File.join(File.dirname(__FILE__), "fruby") 10 | -------------------------------------------------------------------------------- /bin/fconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Copyright (C) Brian Candler 2009. Released under the Ruby licence. 3 | 4 | ENV['RAILS_ENV'] = ARGV.shift if ARGV[0] =~ /^\w/ 5 | 6 | ARGV[0,0] = [ 7 | "-rirb", 8 | "-rirb/completion", 9 | "-rconsole_app", 10 | "-rconsole_with_helpers", 11 | "-eIRB.start", "--", "--simple-prompt" 12 | ] 13 | load File.join(File.dirname(__FILE__), "fruby") 14 | -------------------------------------------------------------------------------- /bin/fcucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Copyright (C) Brian Candler 2009. Released under the Ruby licence. 3 | 4 | $DEFAULT_ENV='cucumber' 5 | ARGV[0,0] = ['cucumber'] 6 | load File.join(File.dirname(__FILE__), 'fruby') 7 | -------------------------------------------------------------------------------- /bin/frake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Copyright (C) Brian Candler 2009. Released under the Ruby licence. 3 | # Here we run rake within a pre-forked ruby process 4 | 5 | # Pre-parse environment - see collect_tasks in rake/lib/rake.rb 6 | ARGV.each do |arg| 7 | if arg =~ /^(\w+)=(.*)$/ 8 | ENV[$1] = $2 9 | end 10 | end 11 | 12 | # Rails/Merb: make a guess at the correct environment 13 | if File.exist?("config/boot.rb") || File.exist?("config/init.rb") 14 | if ARGV.find { |arg| arg =~ /\bcucumber\b/ } 15 | $DEFAULT_ENV='cucumber' 16 | elsif ARGV.find { |arg| arg =~ /\b(test|spec)\b/ } || !ARGV.find { |arg| arg !~ /^-/ } 17 | $DEFAULT_ENV='test' 18 | end 19 | end 20 | 21 | ARGV[0,0] = ["-rrake", "-eRake.application.run", "--"] 22 | load File.join(File.dirname(__FILE__), "fruby") 23 | -------------------------------------------------------------------------------- /bin/fruby: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Copyright (C) Brian Candler 2009. Released under the Ruby licence. 3 | # This could be rewritten in C for even faster startup 4 | 5 | require 'socket' 6 | if ENV['SNAILGUN_SOCK'] 7 | sockname = ENV['SNAILGUN_SOCK'] 8 | elsif File.directory?('tmp/sockets/snailgun') 9 | env = ENV['RAILS_ENV'] || ENV['MERB_ENV'] 10 | unless env 11 | # Normally default to development: see railties/lib/tasks/misc.rake 12 | env = $DEFAULT_ENV || 'development' 13 | STDERR.puts "Snailgun assuming environment '#{env}'" 14 | end 15 | sockname = "tmp/sockets/snailgun/#{env}" 16 | end 17 | 18 | unless sockname and File.exists? sockname 19 | STDERR.puts < e 55 | exit_status = e.status 56 | raise # for the benefit of Test::Unit 57 | rescue Exception => e 58 | STDERR.puts "#{e}\n\t#{e.backtrace.join("\n\t")}" 59 | exit 1 60 | ensure 61 | $LOAD_PATH.shift if rubylib 62 | end 63 | end 64 | Process.detach(pid) if pid && pid > 0 65 | client.close 66 | end 67 | ensure 68 | File.delete(@sockname) rescue nil 69 | end 70 | 71 | # Process the received ruby command line. (TODO: implement more options) 72 | def start_ruby(args) 73 | e = [] 74 | OptionParser.new do |opts| 75 | opts.on("-e EXPR") do |v| 76 | e << v 77 | end 78 | opts.on("-I DIR") do |v| 79 | v.split(':').each do |value| 80 | $:.unshift value 81 | end 82 | end 83 | opts.on("-r LIB") do |v| 84 | require v 85 | end 86 | # opts.on("-rcatch_exception") do |v| 87 | # end 88 | opts.on("-KU") do |v| 89 | $KCODE = 'u' if RUBY_VERSION < "1.9" 90 | end 91 | end.order!(args) 92 | 93 | ARGV.replace(args) 94 | if !e.empty? 95 | $0 = '-e' 96 | e.each { |expr| eval(expr, TOPLEVEL_BINDING) } 97 | elsif ARGV.empty? 98 | $0 = '-' 99 | eval(STDIN.read, TOPLEVEL_BINDING) 100 | else 101 | cmd = ARGV.shift 102 | $0 = cmd 103 | load(cmd) 104 | end 105 | end 106 | 107 | def self.shell 108 | shell_opts = ENV['SNAILGUN_SHELL_OPTS'] 109 | args = shell_opts ? Shellwords.shellwords(shell_opts) : [] 110 | system(ENV['SHELL'] || 'bash', *args) 111 | end 112 | 113 | # Interactive mode (start a subshell with SNAILGUN_SOCK set up, 114 | # and terminate the snailgun server when the subshell exits) 115 | def interactive! 116 | ENV['SNAILGUN_SOCK'] = @sockname 117 | pid = Process.fork { 118 | STDERR.puts "Snailgun starting on #{sockname} - 'exit' to end" 119 | run 120 | } 121 | self.class.shell 122 | Process.kill('TERM',pid) 123 | # TODO: wait a few secs for it to die, 'KILL' if required 124 | STDERR.puts "Snailgun ended" 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /ruby-1.9.2-p0.patch: -------------------------------------------------------------------------------- 1 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: Makefile 2 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: ancdata.o 3 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: basicsocket.o 4 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: constants.o 5 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: constdefs.c 6 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: constdefs.h 7 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: extconf.h 8 | diff -u socket/extconf.rb /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket/extconf.rb 9 | --- socket/extconf.rb 2010-05-19 15:48:50.000000000 +0200 10 | +++ /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket/extconf.rb 2010-10-24 10:44:17.000000000 +0200 11 | @@ -117,7 +117,7 @@ 12 | } 13 | end 14 | 15 | -if (have_func("sendmsg") | have_func("recvmsg")) && /64-darwin/ !~ RUBY_PLATFORM 16 | +if have_func("sendmsg") | have_func("recvmsg") 17 | # CMSG_ macros are broken on 64bit darwin, because of use of __DARWIN_ALIGN. 18 | have_struct_member('struct msghdr', 'msg_control', ['sys/types.h', 'sys/socket.h']) 19 | have_struct_member('struct msghdr', 'msg_accrights', ['sys/types.h', 'sys/socket.h']) 20 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: init.o 21 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: ipsocket.o 22 | Common subdirectories: socket/lib and /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket/lib 23 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: mkmf.log 24 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: option.o 25 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: raddrinfo.o 26 | diff -u socket/rubysocket.h /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket/rubysocket.h 27 | --- socket/rubysocket.h 2010-04-28 09:16:30.000000000 +0200 28 | +++ /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket/rubysocket.h 2010-10-24 10:43:51.000000000 +0200 29 | @@ -138,6 +138,17 @@ 30 | }; 31 | #endif 32 | 33 | +#if defined __APPLE__ && defined __MACH__ 34 | +/* 35 | + * CMSG_ macros are broken on 64bit darwin, because __DARWIN_ALIGN 36 | + * aligns up to __darwin_size_t which is 64bit, but CMSG_DATA is 37 | + * 32bit-aligned. 38 | + */ 39 | +#undef __DARWIN_ALIGNBYTES 40 | +#define __DARWIN_ALIGNBYTES (sizeof(unsigned int) - 1) 41 | +#endif 42 | + 43 | + 44 | #if defined(_AIX) 45 | #ifndef CMSG_SPACE 46 | # define CMSG_SPACE(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + _CMSG_ALIGN(len)) 47 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: socket.bundle 48 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: socket.o 49 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: sockssocket.o 50 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: tcpserver.o 51 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: tcpsocket.o 52 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: udpsocket.o 53 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: unixserver.o 54 | Only in /Users/thieso/.rvm/src/ruby-1.9.2-p0/ext/socket: unixsocket.o 55 | -------------------------------------------------------------------------------- /snailgun.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = %q{snailgun} 3 | s.version = "1.1.1" 4 | 5 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 6 | s.authors = ["Brian Candler"] 7 | s.date = %q{2010-10-24} 8 | s.description = %q{Snailgun accelerates the startup of Ruby applications which require large numbers of libraries} 9 | s.email = %q{b.candler@pobox.com} 10 | s.files = [ 11 | "bin/fautotest", "bin/fconsole", "bin/fcucumber", "bin/frake", "bin/fruby", "bin/snailgun", 12 | "lib/snailgun/server.rb", "README.markdown", "README-snowleopard", "ruby-1.9.2-p0.patch", "textmate.patch", "README-textmate" 13 | ] 14 | s.executables = ["fautotest", "fconsole", "fcucumber", "frake", "fruby", "snailgun"] 15 | s.extra_rdoc_files = ["README.markdown"] 16 | s.has_rdoc = true 17 | s.homepage = %q{http://github.com/candlerb/snailgun} 18 | s.rdoc_options = ["--inline-source", "--charset=UTF-8"] 19 | s.require_paths = ["lib"] 20 | s.rubyforge_project = %q{snailgun} 21 | s.rubygems_version = %q{1.3.1} 22 | s.summary = %q{Command-line startup accelerator} 23 | if s.respond_to? :specification_version then 24 | s.specification_version = 2 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /textmate.patch: -------------------------------------------------------------------------------- 1 | --- run_script.rb.orig 2010-10-25 09:23:45.000000000 +0200 2 | +++ run_script.rb 2010-10-25 09:24:06.000000000 +0200 3 | @@ -90,6 +90,22 @@ 4 | return path, '', path 5 | end 6 | 7 | +def snailgun_socket 8 | + Pathname.new(Dir.pwd).ascend do |path| 9 | + if File.exists?(path.join("config", "boot.rb")) 10 | + if File.exists?(path.join("tmp", "sockets", "snailgun", "test")) 11 | + return path.join("tmp", "sockets", "snailgun", "test").to_s 12 | + end 13 | + end 14 | + end 15 | + nil 16 | +end 17 | + 18 | +if socket = snailgun_socket 19 | + ENV['SNAILGUN_SOCK'] = socket 20 | + cmd[0] = 'fruby' 21 | +end 22 | + 23 | TextMate::Executor.run( cmd, :version_args => ["--version"], 24 | :script_args => args ) do |line, type| 25 | if is_test_script and type == :out 26 | --------------------------------------------------------------------------------