spring 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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