homeq 1.1.4
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/CHANGELOG +103 -0
- data/COPYING +348 -0
- data/README.rdoc +64 -0
- data/Rakefile +131 -0
- data/bin/hq +6 -0
- data/config/boot.rb +224 -0
- data/config/databases/frontbase.yml +28 -0
- data/config/databases/mysql.yml +54 -0
- data/config/databases/oracle.yml +39 -0
- data/config/databases/postgresql.yml +48 -0
- data/config/databases/sqlite2.yml +16 -0
- data/config/databases/sqlite3.yml +19 -0
- data/config/environment.rb +20 -0
- data/config/environments/development.cfg +35 -0
- data/config/environments/production.cfg +35 -0
- data/config/environments/test.cfg +35 -0
- data/config/generators/job/templates/job.rb.erb +20 -0
- data/config/generators/message/templates/messages/MESSAGE.proto.erb +12 -0
- data/config/generators/model/templates/models/MODEL.rb.erb +3 -0
- data/config/generators/service/templates/services/SERVICE.rb.erb +43 -0
- data/config/homeq.cfg +35 -0
- data/extras/consumer.rb +85 -0
- data/extras/homeq.cfg +49 -0
- data/extras/hqd.rb +33 -0
- data/extras/producer.rb +79 -0
- data/extras/simple_consumer.rb +53 -0
- data/lib/homeq/base/base.rb +44 -0
- data/lib/homeq/base/commando.rb +81 -0
- data/lib/homeq/base/config.rb +99 -0
- data/lib/homeq/base/exception.rb +48 -0
- data/lib/homeq/base/histogram.rb +141 -0
- data/lib/homeq/base/logger.rb +185 -0
- data/lib/homeq/base/ohash.rb +297 -0
- data/lib/homeq/base/options.rb +171 -0
- data/lib/homeq/base/poolable.rb +100 -0
- data/lib/homeq/base/system.rb +446 -0
- data/lib/homeq/cli.rb +35 -0
- data/lib/homeq/cp/commands.rb +71 -0
- data/lib/homeq/cp/connection.rb +97 -0
- data/lib/homeq/cp/cp.rb +30 -0
- data/lib/homeq/cp/server.rb +105 -0
- data/lib/homeq/sobs/client.rb +119 -0
- data/lib/homeq/sobs/connection.rb +635 -0
- data/lib/homeq/sobs/foreman.rb +237 -0
- data/lib/homeq/sobs/job.rb +66 -0
- data/lib/homeq/sobs/message.rb +49 -0
- data/lib/homeq/sobs/queue.rb +224 -0
- data/lib/homeq/sobs/sender.rb +150 -0
- data/lib/homeq/sobs/server.rb +654 -0
- data/lib/homeq/sobs/sobs.rb +45 -0
- data/lib/homeq/sobs/topology.rb +111 -0
- data/lib/homeq.rb +106 -0
- data/lib/tasks/Rakefile +49 -0
- data/lib/tasks/database.rake +387 -0
- data/lib/tasks/gem.rake +9 -0
- data/lib/tasks/generate.rake +192 -0
- data/lib/tasks/hq.rake +171 -0
- data/lib/tasks/testing.rake +95 -0
- data/lib/tasks/utility.rb +17 -0
- data/script/console.rb +45 -0
- data/script/generate +7 -0
- data/test/unittest.rb +51 -0
- metadata +222 -0
@@ -0,0 +1,654 @@
|
|
1
|
+
#############################################################################
|
2
|
+
#
|
3
|
+
# $Id: server.rb 48 2008-11-19 05:11:59Z colin $
|
4
|
+
#
|
5
|
+
# Author:: Colin Steele (colin@colinsteele.org)
|
6
|
+
# Homepage::
|
7
|
+
#
|
8
|
+
# TODO: info
|
9
|
+
#
|
10
|
+
#----------------------------------------------------------------------------
|
11
|
+
#
|
12
|
+
# Copyright (C) 2008 by Colin Steele. All Rights Reserved.
|
13
|
+
# colin@colinsteele.org
|
14
|
+
#
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
19
|
+
#
|
20
|
+
# See the file COPYING for complete licensing information.
|
21
|
+
#
|
22
|
+
#---------------------------------------------------------------------------
|
23
|
+
#
|
24
|
+
#
|
25
|
+
#############################################################################
|
26
|
+
|
27
|
+
module HomeQ
|
28
|
+
|
29
|
+
module SOBS
|
30
|
+
|
31
|
+
######################################################################
|
32
|
+
# Server Job
|
33
|
+
######################################################################
|
34
|
+
#
|
35
|
+
# This class is the server-side representation of a message, or a
|
36
|
+
# "job" - a piece of work that somebody sent, that somebody will
|
37
|
+
# eventually pick up and do something with.
|
38
|
+
#
|
39
|
+
# On the server side, the job lifecycle looks like this:
|
40
|
+
#
|
41
|
+
# put with delay release with delay
|
42
|
+
# ----------------> [DELAYED] <------------.
|
43
|
+
# | |
|
44
|
+
# |(time passes) |
|
45
|
+
# | |
|
46
|
+
# put v reserve | delete
|
47
|
+
# -----------------> [READY] ---------> [RESERVED] --------> *poof*
|
48
|
+
# ^ ^ | |
|
49
|
+
# | \ release | |
|
50
|
+
# | `---------------' |
|
51
|
+
# | |
|
52
|
+
# | kick |
|
53
|
+
# | |
|
54
|
+
# | bury |
|
55
|
+
# [BURIED] <----------------'
|
56
|
+
# |
|
57
|
+
# | delete
|
58
|
+
# `--------> *poof*
|
59
|
+
#
|
60
|
+
# In the simplest case, jobs go from start to ready to reserved to
|
61
|
+
# *poof*.
|
62
|
+
#
|
63
|
+
class ServerJob < SOBS::Job
|
64
|
+
|
65
|
+
# The Foreman watches jobs
|
66
|
+
include Observable
|
67
|
+
|
68
|
+
# We have to carp.
|
69
|
+
include Base::Logging
|
70
|
+
|
71
|
+
# Object pool
|
72
|
+
include Poolable
|
73
|
+
pool_init(10000)
|
74
|
+
|
75
|
+
# Time To Run
|
76
|
+
attr :ttr, true
|
77
|
+
|
78
|
+
# How important? Not implemented.
|
79
|
+
attr :priority, true
|
80
|
+
|
81
|
+
# Delay N seconds before ready
|
82
|
+
attr :delay, true
|
83
|
+
|
84
|
+
# Connection working on this job
|
85
|
+
attr :reserving_connection
|
86
|
+
|
87
|
+
# A count of the total number of jobs that have had a life in
|
88
|
+
# the system.
|
89
|
+
@@job_count = 0
|
90
|
+
|
91
|
+
# Create a new ServerJob. Called by Foreman#create_job.
|
92
|
+
# A Job is based on receipt of a message, from a source, and it
|
93
|
+
# is managed by a foreman. The message is assumed to have three
|
94
|
+
# args: priority (ignored), delay, and ttr (time to run).
|
95
|
+
def initialize(message, source)
|
96
|
+
logger.debug4 {
|
97
|
+
'INIT'
|
98
|
+
}
|
99
|
+
super(message, source)
|
100
|
+
@@job_count += 1
|
101
|
+
@priority, @delay, @ttr = @message.args.collect { |arg|
|
102
|
+
arg.to_i
|
103
|
+
}
|
104
|
+
|
105
|
+
# Create a state machine to drive.
|
106
|
+
@state = Statemachine.build do
|
107
|
+
state :start do
|
108
|
+
event :put, :ready
|
109
|
+
event :put_with_delay, :delayed
|
110
|
+
end
|
111
|
+
state :delayed do
|
112
|
+
on_entry :start_delay_timer
|
113
|
+
event :delay_timeout, :ready
|
114
|
+
end
|
115
|
+
state :ready do
|
116
|
+
on_entry :broadcast_change
|
117
|
+
event :reserve, :reserved
|
118
|
+
end
|
119
|
+
state :reserved do
|
120
|
+
on_entry :start_run_timer
|
121
|
+
event :delete, :deleted
|
122
|
+
event :release, :ready
|
123
|
+
event :run_timeout, :ready
|
124
|
+
event :release_with_delay, :delayed
|
125
|
+
event :bury, :buried
|
126
|
+
on_exit :exit_reserved_state
|
127
|
+
end
|
128
|
+
state :buried do
|
129
|
+
on_entry :broadcast_change
|
130
|
+
event :delete, :deleted
|
131
|
+
event :kick, :ready
|
132
|
+
end
|
133
|
+
state :deleted do
|
134
|
+
on_entry :broadcast_change
|
135
|
+
end
|
136
|
+
end
|
137
|
+
@state.context = self
|
138
|
+
end
|
139
|
+
|
140
|
+
def deinitialize
|
141
|
+
logger.debug4 {
|
142
|
+
'DEINIT'
|
143
|
+
}
|
144
|
+
# first take care of superclass's vars
|
145
|
+
@message = @queue = @job_id = nil
|
146
|
+
|
147
|
+
# now ours
|
148
|
+
instance_variables.each {|v|
|
149
|
+
next if v == "@state" # keeper. largely the point of recycling
|
150
|
+
next if v =~ /observer/ # keeper. our observer(s)
|
151
|
+
eval "#{v} = nil"
|
152
|
+
}
|
153
|
+
@state.state = :start
|
154
|
+
end
|
155
|
+
|
156
|
+
def reinitialize(message, source)
|
157
|
+
# Call super's initialize().
|
158
|
+
SOBS::Job.instance_method(:initialize).bind(self).call(message,source)
|
159
|
+
@@job_count += 1
|
160
|
+
@priority, @delay, @ttr = @message.args.collect { |arg|
|
161
|
+
arg.to_i
|
162
|
+
}
|
163
|
+
logger.debug4 {
|
164
|
+
"REINIT #{@state.state}"
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
def run
|
169
|
+
logger.debug4 {
|
170
|
+
'LAUNCH'
|
171
|
+
}
|
172
|
+
broadcast_change
|
173
|
+
if delay > 0
|
174
|
+
put_with_delay
|
175
|
+
else
|
176
|
+
put
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Takes this job from the start state to ready.
|
181
|
+
def put
|
182
|
+
@state.put
|
183
|
+
end
|
184
|
+
|
185
|
+
# Takes this job from the start state to delayed. As a
|
186
|
+
# consequence, with kick off a timer that will move this job
|
187
|
+
# from delayed to ready when it expires.
|
188
|
+
def put_with_delay
|
189
|
+
@state.put_with_delay
|
190
|
+
end
|
191
|
+
|
192
|
+
# Delete this job (*poof*). A 'deleted' reply is sent to the
|
193
|
+
# connection that sent the delete (which may or may not be the
|
194
|
+
# connection that reserved the job in the first place).
|
195
|
+
def delete(conn)
|
196
|
+
conn.deleted(job_id) # may or may not be @reserving_connection
|
197
|
+
@state.delete
|
198
|
+
end
|
199
|
+
|
200
|
+
# Mark this job as being processed. The reserving connection is
|
201
|
+
# sent a 'reserved' reply along with the contents of this job.
|
202
|
+
# We store a notion of the reserving connection, and push this
|
203
|
+
# job from the 'ready' state to the 'reserved' state.
|
204
|
+
def reserve(conn)
|
205
|
+
@state.reserve
|
206
|
+
conn.reserved(job_id, payload)
|
207
|
+
@reserving_connection = conn
|
208
|
+
end
|
209
|
+
|
210
|
+
# Take this job out of the "being worked on" state and back to
|
211
|
+
# availability in the 'ready' state.
|
212
|
+
def release
|
213
|
+
@state.release
|
214
|
+
end
|
215
|
+
|
216
|
+
# Take this job out of the "being worked on" state and back to
|
217
|
+
# availability in the 'ready' state, but after going through a
|
218
|
+
# 'delayed' state. We kick off a timer that moves the job from
|
219
|
+
# 'delayed' to 'ready' when it expires.
|
220
|
+
def release_with_delay(priority, delay)
|
221
|
+
@priority = priority
|
222
|
+
@delay = delay
|
223
|
+
@state.release_with_delay
|
224
|
+
end
|
225
|
+
|
226
|
+
# Push this job off to the side so it is not available to be
|
227
|
+
# worked on.
|
228
|
+
def bury(conn)
|
229
|
+
conn.buried(job_id) # may or may not be @reserving_connection
|
230
|
+
@state.bury
|
231
|
+
end
|
232
|
+
|
233
|
+
# Move this job from the 'buried' state (see #bury, above) to
|
234
|
+
# the 'ready' state.
|
235
|
+
def kick
|
236
|
+
@state.kick
|
237
|
+
end
|
238
|
+
|
239
|
+
protected
|
240
|
+
|
241
|
+
def start_delay_timer
|
242
|
+
broadcast_change
|
243
|
+
@delay_timer = EventMachine::Timer.new(@delay.to_i) do
|
244
|
+
@delay_timer = nil
|
245
|
+
@state.delay_timeout
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def start_run_timer
|
250
|
+
broadcast_change
|
251
|
+
return if @ttr < 1
|
252
|
+
start_deadline_soon_timer
|
253
|
+
@run_timer = EventMachine::Timer.new(@ttr.to_i) do
|
254
|
+
logger.info {
|
255
|
+
peer = @reserving_connection.remote_endpoint
|
256
|
+
"Job #{job_id} run timeout on SOBS Peer #{peer}."
|
257
|
+
}
|
258
|
+
@run_timer = nil
|
259
|
+
@state.run_timeout
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def start_deadline_soon_timer
|
264
|
+
@deadline_soon_timer = EventMachine::Timer.new(@ttr - 1) do
|
265
|
+
@deadline_soon_timer = nil
|
266
|
+
if !@reserving_connection
|
267
|
+
msg = "Logic error: nil reserving connection."
|
268
|
+
Base::System.instance.die(msg)
|
269
|
+
end
|
270
|
+
if @reserving_connection.state.state == :open
|
271
|
+
@reserving_connection.deadline_soon(job_id)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def exit_reserved_state
|
277
|
+
@run_timer.cancel if @run_timer
|
278
|
+
@deadline_soon_timer.cancel if @deadline_soon_timer
|
279
|
+
@deadline_soon_timer = nil
|
280
|
+
@run_timer = nil
|
281
|
+
@reserving_connection = nil
|
282
|
+
end
|
283
|
+
|
284
|
+
def broadcast_change
|
285
|
+
changed
|
286
|
+
notify_observers(self, @state.state)
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
######################################################################
|
293
|
+
# Server Connection
|
294
|
+
######################################################################
|
295
|
+
#
|
296
|
+
# This class represents the server-side of a connection between a
|
297
|
+
# SOBS client and the SOBS server.
|
298
|
+
#
|
299
|
+
class ServerConnection < SOBS::Connection
|
300
|
+
|
301
|
+
# This connection's state
|
302
|
+
attr :server_state, false
|
303
|
+
|
304
|
+
# Job handler
|
305
|
+
attr :handler, true
|
306
|
+
|
307
|
+
# Create a new ServerConnection, which is a child of SOBS::Connection.
|
308
|
+
# Called from Server#start via EventMachine. When an incoming
|
309
|
+
# TCP connection is received, a ServerConnection is created to
|
310
|
+
# handle it.
|
311
|
+
def initialize
|
312
|
+
super
|
313
|
+
|
314
|
+
# The state machine that drives our behavior.
|
315
|
+
|
316
|
+
@server_state = Statemachine.build do
|
317
|
+
state :start do
|
318
|
+
event :open, :open
|
319
|
+
event :close, :closed
|
320
|
+
end
|
321
|
+
state :waiting_for_job do
|
322
|
+
on_entry :reserve_ready_job
|
323
|
+
event :sent_job, :open
|
324
|
+
event :reserve, :waiting_for_job
|
325
|
+
event :received_job, :waiting_for_job, :reserve_ready_job
|
326
|
+
event :subscribe, :waiting_for_job
|
327
|
+
event :unsubscribe, :open
|
328
|
+
event :close, :closed
|
329
|
+
end
|
330
|
+
state :open do
|
331
|
+
event :subscribe, :waiting_for_job
|
332
|
+
event :unsubscribe, :open
|
333
|
+
event :reserve, :waiting_for_job
|
334
|
+
event :received_job, :open
|
335
|
+
event :close, :closed
|
336
|
+
end
|
337
|
+
state :closed do
|
338
|
+
event :close, :closed
|
339
|
+
end
|
340
|
+
end
|
341
|
+
@server_state.context = self
|
342
|
+
|
343
|
+
# stats
|
344
|
+
@jobs_received = 0
|
345
|
+
|
346
|
+
# An attribute of this connection. If true, the client has
|
347
|
+
# indicated that they want to be sent a ready job whenever
|
348
|
+
# it's available. Otherwise, they must ask for each job via
|
349
|
+
# the 'reserve' command.
|
350
|
+
@subscribed = nil
|
351
|
+
end
|
352
|
+
|
353
|
+
#--
|
354
|
+
# SOBS::Connection entry points
|
355
|
+
#++
|
356
|
+
|
357
|
+
# Called from the lower-level Connection abstraction when a
|
358
|
+
# client connection is complete and ready to use.
|
359
|
+
def opened
|
360
|
+
@server_state.open
|
361
|
+
end
|
362
|
+
|
363
|
+
# Called from the lower-level Connection abstraction when a
|
364
|
+
# client connection is dead.
|
365
|
+
def closed
|
366
|
+
logger.info {
|
367
|
+
"Job pool size: #{ServerJob.pool.size}"
|
368
|
+
}
|
369
|
+
@server_state.close
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
#--
|
374
|
+
# High level message interface - sending
|
375
|
+
#++
|
376
|
+
|
377
|
+
# Send a 'reserved' message to the remote endpoint, indicating
|
378
|
+
# that it's now in charge of doing the indicated piece of work.
|
379
|
+
# Track the server state appropriately (switch state to 'open'
|
380
|
+
# unless the remote endpoint has subscribed).
|
381
|
+
def reserved(job_id, payload)
|
382
|
+
super(job_id, payload) # send the msg to the client
|
383
|
+
@server_state.sent_job unless @subscribed
|
384
|
+
end
|
385
|
+
|
386
|
+
#--
|
387
|
+
# High level message interface - receiving
|
388
|
+
#++
|
389
|
+
|
390
|
+
# Handle a 'put' message from the remote endoint. We create a
|
391
|
+
# message and reply to let the other side know we got it.
|
392
|
+
def receive_put(message)
|
393
|
+
if !ok_to_receive?
|
394
|
+
logger.error {
|
395
|
+
"Connection not ready for 'put'"
|
396
|
+
}
|
397
|
+
close
|
398
|
+
return
|
399
|
+
end
|
400
|
+
if message.args.length != 5
|
401
|
+
logger.error {
|
402
|
+
"Malformed put from SOBS peer #{remote_endpoint}. " +
|
403
|
+
"Closing connection."
|
404
|
+
}
|
405
|
+
close
|
406
|
+
return
|
407
|
+
end
|
408
|
+
create_job(message)
|
409
|
+
@server_state.received_job
|
410
|
+
end
|
411
|
+
|
412
|
+
# Handle a 'reserve' message from the client, which says that
|
413
|
+
# they're ready for work to be sent to them.
|
414
|
+
def receive_reserve(message)
|
415
|
+
if !ok_to_receive?
|
416
|
+
logger.error {
|
417
|
+
"Connction not ready to 'receive'"
|
418
|
+
}
|
419
|
+
return
|
420
|
+
end
|
421
|
+
@server_state.reserve
|
422
|
+
end
|
423
|
+
|
424
|
+
# Handle a 'delete' message from the client, which says that
|
425
|
+
# they've completed the work. We let the foreman know.
|
426
|
+
def receive_delete(message)
|
427
|
+
ok_to_receive? || return
|
428
|
+
@server.foreman.delete_job(self, message.args[0])
|
429
|
+
end
|
430
|
+
|
431
|
+
# Handle a 'release' message from the client, which says that
|
432
|
+
# they're not going to finish (or delete) the work, and to make
|
433
|
+
# it available for someone else to work on.
|
434
|
+
def receive_release(message)
|
435
|
+
ok_to_receive? || return
|
436
|
+
@server.foreman.release_job(self,
|
437
|
+
message.args[0],
|
438
|
+
message.args[1],
|
439
|
+
message.args[2])
|
440
|
+
end
|
441
|
+
|
442
|
+
# Handle a 'bury' message from the client, which says that
|
443
|
+
# the job shouldn't be available for anyone.
|
444
|
+
def receive_bury(message)
|
445
|
+
ok_to_receive? || return
|
446
|
+
@server.foreman.bury_job(self, message.args[0])
|
447
|
+
end
|
448
|
+
|
449
|
+
# Handle a 'kick' message from the client, which move some
|
450
|
+
# number of buried jobs to the 'ready' state.
|
451
|
+
def receive_kick(message)
|
452
|
+
ok_to_receive? || return
|
453
|
+
@server.foreman.kick(self, message.args[0])
|
454
|
+
end
|
455
|
+
|
456
|
+
# Handle a 'subscribe' message from the client, which tells us
|
457
|
+
# that they want to be issued work to do whenever it's available.
|
458
|
+
def receive_subscribe(message)
|
459
|
+
@subscribed = true
|
460
|
+
@server_state.subscribe
|
461
|
+
end
|
462
|
+
|
463
|
+
# Handle a 'unsubscribe' message from the client, which tells us
|
464
|
+
# that they want to send explicit requests for more work via
|
465
|
+
# 'reserve' commands.
|
466
|
+
def receive_unsubscribed(message)
|
467
|
+
@subscribed = false
|
468
|
+
@server_state.unsubscribed
|
469
|
+
end
|
470
|
+
|
471
|
+
#--
|
472
|
+
# Other
|
473
|
+
#++
|
474
|
+
|
475
|
+
def open
|
476
|
+
super
|
477
|
+
@server_state.open
|
478
|
+
end
|
479
|
+
|
480
|
+
def unknown_message(command_name)
|
481
|
+
logger.info {
|
482
|
+
"SOBS peer #{remote_endpoint} sent unknown/unsupported " +
|
483
|
+
"command: #{command_name}"
|
484
|
+
}
|
485
|
+
close
|
486
|
+
end
|
487
|
+
|
488
|
+
def release_job(job)
|
489
|
+
end
|
490
|
+
|
491
|
+
def to_s
|
492
|
+
str = ""
|
493
|
+
str << "#{self.class} state: #{server_state.state} "
|
494
|
+
str << "subscribed?: #{@subscribed ? 'Y' : 'N'}\n"
|
495
|
+
str << "jobs in: #{@jobs_received} "
|
496
|
+
str << "job pool size: #{ServerJob.pool ? ServerJob.pool.size : '?'}\n"
|
497
|
+
str << super.gsub(/\n/m, "\n ")
|
498
|
+
end
|
499
|
+
|
500
|
+
protected
|
501
|
+
|
502
|
+
def reserve_ready_job
|
503
|
+
@server.foreman.dole_out_jobs
|
504
|
+
end
|
505
|
+
|
506
|
+
def create_job(message)
|
507
|
+
logger.debug4 {
|
508
|
+
"Server handler: #{@handler}"
|
509
|
+
}
|
510
|
+
if @handler
|
511
|
+
if @handler.respond_to?(:call)
|
512
|
+
@handler.call(message, self)
|
513
|
+
elsif @handler.respond_to?(:create_job)
|
514
|
+
@handler.create_job(message, self)
|
515
|
+
elsif @handler.respond_to?(:new)
|
516
|
+
job = @handler.new(message, self)
|
517
|
+
if job.respond_to?(:run)
|
518
|
+
job.run
|
519
|
+
end
|
520
|
+
end
|
521
|
+
else
|
522
|
+
@jobs_received += 1
|
523
|
+
@server.foreman.create_job(message, self)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
def get_next_message
|
528
|
+
reserve
|
529
|
+
end
|
530
|
+
|
531
|
+
end # ServerConnection
|
532
|
+
|
533
|
+
|
534
|
+
######################################################################
|
535
|
+
# Server / Listener class
|
536
|
+
######################################################################
|
537
|
+
|
538
|
+
class Server
|
539
|
+
|
540
|
+
DEFAULT_PORT = 56000
|
541
|
+
DEFAULT_HOST = 'localhost'
|
542
|
+
|
543
|
+
include Base::Logging
|
544
|
+
include Base::Configuration
|
545
|
+
|
546
|
+
attr_accessor :connections
|
547
|
+
attr :foreman, true
|
548
|
+
attr :queue_name, true
|
549
|
+
attr :total_connections, false
|
550
|
+
|
551
|
+
# Make our config available
|
552
|
+
module HomeQ::Base::Commando::InstanceMethods
|
553
|
+
config_accessor :queue_name
|
554
|
+
end
|
555
|
+
|
556
|
+
def self.create_home_queue(queuename, handler=nil)
|
557
|
+
logger = HomeQ::Base::Logging::Logger.instance.logger
|
558
|
+
config = HomeQ::Base::Configuration::Configuration.instance
|
559
|
+
if queuename =~ /^__/
|
560
|
+
return
|
561
|
+
elsif !config.topology[queuename]
|
562
|
+
Base::System.instance.die("No queue info for '#{queuename}'")
|
563
|
+
end
|
564
|
+
logger.info {
|
565
|
+
"Starting home queue '#{queuename}'" +
|
566
|
+
(handler ? ", handler: #{handler}" : '')
|
567
|
+
}
|
568
|
+
q, host, port, hostname = config.topology[queuename]
|
569
|
+
server = HomeQ::SOBS::Server.new(host, port, queuename, handler)
|
570
|
+
Base::System.instance.servers << server
|
571
|
+
end
|
572
|
+
|
573
|
+
def initialize(host=nil, port=nil, queuename=nil, handler=nil)
|
574
|
+
@host = host
|
575
|
+
@port = port
|
576
|
+
@connections = []
|
577
|
+
@foreman = nil
|
578
|
+
@queue_name = queuename
|
579
|
+
@total_connections = 0
|
580
|
+
@started_at = nil
|
581
|
+
@stopped_at = nil
|
582
|
+
@handler = handler
|
583
|
+
end
|
584
|
+
|
585
|
+
def start
|
586
|
+
@started_at = Time.now
|
587
|
+
@foreman ||= Foreman.new(self)
|
588
|
+
begin
|
589
|
+
@signature =
|
590
|
+
EventMachine.start_server(@host,
|
591
|
+
@port,
|
592
|
+
HomeQ::SOBS::ServerConnection) { |conn|
|
593
|
+
conn.server = self
|
594
|
+
conn.handler = @handler
|
595
|
+
@connections << conn
|
596
|
+
@total_connections += 1
|
597
|
+
}
|
598
|
+
rescue Exception => e
|
599
|
+
msg = "Error starting SOBS listener '#{@host}:#{@port}'." +
|
600
|
+
"Probably a config error. Original error: #{e.message}."
|
601
|
+
Base::System.instance.die(msg)
|
602
|
+
else
|
603
|
+
logger.info {
|
604
|
+
"SOBS server running on #{@host}:#{@port}"
|
605
|
+
}
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
def stop
|
610
|
+
@stopped_at = Time.now
|
611
|
+
EventMachine.stop_server(@signature)
|
612
|
+
unless wait_for_connections
|
613
|
+
# Still some running
|
614
|
+
EventMachine.add_periodic_timer(1) {
|
615
|
+
wait_for_connections
|
616
|
+
}
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def to_s
|
621
|
+
str = ''
|
622
|
+
str << "#{self.class} Queuename: #{@queue_name}"
|
623
|
+
str << " on #{@host}:#{@port}" if @host || @port
|
624
|
+
str << "\n"
|
625
|
+
str << "Started: #{@started_at} (#{Time.now - @started_at}s)\n"
|
626
|
+
str << "Total connections: #{@total_connections} "
|
627
|
+
str << "Active connections: #{connections.length}"
|
628
|
+
str << ("\n" + connections.join("\n")).gsub(/\n/m, "\n ")
|
629
|
+
str << ("\n" + @foreman.to_s).gsub(/\n/m, "\n ") if @foreman
|
630
|
+
str
|
631
|
+
end
|
632
|
+
|
633
|
+
protected
|
634
|
+
|
635
|
+
def wait_for_connections
|
636
|
+
if @connections.empty?
|
637
|
+
true
|
638
|
+
else
|
639
|
+
logger.info {
|
640
|
+
"Waiting for #{@connections.length} connections to finish."
|
641
|
+
}
|
642
|
+
@connections.each { |c|
|
643
|
+
c.close
|
644
|
+
}
|
645
|
+
false
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
end # Server
|
650
|
+
|
651
|
+
end # SOBS
|
652
|
+
|
653
|
+
end # HomeQ
|
654
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#############################################################################
|
2
|
+
#
|
3
|
+
# $Id: sobs.rb 42 2008-10-24 05:53:35Z colin $
|
4
|
+
#
|
5
|
+
# Author:: Colin Steele (colin@colinsteele.org)
|
6
|
+
# Homepage::
|
7
|
+
#
|
8
|
+
# TODO: info
|
9
|
+
#
|
10
|
+
#----------------------------------------------------------------------------
|
11
|
+
#
|
12
|
+
# Copyright (C) 2008 by Colin Steele. All Rights Reserved.
|
13
|
+
# colin@colinsteele.org
|
14
|
+
#
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
19
|
+
#
|
20
|
+
# See the file COPYING for complete licensing information.
|
21
|
+
#
|
22
|
+
#---------------------------------------------------------------------------
|
23
|
+
#
|
24
|
+
#
|
25
|
+
#############################################################################
|
26
|
+
|
27
|
+
require 'statemachine'
|
28
|
+
require 'observer'
|
29
|
+
require 'uuidtools'
|
30
|
+
|
31
|
+
require 'homeq/sobs/sender'
|
32
|
+
require 'homeq/sobs/connection'
|
33
|
+
require 'homeq/sobs/client'
|
34
|
+
require 'homeq/sobs/message'
|
35
|
+
require 'homeq/sobs/job'
|
36
|
+
require 'homeq/sobs/server'
|
37
|
+
require 'homeq/sobs/topology'
|
38
|
+
require 'homeq/sobs/queue'
|
39
|
+
require 'homeq/sobs/foreman'
|
40
|
+
|
41
|
+
module HomeQ
|
42
|
+
module SOBS
|
43
|
+
PROTOCOL_VERSION = 1
|
44
|
+
end
|
45
|
+
end
|