spring 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .rvmrc
6
7
  Gemfile.lock
7
8
  InstalledFiles
8
9
  _yardoc
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/README.md CHANGED
@@ -79,8 +79,8 @@ That booted our app in the background:
79
79
 
80
80
  ```
81
81
  $ ps ax | grep spring
82
- 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
83
- 8698 pts/6 Sl 0:02 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
82
+ 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
83
+ 8698 pts/6 Sl 0:02 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
84
84
  ```
85
85
 
86
86
  We can see two processes, one is the Spring server, the other is the
@@ -118,8 +118,8 @@ automatically. Note that the application process id is 8698 above. Let's
118
118
  ```
119
119
  $ touch config/application.rb
120
120
  $ ps ax | grep spring
121
- 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
122
- 8876 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
121
+ 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
122
+ 8876 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
123
123
  ```
124
124
 
125
125
  The application process detected the change and exited. The server process
@@ -151,9 +151,9 @@ the application in development mode.
151
151
 
152
152
  ```
153
153
  $ ps ax | grep spring
154
- 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
155
- 8876 pts/6 Sl 0:15 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
156
- 9088 pts/6 Sl 0:01 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup /home/turnip/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/spring-0.0.1/lib/spring/server.rb
154
+ 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
155
+ 8876 pts/6 Sl 0:15 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
156
+ 9088 pts/6 Sl 0:01 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup -r spring/server -e Spring::Server.boot
157
157
  ```
158
158
 
159
159
  Running rake is faster the second time:
@@ -177,7 +177,7 @@ sys 0m0.070s
177
177
 
178
178
  The following commands are shipped by default.
179
179
 
