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