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 +1 -0
- data/.travis.yml +3 -0
- data/README.md +8 -8
- data/Rakefile +16 -5
- data/lib/spring.rb +37 -18
- data/lib/spring/application.rb +9 -3
- data/lib/spring/application_manager.rb +17 -6
- data/lib/spring/commands.rb +1 -1
- data/lib/spring/env.rb +2 -1
- data/lib/spring/server.rb +17 -9
- data/lib/spring/version.rb +1 -1
- data/spring.gemspec +1 -0
- data/test/acceptance/app_test.rb +22 -13
- data/test/apps/rails-3-2/config/{initializers/spring.rb → spring.rb} +0 -0
- data/test/unit/application_watcher_test.rb +10 -9
- metadata +21 -4
data/.gitignore
CHANGED
data/.travis.yml
ADDED
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
|
83
|
-
8698 pts/6 Sl 0:02 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup
|
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
|
122
|
-
8876 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup
|
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
|
155
|
-
8876 pts/6 Sl 0:15 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup
|
156
|
-
9088 pts/6 Sl 0:01 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -r bundler/setup
|
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/
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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: :
|
21
|
+
task default: 'test:all'
|
data/lib/spring.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
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
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
66
|
+
return false unless status == "0"
|
48
67
|
|
49
|
-
|
50
|
-
|
51
|
-
|
68
|
+
application.send_io STDOUT
|
69
|
+
application.send_io STDERR
|
70
|
+
application.send_io stdin_slave
|
52
71
|
|
53
|
-
|
72
|
+
application.puts args.length
|
54
73
|
|
55
74
|
args.each do |arg|
|
56
|
-
|
57
|
-
|
75
|
+
application.puts arg.length
|
76
|
+
application.write arg
|
58
77
|
end
|
59
78
|
|
60
79
|
# FIXME: receive exit status from server
|
61
|
-
|
80
|
+
application.read
|
62
81
|
true
|
63
82
|
rescue Errno::ECONNRESET
|
64
83
|
false
|
65
84
|
ensure
|
66
|
-
|
85
|
+
application.close if application
|
67
86
|
end
|
68
87
|
|
69
88
|
private
|
data/lib/spring/application.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
data/lib/spring/commands.rb
CHANGED
data/lib/spring/env.rb
CHANGED
@@ -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
|
15
|
+
FileUtils.mkdir_p(path) unless path.exist?
|
15
16
|
path
|
16
17
|
end
|
17
18
|
|
data/lib/spring/server.rb
CHANGED
@@ -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 {
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
data/lib/spring/version.rb
CHANGED
data/spring.gemspec
CHANGED
data/test/acceptance/app_test.rb
CHANGED
@@ -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 =
|
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
|
|
File without changes
|
@@ -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
|
-
|
17
|
-
|
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.
|
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:
|
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
|