180
- Custom commands can be specified in `config/initializers/spring.rb`. See
180
+ Custom commands can be specified in `config/spring.rb`. See
181
181
  [`lib/spring/commands.rb`](https://github.com/jonleighton/spring/blob/master/lib/spring/commands.rb)
182
182
  for examples.
183
183
 
data/Rakefile CHANGED
@@ -1,10 +1,21 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
- Rake::TestTask.new do |t|
5
- t.libs << "test"
6
- t.test_files = FileList["test/{acceptance,unit}/*_test.rb"]
7
- t.verbose = true
4
+ namespace :test do
5
+ Rake::TestTask.new(:unit) do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList["test/unit/*_test.rb"]
8
+ t.verbose = true
9
+ end
10
+
11
+ Rake::TestTask.new(:acceptance) do |t|
12
+ t.libs << "test"
13
+ t.test_files = FileList["test/acceptance/*_test.rb"]
14
+ t.verbose = true
15
+ end
16
+
17
+ desc 'run all tests'
18
+ task all: [:unit, :acceptance]
8
19
  end
9
20
 
10
- task default: :test
21
+ task default: 'test:all'
@@ -12,7 +12,8 @@ class Spring
12
12
  SERVER_COMMAND = [
13
13
  File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')),
14
14
  "-r", "bundler/setup",
15
- File.expand_path("../spring/server.rb", __FILE__)
15
+ "-r", "spring/server",
16
+ "-e", "Spring::Server.boot"
16
17
  ]
17
18
 
18
19
  def self.run(args)
@@ -26,44 +27,62 @@ class Spring
26
27
  end
27
28
 
28
29
  def server_running?
29
- env.socket_path.exist?
30
+ if env.pidfile_path.exist?
31
+ pidfile = env.pidfile_path.open('r')
32
+ !pidfile.flock(File::LOCK_EX | File::LOCK_NB)
33
+ else
34
+ false
35
+ end
36
+ ensure
37
+ if pidfile
38
+ pidfile.flock(File::LOCK_UN)
39
+ pidfile.close
40
+ end
30
41
  end
31
42
 
43
+ # Boot the server into the process group of the current session.
44
+ # This will cause it to be automatically killed once the session
45
+ # ends (i.e. when the user closes their terminal).
32
46
  def boot_server
33
- # Boot the server into the process group of the current session.
34
- # This will cause it to be automatically killed once the session
35
- # ends (i.e. when the user closes their terminal).
47
+ env.socket_path.unlink if env.socket_path.exist?
36
48
  Process.spawn(*SERVER_COMMAND, pgroup: SID.pgid)
37
- sleep 0.1 until server_running?
49
+ sleep 0.1 until env.socket_path.exist?
38
50
  end
39
51
 
40
52
  def run(args)
41
53
  boot_server unless server_running?
42
54
 
43
- socket = UNIXSocket.open(env.socket_name)
44
- socket.write rails_env_for(args.first)
45
- socket.close
55
+ application, client = UNIXSocket.pair
56
+
57
+ server = UNIXSocket.open(env.socket_name)
58
+ server.send_io client
59
+ server.puts rails_env_for(args.first)
60
+
61
+ status = server.read(1)
62
+
63
+ server.close
64
+ client.close
46
65
 
47
- socket = UNIXSocket.open(env.socket_name)
66
+ return false unless status == "0"
48
67
 
49
- socket.send_io STDOUT
50
- socket.send_io STDERR
51
- socket.send_io stdin_slave
68
+ application.send_io STDOUT
69
+ application.send_io STDERR
70
+ application.send_io stdin_slave
52
71
 
53
- socket.puts args.length
72
+ application.puts args.length
54
73
 
55
74
  args.each do |arg|
56
- socket.puts arg.length
57
- socket.write arg
75
+ application.puts arg.length
76
+ application.write arg
58
77
  end
59
78
 
60
79
  # FIXME: receive exit status from server
61
- socket.read
80
+ application.read
62
81
  true
63
82
  rescue Errno::ECONNRESET
64
83
  false
65
84
  ensure
66
- socket.close
85
+ application.close if application
67
86
  end
68
87
 
69
88
  private
@@ -47,10 +47,16 @@ class Spring
47
47
  loop do
48
48
  watch_application
49
49
 
50
- client = manager.recv_io
51
- client.autoclose = false # prevent GC closing the FD
50
+ client = manager.recv_io(UNIXSocket)
52
51
 
53
- serve UNIXSocket.for_fd(client.fileno)
52
+ # Confirm that we have received the client socket. This is necessary on OS X
53
+ # to prevent a timing error. The client needs to keep the FD that we are receiving
54
+ # open until it has been received here. Unlike on Linux, it's not sufficient for
55
+ # the FD to just be in the socket buffer, it has to actually get received at this
56
+ # end before it can be closed by the client.
57
+ manager.puts
58
+
59
+ serve client
54
60
  end
55
61
  end
56
62
 
@@ -29,20 +29,31 @@ class Spring
29
29
  @pid
30
30
  end
31
31
 
32
+ # The return value of this method indicates whether or not the application
33
+ # successfully received the client. It might not successfully receive the
34
+ # client if e.g. the application has an exception during initialization, causing
35
+ # the application process to die.
32
36
  def run(client)
33
37
  @client = client
34
38
 
35
39
  synchronize do
36
- start unless alive?
37
-
38
- begin
39
- child.send_io @client
40
- rescue Errno::EPIPE
41
- # EPIPE indicates child has died but has not been collected by the wait thread yet
40
+ if alive?
41
+ begin
42
+ child.send_io @client
43
+ rescue Errno::EPIPE
44
+ # EPIPE indicates child has died but has not been collected by the wait thread yet
45
+ start
46
+ child.send_io @client
47
+ end
48
+ else
42
49
  start
43
50
  child.send_io @client
44
51
  end
52
+
53
+ child.gets
45
54
  end
55
+ rescue Errno::ECONNRESET, Errno::EPIPE
56
+ false
46
57
  ensure
47
58
  @client.close
48
59
  @client = nil
@@ -15,7 +15,7 @@ class Spring
15
15
 
16
16
  # Load custom commands, if any
17
17
  begin
18
- require "./config/initializers/spring"
18
+ require "./config/spring"
19
19
  rescue LoadError
20
20
  end
21
21
 
@@ -1,5 +1,6 @@
1
1
  require "pathname"
2
2
  require "spring/sid"
3
+ require "fileutils"
3
4
 
4
5
  class Spring
5
6
  class Env
@@ -11,7 +12,7 @@ class Spring
11
12
 
12
13
  def tmp_path
13
14
  path = root.join('tmp/spring')
14
- path.mkdir unless path.exist?
15
+ FileUtils.mkdir_p(path) unless path.exist?
15
16
  path
16
17
  end
17
18
 
@@ -14,6 +14,7 @@ class Spring
14
14
  def initialize(env = Env.new)
15
15
  @env = env
16
16
  @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k) }
17
+ @pidfile = env.pidfile_path.open('a')
17
18
  end
18
19
 
