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 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
+