stomp 1.2.4 → 1.2.5

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 (56) hide show
  1. data/CHANGELOG.rdoc +11 -0
  2. data/README.rdoc +38 -26
  3. data/Rakefile +3 -0
  4. data/bin/catstomp +34 -34
  5. data/bin/stompcat +36 -36
  6. data/examples/client11_ex1.rb +64 -55
  7. data/examples/client11_putget1.rb +47 -35
  8. data/examples/conn11_ex1.rb +59 -51
  9. data/examples/conn11_ex2.rb +59 -50
  10. data/examples/conn11_hb1.rb +35 -26
  11. data/examples/consumer.rb +25 -12
  12. data/examples/get11conn_ex1.rb +97 -89
  13. data/examples/get11conn_ex2.rb +55 -47
  14. data/examples/logexamp.rb +66 -52
  15. data/examples/logexamp_ssl.rb +66 -52
  16. data/examples/publisher.rb +21 -10
  17. data/examples/put11conn_ex1.rb +35 -24
  18. data/examples/putget11_rh1.rb +66 -56
  19. data/examples/slogger.rb +65 -52
  20. data/examples/ssl_uc1.rb +24 -13
  21. data/examples/ssl_uc1_ciphers.rb +28 -15
  22. data/examples/ssl_uc2.rb +26 -16
  23. data/examples/ssl_uc2_ciphers.rb +31 -18
  24. data/examples/ssl_uc3.rb +25 -14
  25. data/examples/ssl_uc3_ciphers.rb +31 -18
  26. data/examples/ssl_uc4.rb +26 -15
  27. data/examples/ssl_uc4_ciphers.rb +32 -19
  28. data/examples/ssl_ucx_default_ciphers.rb +25 -12
  29. data/examples/stomp11_common.rb +16 -15
  30. data/examples/topic_consumer.rb +23 -10
  31. data/examples/topic_publisher.rb +22 -8
  32. data/lib/client/utils.rb +116 -0
  33. data/lib/connection/heartbeats.rb +173 -0
  34. data/lib/connection/netio.rb +322 -0
  35. data/lib/connection/utf8.rb +294 -0
  36. data/lib/connection/utils.rb +104 -0
  37. data/lib/stomp/client.rb +127 -179
  38. data/lib/stomp/codec.rb +5 -2
  39. data/lib/stomp/connection.rb +109 -865
  40. data/lib/stomp/constants.rb +52 -33
  41. data/lib/stomp/errors.rb +56 -5
  42. data/lib/stomp/ext/hash.rb +4 -0
  43. data/lib/stomp/message.rb +49 -29
  44. data/lib/stomp/sslparams.rb +83 -71
  45. data/lib/stomp/version.rb +3 -1
  46. data/lib/stomp.rb +18 -9
  47. data/stomp.gemspec +58 -3
  48. data/test/test_client.rb +28 -1
  49. data/test/test_codec.rb +8 -2
  50. data/test/test_connection.rb +29 -0
  51. data/test/test_connection1p.rb +31 -16
  52. data/test/test_helper.rb +20 -3
  53. data/test/test_message.rb +8 -3
  54. data/test/test_ssl.rb +10 -4
  55. data/test/tlogger.rb +16 -15
  56. metadata +59 -4
@@ -6,30 +6,43 @@
6
6
  require "rubygems"
7
7
  require "stomp"
8
8
  #
9
+ # == SSL Use Case 4 - User Supplied Ciphers
10
+ #
9
11
  # If you need your own ciphers list, this is how.
10
12
  # Stomp's default list will work in many cases. If you need to use this, you
11
13
  # will know it because SSL connect will fail. In that case, determining
12
14
  # _what_ should be in the list is your responsibility.
13
15
  #