19
20
  def boot
@@ -25,7 +26,18 @@ class Spring
25
26
  write_pidfile
26
27
 
27
28
  server = UNIXServer.open(env.socket_name)
28
- loop { @applications[server.accept.read].run server.accept }
29
+ loop { serve server.accept }
30
+ end
31
+
32
+ def serve(client)
33
+ app_client = client.recv_io
34
+ rails_env = client.gets.chomp
35
+
36
+ if @applications[rails_env].run(app_client)
37
+ client.write "0"
38
+ else
39
+ client.write "1"
40
+ end
29
41
  end
30
42
 
31
43
  def set_exit_hook
@@ -42,12 +54,10 @@ class Spring
42
54
  end
43
55
 
44
56
  def write_pidfile
45
- file = env.pidfile_path.open('a')
46
-
47
- if file.flock(File::LOCK_EX | File::LOCK_NB)
48
- file.truncate(0)
49
- file.write("#{Process.pid}\n")
50
- file.fsync
57
+ if @pidfile.flock(File::LOCK_EX | File::LOCK_NB)
58
+ @pidfile.truncate(0)
59
+ @pidfile.write("#{Process.pid}\n")
60
+ @pidfile.fsync
51
61
  else
52
62
  STDERR.puts "#{file.path} is locked; it looks like a server is already running"
53
63
  exit(1)
@@ -55,5 +65,3 @@ class Spring
55
65
  end
56
66
  end
57
67
  end
58
-
59
- Spring::Server.boot if __FILE__ == $0
@@ -1,3 +1,3 @@
1
1
  class Spring
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -18,4 +18,5 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_dependency 'activesupport'
21
+ gem.add_development_dependency 'rake'
21
22
  end
@@ -1,5 +1,6 @@
1
1
  require 'helper'
2
2
  require 'io/wait'
3
+ require "timeout"
3
4
 
4
5
  class AppTest < ActiveSupport::TestCase
5
6
  BINFILE = File.expand_path('../bin/spring', TEST_ROOT)
@@ -36,20 +37,28 @@ class AppTest < ActiveSupport::TestCase
36
37
  )
37
38
  end
38
39
 
39
- _, status = Process.wait2
40
+ _, status = Timeout.timeout(opts.fetch(:timeout, 5)) { Process.wait2 }
40
41
 
41
- out, err = [stdout, stderr].map(&:first).map { |s| s.ready? ? s.readpartial(10240) : "" }
42
+ out, err = read_streams
42
43
 
43
44
  @times << (Time.now - start_time) if opts.fetch(:timer, true)
44
45
 
45
- # p status
46
- # puts "---"
47
- # puts out
48
- # puts "***"
49
- # puts err
50
- # puts "---"
51
-
52
46
  [status, out, err]
47
+ rescue Timeout::Error
48
+ print_streams *read_streams
49
+ raise
50
+ end
51
+
52
+ def print_streams(out, err)
53
+ puts "---"
54
+ puts out
55
+ puts "***"
56
+ puts err
57
+ puts "---"
58
+ end
59
+
60
+ def read_streams
61
+ [stdout, stderr].map(&:first).map { |s| s.ready? ? s.readpartial(10240) : "" }
53
62
  end
54
63
 
55
64
  def await_reload
@@ -58,17 +67,17 @@ class AppTest < ActiveSupport::TestCase
58
67
 
59
68
  def assert_successful_run(*args)
60
69
  status, _, _ = app_run(*args)
61
- assert status.success?
70
+ assert status.success?, 'The run should be successful but it was '
62
71
  end
63
72
 
64
73
  def assert_unsuccessful_run(*args)
65
74
  status, _, _ = app_run(*args)
66
- assert !status.success?
75
+ assert !status.success?, 'The run should not be successful but it was'
67
76
  end
68
77
 
69
78
  def assert_stdout(command, expected)
70
79
  _, stdout, _ = app_run(command)
71
- assert stdout.include?(expected)
80
+ assert stdout.include?(expected), "expected '#{expected}' to be printed to stdout. But it wasn't, the stdout is:\n#{stdout}"
72
81
  end
73
82
 
74
83
  def assert_speedup(opts = {})
@@ -92,7 +101,7 @@ class AppTest < ActiveSupport::TestCase
92
101
 
93
102
  @times = []
94
103
 
