stomper 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ pkg/*
3
3
  *.gem
4
4
  .bundle
5
5
  .yardoc
6
+ nbproject
data/examples/events.rb CHANGED
@@ -31,6 +31,10 @@ client.on_connection_closed do |con|
31
31
  $stdout.puts "Connection has been closed"
32
32
  end
33
33
 
34
+ client.on_connection_terminated do |con|
35
+ $stdout.puts "Connection closed unexpectedly"
36
+ end
37
+
34
38
  client.send("/queue/stomper/test", "hello world")
35
39
  client.disconnect
36
40
 
@@ -10,6 +10,7 @@ Feature: Acking messages
10
10
  | message-id | m-1234 |
11
11
  | subscription | s-5678 |
12
12
  | destination | /queue/testing |
13
+ #When the client waits for 1 "MESSAGE" frame
13
14
  When the client acks the last MESSAGE
14
15
  And the frame exchange is completed
15
16
  Then the broker should have received an "ACK" frame with headers
@@ -6,12 +6,14 @@ Feature: Receipts
6
6
  Scenario: RECEIPT on SEND
7
7
  Given a 1.1 connection between client and broker
8
8
  When the client sends a receipted message "test message" to "/queue/test"
9
+ And the client waits for 1 "RECEIPT" frame
9
10
  And the frame exchange is completed
10
11
  Then the client should have received a receipt for the last "SEND"
11
12
 
12
13
  Scenario: RECEIPT on SUBSCRIBE
13
14
  Given a 1.1 connection between client and broker
14
15
  When the client subscribes to "/queue/test" with a receipt
16
+ And the client waits for 1 "RECEIPT" frame
15
17
  And the frame exchange is completed
16
18
  Then the client should have received a receipt for the last "SUBSCRIBE"
17
19
 
@@ -21,42 +23,49 @@ Feature: Receipts
21
23
  | header-name | header-value |
22
24
  | id | s-1234 |
23
25
  When the client unsubscribes from "s-1234" with a receipt
26
+ And the client waits for 1 "RECEIPT" frame
24
27
  And the frame exchange is completed
25
28
  Then the client should have received a receipt for the last "UNSUBSCRIBE"
26
29
 
27
30
  Scenario: RECEIPT on BEGIN
28
31
  Given a 1.1 connection between client and broker
29
32
  When the client begins transaction "t-1234" with a receipt
33
+ And the client waits for 1 "RECEIPT" frame
30
34
  And the frame exchange is completed
31
35
  Then the client should have received a receipt for the last "BEGIN"
32
36
 
33
37
  Scenario: RECEIPT on COMMIT
34
38
  Given a 1.1 connection between client and broker
35
39
  When the client commits transaction "t-1234" with a receipt
40
+ And the client waits for 1 "RECEIPT" frame
36
41
  And the frame exchange is completed
37
42
  Then the client should have received a receipt for the last "COMMIT"
38
43
 
39
44
  Scenario: RECEIPT on ABORT
40
45
  Given a 1.1 connection between client and broker
41
46
  When the client aborts transaction "t-1234" with a receipt
47
+ And the client waits for 1 "RECEIPT" frame
42
48
  And the frame exchange is completed
43
49
  Then the client should have received a receipt for the last "ABORT"
44
50
 
45
51
  Scenario: RECEIPT on ACK
46
52
  Given a 1.1 connection between client and broker
47
53
  When the client acks message "m-1234" from "s-5678" with a receipt
54
+ And the client waits for 1 "RECEIPT" frame
48
55
  And the frame exchange is completed
49
56
  Then the client should have received a receipt for the last "ACK"
50
57
 
51
58
  Scenario: RECEIPT on NACK
52
59
  Given a 1.1 connection between client and broker
53
60
  When the client nacks message "m-1234" from "s-5678" with a receipt
61
+ And the client waits for 1 "RECEIPT" frame
54
62
  And the frame exchange is completed
55
63
  Then the client should have received a receipt for the last "NACK"
56
64
 
57
65
  Scenario: RECEIPT on DISCONNECT
58
66
  Given a 1.1 connection between client and broker
59
67
  When the client disconnects with a receipt
68
+ And the client waits for 1 "RECEIPT" frame
60
69
  And the frame exchange is completed without client disconnect
61
70
  Then the client should have received a receipt for the last "DISCONNECT"
62
71
 
@@ -15,6 +15,7 @@ Feature: Scopes
15
15
  | header-name | header-value |
16
16
  | id | s-9012 |
17
17
  | ack | client-individual |
18
+ And the frame exchange is completed
18
19
  Then the broker should have received an "ACK" frame with headers
19
20
  | header-name | header-value |
20
21
  | x-my-header | some value |
@@ -7,6 +7,7 @@ Feature: Send and message
7
7
  Given a 1.1 connection between client and broker
8
8
  And the client subscribes to <destination>
9
9
  When the client sends a <content-type> <body> to <destination>
10
+ And the client waits for 1 "MESSAGE" frame
10
11
  And the frame exchange is completed
11
12
  Then the client should have received a <content-type> message of <body>
12
13
 
@@ -19,6 +20,7 @@ Feature: Send and message
19
20
  Given a 1.1 connection between client and broker
20
21
  And the client subscribes to <destination>
21
22
  When the client sends a <body> encoded as <encoding> to <destination>
23
+ And the client waits for 1 "MESSAGE" frame
22
24
  And the frame exchange is completed
23
25
  Then the client should have received a <content-type> message of <body> encoded as <final encoding>
24
26
 
@@ -3,6 +3,7 @@ When /^the client acks a message by ID "([^"]*)"$/ do |message_id|
3
3
  end
4
4
 
5
5
  When /^the client acks the last MESSAGE$/ do
6
+ When("the client waits for 1 \"MESSAGE\" frame")
6
7
  @connection.ack @received_frames.select { |f| f.command == "MESSAGE" }.last
7
8
  end
8
9
 
@@ -15,6 +16,7 @@ When /^the client acks a message by ID "([^"]*)" and subscription "([^"]*)"$/ do
15
16
  end
16
17
 
17
18
  When /^the client nacks the last MESSAGE$/ do
19
+ When("the client waits for 1 \"MESSAGE\" frame")
18
20
  @connection.nack @received_frames.select { |f| f.command == "MESSAGE" }.last
19
21
  end
20
22
 
@@ -1,6 +1,9 @@
1
1
  After do |s|
2
- @connection && @connection.stop rescue nil
3
- @broker && @broker.force_stop
2
+ begin
3
+ @connection && @connection.stop
4
+ @broker && @broker.force_stop
5
+ rescue Exception => ex
6
+ end
4
7
  end
5
8
 
6
9
  Given /^a (\d+\.\d+)?\s*connection between client and broker$/ do |version|
@@ -12,10 +15,10 @@ Given /^a (\d+\.\d+)?\s*connection between client and broker$/ do |version|
12
15
  @connection = Stomper::Connection.new(@broker_uri)
13
16
  @received_frames = []
14
17
  @sent_frames = []
15
- @connection.before_transmitting do |c, f|
18
+ @connection.before_transmitting do |f, c|
16
19
  @sent_frames << f
17
20
  end
18
- @connection.after_receiving do |c, f|
21
+ @connection.after_receiving do |f, c|
19
22
  @received_frames << f
20
23
  end
21
24
  @connection.start
@@ -32,4 +32,9 @@ end
32
32
  When /^the frame exchange is completed without client disconnect$/ do
33
33
  @connection.stop
34
34
  @broker.stop
35
- end
35
+ end
36
+
37
+ When /^the client waits for (\d+) "([^"]*)" frames?$/ do |count, command|
38
+ count = count.to_i
39
+ Thread.pass while @received_frames.select { |f| f.command == command }.size < count
40
+ end
@@ -1,75 +1,56 @@
1
1
  When /^the client sends a receipted message "([^"]*)" to "([^"]*)"$/ do |body, destination|
2
- @receipts_received ||= {}
3
2
  @connection.send(destination, body) do |r|
4
- @receipts_received[r[:'receipt-id']] = r
5
3
  end
6
4
  end
7
5
 
8
6
  Then /^the client should have received a receipt for the last "([^"]*)"$/ do |command|
9
7
  fr = @sent_frames.select { |f| f.command == command }.last
10
- (@receipts_received && @receipts_received[fr[:'receipt']]).should_not be_nil
8
+ r = @received_frames.select { |f| f.command == 'RECEIPT' && f[:'receipt-id'] == fr[:receipt] }.last
9
+ r.should_not be_nil
11
10
  end
12
11
 
13
12
  When /^the client subscribes to "([^"]*)" with a receipt$/ do |destination|
14
- @receipts_received ||= {}
15
13
  @connection.with_receipt do |r|
16
- @receipts_received[r[:'receipt-id']] = r
17
14
  end.subscribe(destination)
18
15
  end
19
16
 
20
17
  When /^the client unsubscribes from "([^"]*)" with a receipt$/ do |subscription|
21
- @receipts_received ||= {}
22
18
  @connection.with_receipt do |r|
23
- @receipts_received[r[:'receipt-id']] = r
24
19
  end.unsubscribe(subscription)
25
20
  end
26
21
 
27
22
  When /^the client begins transaction "([^"]*)" with a receipt$/ do |tx|
28
- @receipts_received ||= {}
29
23
  @connection.with_receipt do |r|
30
- @receipts_received[r[:'receipt-id']] = r
31
24
  end.begin(tx)
32
25
  end
33
26
 
34
27
  When /^the client aborts transaction "([^"]*)" with a receipt$/ do |tx|
35
- @receipts_received ||= {}
36
28
  @connection.with_receipt do |r|
37
- @receipts_received[r[:'receipt-id']] = r
38
29
  end.abort(tx)
39
30
  end
40
31
 
41
32
  When /^the client commits transaction "([^"]*)" with a receipt$/ do |tx|
42
- @receipts_received ||= {}
43
33
  @connection.with_receipt do |r|
44
- @receipts_received[r[:'receipt-id']] = r
45
34
  end.commit(tx)
46
35
  end
47
36
 
48
37
  When /^the client acks message "([^"]*)" from "([^"]*)" with a receipt$/ do |m_id, sub_id|
49
- @receipts_received ||= {}
50
38
  @connection.with_receipt do |r|
51
- @receipts_received[r[:'receipt-id']] = r
52
39
  end.ack(m_id, sub_id)
53
40
  end
54
41
 
55
42
  When /^the client nacks message "([^"]*)" from "([^"]*)" with a receipt$/ do |m_id, sub_id|
56
- @receipts_received ||= {}
57
43
  @connection.with_receipt do |r|
58
- @receipts_received[r[:'receipt-id']] = r
59
44
  end.nack(m_id, sub_id)
60
45
  end
61
46
 
62
47
  When /^the client disconnects with a receipt$/ do
63
- @receipts_received ||= {}
64
48
  @connection.with_receipt do |r|
65
- @receipts_received[r[:'receipt-id']] = r
66
49
  end.disconnect
67
50
  end
68
51
 
69
52
  When /^the client connects with a receipt$/ do
70
- @receipts_received ||= {}
71
53
  @connection.with_receipt do |r|
72
- @receipts_received[r[:'receipt-id']] = r
73
54
  end.transmit(Stomper::Frame.new('CONNECT'))
74
55
  end
75
56
 
@@ -25,6 +25,8 @@ end
25
25
 
26
26
  Then /^connecting should raise an openssl error$/ do
27
27
  lambda { @connection.connect }.should raise_error(OpenSSL::SSL::SSLError)
28
+ # It is problematic that this is needed...
29
+ @broker.stop
28
30
  end
29
31
 
30
32
  When /^an SSL post connection check is not performed$/ do
@@ -19,6 +19,7 @@ Feature: Subscribing
19
19
  | message-id | m-1235 |
20
20
  | subscription | s-5678 |
21
21
  | destination | /queue/testing |
22
+ And the client waits for 2 "MESSAGE" frames
22
23
  And the frame exchange is completed
23
24
  Then the client should have received a "MESSAGE" frame with headers
24
25
  | header-name | header-value |
@@ -50,6 +51,7 @@ Feature: Subscribing
50
51
  | header-name | header-value |
51
52
  | message-id | m-1236 |
52
53
  | destination | /queue/testing |
54
+ And the client waits for 3 "MESSAGE" frame
53
55
  And the frame exchange is completed
54
56
  Then the client should have received a "MESSAGE" frame with headers
55
57
  | header-name | header-value |
@@ -73,6 +75,7 @@ Feature: Subscribing
73
75
  | message-id | m-1234 |
74
76
  | destination | /queue/testing |
75
77
  | subscription | s-9999 |
78
+ And the client waits for 1 "MESSAGE" frame
76
79
  And the frame exchange is completed
77
80
  Then the client should have received a "MESSAGE" frame with headers
78
81
  | header-name | header-value |
@@ -91,13 +94,15 @@ Feature: Subscribing
91
94
  | message-id | m-1234 |
92
95
  | destination | /queue/testing |
93
96
  | subscription | s-5678 |
97
+ And the client waits for 1 "MESSAGE" frame
94
98
  And the client unsubscribes by ID
95
99
  And the broker sends a "MESSAGE" frame with headers
96
100
  | header-name | header-value |
97
101
  | message-id | m-1235 |
98
102
  | destination | /queue/testing |
99
103
  | subscription | s-5678 |
100
- And the client disconnects
104
+ And the client waits for 1 "MESSAGE" frame
105
+ And the frame exchange is completed
101
106
  Then the default subscription callback should have been triggered 1 time
102
107
 
103
108
  Scenario: No callbacks after unsubscribing by frame
@@ -110,12 +115,14 @@ Feature: Subscribing
110
115
  | message-id | m-1234 |
111
116
  | destination | /queue/testing |
112
117
  | subscription | s-5678 |
118
+ And the client waits for 1 "MESSAGE" frame
113
119
  And the client unsubscribes by frame
114
120
  And the broker sends a "MESSAGE" frame with headers
115
121
  | header-name | header-value |
116
122
  | message-id | m-1235 |
117
123
  | destination | /queue/testing |
118
124
  | subscription | s-5678 |
125
+ And the client waits for 1 "MESSAGE" frame
119
126
  And the frame exchange is completed
120
127
  Then the default subscription callback should have been triggered 1 time
121
128
 
@@ -136,11 +143,13 @@ Feature: Subscribing
136
143
  | header-name | header-value |
137
144
  | message-id | m-1235 |
138
145
  | destination | /queue/testing |
146
+ And the client waits for 2 "MESSAGE" frames
139
147
  And the client unsubscribes from destination "/queue/testing"
140
148
  And the broker sends a "MESSAGE" frame with headers
141
149
  | header-name | header-value |
142
150
  | message-id | m-1235 |
143
151
  | destination | /queue/testing |
152
+ And the client waits for 1 "MESSAGE" frame
144
153
  And the frame exchange is completed
145
154
  Then the default subscription callback should have been triggered 3 times
146
155
  And the broker should have received an "UNSUBSCRIBE" frame with headers
@@ -1,10 +1,15 @@
1
1
  class TestStompServer
2
+ class StopThread < StandardError; end
3
+
2
4
  attr_accessor :session_class
3
5
  attr_reader :session
4
6
 
5
7
  def initialize(version=nil)
6
8
  @port = 61613
7
- @socket = TCPServer.new(@port)
9
+ begin
10
+ @socket = TCPServer.new(@port)
11
+ rescue Exception => ex
12
+ end
8
13
  @session = nil
9
14
  @version = version
10
15
  @session_class = StompSession
@@ -23,14 +28,14 @@ class TestStompServer
23
28
  def stop
24
29
  @session.stop if @session
25
30
  @socket.close rescue nil
26
- @listener.kill rescue nil
31
+ @listener.raise(StopThread.new)
27
32
  @listener.join rescue nil
28
33
  end
29
34
 
30
35
  def force_stop
31
36
  @session.force_stop if @session
32
37
  @socket.close rescue nil
33
- @listener.kill rescue nil
38
+ @listener.raise(StopThread.new)
34
39
  @listener.join rescue nil
35
40
  end
36
41
 
@@ -54,10 +59,11 @@ class TestStompServer
54
59
  connect_to_client(headers)
55
60
  @serializer.extend_for_protocol('1.1') if version == '1.1'
56
61
  @receive_thread = Thread.new do
57
- while @running
62
+ while true
58
63
  begin
59
64
  read_frame
60
65
  rescue Exception => ex
66
+ break
61
67
  end
62
68
  end
63
69
  end
@@ -70,6 +76,7 @@ class TestStompServer
70
76
 
71
77
  def force_stop
72
78
  @running = false
79
+ @receive_thread.raise(StopThread.new)
73
80
  @client_socket.close rescue nil
74
81
  @receive_thread.join rescue nil
75
82
  end
@@ -112,8 +119,11 @@ class TestStompServer
112
119
 
113
120
  def send_frame cmd, headers={}, body=nil
114
121
  frame = cmd.is_a?(Stomper::Frame) ? cmd : Stomper::Frame.new(cmd, headers, body)
115
- @serializer.write_frame(frame).tap do |f|
116
- @sent_frames << f
122
+ begin
123
+ @serializer.write_frame(frame).tap do |f|
124
+ @sent_frames << f
125
+ end
126
+ rescue Exception => ex
117
127
  end
118
128
  end
119
129
  end
@@ -135,7 +145,11 @@ class TestSSLStompServer < TestStompServer
135
145
 
136
146
  def initialize(version=nil, certs=:default)
137
147
  @port = 61612
138
- @tcp_socket = TCPServer.new(@port)
148
+ begin
149
+ @tcp_socket = TCPServer.new(@port)
150
+ rescue Exception => ex
151
+ retry
152
+ end
139
153
  @ssl_context = OpenSSL::SSL::SSLContext.new
140
154
  @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
141
155
  cert_files = SSL_CERT_FILES[certs]
@@ -10,6 +10,7 @@ Feature: Transactions
10
10
  And the client acks a message by ID "m-1234" and subscription "s-5678" within the scope
11
11
  And the client nacks a message by ID "m-9012" and subscription "s-5678" within the scope
12
12
  And the client aborts the transaction scope
13
+ And the frame exchange is completed
13
14
  Then the broker should have received a "BEGIN" frame with headers
14
15
  | header-name | header-value |
15
16
  | transaction | t-0001 |
@@ -30,6 +31,7 @@ Feature: Transactions
30
31
  Scenario: Applying a transaction to a successful block
31
32
  Given a 1.1 connection between client and broker
32
33
  When the client executes a successful transaction block named "t-0002"
34
+ And the frame exchange is completed
33
35
  Then the broker should have received a "BEGIN" frame with headers
34
36
  | header-name | header-value |
35
37
  | transaction | t-0002 |
@@ -49,6 +51,7 @@ Feature: Transactions
49
51
  Scenario: Applying a transaction to an unsuccessful block
50
52
  Given a 1.1 connection between client and broker
51
53
  When the client executes an unsuccessful transaction block named "t-0002"
54
+ And the frame exchange is completed
52
55
  Then the broker should have received a "BEGIN" frame with headers
53
56
  | header-name | header-value |
54
57
  | transaction | t-0002 |
@@ -170,11 +170,11 @@ class Stomper::Connection
170
170
  @receipt_manager = ::Stomper::ReceiptManager.new(self)
171
171
  @connecting = false
172
172
  @disconnecting = false
173
+ @disconnected = false
174
+ @socket_mutex = ::Mutex.new
173
175
 
174
176
  on_connected do |cf, con|
175
177
  unless connected?
176
- @connecting = false
177
- @disconnecting = false
178
178
  @version = (cf[:version].nil?||cf[:version].empty?) ? '1.0' : cf[:version]
179
179
  unless @versions.include?(@version)
180
180
  close
@@ -192,9 +192,12 @@ class Stomper::Connection
192
192
  extend_for_protocol
193
193
  end
194
194
  end
195
-
196
- on_disconnect do |df, con|
195
+
196
+ before_disconnect do |df, con|
197
197
  @disconnecting = true
198
+ end
199
+ on_disconnect do |df, con|
200
+ @disconnected = true
198
201
  close unless df[:receipt]
199
202
  end
200
203
  end
@@ -269,28 +272,34 @@ class Stomper::Connection
269
272
  # connection has been established and you're ready to go, otherwise the
270
273
  # socket will be closed and an error will be raised.
271
274
  def connect(headers={})
272
- @socket = @uri.create_socket(@ssl)
273
- @serializer = ::Stomper::FrameSerializer.new(@socket)
274
- m_headers = {
275
- :'accept-version' => @versions.join(','),
276
- :host => @host,
277
- :'heart-beat' => @heartbeats.join(','),
278
- :login => @login,
279
- :passcode => @passcode
280
- }
281
- @connecting = true
282
- transmit create_frame('CONNECT', headers, m_headers)
283
- receive.tap do |f|
284
- if f.command == 'CONNECTED'
285
- @connected_frame = f
286
- else
287
- close
288
- raise ::Stomper::Errors::ConnectFailedError, 'broker did not send CONNECTED frame'
275
+ #@socket_mutex.synchronize do
276
+ unless @connected
277
+ @socket = @uri.create_socket(@ssl)
278
+ @serializer = ::Stomper::FrameSerializer.new(@socket)
279
+ m_headers = {
280
+ :'accept-version' => @versions.join(','),
281
+ :host => @host,
282
+ :'heart-beat' => @heartbeats.join(','),
283
+ :login => @login,
284
+ :passcode => @passcode
285
+ }
286
+ @disconnecting = false
287
+ @disconnected = false
288
+ @connecting = true
289
+ transmit create_frame('CONNECT', headers, m_headers)
290
+ receive.tap do |f|
291
+ if f.command == 'CONNECTED'
292
+ @connected_frame = f
293
+ @connected = true
294
+ @connecting = false
295
+ trigger_event(:on_connection_established, self)
296
+ else
297
+ close
298
+ raise ::Stomper::Errors::ConnectFailedError, 'broker did not send CONNECTED frame'
299
+ end
300
+ end
289
301
  end
290
- end
291
- @connected = true
292
- trigger_event(:on_connection_established, self) if @connected
293
- @connected_frame
302
+ #end
294
303
  end
295
304
  alias :open :connect
296
305
 
@@ -362,17 +371,21 @@ class Stomper::Connection
362
371
  # @see Stomper::Extensions::Events#on_connection_terminated
363
372
  # @param [true,false] fire_terminated (false) If true, trigger
364
373
  # {Stomper::Extensions::Events#on_connection_terminated}
365
- def close(fire_terminated=false)
366
- begin
367
- trigger_event(:on_connection_terminated, self) if fire_terminated
368
- ensure
369
- unless @socket.closed?
370
- @socket.shutdown(2) rescue nil
371
- @socket.close rescue nil
374
+ def close
375
+ #@socket_mutex.synchronize do
376
+ if @connected
377
+ begin
378
+ trigger_event(:on_connection_terminated, self) unless @disconnected
379
+ ensure
380
+ unless @socket.closed?
381
+ @socket.shutdown(2) rescue nil
382
+ @socket.close rescue nil
383
+ end
384
+ @connected = false
385
+ end
386
+ trigger_event(:on_connection_closed, self)
372
387
  end
373
- @connected = false
374
- end
375
- trigger_event(:on_connection_closed, self)
388
+ #end
376
389
  end
377
390
 
378
391
  # Transmits a frame to the broker. This is a low-level method used internally
@@ -389,7 +402,7 @@ class Stomper::Connection
389
402
  trigger_transmitted_frame(frame, self)
390
403
  end
391
404
  rescue ::IOError, ::SystemCallError
392
- close(true)
405
+ close
393
406
  raise
394
407
  end
395
408
  end
@@ -403,7 +416,7 @@ class Stomper::Connection
403
416
  begin
404
417
  @serializer.read_frame.tap do |f|
405
418
  if f.nil?
406
- close(true) if @connected
419
+ close
407
420
  else
408
421
  @last_received_at = Time.now
409
422
  trigger_event(:after_receiving, f, self)
@@ -411,7 +424,7 @@ class Stomper::Connection
411
424
  end
412
425
  end
413
426
  rescue ::IOError, ::SystemCallError
414
- close(true)
427
+ close
415
428
  raise
416
429
  end
417
430
  end
@@ -424,14 +437,7 @@ class Stomper::Connection
424
437
  # if any data is available it will block until a complete frame has been read.
425
438
  # @return [Stomper::Frame, nil]
426
439
  def receive_nonblock
427
- trigger_event(:on_connection_died, self) if dead?
428
- trigger_event(:before_receiving, self)
429
- if @socket.ready?
430
- @serializer.read_frame.tap do |f|
431
- trigger_event(:after_receiving, self, f)
432
- trigger_received_frame(f, self)
433
- end
434
- end
440
+ receive if @socket.ready?
435
441
  end
436
442
 
437
443
  # Duration in milliseconds since a frame has been transmitted to the broker.
@@ -457,4 +463,3 @@ end
457
463
 
458
464
  # Alias Stomper::Client to Stomper::Connection
459
465
  ::Stomper::Client = ::Stomper::Connection
460
-
@@ -60,13 +60,9 @@ module Stomper::Extensions::Common
60
60
  def unsubscribe(frame_or_id, headers={})
61
61
  sub_id = frame_or_id.is_a?(::Stomper::Frame) ? frame_or_id[:id] : frame_or_id
62
62
  raise ArgumentError, 'subscription ID could not be determined' if sub_id.nil? || sub_id.empty?
63
- if subscription_manager.subscribed_id? sub_id
64
- transmit create_frame('UNSUBSCRIBE', headers, { :id => sub_id })
65
- elsif subscription_manager.subscribed_destination? sub_id
66
- subscription_manager.ids_for_destination(sub_id).map do |id|
67
- transmit create_frame('UNSUBSCRIBE', headers, { :id => id })
68
- end
69
- end
63
+ subscription_manager.remove(sub_id).map do |id|
64
+ transmit create_frame('UNSUBSCRIBE', headers, { :id => id })
65
+ end.last
70
66
  end
71
67
 
72
68
  # Transmits a BEGIN frame to the broker to start a transaction named by +tx_id+.
@@ -12,4 +12,11 @@
12
12
  # @see Stomper::Support::Ruby1_9::Headers Implementation for Ruby 1.9
13
13
  class Stomper::Headers
14
14
  include ::Enumerable
15
+
16
+ # Returns a new +Hash+ object associating symbolized header names and their
17
+ # principle values.
18
+ # @return [Hash]
19
+ def to_hash
20
+ to_a.inject({}) { |h, (k,v)| h[k.to_sym] ||= v; h }
21
+ end
15
22
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  # Basic threaded receiver
4
4
  class Stomper::Receivers::Threaded
5
+ # Stop Receiver
6
+ class StopReceiver < StandardError; end
7
+
5
8
  # Returns true if the receiver is currently running, false otherwise.
6
9
  # If the polling thread is terminated due to a raised exception, this
7
10
  # attribute will be false.
@@ -20,7 +23,6 @@ class Stomper::Receivers::Threaded
20
23
  @running = false
21
24
  @run_mutex = ::Mutex.new
22
25
  @run_thread = nil
23
- @raised_while_running = nil
24
26
  end
25
27
 
26
28
  # Starts the receiver by creating a new thread to continually poll the
@@ -32,14 +34,15 @@ class Stomper::Receivers::Threaded
32
34
  is_starting = @run_mutex.synchronize { @running = true unless @running }
33
35
  if is_starting
34
36
  @run_thread = Thread.new do
35
- while @running
36
- begin
37
- @running = false if @connection.receive.nil?
38
- rescue Exception => ex
39
- @running = false
40
- raise ex
37
+ begin
38
+ until @connection.receive.nil?
41
39
  end
40
+ rescue ::Stomper::Receivers::Threaded::StopReceiver
41
+ rescue Exception => ex
42
+ @running = false
43
+ raise ex
42
44
  end
45
+ @running = false
43
46
  end
44
47
  end
45
48
  self
@@ -58,11 +61,12 @@ class Stomper::Receivers::Threaded
58
61
  def stop
59
62
  stopped = @run_mutex.synchronize { @run_thread.nil? }
60
63
  unless stopped
61
- @running = false
64
+ @run_thread.raise(::Stomper::Receivers::Threaded::StopReceiver.new)
62
65
  begin
63
66
  @run_thread.join
64
- rescue IOError
67
+ rescue ::IOError, ::SystemCallError
65
68
  raise if @connection.connected?
69
+ rescue ::Stomper::Receivers::Threaded::StopReceiver => ex
66
70
  end
67
71
  @run_thread = nil
68
72
  end
@@ -6,10 +6,10 @@ class Stomper::SubscriptionManager
6
6
  # @param [Stomper::Connection] connection
7
7
  def initialize(connection)
8
8
  @mon = ::Monitor.new
9
- @callbacks = {}
10
- @dests_to_ids = {}
9
+ @subscriptions = {}
11
10
  connection.on_message { |m, con| dispatch(m) }
12
- connection.on_unsubscribe { |u, con| remove(u) }
11
+ connection.on_unsubscribe { |u, con| remove(u[:id]) }
12
+ connection.on_connection_closed { |con| @subscriptions.clear }
13
13
  end
14
14
 
15
15
  # Adds a callback handler for a MESSAGE frame that is sent via the subscription
@@ -22,58 +22,60 @@ class Stomper::SubscriptionManager
22
22
  s_id = subscribe[:id]
23
23
  dest = subscribe[:destination]
24
24
  @mon.synchronize do
25
- @callbacks[s_id] = callback
26
- @dests_to_ids[dest] ||= []
27
- @dests_to_ids[dest] << s_id
25
+ @subscriptions[s_id] = Subscription.new(subscribe, callback)
28
26
  end
29
27
  end
30
28
 
31
- # Returns true if the subscription ID is registered
32
- # @param [String] id
33
- # @return [true,false]
34
- def subscribed_id?(id)
35
- @mon.synchronize { @callbacks.key? id }
36
- end
37
-
38
- # Returns true if the subscription destination is registered
39
- # @param [String] destination
40
- # @return [true,false]
41
- def subscribed_destination?(destination)
42
- @mon.synchronize { @dests_to_ids.key? destination }
43
- end
44
-
45
- # Returns an array of subscription IDs that correspond to
46
- # the given subscription destination. If the destination is unknown,
47
- # returns +nil+.
48
- # @param [String] destination
49
- # @return [Array<String>, nil]
50
- def ids_for_destination(destination)
51
- @mon.synchronize { @dests_to_ids[destination] && @dests_to_ids[destination].dup }
52
- end
53
-
54
- private
55
- def remove(unsub)
56
- s_id = unsub[:id]
29
+ # Removes a subscription by ID or destination.
30
+ # @param [String] sub_id ID or destination of the subscription
31
+ # @return [Array<String>] array of subscription IDs matching +sub_id+
32
+ def remove(sub_id)
57
33
  @mon.synchronize do
58
- @dests_to_ids.each do |dest, ids|
59
- ids.delete s_id
60
- @dests_to_ids.delete dest if ids.empty?
34
+ if @subscriptions.key? sub_id
35
+ @subscriptions.delete sub_id
36
+ [sub_id]
37
+ else
38
+ @subscriptions.values.inject([]) do |ids, sub|
39
+ if sub.destination == sub_id
40
+ @subscriptions.delete sub.id
41
+ ids << sub.id
42
+ end
43
+ ids
44
+ end
61
45
  end
62
- @callbacks.delete(s_id)
63
46
  end
64
47
  end
65
48
 
49
+ # Returns all current subscriptions in the form of their SUBSCRIBE frames.
50
+ # @return [Array<Stomper::Frame>]
51
+ def subscriptions
52
+ @mon.synchronize { @subscriptions.values }
53
+ end
54
+
55
+ private
66
56
  def dispatch(message)
67
57
  s_id = message[:subscription]
68
58
  dest = message[:destination]
69
59
  if s_id.nil? || s_id.empty?
70
- cbs = @mon.synchronize do
71
- @dests_to_ids[dest] && @dests_to_ids[dest].map { |id| @callbacks[id] }
72
- end
73
- cbs && cbs.each { |cb| cb.call(message) }
60
+ @mon.synchronize do
61
+ @subscriptions.values.map do |sub|
62
+ (sub.destination == dest) && sub
63
+ end
64
+ end.each { |cb| cb && cb.call(message) }
74
65
  else
75
- cb = @mon.synchronize { @callbacks[s_id] }
66
+ cb = @mon.synchronize { @subscriptions[s_id] }
76
67
  cb && cb.call(message)
77
68
  end
78
69
  end
70
+
71
+ class Subscription
72
+ attr_reader :frame, :callback
73
+ def initialize(fr, cb)
74
+ @frame = fr
75
+ @callback = cb
76
+ end
77
+ def id; @frame[:id]; end
78
+ def destination; @frame[:destination]; end
79
+ def call(m); @callback.call(m); end
80
+ end
79
81
  end
@@ -3,5 +3,5 @@
3
3
  # Primary namespace of the stomper gem.
4
4
  module Stomper
5
5
  # The current version of the stomper gem.
6
- VERSION = '2.0.0'
6
+ VERSION = '2.0.1'
7
7
  end
@@ -271,9 +271,17 @@ module Stomper
271
271
  it "should close the socket if reading a frame returns nil" do
272
272
  @connection.connect
273
273
  @serializer.should_receive(:read_frame).and_return(nil)
274
- @connection.should_receive(:close).with(true)
274
+ @connection.should_receive(:close)
275
275
  @connection.receive.should be_nil
276
276
  end
277
+
278
+ it "should close the socket if reading a frame returns nil" do
279
+ @connection.connect
280
+ @socket.should_receive(:ready?).and_return(true)
281
+ @serializer.should_receive(:read_frame).and_return(nil)
282
+ @connection.should_receive(:close)
283
+ @connection.receive_nonblock.should be_nil
284
+ end
277
285
  end
278
286
 
279
287
  describe "connection state events" do
@@ -356,12 +364,33 @@ module Stomper
356
364
  lambda { @connection.receive }.should raise_error(IOError)
357
365
  triggered.should be_true
358
366
  end
367
+
368
+ it "should not trigger on_connection_terminated if the socket raises an error after disconnecting" do
369
+ triggered = false
370
+ @connection.on_connection_terminated { triggered = true }
371
+ @connection.connect
372
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT'))
373
+ @connection.disconnect
374
+ @connection.should_receive(:alive?).at_least(:once).and_return(true)
375
+ @serializer.should_receive(:read_frame).and_raise(IOError.new('Error while reading frame'))
376
+ lambda { @connection.receive }.should raise_error(IOError)
377
+ triggered.should be_false
378
+ end
379
+
380
+ it "should trigger on_connection_terminated if the socket raises an error before DISCONNECT is written" do
381
+ triggered = false
382
+ @connection.on_connection_terminated { triggered = true }
383
+ @connection.connect
384
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT')).and_raise(IOError.new('Error before DISCONNECT'))
385
+ lambda { @connection.disconnect }.should raise_error(IOError)
386
+ triggered.should be_true
387
+ end
359
388
 
360
389
  it "should trigger on_connection_terminated if the socket raises a SystemCallError while receiving" do
361
390
  triggered = false
362
391
  @connection.on_connection_terminated { triggered = true }
363
392
  @connection.connect
364
- @serializer.stub!(:read_frame).and_raise(SystemCallError.new('syscall error'))
393
+ @serializer.should_receive(:read_frame).and_raise(SystemCallError.new('syscall error'))
365
394
  lambda { @connection.receive }.should raise_error(SystemCallError)
366
395
  triggered.should be_true
367
396
  end
@@ -45,13 +45,13 @@ module Stomper::Extensions
45
45
  end
46
46
 
47
47
  it "should transmit an UNSUBSCRIBE frame for a given subscription ID" do
48
- @subscription_manager.should_receive(:subscribed_id?).with('subscription-1234').and_return(true)
48
+ @subscription_manager.should_receive(:remove).with('subscription-1234').and_return(['subscription-1234'])
49
49
  @common.should_receive(:transmit).with(stomper_frame_with_headers({'id' => 'subscription-1234'}, 'UNSUBSCRIBE'))
50
50
  @common.unsubscribe('subscription-1234')
51
51
  end
52
52
 
53
53
  it "should transmit an UNSUBSCRIBE frame for a given SUBSCRIBE frame" do
54
- @subscription_manager.should_receive(:subscribed_id?).with('id-in-frame-4321').and_return(true)
54
+ @subscription_manager.should_receive(:remove).with('id-in-frame-4321').and_return(['id-in-frame-4321'])
55
55
  subscribe = ::Stomper::Frame.new('SUBSCRIBE', { :id => 'id-in-frame-4321' })
56
56
  @common.should_receive(:transmit).with(stomper_frame_with_headers({'id' => 'id-in-frame-4321'}, 'UNSUBSCRIBE'))
57
57
  @common.unsubscribe(subscribe)
@@ -94,9 +94,7 @@ module Stomper::Extensions
94
94
  # as well.
95
95
  @common.should_receive(:transmit).with(stomper_frame_with_headers({:id => '1234'}, 'UNSUBSCRIBE'))
96
96
  @common.should_receive(:transmit).with(stomper_frame_with_headers({:id => '4567'}, 'UNSUBSCRIBE'))
97
- @subscription_manager.should_receive(:subscribed_id?).with('/queue/test').and_return(false)
98
- @subscription_manager.should_receive(:subscribed_destination?).with('/queue/test').and_return(true)
99
- @subscription_manager.should_receive(:ids_for_destination).with('/queue/test').and_return(['1234', '4567'])
97
+ @subscription_manager.should_receive(:remove).with('/queue/test').and_return(['1234', '4567'])
100
98
  @common.unsubscribe("/queue/test")
101
99
  end
102
100
  end
@@ -28,13 +28,6 @@ module Stomper
28
28
  @headers['test header'].should == ''
29
29
  end
30
30
 
31
- it "should return an array of header key/value pairs" do
32
- @headers['header 1'] = 'testing'
33
- @headers['other header'] = 19
34
- @headers[:tom] = 'servo'
35
- @headers.to_a.should == [ ['header 1', 'testing'], ['other header', '19'], ['tom', 'servo'] ]
36
- end
37
-
38
31
  it "should preserve the order of keys" do
39
32
  expected_keys = []
40
33
  20.times do |n|
@@ -110,6 +103,20 @@ module Stomper
110
103
  ['header 3', 'h3 value 2'] ]
111
104
  end
112
105
 
106
+ it "should be convertable to a hash of principle values" do
107
+ @headers.append('header 1', 'h1 value 1')
108
+ @headers.append('header 1', 'h1 value 2')
109
+ @headers.append('header 1', 'h1 value 3')
110
+ @headers['header 2'] = 'h2 value 1'
111
+ @headers['header 3'] = 'h3 value 1'
112
+ @headers.append('header 3', 'h3 value 2')
113
+ @headers.to_hash.should == {
114
+ :'header 1' => 'h1 value 1',
115
+ :'header 2' => 'h2 value 1',
116
+ :'header 3' => 'h3 value 1'
117
+ }
118
+ end
119
+
113
120
  it "should set a header value to the first encountered in a chain of appends" do
114
121
  @headers.append('header 1', 'first value')
115
122
  @headers.append('header 1', 'second value')
@@ -7,7 +7,7 @@ module Stomper::Scopes
7
7
  @connection = mock("connection", :is_a? => true, :version => '1.1')
8
8
  @headers = { :global_1 => 'turbo', 'global_2' => 'is me', :persistent => true }
9
9
  @connection.stub!(:subscription_manager).and_return(mock('subscription manager', {
10
- :subscribed_id? => true
10
+ :remove => ['no-real-destination']
11
11
  }))
12
12
  @scope = HeaderScope.new(@connection, @headers)
13
13
  end
@@ -8,7 +8,7 @@ module Stomper::Scopes
8
8
  @connection = mock("connection", :is_a? => true, :version => '1.1')
9
9
  @connection.stub!(:receipt_manager => @receipt_manager)
10
10
  @connection.stub!(:subscription_manager).and_return(mock('subscription manager', {
11
- :subscribed_id? => true
11
+ :remove => ['/queue/test']
12
12
  }))
13
13
  @scope = ReceiptScope.new(@connection, {})
14
14
  end
@@ -7,7 +7,7 @@ module Stomper::Scopes
7
7
  @connection = mock("connection", :is_a? => true, :version => '1.1')
8
8
  @headers = { :transaction => 'tx-1234' }
9
9
  @connection.stub!(:subscription_manager).and_return(mock('subscription manager', {
10
- :subscribed_id? => true
10
+ :remove => ['no-real-destination']
11
11
  }))
12
12
  @scope = TransactionScope.new(@connection, @headers)
13
13
  @connection.should_receive(:transmit).at_most(:once).with(stomper_frame_with_headers(@headers, 'BEGIN'))
@@ -11,6 +11,7 @@ module Stomper
11
11
  receipt = mock('receipt')
12
12
  @connection.should_receive(:on_message)
13
13
  @connection.should_receive(:on_unsubscribe)
14
+ @connection.should_receive(:on_connection_closed)
14
15
  @subscription_manager = SubscriptionManager.new(@connection)
15
16
  end
16
17
 
@@ -19,36 +20,53 @@ module Stomper
19
20
  @connection.extend ::Stomper::Extensions::Events
20
21
  @subscription_manager = SubscriptionManager.new(@connection)
21
22
  @subscribe_frame = ::Stomper::Frame.new('SUBSCRIBE', {:id => '1234', :destination => '/queue/testing'})
22
- @unsubscribe_frame = ::Stomper::Frame.new('UNSUBSCRIBE', {:id => '1234'})
23
23
  end
24
24
 
25
- it "should correctly report subscribed IDs" do
25
+ it "should correctly report subscriptoins" do
26
26
  @subscription_manager.add(@subscribe_frame, lambda { |m| true })
27
- @subscription_manager.subscribed_id?('1234').should be_true
28
- @subscription_manager.subscribed_id?('4321').should be_false
29
- @subscription_manager.__send__(:remove, @unsubscribe_frame)
30
- @subscription_manager.subscribed_id?('1234').should be_false
27
+ @subscription_manager.subscriptions.any? { |s| s.id == '1234' }.should be_true
28
+ @subscription_manager.subscriptions.any? { |s| s.id == '4321' }.should be_false
29
+ @subscription_manager.remove('1234')
30
+ @subscription_manager.subscriptions.any? { |s| s.id == '1234' }.should be_false
31
31
  end
32
-
32
+
33
33
  it "should correctly report subscribed destinations" do
34
34
  @subscription_manager.add(@subscribe_frame, lambda { |m| true })
35
- @subscription_manager.subscribed_destination?('/queue/testing').should be_true
36
- @subscription_manager.subscribed_destination?('/queue/test').should be_false
37
- @subscription_manager.__send__(:remove, @unsubscribe_frame)
38
- @subscription_manager.subscribed_destination?('/queue/testing').should be_false
35
+ @subscription_manager.subscriptions.any? { |s| s.destination == '/queue/testing' }.should be_true
36
+ @subscription_manager.subscriptions.any? { |s| s.destination == '/queue/test' }.should be_false
37
+ @subscription_manager.remove('/queue/testing')
38
+ @subscription_manager.subscriptions.any? { |s| s.destination == '/queue/testing' }.should be_false
39
39
  end
40
40
 
41
41
  it "should correctly map subscribed destinations to their IDs" do
42
- alt_subscribe_frame = ::Stomper::Frame.new('SUBSCRIBE', {:id => '4567', :destination => '/queue/testing'})
43
- alt_unsubscribe_frame = ::Stomper::Frame.new('UNSUBSCRIBE', {:id => '4567'})
42
+ alt_subscribe_frame = ::Stomper::Frame.new('SUBSCRIBE', {:id => '4567', :destination => '/queue/test_further'})
43
+ @subscription_manager.add(@subscribe_frame, lambda { |m| true })
44
+ @subscription_manager.add(alt_subscribe_frame, lambda { |m| true })
45
+ @subscription_manager.subscriptions.map { |s| s.id }.should == ['1234', '4567']
46
+ @subscription_manager.remove('/queue/testing')
47
+ @subscription_manager.subscriptions.map { |s| s.id }.should == ['4567']
48
+ @subscription_manager.remove('/queue/test_further')
49
+ @subscription_manager.subscriptions.should be_empty
50
+ end
51
+
52
+ it "should clear out all remaining subscriptions when the connection is closed" do
53
+ alt_subscribe_frame = ::Stomper::Frame.new('SUBSCRIBE', {:id => '4567', :destination => '/queue/test_further'})
44
54
  @subscription_manager.add(@subscribe_frame, lambda { |m| true })
45
55
  @subscription_manager.add(alt_subscribe_frame, lambda { |m| true })
46
- @subscription_manager.ids_for_destination('/queue/testing').should == ['1234', '4567']
47
- @subscription_manager.ids_for_destination('/queue/test').should be_nil
48
- @subscription_manager.__send__(:remove, @unsubscribe_frame)
49
- @subscription_manager.ids_for_destination('/queue/testing').should == ['4567']
50
- @subscription_manager.__send__(:remove, alt_unsubscribe_frame)
51
- @subscription_manager.ids_for_destination('/queue/testing').should be_nil
56
+ @subscription_manager.remove('4567')
57
+ @subscription_manager.subscriptions.map { |s| s.id }.should == ['1234']
58
+ @connection.__send__(:trigger_event, :on_connection_closed, @connection)
59
+ @subscription_manager.subscriptions.should be_empty
60
+ end
61
+
62
+ it "should trigger subscriptions for MESSAGE frames with only a destination" do
63
+ triggered = [0, 0]
64
+ alt_subscribe_frame = ::Stomper::Frame.new('SUBSCRIBE', {:id => '4567', :destination => '/queue/testing'})
65
+ @subscription_manager.add(@subscribe_frame, lambda { |m| triggered[0] += 1 })
66
+ @subscription_manager.add(alt_subscribe_frame, lambda { |m| triggered[1] += 1 })
67
+ @connection.__send__(:trigger_received_frame, ::Stomper::Frame.new('MESSAGE', { :destination => '/queue/testing' }))
68
+ @connection.__send__(:trigger_received_frame, ::Stomper::Frame.new('MESSAGE', { :subscription => '4567' }))
69
+ triggered.should == [1, 2]
52
70
  end
53
71
 
54
72
  it "should allow a subscription handler to be registered within a callback" do
@@ -76,7 +94,7 @@ module Stomper
76
94
  triggered.should == 1
77
95
  end
78
96
 
79
- it "should allow a receipt handler to be registered within a callback in separate threads" do
97
+ it "should allow a subscription handler to be registered within a callback in separate threads" do
80
98
  alt_subscribe_frame = ::Stomper::Frame.new('SUBSCRIBE', {:id => '4567', :destination => '/queue/testing'})
81
99
  triggered = [false, false]
82
100
  started_r1 = false
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 2
7
7
  - 0
8
- - 0
9
- version: 2.0.0
8
+ - 1
9
+ version: 2.0.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ian D. Eccles
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-02-22 00:00:00 -05:00
17
+ date: 2011-02-27 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency