bullring 0.7.4 → 0.8
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/lib/bullring.rb +16 -22
- data/lib/bullring/util/dummy_logger.rb +10 -0
- data/lib/bullring/util/exceptions.rb +0 -1
- data/lib/bullring/util/server_proxy.rb +96 -0
- data/lib/bullring/util/server_registry.rb +283 -0
- data/lib/bullring/version.rb +1 -1
- data/lib/bullring/worker.rb +33 -13
- data/lib/bullring/workers/common.rb +66 -0
- data/lib/bullring/workers/racer_worker.rb +77 -0
- data/lib/bullring/workers/rhino_server.rb +81 -120
- data/lib/bullring/workers/rhino_server.sh +6 -5
- data/lib/bullring/workers/rhino_server_worker.rb +32 -61
- data/test/bullring_test.rb +44 -183
- data/test/dummy/log/development.log +152042 -0
- metadata +25 -22
- data/lib/bullring/workers/racer_dev_worker.rb +0 -167
- data/lib/bullring/workers/rhino_simple_worker.rb +0 -0
data/lib/bullring.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'bullring/version'
|
2
2
|
require 'bullring/worker'
|
3
|
+
require 'bullring/util/dummy_logger'
|
4
|
+
require 'bullring/workers/common'
|
3
5
|
require 'uglifier'
|
4
6
|
|
5
7
|
module Bullring
|
@@ -14,11 +16,6 @@ module Bullring
|
|
14
16
|
@logger ||= DummyLogger.new
|
15
17
|
end
|
16
18
|
|
17
|
-
# Order is important (and relative to calls to add_library)
|
18
|
-
def add_library_file(name, filename)
|
19
|
-
worker.add_library_file(name, filename)
|
20
|
-
end
|
21
|
-
|
22
19
|
# Order is important (and relative to calls to add_library_script)
|
23
20
|
def add_library(name, script)
|
24
21
|
script = Uglifier.compile(script, :copyright => false) if configuration.minify_libraries
|
@@ -42,6 +39,10 @@ module Bullring
|
|
42
39
|
worker.discard
|
43
40
|
end
|
44
41
|
|
42
|
+
def refresh
|
43
|
+
worker.refresh
|
44
|
+
end
|
45
|
+
|
45
46
|
def root
|
46
47
|
@root ||= File.expand_path("..", __FILE__)
|
47
48
|
end
|
@@ -69,44 +70,37 @@ module Bullring
|
|
69
70
|
|
70
71
|
class Configuration
|
71
72
|
attr_accessor :execution_timeout_secs
|
72
|
-
attr_accessor :
|
73
|
+
attr_accessor :server_host
|
74
|
+
attr_accessor :first_server_port
|
75
|
+
attr_accessor :registry_port
|
73
76
|
attr_accessor :jvm_init_heap_size
|
74
77
|
attr_accessor :jvm_max_heap_size
|
75
78
|
attr_accessor :jvm_young_heap_size
|
76
79
|
attr_accessor :minify_libraries
|
77
80
|
attr_accessor :server_max_bringup_time
|
81
|
+
attr_accessor :use_rhino
|
78
82
|
|
79
83
|
def initialize
|
80
84
|
@execution_timeout_secs = 0.5
|
81
|
-
@
|
85
|
+
@server_host = "127.0.0.1"
|
86
|
+
@first_server_port = 3030
|
87
|
+
@registry_port = 2999
|
82
88
|
@jvm_init_heap_size = '128m'
|
83
89
|
@jvm_max_heap_size = '128m'
|
84
90
|
@jvm_young_heap_size = '64m'
|
85
91
|
@minify_libraries = true
|
86
92
|
@server_max_bringup_time = 20 #seconds
|
93
|
+
@use_rhino = true
|
87
94
|
super
|
88
95
|
end
|
89
96
|
end
|
90
97
|
|
91
98
|
private
|
92
99
|
|
93
|
-
attr_accessor :worker
|
94
|
-
|
95
100
|
def worker
|
96
|
-
|
97
|
-
# TODO here, choose the appropriate worker (may be a non-server one for dev)
|
98
|
-
@worker = RhinoServerWorker.new
|
99
|
-
end
|
100
|
-
|
101
|
-
@worker
|
101
|
+
@worker ||= configuration.use_rhino ? RhinoServerWorker.new : RacerWorker.new
|
102
102
|
end
|
103
103
|
|
104
104
|
end
|
105
|
-
|
106
|
-
class DummyLogger
|
107
|
-
def method_missing(m, *args, &block)
|
108
|
-
# ignore
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
105
|
+
|
112
106
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'drb'
|
2
|
+
require 'bullring/util/network'
|
3
|
+
require 'bullring/util/server_registry'
|
4
|
+
|
5
|
+
# Interacts with the server registry to act like a server to its user.
|
6
|
+
#
|
7
|
+
module Bullring
|
8
|
+
class ServerProxy
|
9
|
+
|
10
|
+
# options looks like:
|
11
|
+
#
|
12
|
+
# {
|
13
|
+
# :server => {
|
14
|
+
# :command => [the command to run the process],
|
15
|
+
# :args => [the arguments to the process command],
|
16
|
+
# :first_port => [the first port to be used by a server]
|
17
|
+
# }
|
18
|
+
# :registry => {
|
19
|
+
# :host => [the host of the registry]
|
20
|
+
# :port => [the port the registry listens on]
|
21
|
+
# }
|
22
|
+
# :proxy => {
|
23
|
+
# :host => [the host that this proxy runs on (and listens on)]
|
24
|
+
# }
|
25
|
+
# }
|
26
|
+
#
|
27
|
+
def initialize(options)
|
28
|
+
@options = options
|
29
|
+
end
|
30
|
+
|
31
|
+
def store_in_registry(dictionary, key, value)
|
32
|
+
server_registry[dictionary, key] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(m, *args, &block)
|
36
|
+
result = nil
|
37
|
+
num_retries = 0
|
38
|
+
|
39
|
+
server = nil
|
40
|
+
begin
|
41
|
+
server = server_registry.lease_server!
|
42
|
+
|
43
|
+
server.logger = Bullring.logger
|
44
|
+
result = server.send(m, *args, &block)
|
45
|
+
server.logger = nil
|
46
|
+
rescue ServerRegistryOffline => e
|
47
|
+
Bullring.logger.debug {"Lost connection to the server registry (proxy)"}
|
48
|
+
connect!
|
49
|
+
num_retries = num_retries + 1
|
50
|
+
if (num_retries < 3)
|
51
|
+
retry
|
52
|
+
else
|
53
|
+
raise
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
server_registry.release_server if !server.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def discard
|
63
|
+
server_registry.close!
|
64
|
+
sleep(0.5) while !server_registry.registry_unavailable?
|
65
|
+
DRb.stop_service
|
66
|
+
@server_registry = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def refresh
|
70
|
+
server_registry.expire_servers
|
71
|
+
end
|
72
|
+
|
73
|
+
def server_registry
|
74
|
+
connect! if @server_registry.nil?
|
75
|
+
@server_registry
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def connect!
|
81
|
+
DRb.stop_service
|
82
|
+
@local_service = DRb.start_service "druby://#{@options[:proxy][:host]}:0"
|
83
|
+
@server_registry = ServerRegistry.new(@options[:registry][:host],@options[:registry][:port], @options[:server][:first_port]) do
|
84
|
+
# Block to start a new server (spawn in its own process group so it
|
85
|
+
# stays alive even if the originating process dies)
|
86
|
+
pid = Process.spawn([@options[:server][:command],
|
87
|
+
@options[:server][:args]].flatten.join(" "),
|
88
|
+
{:pgroup => true})
|
89
|
+
Process.detach(pid)
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'drb'
|
2
|
+
require 'rinda/tuplespace'
|
3
|
+
|
4
|
+
unless Kernel.respond_to?(:require_relative)
|
5
|
+
module Kernel
|
6
|
+
def require_relative(path)
|
7
|
+
require File.join(File.dirname(caller[0]), path.to_str)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require_relative 'network'
|
13
|
+
|
14
|
+
module Bullring
|
15
|
+
|
16
|
+
class TuplespaceWrapper
|
17
|
+
def initialize(uri)
|
18
|
+
@tuplespace = DRbObject.new_with_uri(uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(m, *args, &block)
|
22
|
+
begin
|
23
|
+
@tuplespace.send(m, *args, &block)
|
24
|
+
rescue DRb::DRbConnError, Errno::ECONNREFUSED => e
|
25
|
+
Bullring.logger.debug {"Lost connection to the server registry"}
|
26
|
+
raise ServerRegistryOffline, "The connection to the server registry was lost"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ServerWrapper
|
32
|
+
|
33
|
+
attr_reader :server
|
34
|
+
|
35
|
+
def initialize(uri)
|
36
|
+
@uri = uri
|
37
|
+
@server = DRbObject.new_with_uri(uri)
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(m, *args, &block)
|
41
|
+
begin
|
42
|
+
@server.send(m, *args, &block)
|
43
|
+
rescue DRb::DRbConnError, Errno::ECONNREFUSED => e
|
44
|
+
Bullring.logger.debug {"Lost connection to the server at #{@uri}"}
|
45
|
+
raise ServerOffline, "The connection to the server at #{@uri} was lost"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ServerRegistry
|
51
|
+
|
52
|
+
attr_reader :tuplespace
|
53
|
+
|
54
|
+
MAX_SERVERS_PER_GENERATION = 1
|
55
|
+
|
56
|
+
def initialize(host, port, first_server_port, &start_server_block)
|
57
|
+
@registry_host = @server_host = host
|
58
|
+
@registry_port = port
|
59
|
+
@start_server_block = start_server_block
|
60
|
+
|
61
|
+
@servers = {}
|
62
|
+
|
63
|
+
registry_uri = "druby://#{host}:#{port}"
|
64
|
+
|
65
|
+
if registry_unavailable?
|
66
|
+
pid = Kernel.fork do
|
67
|
+
@tuplespace = Rinda::TupleSpaceProxy.new(Rinda::TupleSpace.new)
|
68
|
+
@tuplespace.write([:global_lock])
|
69
|
+
@tuplespace.write([:next_client_id, 0])
|
70
|
+
@tuplespace.write([:server_generation, 0])
|
71
|
+
@tuplespace.write([:next_server_port, "#{first_server_port}"]) if !first_server_port.nil?
|
72
|
+
DRb.start_service registry_uri, @tuplespace
|
73
|
+
|
74
|
+
Thread.new do
|
75
|
+
@client_id = 'registry'
|
76
|
+
@tuplespace.notify("write", [:registry_closed]).pop
|
77
|
+
kill_available_servers
|
78
|
+
Thread.main.exit
|
79
|
+
end
|
80
|
+
|
81
|
+
DRb.thread.join
|
82
|
+
end
|
83
|
+
Process.detach(pid)
|
84
|
+
end
|
85
|
+
|
86
|
+
time_sleeping = 0
|
87
|
+
while (registry_unavailable?)
|
88
|
+
sleep(0.2)
|
89
|
+
if (time_sleeping += 0.2) > 20 #@options[:process][:max_bringup_time]
|
90
|
+
Bullring.logger.error {"#{caller_name}: Timed out waiting to bring up the registry server"}
|
91
|
+
raise StandardError, "#{caller_name}: Timed out waiting to bring up the registry server", caller
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# The registry should be available here so connect to it if we don't
|
96
|
+
# already serve it.
|
97
|
+
@tuplespace ||= TuplespaceWrapper.new(registry_uri)
|
98
|
+
|
99
|
+
# Every user (client) of server registry has its own instance of the registry, so that
|
100
|
+
# instance can store its own client id.
|
101
|
+
_, @client_id = @tuplespace.take([:next_client_id, nil])
|
102
|
+
@tuplespace.write([:next_client_id, @client_id + 1])
|
103
|
+
end
|
104
|
+
|
105
|
+
def next_server_port
|
106
|
+
_, port = @tuplespace.take([:next_server_port, nil])
|
107
|
+
port = port.to_i + 1 while !Network::is_port_open?(@server_host, port)
|
108
|
+
@tuplespace.write([:next_server_port, "#{port.to_i + 1}"])
|
109
|
+
port
|
110
|
+
end
|
111
|
+
|
112
|
+
# First starts up a server if needed then blocks until it is available and returns it
|
113
|
+
def lease_server!
|
114
|
+
debugger
|
115
|
+
begin
|
116
|
+
if num_current_generation_servers < MAX_SERVERS_PER_GENERATION && registry_open?
|
117
|
+
start_a_server
|
118
|
+
end
|
119
|
+
|
120
|
+
lease_server
|
121
|
+
rescue ServerOffline => e
|
122
|
+
Bullring.logger.debug {"Lost connection with a server, retrying..."}
|
123
|
+
retry
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Blocks until a server is available, then returns it
|
128
|
+
def lease_server
|
129
|
+
server = _lease_server(:timeout => 0) until !server.nil?
|
130
|
+
server
|
131
|
+
end
|
132
|
+
|
133
|
+
def release_server
|
134
|
+
# In case the lease wasn't successful, don't hang on the release
|
135
|
+
begin
|
136
|
+
_, _, generation, uri = @tuplespace.take(['leased', @client_id, nil, nil], 0)
|
137
|
+
|
138
|
+
# Only register the server if its generation hasn't expired, otherwise
|
139
|
+
# kill and forget
|
140
|
+
if generation < current_server_generation || !registry_open?
|
141
|
+
@servers[uri].kill rescue ServerOffline
|
142
|
+
@servers[uri] = nil
|
143
|
+
else
|
144
|
+
register_server(uri)
|
145
|
+
end
|
146
|
+
rescue Rinda::RequestExpiredError => e; end
|
147
|
+
end
|
148
|
+
|
149
|
+
def expire_servers
|
150
|
+
with_lock do
|
151
|
+
_, generation = @tuplespace.take([:server_generation, nil])
|
152
|
+
@tuplespace.write([:server_generation, generation + 1])
|
153
|
+
kill_available_servers(generation)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def close!
|
158
|
+
@tuplespace.write([:registry_closed])
|
159
|
+
end
|
160
|
+
|
161
|
+
def register_server(uri)
|
162
|
+
fail_unless_registry_open!
|
163
|
+
@tuplespace.write(['available', current_server_generation, uri])
|
164
|
+
end
|
165
|
+
|
166
|
+
def []=(dictionary, key, value)
|
167
|
+
with_lock do
|
168
|
+
@tuplespace.take(['data', dictionary, key, nil], 0) rescue nil
|
169
|
+
@tuplespace.write(['data', dictionary, key, value])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def [](dictionary, key)
|
174
|
+
with_lock do
|
175
|
+
_, _, _, value = @tuplespace.read(['data', dictionary, key, nil], 0) rescue nil
|
176
|
+
return value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def current_server_generation
|
181
|
+
@tuplespace.read([:server_generation, nil])[1]
|
182
|
+
end
|
183
|
+
|
184
|
+
def num_servers(generation = nil)
|
185
|
+
@tuplespace.read_all(['available', generation, nil]).count + \
|
186
|
+
@tuplespace.read_all(['leased', nil, generation, nil]).count
|
187
|
+
end
|
188
|
+
|
189
|
+
def num_current_generation_servers
|
190
|
+
num_servers(current_server_generation)
|
191
|
+
end
|
192
|
+
|
193
|
+
def registry_open?
|
194
|
+
!tuple_present?([:registry_closed])
|
195
|
+
end
|
196
|
+
|
197
|
+
def test!
|
198
|
+
@tuplespace.write([:temp])
|
199
|
+
end
|
200
|
+
|
201
|
+
def registry_unavailable?
|
202
|
+
Network::is_port_open?(@registry_host, @registry_port)
|
203
|
+
end
|
204
|
+
|
205
|
+
def dump_tuplespace
|
206
|
+
"Available: " + @tuplespace.read_all(['available', nil, nil]).inspect + \
|
207
|
+
", Leased: " + @tuplespace.read_all(['leased', nil, nil, nil]).inspect + \
|
208
|
+
", Data: " + @tuplespace.read_all(['data', nil, nil, nil]).inspect
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# If a server is unavailable after the timeout, either returns nil or throws
|
214
|
+
# an exception if the registry is closed at that time.
|
215
|
+
# options[:timeout] => a number of seconds or nil for no timeout (only use 0 or nil)
|
216
|
+
# options[:generation] => a generation number or nil for no generation requirement
|
217
|
+
# options[:ignore_closed_registry] => if true, don't throw exception if registry closed
|
218
|
+
def _lease_server(options)
|
219
|
+
options[:ignore_closed_registry] ||= false
|
220
|
+
|
221
|
+
begin
|
222
|
+
# Get the server from the TS
|
223
|
+
_, generation, uri = @tuplespace.take(['available', options[:generation], nil], options[:timeout])
|
224
|
+
# Get the DRb object for it
|
225
|
+
@servers[uri] ||= ServerWrapper.new(uri)
|
226
|
+
# Check that the server is still up; the following call will throw if it is down
|
227
|
+
@servers[uri].alive?
|
228
|
+
# Note that we've leased this server
|
229
|
+
@tuplespace.write(['leased', @client_id, generation, uri])
|
230
|
+
# Return it
|
231
|
+
@servers[uri] #.server
|
232
|
+
rescue ServerOffline => e
|
233
|
+
@servers[uri] = nil
|
234
|
+
raise
|
235
|
+
rescue Rinda::RequestExpiredError => e
|
236
|
+
fail_unless_registry_open! if !options[:ignore_closed_registry]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def tuple_present?(tuple)
|
241
|
+
begin
|
242
|
+
@tuplespace.read(tuple, 0)
|
243
|
+
true
|
244
|
+
rescue
|
245
|
+
false
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def with_lock
|
250
|
+
lock = @tuplespace.take([:global_lock])
|
251
|
+
yield
|
252
|
+
ensure
|
253
|
+
@tuplespace.write(lock) if lock
|
254
|
+
end
|
255
|
+
|
256
|
+
def kill_available_servers(generation = nil)
|
257
|
+
# Leasing and releasing will guarantee that we have a DRbObject for the server
|
258
|
+
# and release_server already has the code for killing a server
|
259
|
+
while num_servers(generation) != 0
|
260
|
+
_lease_server({:timeout => 0, :ignore_closed_registry => true, :generation => generation})
|
261
|
+
release_server
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def start_a_server
|
266
|
+
raise IllegalState "The command to start a server is unavailable." if @start_server_block.nil?
|
267
|
+
|
268
|
+
@start_server_block.call
|
269
|
+
end
|
270
|
+
|
271
|
+
def fail_unless_registry_open!
|
272
|
+
raise ServerRegistryClosed if !registry_open?
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
class ServerRegistryClosed < StandardError; end
|
278
|
+
class ServerRegistryOffline < StandardError; end
|
279
|
+
class ServerOffline < StandardError; end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
|