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.
Files changed (63) hide show
  1. data/CHANGELOG +103 -0
  2. data/COPYING +348 -0
  3. data/README.rdoc +64 -0
  4. data/Rakefile +131 -0
  5. data/bin/hq +6 -0
  6. data/config/boot.rb +224 -0
  7. data/config/databases/frontbase.yml +28 -0
  8. data/config/databases/mysql.yml +54 -0
  9. data/config/databases/oracle.yml +39 -0
  10. data/config/databases/postgresql.yml +48 -0
  11. data/config/databases/sqlite2.yml +16 -0
  12. data/config/databases/sqlite3.yml +19 -0
  13. data/config/environment.rb +20 -0
  14. data/config/environments/development.cfg +35 -0
  15. data/config/environments/production.cfg +35 -0
  16. data/config/environments/test.cfg +35 -0
  17. data/config/generators/job/templates/job.rb.erb +20 -0
  18. data/config/generators/message/templates/messages/MESSAGE.proto.erb +12 -0
  19. data/config/generators/model/templates/models/MODEL.rb.erb +3 -0
  20. data/config/generators/service/templates/services/SERVICE.rb.erb +43 -0
  21. data/config/homeq.cfg +35 -0
  22. data/extras/consumer.rb +85 -0
  23. data/extras/homeq.cfg +49 -0
  24. data/extras/hqd.rb +33 -0
  25. data/extras/producer.rb +79 -0
  26. data/extras/simple_consumer.rb +53 -0
  27. data/lib/homeq/base/base.rb +44 -0
  28. data/lib/homeq/base/commando.rb +81 -0
  29. data/lib/homeq/base/config.rb +99 -0
  30. data/lib/homeq/base/exception.rb +48 -0
  31. data/lib/homeq/base/histogram.rb +141 -0
  32. data/lib/homeq/base/logger.rb +185 -0
  33. data/lib/homeq/base/ohash.rb +297 -0
  34. data/lib/homeq/base/options.rb +171 -0
  35. data/lib/homeq/base/poolable.rb +100 -0
  36. data/lib/homeq/base/system.rb +446 -0
  37. data/lib/homeq/cli.rb +35 -0
  38. data/lib/homeq/cp/commands.rb +71 -0
  39. data/lib/homeq/cp/connection.rb +97 -0
  40. data/lib/homeq/cp/cp.rb +30 -0
  41. data/lib/homeq/cp/server.rb +105 -0
  42. data/lib/homeq/sobs/client.rb +119 -0
  43. data/lib/homeq/sobs/connection.rb +635 -0
  44. data/lib/homeq/sobs/foreman.rb +237 -0
  45. data/lib/homeq/sobs/job.rb +66 -0
  46. data/lib/homeq/sobs/message.rb +49 -0
  47. data/lib/homeq/sobs/queue.rb +224 -0
  48. data/lib/homeq/sobs/sender.rb +150 -0
  49. data/lib/homeq/sobs/server.rb +654 -0
  50. data/lib/homeq/sobs/sobs.rb +45 -0
  51. data/lib/homeq/sobs/topology.rb +111 -0
  52. data/lib/homeq.rb +106 -0
  53. data/lib/tasks/Rakefile +49 -0
  54. data/lib/tasks/database.rake +387 -0
  55. data/lib/tasks/gem.rake +9 -0
  56. data/lib/tasks/generate.rake +192 -0
  57. data/lib/tasks/hq.rake +171 -0
  58. data/lib/tasks/testing.rake +95 -0
  59. data/lib/tasks/utility.rb +17 -0
  60. data/script/console.rb +45 -0
  61. data/script/generate +7 -0
  62. data/test/unittest.rb +51 -0
  63. metadata +222 -0