14
- ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", "TLSv1/SSLv3", 128, 128], ["EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 56, 56],
15
- ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-RC2-CBC-MD5", "TLSv1/SSLv3", 40, 128], ["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128]]
16
- #
17
- # SSL Use Case 4
18
- #
19
- ssl_opts = Stomp::SSLParams.new(:key_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.key",
20
- :cert_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.crt",
21
- :ts_files => "/home/gmallard/sslwork/twocas_tj/serverCA/ServerTJCA.crt",
22
- :ciphers => ciphers_list)
23
- #
24
- hash = { :hosts => [
25
- {:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
16
+ class ExampleSSL4C
17
+ # Initialize.
18
+ def initialize
19
+ end
20
+ # Run example.
21
+ def run
22
+ ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", "TLSv1/SSLv3", 128, 128], ["EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 56, 56],
23
+ ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-RC2-CBC-MD5", "TLSv1/SSLv3", 40, 128], ["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128]]
24
+ #
25
+ # SSL Use Case 4
26
+ #
27
+ ssl_opts = Stomp::SSLParams.new(:key_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.key",
28
+ :cert_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.crt",
29
+ :ts_files => "/home/gmallard/sslwork/twocas_tj/serverCA/ServerTJCA.crt",
30
+ :ciphers => ciphers_list)
31
+ #
32
+ hash = { :hosts => [
33
+ {:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
26
34
  ]
27
35
  }
28
- #
29
- puts "Connect starts, SSL Use Case 4"
30
- c = Stomp::Connection.new(hash)
31
- puts "Connect completed"
32
- puts "SSL Verify Result: #{ssl_opts.verify_result}"
33
- # puts "SSL Peer Certificate:\n#{ssl_opts.peer_cert}"
34
- c.disconnect
36
+ #
37
+ puts "Connect starts, SSL Use Case 4"
38
+ c = Stomp::Connection.new(hash)
39
+ puts "Connect completed"
40
+ puts "SSL Verify Result: #{ssl_opts.verify_result}"
41
+ # puts "SSL Peer Certificate:\n#{ssl_opts.peer_cert}"
42
+ c.disconnect
43
+ end
44
+ end
45
+ #
46
+ e = ExampleSSL4C.new
47
+ e.run
35
48
 
@@ -6,23 +6,36 @@
6
6
  require "rubygems"
7
7
  require "stomp"
8
8
  #
9
+ # == Example: Use Ruby Supplied Ciphers
10
+ #
9
11
  # If you use SSLParams, and need the _default_ Ruby ciphers, this is how.
10
12
  #
11
13
  # NOTE: JRuby users may find that this is a *required* action. YMMV.
12
14
  #
13
- ssl_opts = Stomp::SSLParams.new(:use_ruby_ciphers => true) # Plus other parameters as needed
14
- #
15
- # SSL Use Case: Using default Stomp ciphers
16
- #
17
- hash = { :hosts => [
18
- {:login => 'guest', :passcode => 'guest', :host => 'localhost',
15
+ class ExampleRubyCiphers
16
+ # Initialize.
17
+ def initialize
18
+ end
19
+ # Run example.
20
+ def run
21
+ ssl_opts = Stomp::SSLParams.new(:use_ruby_ciphers => true) # Plus other parameters as needed
22
+ #
23
+ # SSL Use Case: Using default Stomp ciphers
24
+ #
25
+ hash = { :hosts => [
26
+ {:login => 'guest', :passcode => 'guest', :host => 'localhost',
19
27
  :port => 61612, :ssl => ssl_opts},
20
28
  ]
21
29
  }
22
- #
23
- puts "Connect starts, SSL , Use Default Ruby Ciphers"
24
- c = Stomp::Connection.new(hash)
25
- puts "Connect completed"
26
- #
27
- c.disconnect
30
+ #
31
+ puts "Connect starts, SSL , Use Default Ruby Ciphers"
32
+ c = Stomp::Connection.new(hash)
33
+ puts "Connect completed"
34
+ #
35
+ c.disconnect
36
+ end
37
+ end
38
+ #
39
+ e = ExampleRubyCiphers.new
40
+ e.run
28
41
 
@@ -7,39 +7,40 @@ require "rubygems" if RUBY_VERSION < "1.9"
7
7
  require "stomp"
8
8
  #
9
9
  module Stomp11Common
10
- #
10
+ # User id
11
11
  def login()
12
12
  ENV['STOMP_USER'] || 'guest'
13
13
  end
14
- #
14
+ # Password
15
15
  def passcode()
16
16
  ENV['STOMP_PASSCODE'] || 'guest'
17
17
  end
18
- #
18
+ # Server host
19
19
  def host()
20
20
  ENV['STOMP_HOST'] || "localhost" # The connect host name
21
21
  end
22
- #
22
+ # Server port
23
23
  def port()
24
24
  (ENV['STOMP_PORT'] || 62613).to_i # !! The author runs Apollo listening here
25
25
  end
26
- #
26
+ # Required vhost name
27
27
  def virt_host()
28
- ENV['STOMP_VHOST'] || "localhost" # The 1.1 virtual host name
28
+ ENV['STOMP_VHOST'] || "localhost" # The 1.1 virtual host name
29
29
  end
30
- #
30
+ # Create a 1.1 commection
31
31
  def get_connection()
32
32
  conn_hdrs = {"accept-version" => "1.1", # 1.1 only
33
- "host" => virt_host, # the vhost
34
- }
35
- conn_hash = { :hosts => [
36
- {:login => login, :passcode => passcode, :host => host, :port => port},
37
- ],
38
- :connect_headers => conn_hdrs,
39
- }
33
+ "host" => virt_host, # the vhost
34
+ }
35
+ conn_hash = { :hosts => [
36
+ {:login => login, :passcode => passcode, :host => host, :port => port},
37
+ ],
38
+ :connect_headers => conn_hdrs,
39
+ }
40
40
  conn = Stomp::Connection.new(conn_hash)
41
41
  end
42
- #
42
+
43
+ # Number of messages
43
44
  def nmsgs()
44
45
  (ENV['STOMP_NMSGS'] || 1).to_i # Number of messages
45
46
  end
@@ -2,18 +2,31 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'stomp'
5
+ #
6
+ # == Example topic consumer.
7
+ #
8
+ class ExampleTopicConsumer
9
+ # Initialize.
10
+ def initialize
11
+ end
12
+ # Run example.
13
+ def run
14
+ client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
5
15
 
6
- client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
16
+ puts "Subscribing to /topic/ronaldo"
7
17
 
8
- puts "Subscribing to /topic/ronaldo"
18
+ client.subscribe("/topic/ronaldo") do |msg|
19
+ puts msg.to_s
20
+ puts "----------------"
21
+ end
9
22
 
10
- client.subscribe("/topic/ronaldo") do |msg|
11
- puts msg.to_s
12
- puts "----------------"
13
- end
14
-
15
- loop do
16
- sleep(1)
17
- puts "."
23
+ loop do
24
+ sleep(1)
25
+ puts "."
26
+ end
27
+ end
18
28
  end
29
+ #
30
+ e = ExampleTopicConsumer.new
31
+ e.run
19
32
 
@@ -3,13 +3,27 @@
3
3
  require 'rubygems'
4
4
  require 'stomp'
5
5
 
6
- client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
7
- message = "ronaldo #{ARGV[0]}"
8
-
9
- for i in (1..300)
10
- puts "Sending message"
11
- client.publish("/topic/ronaldo", "#{i}: #{message}")
12
- puts "(#{Time.now})Message sent: #{i}"
13
- sleep 1
6
+ #
7
+ # == Example topic publisher.
8
+ #
9
+ class ExampleTopicPublisher
10
+ # Initialize.
11
+ def initialize
12
+ end
13
+ # Run example.
14
+ def run
15
+ client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
16
+ message = "ronaldo #{ARGV[0]}"
17
+
18
+ for i in (1..50)
19
+ puts "Sending message"
20
+ client.publish("/topic/ronaldo", "#{i}: #{message}")
21
+ puts "(#{Time.now})Message sent: #{i}"
22
+ sleep 0.2
23
+ end
24
+ end
14
25
  end
26
+ #
27
+ e = ExampleTopicPublisher.new
28
+ e.run
15
29
 
@@ -0,0 +1,116 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'thread'
4
+ require 'digest/sha1'
5
+
6
+ module Stomp
7
+
8
+ class Client
9
+
10
+ private
11
+
12
+ # Set a subscription id in the headers hash if one does not already exist.
13
+ # For simplicities sake, all subscriptions have a subscription ID.
14
+ # setting an id in the SUBSCRIPTION header is described in the stomp protocol docs:
15
+ # http://stomp.github.com/
16
+ def set_subscription_id_if_missing(destination, headers)
17
+ headers[:id] = headers[:id] ? headers[:id] : headers['id']
18
+ if headers[:id] == nil
19
+ headers[:id] = Digest::SHA1.hexdigest(destination)
20
+ end
21
+ end
22
+
23
+ # Register a receipt listener.
24
+ def register_receipt_listener(listener)
25
+ id = -1
26
+ @id_mutex.synchronize do
27
+ id = @ids.to_s
28
+ @ids = @ids.succ
29
+ end
30
+ @receipt_listeners[id] = listener
31
+ id
32
+ end
33
+
34
+ # url_regex defines a regex for e.g. login:passcode@host:port or host:port
35
+ def url_regex
36
+ '(([\w\.\-]*):(\w*)@)?([\w\.\-]+):(\d+)'
37
+ end
38
+
39
+ # Parse a stomp URL.
40
+ def parse_hosts(url)
41
+ hosts = []
42
+
43
+ host_match = /stomp(\+ssl)?:\/\/(([\w\.]*):(\w*)@)?([\w\.]+):(\d+)\)/
44
+ url.scan(host_match).each do |match|
45
+ host = {}
46
+ host[:ssl] = !match[0].nil?
47
+ host[:login] = match[2] || ""
48
+ host[:passcode] = match[3] || ""
49
+ host[:host] = match[4]
50
+ host[:port] = match[5].to_i
51
+
52
+ hosts << host
53
+ end
54
+
55
+ hosts
56
+ end
57
+
58
+ # A very basic check of required arguments.
59
+ # *NOTE* This method will be made private in the next release.
60
+ def check_arguments!()
61
+ raise ArgumentError if @host.nil? || @host.empty?
62
+ raise ArgumentError if @port.nil? || @port == '' || @port < 1 || @port > 65535
63
+ raise ArgumentError unless @reliable.is_a?(TrueClass) || @reliable.is_a?(FalseClass)
64
+ end
65
+
66
+ # filter_options returns a new Hash of filtered options.
67
+ def filter_options(options)
68
+ new_options = {}
69
+ new_options[:initial_reconnect_delay] = (options["initialReconnectDelay"] || 10).to_f / 1000 # In ms
70
+ new_options[:max_reconnect_delay] = (options["maxReconnectDelay"] || 30000 ).to_f / 1000 # In ms
71
+ new_options[:use_exponential_back_off] = !(options["useExponentialBackOff"] == "false") # Default: true
72
+ new_options[:back_off_multiplier] = (options["backOffMultiplier"] || 2 ).to_i
73
+ new_options[:max_reconnect_attempts] = (options["maxReconnectAttempts"] || 0 ).to_i
74
+ new_options[:randomize] = options["randomize"] == "true" # Default: false
75
+ new_options[:backup] = false # Not implemented yet: I'm using a master X slave solution
76
+ new_options[:timeout] = -1 # Not implemented yet: a "timeout(5) do ... end" would do the trick, feel free
77
+
78
+ new_options
79
+ end
80
+
81
+ # find_listener returns the listener for a given subscription in a given message.
82
+ def find_listener(message)
83
+ subscription_id = message.headers['subscription']
84
+ if subscription_id == nil
85
+ # For backward compatibility, some messages may already exist with no
86
+ # subscription id, in which case we can attempt to synthesize one.
87
+ set_subscription_id_if_missing(message.headers['destination'], message.headers)
88
+ subscription_id = message.headers[:id]
89
+ end
90
+ @listeners[subscription_id]
91
+ end
92
+
93
+ # Start a single listener thread. Misnamed I think.
94
+ def start_listeners()
95
+ @listeners = {}
96
+ @receipt_listeners = {}
97
+ @replay_messages_by_txn = {}
98
+
99
+ @listener_thread = Thread.start do
100
+ while true
101
+ message = @connection.receive
102
+ if message.command == Stomp::CMD_MESSAGE
103
+ if listener = find_listener(message)
104
+ listener.call(message)
105
+ end
106
+ elsif message.command == Stomp::CMD_RECEIPT
107
+ if listener = @receipt_listeners[message.headers['receipt-id']]
108
+ listener.call(message)
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
@@ -0,0 +1,173 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'socket'
4
+ require 'timeout'
5
+ require 'io/wait'
6
+ require 'digest/sha1'
7
+
8
+ module Stomp
9
+
10
+ class Connection
11
+
12
+ private
13
+
14
+ def _validate_hbheader()
15
+ return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
16
+ parts = @connect_headers[:"heart-beat"].split(",")
17
+ if (parts.size != 2) || (parts[0] != parts[0].to_i.to_s) || (parts[1] != parts[1].to_i.to_s)
18
+ raise Stomp::Error::InvalidHeartBeatHeaderError
19
+ end
20
+ end
21
+
22
+ def _init_heartbeats()
23
+ return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
24
+
25
+ # Init.
26
+
27
+ #
28
+ @cx = @cy = @sx = @sy = 0, # Variable names as in spec
29
+
30
+ #
31
+ @hbsend_interval = @hbrecv_interval = 0.0 # Send/Receive ticker interval.
32
+
33
+ #
34
+ @hbsend_count = @hbrecv_count = 0 # Send/Receive ticker counts.
35
+
36
+ #
37
+ @ls = @lr = -1.0 # Last send/receive time (from Time.now.to_f)
38
+
39
+ #
40
+ @st = @rt = nil # Send/receive ticker thread
41
+
42
+ # Handle current client / server capabilities.
43
+
44
+ #
45
+ cfh = @connection_frame.headers.symbolize_keys
46
+ return if cfh[:"heart-beat"] == "0,0" # Server does not want heartbeats
47
+
48
+ # Conect header parts.
49
+ parts = @connect_headers[:"heart-beat"].split(",")
50
+ @cx = parts[0].to_i
51
+ @cy = parts[1].to_i
52
+
53
+ # Connected frame header parts.
54
+ parts = cfh[:"heart-beat"].split(",")
55
+ @sx = parts[0].to_i
56
+ @sy = parts[1].to_i
57
+
58
+ # Catch odd situations like server has used => heart-beat:000,00000
59
+ return if (@cx == 0 && @cy == 0) || (@sx == 0 && @sy == 0)
60
+
61
+ # See if we are doing anything at all.
62
+
63
+ #
64
+ @hbs = @hbr = true # Sending/Receiving heartbeats. Assume yes at first.
65
+ # Check if sending is possible.
66
+ @hbs = false if @cx == 0 || @sy == 0
67
+ # Check if receiving is possible.
68
+ @hbr = false if @sx == 0 || @cy == 0
69
+ # Should not do heartbeats at all
70
+ return if (!@hbs && !@hbr)
71
+
72
+ # If sending
73
+ if @hbs
74
+ sm = @cx >= @sy ? @cx : @sy # ticker interval, ms
75
+ @hbsend_interval = 1000.0 * sm # ticker interval, μs
76
+ @ls = Time.now.to_f # best guess at start
77
+ _start_send_ticker()
78
+ end
79
+
80
+ # If receiving
81
+ if @hbr
82
+ rm = @sx >= @cy ? @sx : @cy # ticker interval, ms
83
+ @hbrecv_interval = 1000.0 * rm # ticker interval, μs
84
+ @lr = Time.now.to_f # best guess at start
85
+ _start_receive_ticker()
86
+ end
87
+
88
+ end
89
+
90
+ # _start_send_ticker starts a thread to send heartbeats when required.
91
+ def _start_send_ticker()
92
+ sleeptime = @hbsend_interval / 1000000.0 # Sleep time secs
93
+ @st = Thread.new {
94
+ while true do
95
+ sleep sleeptime
96
+ curt = Time.now.to_f
97
+ if @logger && @logger.respond_to?(:on_hbfire)
98
+ @logger.on_hbfire(log_params, "send_fire", curt)
99
+ end
100
+ delta = curt - @ls
101
+ if delta > (@hbsend_interval - (@hbsend_interval/5.0)) / 1000000.0 # Be tolerant (minus)
102
+ if @logger && @logger.respond_to?(:on_hbfire)
103
+ @logger.on_hbfire(log_params, "send_heartbeat", curt)
104
+ end
105
+ # Send a heartbeat
106
+ @transmit_semaphore.synchronize do
107
+ begin
108
+ @socket.puts
109
+ @ls = curt # Update last send
110
+ @hb_sent = true # Reset if necessary
111
+ @hbsend_count += 1
112
+ rescue Exception => sendex
113
+ @hb_sent = false # Set the warning flag
114
+ if @logger && @logger.respond_to?(:on_hbwrite_fail)
115
+ @logger.on_hbwrite_fail(log_params, {"ticker_interval" => @hbsend_interval,
116
+ "exception" => sendex})
117
+ end
118
+ raise # Re-raise. What else could be done here?
119
+ end
120
+ end
121
+ end
122
+ Thread.pass
123
+ end
124
+ }
125
+ end
126
+
127
+ # _start_receive_ticker starts a thread that receives heartbeats when required.
128
+ def _start_receive_ticker()
129
+ sleeptime = @hbrecv_interval / 1000000.0 # Sleep time secs
130
+ @rt = Thread.new {
131
+ while true do
132
+ sleep sleeptime
133
+ curt = Time.now.to_f
134
+ if @logger && @logger.respond_to?(:on_hbfire)
135
+ @logger.on_hbfire(log_params, "receive_fire", curt)
136
+ end
137
+ delta = curt - @lr
138
+ if delta > ((@hbrecv_interval + (@hbrecv_interval/5.0)) / 1000000.0) # Be tolerant (plus)
139
+ if @logger && @logger.respond_to?(:on_hbfire)
140
+ @logger.on_hbfire(log_params, "receive_heartbeat", curt)
141
+ end
142
+ # Client code could be off doing something else (that is, no reading of
143
+ # the socket has been requested by the caller). Try to handle that case.
144
+ lock = @read_semaphore.try_lock
145
+ if lock
146
+ last_char = @socket.getc
147
+ plc = parse_char(last_char)
148
+ if plc == "\n" # Server Heartbeat
149
+ @lr = Time.now.to_f
150
+ else
151
+ @socket.ungetc(last_char)
152
+ end
153
+ @read_semaphore.unlock
154
+ @hbrecv_count += 1
155
+ else
156
+ # Shrug. Have not received one. Just set warning flag.
157
+ @hb_received = false
158
+ if @logger && @logger.respond_to?(:on_hbread_fail)
159
+ @logger.on_hbread_fail(log_params, {"ticker_interval" => @hbrecv_interval})
160
+ end
161
+ end
162
+ else
163
+ @hb_received = true # Reset if necessary
164
+ end
165
+ Thread.pass
166
+ end
167
+ }
168
+ end
169
+
170
+ end # class
171
+
172
+ end # module
173
+