bullring 0.7.4 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
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 :server_port
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
- @server_port = 3030
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
- if @worker.nil?
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,10 @@
1
+
2
+ module Bullring
3
+
4
+ class DummyLogger
5
+ def method_missing(m, *args, &block)
6
+ # ignore
7
+ end
8
+ end
9
+
10
+ end
@@ -7,6 +7,5 @@ module Bullring
7
7
  class IllegalArgument < StandardError; end
8
8
  class IllegalState < StandardError; end
9
9
  class PidDirUnavailable < StandardError; end
10
- class JSError < StandardError; end
11
10
 
12
11
  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
+