95
- app_run "bundle check || bundle update", timer: false
104
+ app_run "bundle check || bundle update", timer: false, timeout: nil
96
105
  app_run "bundle exec rake db:migrate", timer: false
97
106
  end
98
107
 
@@ -1,5 +1,6 @@
1
1
  require "helper"
2
2
  require "fileutils"
3
+ require "active_support/core_ext/numeric/time"
3
4
  require "spring/application_watcher"
4
5
 
5
6
  class ApplicationWatcherTest < Test::Unit::TestCase
@@ -12,21 +13,21 @@ class ApplicationWatcherTest < Test::Unit::TestCase
12
13
  FileUtils.rm_r(@dir)
13
14
  end
14
15
 
15
- def touch(file)
16
- sleep 0.01
17
- File.write(file, "omg")
16
+ def touch(file, mtime = nil)
17
+ options = {}
18
+ options[:mtime] = mtime if mtime
19
+ FileUtils.touch(file, options)
18
20
  end
19
21
 
20
22
  def test_file_mtime
21
23
  file = "#{@dir}/omg"
22
- touch file
24
+ touch file, Time.now - 2.seconds
23
25
 
24
26
  watcher = Spring::ApplicationWatcher.new
25
27
  watcher.add_files [file]
26
28
 
27
29
  assert !watcher.stale?
28
-
29
- touch file
30
+ touch file, Time.now
30
31
  assert watcher.stale?
31
32
  end
32
33
 
@@ -39,16 +40,16 @@ class ApplicationWatcherTest < Test::Unit::TestCase
39
40
 
40
41
  assert !watcher.stale?
41
42
 
42
- touch "#{@dir}/1/foo"
43
+ touch "#{@dir}/1/foo", Time.now - 1.minute
43
44
  assert !watcher.stale?
44
45
 
45
- touch "#{@dir}/1/foo.rb"
46
+ touch "#{@dir}/1/foo.rb", 2.seconds
46
47
  assert watcher.stale?
47
48
 
48
49
  watcher.reset
49
50
  assert !watcher.stale?
50
51
 
51
- touch "#{@dir}/2/foo"
52
+ touch "#{@dir}/2/foo", Time.now
52
53
  assert watcher.stale?
53
54
  end
54
55
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spring
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-29 00:00:00.000000000 Z
12
+ date: 2013-01-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  description: Rails application preloader
31
47
  email:
32
48
  - j@jonathanleighton.com
@@ -36,6 +52,7 @@ extensions: []
36
52
  extra_rdoc_files: []
37
53
  files:
38
54
  - .gitignore
55
+ - .travis.yml
39
56
  - Gemfile
40
57
  - LICENSE.txt
41
58
  - README.md
@@ -88,10 +105,10 @@ files:
88
105
  - test/apps/rails-3-2/config/initializers/mime_types.rb
89
106
  - test/apps/rails-3-2/config/initializers/secret_token.rb
90
107
  - test/apps/rails-3-2/config/initializers/session_store.rb
91
- - test/apps/rails-3-2/config/initializers/spring.rb
92
108
  - test/apps/rails-3-2/config/initializers/wrap_parameters.rb
93
109
  - test/apps/rails-3-2/config/locales/en.yml
94
110
  - test/apps/rails-3-2/config/routes.rb
111
+ - test/apps/rails-3-2/config/spring.rb
95
112
  - test/apps/rails-3-2/db/migrate/20121109171227_create_posts.rb
96
113
  - test/apps/rails-3-2/db/schema.rb
97
114
  - test/apps/rails-3-2/db/seeds.rb
@@ -182,10 +199,10 @@ test_files:
182
199
  - test/apps/rails-3-2/config/initializers/mime_types.rb
183
200
  - test/apps/rails-3-2/config/initializers/secret_token.rb
184
201
  - test/apps/rails-3-2/config/initializers/session_store.rb
185
- - test/apps/rails-3-2/config/initializers/spring.rb
186
202
  - test/apps/rails-3-2/config/initializers/wrap_parameters.rb
187
203
  - test/apps/rails-3-2/config/locales/en.yml
188
204
  - test/apps/rails-3-2/config/routes.rb
205
+ - test/apps/rails-3-2/config/spring.rb
189
206
  - test/apps/rails-3-2/db/migrate/20121109171227_create_posts.rb
190
207
  - test/apps/rails-3-2/db/schema.rb
191
208
  - test/apps/rails-3-2/db/seeds.rb