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,635 @@
1
+ #############################################################################
2
+ #
3
+ # $Id: connection.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 'socket'
28
+
29
+ module HomeQ
30
+
31
+ module SOBS
32
+
33
+ class Connection < EventMachine::Connection
34
+
35
+ HEADER_REGEX = /([^\r\n]+?)\r\n/m
36
+ PUSH_TIMER_LENGTH = 1
37
+ HELLO_TIMER_LENGTH = 5
38
+ OUTBOUND_MAX = 1024 * 64 # 64 KB
39
+ REFUSE_SEND_THRESHOLD = 1024 * 1024 # 1 MB
40
+
41
+ # Shouldn't make any difference to throughput, but a low(ish)
42
+ # number should smooth out latency.
43
+ MAX_INPUT_MSGS_PER_READ_CYCLE = 2
44
+
45
+ include Base::Logging
46
+ include Sender
47
+ include HomeQ
48
+
49
+ attr :server, true # our server/listener
50
+ attr :peer, false
51
+ attr :state, false
52
+ attr :outbound_max, true
53
+ attr :refuse_send_threshold, true
54
+
55
+ ######################################################################
56
+ # EventMachine::Connection entry points
57
+ ######################################################################
58
+
59
+ def post_init
60
+ @state = Statemachine.build do
61
+ state :start do
62
+ event :connect, :waiting_for_hello, :hello # send hello
63
+ event :close, :closing
64
+ event :unbind, :closed
65
+ end
66
+ state :waiting_for_hello do
67
+ on_entry :wait_for_hello
68
+ on_exit :cancel_hello_timer
69
+ event :close, :closing
70
+ event :good_hello, :open
71
+ event :bad_hello, :closing
72
+ event :hello_timeout, :closing
73
+ event :unbind, :closed
74
+ end
75
+ state :open do
76
+ on_entry :opened
77
+ event :close, :closing
78
+ event :unbind, :closed
79
+ end
80
+ state :closing do
81
+ on_entry :start_closing
82
+ event :unbind, :closed
83
+ event :good_hello, :closing
84
+ event :bad_hello, :closing
85
+ event :hello_timeout, :closing
86
+ event :close, :closing
87
+ end
88
+ state :closed do
89
+ on_entry :closed
90
+ event :close, :closed
91
+ end
92
+ end
93
+ @state.context = self
94
+
95
+ @hello_timer = nil
96
+ @data = ""
97
+ @scanner = StringScanner.new(@data)
98
+ @peer = nil
99
+ @outgoing = []
100
+
101
+ # Stats
102
+
103
+ @rcvs = 0
104
+ @bytes_in = 0
105
+ @bytes_out = 0
106
+ @msgs_in = 0
107
+ @msgs_out = 0
108
+ @rcv_stats = {}
109
+ @snd_stats = {}
110
+ @opened_at = nil
111
+ @closed_at = nil
112
+ @outbound_bytes_highwater = 0
113
+
114
+ # Congestion
115
+
116
+ @congested = false
117
+ @refused_send = 0
118
+ @outbound_max ||= OUTBOUND_MAX
119
+ @refuse_send_threshold ||= REFUSE_SEND_THRESHOLD
120
+
121
+ setup_peer
122
+ @state.connect if peer
123
+
124
+ end
125
+
126
+
127
+ # NB: EM does NOT call this for passive (server) connections
128
+ def connection_completed
129
+ @opened_at = Time.now
130
+ end
131
+
132
+ # Called by EM when data is recieved from the peer
133
+ def receive_data data
134
+ return if (@state.state == :closed || @state.state == :closing)
135
+ logger.debug3 {
136
+ "Receive data callback."
137
+ }
138
+ @rcvs += 1
139
+ @bytes_in += data.length
140
+ @data << data
141
+ process_data
142
+ end
143
+
144
+ # Called by EM when the connection goes away.
145
+ def unbind
146
+ @closed_at = Time.now
147
+ if peer
148
+ logger.info {
149
+ "Connection has terminated to sobs peer #{remote_endpoint}."
150
+ }
151
+ end
152
+ if @opened_at
153
+ logger.info {
154
+ "Open for #{'%6.4fs' % (@closed_at - @opened_at)}. "+
155
+ "In: #{@msgs_in}. Out: #{@msgs_out}. "+
156
+ "IO/sec: #{io_per_sec}"
157
+ }
158
+ end
159
+ @data = '' # drop anything we were doing for the peer.
160
+ @state.unbind
161
+ server.connections.delete(self) if server
162
+ end
163
+
164
+ ######################################################################
165
+ # Message parsing routines
166
+ ######################################################################
167
+
168
+ # Do some parsing, and then, if needed, schedule a block to
169
+ # parse some more the next spin of the reactor.
170
+ def process_data
171
+ parse_incoming_data
172
+ logger.debug4 {
173
+ "check if callback needed? " +
174
+ "needed: #{@need_next_tick_parse.inspect} && " +
175
+ "ticker: #{@process_ticker.inspect}"
176
+ }
177
+ if @need_next_tick_parse && !@ticker
178
+ EventMachine::next_tick {
179
+ logger.debug3 {
180
+ "Next tick parse callback."
181
+ }
182
+ @process_ticker = false
183
+ process_data
184
+ }
185
+ @process_ticker = true
186
+ logger.debug3 {
187
+ "Scheduled tick parse callback."
188
+ }
189
+ end
190
+ @need_next_tick_parse = false
191
+ end
192
+
193
+ def parse_incoming_data
194
+ if outbound_maxed_out?
195
+ # No more. Please. Wait to gulp more down until we've spit
196
+ # some out.
197
+ logger.debug3 {
198
+ "Short circuit deferring parsing incoming data until next tick. " +
199
+ "'#{@data[0..20]}' #{@data.length}"
200
+ }
201
+ @need_next_tick_parse = true
202
+ return
203
+ end
204
+ msgs_this_spin = 0
205
+ logger.debug4 {
206
+ '<-- ' + @data.inspect
207
+ }
208
+ @scanner.pos = 0
209
+ while (header = @scanner.scan(HEADER_REGEX)) do
210
+ len = @data.length
211
+ parse_header(header)
212
+ if len == @data.length
213
+ break # it didn't take anything out, so there's not enough
214
+ end
215
+
216
+ logger.debug3 {
217
+ "<<<< #{header.inspect}"
218
+ }
219
+
220
+ @msgs_in += 1
221
+ msgs_this_spin += 1
222
+
223
+ logger.debug3 {
224
+ "Msgs in: #{@msgs_in}"
225
+ }
226
+
227
+ # We've done enough work this turn of the reactor. If we're
228
+ # closed/closing and we still have data we haven't handled,
229
+ # keep going. Otherwise, go do some IO.
230
+
231
+ if (msgs_this_spin == MAX_INPUT_MSGS_PER_READ_CYCLE &&
232
+ @data.length > 0 &&
233
+ (@state.state != :closed || @state.state != :closing))
234
+ logger.debug3 {
235
+ "Max input msgs per read cycle. " +
236
+ "Deferring parsing incoming data until next tick. " +
237
+ "'#{@data[0..20]}' #{@data.class} #{@data.length}"
238
+ }
239
+ @scanner = StringScanner.new(@data)
240
+ @need_next_tick_parse = true
241
+ return
242
+ end
243
+ logger.debug4 {
244
+ "BUF NOW: '#{@data}'"
245
+ }
246
+ @scanner.pos = 0
247
+ end
248
+ end
249
+
250
+ def parse_header(header_data)
251
+ elements = header_data.split(/\s/)
252
+ command_name = elements[0].downcase
253
+ if self.respond_to?("parse_#{command_name}")
254
+ self.send("parse_#{command_name}", elements[1..-1])
255
+ else
256
+ unknown_message(command_name)
257
+ @data.slice!(0, @scanner.pos)
258
+ end
259
+ end
260
+
261
+ NO_ARG_MESSAGES = [
262
+ 'not_ignored',
263
+ 'unknown_command',
264
+ 'internal_error',
265
+ 'out_of_memory',
266
+ 'draining',
267
+ 'bad_format',
268
+ 'job_too_big',
269
+ 'timed_out',
270
+ 'reserve',
271
+ 'subscribe',
272
+ 'unsubscribe'
273
+ ]
274
+ NO_ARG_MESSAGES.each { |method_name|
275
+ class_eval do
276
+ define_method "parse_#{method_name}" do |args|
277
+ m = Message.new(method_name)
278
+ receive_message(m)
279
+ @data.slice!(0, @scanner.pos)
280
+ end
281
+ end
282
+ }
283
+
284
+
285
+ ARG_MESSAGES = [
286
+ 'kicked',
287
+ 'inserted',
288
+ 'buried',
289
+ 'hello',
290
+ 'release',
291
+ 'delete',
292
+ 'bury',
293
+ 'kick',
294
+ 'deadline_soon',
295
+ 'not_found',
296
+ 'deleted',
297
+ 'released',
298
+ ]
299
+ ARG_MESSAGES.each { |method_name|
300
+ class_eval do
301
+ define_method "parse_#{method_name}" do |args|
302
+ m = Message.new(method_name, nil, nil, args)
303
+ receive_message(m)
304
+ @data.slice!(0, @scanner.pos)
305
+ end
306
+ end
307
+ }
308
+
309
+ BODY_MESSAGES = ['ok']
310
+ BODY_MESSAGES.each { |method_name|
311
+ class_eval do
312
+ define_method "parse_#{method_name}" do |args|
313
+ body_length = args[0].to_i
314
+ # Add two bytes for delimiting "\r\n"
315
+ if @scanner.rest_size >= (body_length + 2)
316
+ m = Message.new(method_name,
317
+ nil,
318
+ @scanner.rest[0..(body_length - 1)])
319
+ receive_message(m)
320
+ @scanner.pos = @scanner.pos + body_length + 2
321
+ @data.slice!(0, @scanner.pos)
322
+ end
323
+ end
324
+ end
325
+ }
326
+
327
+ ARG_AND_BODY_MESSAGES = ['reserved']
328
+ ARG_AND_BODY_MESSAGES.each { |method_name|
329
+ class_eval do
330
+ define_method "parse_#{method_name}" do |args|
331
+ job_id = args[0]
332
+ body_length = args[1].to_i
333
+ # Add two bytes for delimiting "\r\n"
334
+ if @scanner.rest_size >= (body_length + 2)
335
+ m = Message.new(method_name,
336
+ job_id,
337
+ @scanner.rest[0..(body_length - 1)])
338
+ receive_message(m)
339
+ @scanner.pos = @scanner.pos + body_length + 2
340
+ @data.slice!(0, @scanner.pos)
341
+ end
342
+ end
343
+ end
344
+ }
345
+
346
+ ARGS_AND_BODY_MESSAGES = ['put']
347
+ ARGS_AND_BODY_MESSAGES.each { |method_name|
348
+ class_eval do
349
+ define_method "parse_#{method_name}" do |args|
350
+ body_length = args.last.to_i
351
+ # Add two bytes for delimiting "\r\n"
352
+ if @scanner.rest_size >= (body_length + 2)
353
+ pos = @scanner.pos
354
+ endpos = pos + (body_length - 1)
355
+ m = Message.new(method_name,
356
+ nil,
357
+ @data[pos..endpos],
358
+ args)
359
+ receive_message(m)
360
+ @data.slice!(0, endpos + 3) # endpos is an index; plus "\r\n"
361
+ end
362
+ end
363
+ end
364
+ }
365
+
366
+ ALL_MESSAGES =
367
+ NO_ARG_MESSAGES + ARG_MESSAGES +
368
+ BODY_MESSAGES + ARG_AND_BODY_MESSAGES + ARGS_AND_BODY_MESSAGES
369
+
370
+ def receive_message(message)
371
+ @rcv_stats[message.type] ||= 0
372
+ @rcv_stats[message.type] += 1
373
+ if self.respond_to?("receive_#{message.type}")
374
+ self.send("receive_#{message.type}", message)
375
+ else
376
+ logger.error {
377
+ "Ignoring message: #{message.type}"
378
+ }
379
+ end
380
+ end
381
+
382
+ ######################################################################
383
+ # Message interface - receiving
384
+ ######################################################################
385
+
386
+ ALL_MESSAGES.each{ |method_name|
387
+ class_eval do
388
+ define_method "receive_#{method_name}" do |message|
389
+ if !ok_to_receive?
390
+ return
391
+ end
392
+ logger.debug4 {
393
+ "Got message #{message.type}"
394
+ }
395
+ end
396
+ end
397
+ }
398
+
399
+ def receive_hello(message)
400
+ if !protocol_version_ok?(message.args[0].to_i)
401
+ logger.info {
402
+ "SOBS Peer #{remote_endpoint} sent incompatible " +
403
+ "protocol version #{message.args[0].to_i}"
404
+ }
405
+ @state.bad_hello
406
+ else
407
+ @state.good_hello
408
+ end
409
+ end
410
+
411
+ ######################################################################
412
+ # Other
413
+ ######################################################################
414
+
415
+ def to_s
416
+ str = ''
417
+ str << "EM signature: #{signature} peer: #{remote_endpoint} \n"
418
+ str << "state: #{state.state} "
419
+ str << "opened: #{@opened_at} (#{Time.now - @opened_at}s)\n"
420
+ str << "rcvs: #{@rcvs} in: #{@msgs_in} out: #{@msgs_out}\n"
421
+ str << "bytes in: #{@bytes_in} bytes_out: #{@bytes_out}\n"
422
+ str << "outgoing buffers: #{@outgoing.length}, "
423
+ str << "#{queued_outgoing_data_size} total bytes\n"
424
+ str << "#{self.get_outbound_data_size} total outbound bytes\n"
425
+ str << "send ticker is #{@send_ticker ? '' : 'not '}running\n"
426
+ str << "outbound_max: #{@outbound_max || OUTBOUND_MAX} "
427
+ str << "refuse_send_threshold: "
428
+ str << "#{@refuse_send_threshold || REFUSE_SEND_THRESHOLD}\n"
429
+ str << "congested?: #{@congested ? 'Y' : 'N'}, "
430
+ str << "refused sends: #{@refused_send}\n"
431
+ str << "outbound_bytes_highwater: #{@outbound_bytes_highwater}\n"
432
+ @rcv_stats.each {|msg_name,count|
433
+ str << "#{msg_name} in: #{count}\n"
434
+ }
435
+ @snd_stats.each {|msg_name,count|
436
+ str << "#{msg_name} out: #{count}\n"
437
+ }
438
+ str
439
+ end
440
+
441
+ def remote_endpoint
442
+ peer ? "#{peer[1]}:#{peer[0]}" : '?:?'
443
+ end
444
+
445
+ def should_refuse_send?
446
+ outbound_size = queued_outgoing_data_size
447
+ @outbound_bytes_highwater = [
448
+ @outbound_bytes_highwater,
449
+ outbound_size
450
+ ].max
451
+ outbound_size > (@refuse_send_threshold || REFUSE_SEND_THRESHOLD)
452
+ end
453
+
454
+ def enter_congestion
455
+ unless @congested
456
+ logger.warn {
457
+ "Connection #{remote_endpoint} congested. Peer having problems?"
458
+ }
459
+ @congested = true
460
+ @congested_at = Time.now
461
+ end
462
+ end
463
+
464
+ def exit_congestion
465
+ @congested = false
466
+ logger.warn {
467
+ "Congested #{remote_endpoint} cleared. Was congested for " +
468
+ "#{Time.now - @congested_at} seconds."
469
+ }
470
+ end
471
+
472
+ def send_to_peer(data)
473
+ if should_refuse_send?
474
+ @refused_send += 1
475
+ enter_congestion unless @congested
476
+ return false
477
+ end
478
+ @outgoing.push(data)
479
+ push_to_peer
480
+ true # could return queued_outgoing_data_size?
481
+ end
482
+
483
+ def unknown_message(command_name)
484
+ msg = "Unknown/unsupported command: #{command_name}"
485
+ sys.die(msg)
486
+ end
487
+
488
+ def setup_peer
489
+ if (peername = get_peername)
490
+ @peer = Socket.unpack_sockaddr_in(peername)
491
+ @opened_at = Time.now
492
+ logger.info {
493
+ "SOBS connection to #{remote_endpoint} complete."
494
+ }
495
+ end
496
+ end
497
+
498
+ def protocol_version_ok?(their_version)
499
+ their_version == HomeQ::SOBS::PROTOCOL_VERSION
500
+ end
501
+
502
+ def ok_to_receive?
503
+ if (@state.state == :start ||
504
+ @state.state == :waiting_for_hello ||
505
+ @state.state == :closed)
506
+ logger.error {
507
+ "SOBS peer #{remote_endpoint} sent isn't open."
508
+ }
509
+ close
510
+ false
511
+ else
512
+ true
513
+ end
514
+ end
515
+
516
+ def close
517
+ @state.close
518
+ end
519
+
520
+ def wait_for_hello
521
+ @hello_timer = EventMachine::Timer.new(HELLO_TIMER_LENGTH) do
522
+ logger.info {
523
+ "No hello received from #{remote_endpoint}. Bye."
524
+ }
525
+ @hello_timer = nil
526
+ @state.hello_timeout
527
+ end
528
+ end
529
+
530
+ def cancel_hello_timer
531
+ @hello_timer.cancel if @hello_timer
532
+ @hello_timer = nil
533
+ end
534
+
535
+ def start_closing
536
+ close_connection_after_writing
537
+ closing
538
+ end
539
+
540
+ ######################################################################
541
+ # Higher-level interface for subclasses
542
+ ######################################################################
543
+
544
+ def opened
545
+ end
546
+ def closing
547
+ end
548
+ def closed
549
+ end
550
+
551
+ ######################################################################
552
+ # Workhorse methods
553
+ ######################################################################
554
+
555
+ protected
556
+
557
+ def queued_outgoing_data_size
558
+ @outgoing.inject(0) { |sum,data| sum += data.length }
559
+ end
560
+
561
+ def outbound_maxed_out?
562
+ self.get_outbound_data_size >= @outbound_max
563
+ end
564
+
565
+ def push_to_peer
566
+ logger.debug2 {
567
+ "push #{self.get_outbound_data_size}"
568
+ }
569
+
570
+ while !outbound_maxed_out? do
571
+ data = @outgoing.shift
572
+ unless data
573
+ logger.debug2 {
574
+ "No more to send."
575
+ }
576
+ exit_congestion if @congested
577
+ return
578
+ end
579
+ logger.debug4 {
580
+ '--> ' + data.inspect
581
+ }
582
+ logger.debug3 {
583
+ '>>>> ' + data[0..20].chomp
584
+ }
585
+ @msgs_out += 1
586
+ @bytes_out += data.length
587
+ send_data(data)
588
+ end
589
+
590
+ exit_congestion if (!should_refuse_send? && @congested)
591
+
592
+ logger.debug4 {
593
+ "push maxed #{outbound_maxed_out?}; " +
594
+ "outbound #{self.get_outbound_data_size}; " +
595
+ "queued #{self.queued_outgoing_data_size}; " +
596
+ "ticker #{@send_ticker}"
597
+ }
598
+
599
+ raise 'WTF' unless outbound_maxed_out?
600
+
601
+ if @outgoing.any? && !@send_ticker
602
+ logger.debug2 {
603
+ "Send buffer size exceeded (now " +
604
+ "#{self.get_outbound_data_size}). Deferring sends."
605
+ }
606
+
607
+ @send_ticker = true
608
+ @push_timer = EventMachine::Timer.new(PUSH_TIMER_LENGTH) do
609
+ logger.debug3 {
610
+ "Push to peer callback."
611
+ }
612
+ @send_ticker = false
613
+ @push_timer = nil
614
+ push_to_peer
615
+ end
616
+
617
+ logger.debug3 {
618
+ "Scheduled push callback."
619
+ }
620
+ end
621
+ end
622
+
623
+ def io_per_sec
624
+ '%6.4f.' % ((@msgs_in + @msgs_out) / how_long_open).to_f
625
+ end
626
+
627
+ def how_long_open
628
+ (@closed_at || Time.now) - (@opened_at || Time.now)
629
+ end
630
+
631
+ end # Connection
632
+
633
+ end # module SOBS
634
+
635
+ end # module HomeQ