@@ -0,0 +1,237 @@
1
+ #############################################################################
2
+ #
3
+ # $Id: foreman.rb 40 2008-10-23 14:20:06Z 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
+ # Job Manager
33
+ ######################################################################
34
+
35
+ class Foreman
36
+
37
+ MAX_JOBS_TO_DOLE_OUT_PER_TICK = 100
38
+
39
+ include Base::Logging
40
+
41
+ attr :server, true
42
+ attr :jobs, true
43
+
44
+ def initialize(server)
45
+ @serial_number = 0
46
+ @server = server
47
+ @jobs = {} # Each element is a [job, job_state, time] tuple
48
+ @states = {
49
+ :start => HomeQ::OHash.new,
50
+ :ready => HomeQ::OHash.new,
51
+ :deleted => HomeQ::OHash.new,
52
+ :delayed => HomeQ::OHash.new,
53
+ :reserved => HomeQ::OHash.new,
54
+ :buried => HomeQ::OHash.new,
55
+ }
56
+ @jobs_created = 0
57
+ @jobs_deleted = 0
58
+ @jobs_doled_out = 0
59
+ @histo = HomeQ::Histogram.new(0,10,0.5)
60
+ dole_out_jobs
61
+ end
62
+
63
+ def create_job(message, connection)
64
+ @jobs_created += 1
65
+ j = ServerJob.new(message, connection)
66
+ j.add_observer(self)
67
+ j.run
68
+ end
69
+
70
+ def update(job, state)
71
+ track_job_by_state(job, state) if job.job_id
72
+ case state
73
+ when :start
74
+ job.job_id = unique_job_id
75
+ @jobs[job.job_id] = [job, state, Time.now]
76
+ # call now b/c we now have a job_id
77
+ track_job_by_state(job, state)
78
+ job.queue.inserted(job.job_id, job.message.args[3])
79
+ when :deleted
80
+ @histo << (Time.now - @jobs[job.job_id][2])
81
+ @jobs.delete(job.job_id)
82
+ remove_job_from_all_states(job)
83
+ job.delete_observer(self)
84
+ job.recycle
85
+ @jobs_deleted += 1
86
+ when :ready
87
+ dole_out_jobs
88
+ when :delayed
89
+ when :reserved
90
+ when :buried
91
+ else
92
+ Base::System.instance.die("Unknown job state #{state}")
93
+ end
94
+ end
95
+
96
+ def release_job(conn, job_id, priority, delay)
97
+ j = job_id_in_state?(job_id, :reserved)
98
+ if !j
99
+ conn.not_found(job_id)
100
+ return
101
+ end
102
+ if delay.to_i > 0
103
+ j.release_with_delay(priority.to_i, delay.to_i)
104
+ else
105
+ j.release
106
+ end
107
+ conn.released(job_id)
108
+ end
109
+
110
+ def delete_job(conn, job_id)
111
+ [:buried, :reserved].each { |state|
112
+ j = job_id_in_state?(job_id, state)
113
+ if j
114
+ j.delete(conn)
115
+ return
116
+ end
117
+ }
118
+ conn.not_found(job_id)
119
+ end
120
+
121
+ def bury_job(conn, job_id)
122
+ j = job_id_in_state?(job_id, :reserved)
123
+ if j
124
+ j.bury(conn)
125
+ return
126
+ end
127
+ conn.not_found(job_id)
128
+ end
129
+
130
+ def kick(conn, max_jobs)
131
+ kicked = []
132
+ 0.upto(max_jobs.to_i - 1) { |i|
133
+ kicked << @states[:buried].shift
134
+ }
135
+ conn.kicked(kicked.length)
136
+ kicked.each { |jid,j|
137
+ @jobs[jid][0].kick
138
+ }
139
+ end
140
+
141
+ def dole_out_jobs
142
+ # To give the reactor (EM) loop a chance to actually run, we
143
+ # only send out a few jobs at a time.
144
+ 1.upto(MAX_JOBS_TO_DOLE_OUT_PER_TICK) do
145
+ return unless ready_jobs?
146
+ c = find_waiting_connection
147
+ return unless c
148
+ j = get_next_ready_job
149
+ @jobs_doled_out += 1
150
+ j.reserve(c)
151
+ end
152
+ EventMachine::next_tick {
153
+ dole_out_jobs
154
+ }
155
+ end
156
+
157
+ def to_s
158
+ str = "#{self.class} "
159
+ str << "Pool size: #{ServerJob.pool.size}\n"
160
+ str << "created: #{@jobs_created} deleted: #{@jobs_deleted} "
161
+ str << "doled out: #{@jobs_doled_out}\n"
162
+ @states.each { |state, oh|
163
+ str << "#{state.to_s.capitalize} jobs: #{oh.length}\n" if oh.length>0
164
+ }
165
+ str << @histo.report
166
+ str
167
+ end
168
+
169
+ protected
170
+
171
+ def ready_jobs?
172
+ @states[:ready].length > 0
173
+ end
174
+
175
+ def get_next_ready_job
176
+ if @states[:ready] && (@states[:ready].length > 0)
177
+ jid, j = @states[:ready].shift # pull it from the front of the dict
178
+ if j != @jobs[jid][0]
179
+ Base::System.instance.die("Foreman corrupted, bye.")
180
+ end
181
+ return j
182
+ end
183
+ end
184
+
185
+ def job_id_in_state?(jid, state)
186
+ jid = jid.to_sym
187
+ return false unless @jobs[jid][0]
188
+ if @states[state].length > 0
189
+ if @states[state].has_key?(jid)
190
+ return @jobs[jid][0]
191
+ end
192
+ end
193
+ false
194
+ end
195
+
196
+ def find_waiting_connection
197
+ waiters = @server.connections.find_all { |c|
198
+ c.server_state.state == :waiting_for_job
199
+ }
200
+ if waiters.any?
201
+ waiters[rand(waiters.length)]
202
+ end
203
+ end
204
+
205
+ def track_job_by_state(job, new_state)
206
+ remove_job_from_old_state(job)
207
+ @jobs[job.job_id][1] = new_state
208
+ @states[new_state].push(job.job_id, job)
209
+ end
210
+
211
+ def remove_job_from_old_state(job)
212
+ old_state = @jobs[job.job_id][1]
213
+ @states[old_state].delete(job.job_id)
214
+ end
215
+
216
+ def remove_job_from_all_states(job)
217
+ @states.each { |state_name,dict|
218
+ dict.delete(job.job_id) if dict.has_key?(job.job_id)
219
+ }
220
+ end
221
+
222
+ # Get a job id unique to this foreman
223
+ def unique_job_id
224
+ loop do
225
+ # This was the old way
226
+ # jid = UUID.timestamp_create.to_s.to_sym
227
+ jid = "#{@serial_number}".to_sym
228
+ @serial_number += 1
229
+ return jid if !@jobs.include?(jid)
230
+ end
231
+ end
232
+
233
+ end # Foreman
234
+
235
+ end # SOBS
236
+
237
+ end # HomeQ
@@ -0,0 +1,66 @@
1
+ #############################################################################
2
+ #
3
+ # $Id: job.rb 40 2008-10-23 14:20:06Z 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
+ class Job
32
+
33
+ include Base::Options
34
+ include Base::Configuration
35
+ include Base::Logging
36
+
37
+ attr :message, true
38
+ attr :queue, true
39
+ attr :job_id, true
40
+
41
+ def initialize(message, queue)
42
+ @queue = queue
43
+ @message = message
44
+ @job_id = @message.job_id
45
+ end
46
+
47
+ def payload
48
+ @message.payload
49
+ end
50
+
51
+ # Override here, and call super
52
+ def run
53
+ complete
54
+ end
55
+
56
+ protected
57
+
58
+ def complete
59
+ @queue.release_job(self) if @queue
60
+ end
61
+
62
+ end # Job
63
+
64
+ end # SOBS
65
+
66
+ end # HomeQ
@@ -0,0 +1,49 @@
1
+ #############################################################################
2
+ #
3
+ # $Id: message.rb 40 2008-10-23 14:20:06Z 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
+ class Message
32
+
33
+ attr :type, true
34
+ attr :job_id, true
35
+ attr :payload, true
36
+ attr :args, true
37
+
38
+ def initialize(message_type=nil, job_id=nil, payload=nil, args=nil)
39
+ @type = message_type
40
+ @job_id = job_id
41
+ @payload = payload
42
+ @args = args
43
+ end
44
+
45
+ end # Message
46
+
47
+ end # SOBS
48
+
49
+ end # HomeQ
@@ -0,0 +1,224 @@
1
+ #############################################################################
2
+ #
3
+ # $Id: queue.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
+ require 'observer'
28
+
29
+ module HomeQ
30
+
31
+ module SOBS
32
+
33
+ class Queue
34
+
35
+ include Observable
36
+ include Base::Logging
37
+ include Base::Configuration
38
+ include HomeQ
39
+
40
+ READ_ONLY = 'r'
41
+ WRITE_ONLY = 'w'
42
+ READ_WRITE = 'w+'
43
+ MODES = [READ_ONLY, WRITE_ONLY, READ_WRITE]
44
+
45
+ attr :queue_name, true
46
+ attr :host, true
47
+ attr :port, true
48
+ attr :mode, true
49
+ attr :args, true
50
+ attr :handler, true
51
+
52
+ # Make our config available
53
+ module HomeQ::Base::Commando::InstanceMethods
54
+ config_accessor :queue_retry
55
+ document_command "queue_retry [int]", "Get/set reconnect interval"
56
+ end
57
+
58
+ def self.[](queue_name)
59
+ sys.queues.find { |q|
60
+ q.queue_name == queue_name
61
+ }
62
+ end
63
+
64
+ def self.send(queue_name, data, app_data=nil, &block)
65
+ dest = Queue[queue_name]
66
+ unless dest
67
+ raise HomeQ::Base::UnknownQueue.new("Can't find queue #{queue_name}")
68
+ end
69
+ raise HomeQ::Base::ReadOnlyQueue unless dest.writeable?
70
+ dest.send(data, app_data, block)
71
+ end
72
+
73
+ def self.create_queues_from_topology(queuename)
74
+ config = HomeQ::Base::Configuration::Configuration.instance
75
+ q, host, port, hostname = config.topology[queuename]
76
+ config.topology.connections(q).each { |peer,mode,threshold|
77
+ raise "No config for queue '#{peer}'." unless config.topology[peer]
78
+ q, host, port = config.topology[peer]
79
+ queue = HomeQ::SOBS::Queue.new(q, host, port, mode, threshold)
80
+ Base::System.instance.queues << queue
81
+ }
82
+ end
83
+
84
+ def initialize(*args)
85
+ @queue_name, @host, @port, @mode, @threshold, @args = args
86
+ raise "Nil queue_name, probably a config error" unless @queue_name
87
+
88
+ @conn = nil
89
+ @retry_timer = config.queue_retry || 5
90
+ @acks = {}
91
+
92
+ # stats
93
+
94
+ @concurrent_outstanding_jobs = 0
95
+ @concurrent_outstanding_jobs_highwater = 0
96
+ @jobs_received = 0
97
+
98
+ end
99
+
100
+ def start
101
+ logger.info {
102
+ "Initiating connection to queue '#{@queue_name}' " +
103
+ "at #{@host}:#{@port}, mode '#{@mode}', " +
104
+ "handler #{(@handler || Job)}."
105
+ }
106
+ Client.connect(host, port, config.queue_retry || 5, nil, self)
107
+ end
108
+
109
+ def closed
110
+ if @conn
111
+ logger.info {
112
+ "Disconnected from queue '#{queue_name}' " +
113
+ "on #{@conn.remote_endpoint}."
114
+ }
115
+ @started_at = nil
116
+ @conn = nil
117
+ end
118
+ broadcast_change
119
+ end
120
+
121
+ def opened(conn)
122
+ @conn = conn
123
+ @conn.refuse_send_threshold = @threshold if @threshold
124
+ @started_at = Time.now
125
+ logger.info {
126
+ "Connected to '#{queue_name}' on #{conn.remote_endpoint}."
127
+ }
128
+ broadcast_change
129
+ end
130
+
131
+ def stop
132
+ @conn.close_connection_after_writing if @conn
133
+ end
134
+
135
+ def release_job(job)
136
+ @conn.delete(job.job_id)
137
+ @concurrent_outstanding_jobs -= 1
138
+ end
139
+
140
+ def ack(message)
141
+ app_data = message.args[1]
142
+ if @acks[app_data.to_sym]
143
+ @acks[app_data.to_sym].call(app_data)
144
+ @acks.delete(app_data.to_sym)
145
+ end
146
+ end
147
+
148
+ def deleted(message)
149
+ end
150
+
151
+ def create_job(message)
152
+ @jobs_received += 1
153
+ @concurrent_outstanding_jobs += 1
154
+ if @concurrent_outstanding_jobs > 100
155
+ logger.warn {
156
+ "Outstanding jobs: #{@concurrent_outstanding_jobs}"
157
+ }
158
+ end
159
+ set_jobs_highwater
160
+ j = (@handler || Job).new(message, self)
161
+ j.run if j.respond_to?(:run)
162
+ end
163
+
164
+ def set_jobs_highwater
165
+ @concurrent_outstanding_jobs_highwater = [
166
+ @concurrent_outstanding_jobs_highwater,
167
+ @concurrent_outstanding_jobs
168
+ ].max
169
+ end
170
+
171
+ def send(data, app_data=nil, proc=nil)
172
+ return if !@conn || @conn.state.state != :open
173
+ app_data ||= generate_app_data
174
+ @acks[app_data.to_s.to_sym] = proc if proc
175
+ @conn.put(:body=>data, :app_data=>app_data)
176
+ end
177
+
178
+ def writeable?
179
+ mode != READ_ONLY
180
+ end
181
+
182
+ def readable?
183
+ mode != WRITE_ONLY
184
+ end
185
+
186
+ def connected?
187
+ @conn ? true : false
188
+ end
189
+
190
+ protected
191
+
192
+ # A unique id
193
+ def generate_app_data
194
+ UUID.timestamp_create.to_sym
195
+ end
196
+
197
+ # Tell folks something.
198
+ def broadcast_change
199
+ changed
200
+ notify_observers(self)
201
+ end
202
+
203
+ # String representation of queue
204
+ def to_s
205
+ str = ''
206
+ str << "Queuename: #{@queue_name}"
207
+ str << " on #{@host}:#{@port}" if @host || @port
208
+ str << " mode #{@mode}"
209
+ str << " handler #{(@handler || Job)}"
210
+ str << "\n"
211
+ str << "Jobs rcvd: #{@jobs_received}, "
212
+ str << " highwater: #{@concurrent_outstanding_jobs_highwater}"
213
+ str << "\n"
214
+ if connected?
215
+ str << @conn.to_s
216
+ end
217
+ str
218
+ end
219
+
220
+ end # Queue
221
+
222
+ end # SOBS
223
+
224
+ end # HomeQ