amqp 0.7.0.pre → 0.7.0

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 (71) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +2 -0
  3. data/CHANGELOG +8 -2
  4. data/CONTRIBUTORS +22 -0
  5. data/Gemfile +3 -3
  6. data/README.md +20 -11
  7. data/Rakefile +30 -6
  8. data/amqp.gemspec +1 -1
  9. data/bin/cleanify.rb +50 -0
  10. data/examples/amqp/simple.rb +6 -4
  11. data/examples/mq/ack.rb +8 -6
  12. data/examples/mq/automatic_binding_for_default_direct_exchange.rb +65 -0
  13. data/examples/mq/callbacks.rb +9 -1
  14. data/examples/mq/clock.rb +17 -17
  15. data/examples/mq/hashtable.rb +19 -10
  16. data/examples/mq/internal.rb +13 -11
  17. data/examples/mq/logger.rb +38 -36
  18. data/examples/mq/multiclock.rb +16 -7
  19. data/examples/mq/pingpong.rb +16 -7
  20. data/examples/mq/pop.rb +8 -6
  21. data/examples/mq/primes-simple.rb +2 -0
  22. data/examples/mq/primes.rb +7 -5
  23. data/examples/mq/stocks.rb +14 -5
  24. data/lib/amqp.rb +12 -8
  25. data/lib/amqp/buffer.rb +35 -158
  26. data/lib/amqp/client.rb +34 -22
  27. data/lib/amqp/frame.rb +8 -64
  28. data/lib/amqp/protocol.rb +21 -70
  29. data/lib/amqp/server.rb +11 -9
  30. data/lib/amqp/spec.rb +8 -6
  31. data/lib/amqp/version.rb +2 -0
  32. data/lib/ext/blankslate.rb +3 -1
  33. data/lib/ext/em.rb +2 -0
  34. data/lib/ext/emfork.rb +13 -11
  35. data/lib/mq.rb +253 -156
  36. data/lib/mq/collection.rb +6 -88
  37. data/lib/mq/exchange.rb +70 -13
  38. data/lib/mq/header.rb +12 -6
  39. data/lib/mq/logger.rb +9 -7
  40. data/lib/mq/queue.rb +42 -30
  41. data/lib/mq/rpc.rb +6 -4
  42. data/protocol/codegen.rb +20 -18
  43. data/research/api.rb +10 -46
  44. data/research/primes-forked.rb +9 -7
  45. data/research/primes-processes.rb +74 -72
  46. data/research/primes-threaded.rb +9 -7
  47. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +61 -0
  48. data/spec/mq_helper.rb +70 -0
  49. data/spec/spec_helper.rb +84 -29
  50. data/spec/unit/amqp/buffer_spec.rb +178 -0
  51. data/spec/unit/amqp/client_spec.rb +472 -0
  52. data/spec/unit/amqp/frame_spec.rb +60 -0
  53. data/spec/unit/amqp/misc_spec.rb +123 -0
  54. data/spec/unit/amqp/protocol_spec.rb +53 -0
  55. data/spec/unit/mq/channel_close_spec.rb +15 -0
  56. data/spec/unit/mq/collection_spec.rb +129 -0
  57. data/spec/unit/mq/exchange_declaration_spec.rb +524 -0
  58. data/spec/unit/mq/misc_spec.rb +228 -0
  59. data/spec/unit/mq/mq_basic_spec.rb +39 -0
  60. data/spec/unit/mq/queue_declaration_spec.rb +97 -0
  61. data/spec/unit/mq/queue_spec.rb +71 -0
  62. metadata +33 -21
  63. data/Gemfile.lock +0 -16
  64. data/old/README +0 -30
  65. data/old/Rakefile +0 -12
  66. data/old/amqp-0.8.json +0 -606
  67. data/old/amqp_spec.rb +0 -796
  68. data/old/amqpc.rb +0 -695
  69. data/old/codegen.rb +0 -148
  70. data/spec/channel_close_spec.rb +0 -13
  71. data/spec/sync_async_spec.rb +0 -52
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class MQ
4
+ # MQ::Collection is used to store named AMQ model entities (exchanges, queues)
4
5
  class Collection < ::Array
5
6
  class IncompatibleItemError < ArgumentError
6
7
  def initialize(item)
@@ -21,15 +22,17 @@ class MQ
21
22
  # run declare twice in order to change options, because the AMQP
22
23
  # broker closes the connection if we try to do so).
23
24
 
24
- # Use Collection#<< for adding items to the collection.
25
+ # Use Collection# << for adding items to the collection.
25
26
  undef_method :[]=
26
27
 
27
28
  def <<(item)
28
- if (item.name rescue nil).nil? || ! self[item.name]
29
+ if (item.name rescue nil).nil? || !self[item.name]
29
30
  self.add!(item)
30
31
  end
31
32
 
32
- return item
33
+ # We can't just return the item, because in case the item isn't added
34
+ # to the collection, then it'd be different from self[item.name].
35
+ return self[item.name]
33
36
  end
34
37
 
35
38
  alias_method :__push__, :push
@@ -45,88 +48,3 @@ class MQ
45
48
  end
46
49
  end
47
50
  end
48
-
49
- if $0 =~ /bacon/ or $0 == __FILE__
50
- require "bacon"
51
-
52
- Item = Struct.new(:name)
53
-
54
- describe MQ::Collection do
55
- before do
56
- @items = 3.times.map { |int| Item.new("name-#{int}") }
57
- @collection = MQ::Collection.new(@items)
58
- end
59
-
60
- describe "accessors" do
61
- should "be accessible by its name" do
62
- @collection["name-1"].should.not.be.nil
63
- @collection["name-1"].should.eql(@items[1])
64
- end
65
-
66
- should "not allow to change already existing object" do
67
- lambda { @collection["name-1"] = Item.new("test") }.should.raise(NoMethodError)
68
- end
69
- end
70
-
71
- describe "#<<" do
72
- should "raise IncompatibleItemError if the argument doesn't have method :name" do
73
- lambda { @collection << nil }.should.raise(MQ::Collection::IncompatibleItemError)
74
- end
75
-
76
- should "add an item into the collection" do
77
- length = @collection.length
78
- @collection << Item.new("test")
79
- @collection.length.should.eql(length + 1)
80
- end
81
-
82
- should "not add an item to the collection if another item with given name already exists and the name IS NOT nil" do
83
- @collection << Item.new("test")
84
- length = @collection.length
85
- @collection << Item.new("test")
86
- @collection.length.should.eql(length)
87
- end
88
-
89
- should "add an item to the collection if another item with given name already exists and the name IS nil" do
90
- @collection << Item.new(nil)
91
- length = @collection.length
92
- @collection << Item.new(nil)
93
- @collection.length.should.eql(length + 1)
94
- end
95
-
96
- should "return the item" do
97
- item = Item.new("test")
98
- (@collection << item).should.eql item
99
- end
100
-
101
- should "return the item even if it already existed" do
102
- item = Item.new("test")
103
- @collection << item
104
- (@collection << item).should.eql item
105
- end
106
- end
107
-
108
- describe "#add!" do
109
- should "raise IncompatibleItemError if the argument doesn't have method :name" do
110
- lambda { @collection << nil }.should.raise(MQ::Collection::IncompatibleItemError)
111
- end
112
-
113
- should "add an item into the collection" do
114
- length = @collection.length
115
- @collection << Item.new("test")
116
- @collection.length.should.eql(length + 1)
117
- end
118
-
119
- should "add an item to the collection if another item with given name already exists" do
120
- @collection.add! Item.new("test")
121
- length = @collection.length
122
- @collection.add! Item.new("test")
123
- @collection.length.should.eql(length + 1)
124
- end
125
-
126
- should "return the item" do
127
- item = Item.new("test")
128
- (@collection << item).should.eql item
129
- end
130
- end
131
- end
132
- end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  # An Exchange acts as an ingress point for all published messages. An
3
5
  # exchange may also be described as a router or a matcher. Every
@@ -20,8 +22,41 @@ class MQ
20
22
  # the default exchange for publishing the messages.
21
23
  #
22
24
  class Exchange
25
+
26
+ #
27
+ # Behaviors
28
+ #
29
+
23
30
  include AMQP
24
31
 
32
+
33
+
34
+ #
35
+ # API
36
+ #
37
+
38
+
39
+ # The default exchange.
40
+ # Every queue is bind to this (direct) exchange by default.
41
+ # You can't remove it or bind there queue explicitly.
42
+
43
+ # Do NOT confuse with amq.direct: it's only a normal direct
44
+ # exchange and the only special thing about it is that it's
45
+ # predefined in the system, so you can use it straightaway.
46
+
47
+ # Example:
48
+ # MQ.new.queue("tasks")
49
+ # MQ::Exchange.default.publish("make clean", routing_key: "tasks")
50
+
51
+ # For more info see section 2.1.2.4 Automatic Mode of the AMQP 0.9.1 spec.
52
+ def self.default
53
+ @@default ||= self.new(MQ.new, :direct, "", :no_declare => true)
54
+ end
55
+
56
+ def self.add_default_options(type, name, opts, block)
57
+ { :exchange => name, :type => type, :nowait => block.nil? }.merge(opts)
58
+ end
59
+
25
60
  # Defines, intializes and returns an Exchange to act as an ingress
26
61
  # point for all published messages.
27
62
  #
@@ -194,14 +229,15 @@ class MQ
194
229
  # sensitive to the ordering of clients' actions!
195
230
  #
196
231
  # == Exceptions
197
- # Doing any of these activities are illegal and will raise MQ:Error.
198
- # * redeclare an already-declared exchange to a different type
232
+ # Doing any of these activities are illegal and will raise exceptions:
233
+ #
234
+ # * redeclare an already-declared exchange to a different type (raises MQ::IncompatibleOptionsError)
199
235
  # * :passive => true and the exchange does not exist (NOT_FOUND)
200
236
  #
201
- def initialize mq, type, name, opts = {}, &block
237
+ def initialize(mq, type, name, opts = {}, &block)
202
238
  @mq = mq
203
239
  @type, @opts = type, opts
204
- @opts = { :exchange => name, :type => type, :nowait => block.nil? }.merge(opts)
240
+ @opts = self.class.add_default_options(type, name, opts, block)
205
241
  @key = opts[:key]
206
242
  @name = name unless name.empty?
207
243
  @status = :unknown
@@ -225,7 +261,7 @@ class MQ
225
261
  # Call the callback immediately, as given exchange is already
226
262
  # declared.
227
263
  @status = :finished
228
- block.call(self)
264
+ block.call(self) if block
229
265
  end
230
266
 
231
267
  self.callback = block
@@ -274,8 +310,8 @@ class MQ
274
310
  # message stays in memory and is never persisted to non-volatile (slow)
275
311
  # storage.
276
312
  #
277
- def publish data, opts = {}
278
- @mq.callback{
313
+ def publish(data, opts = {})
314
+ @mq.callback {
279
315
  out = []
280
316
 
281
317
  out << Protocol::Basic::Publish.new({ :exchange => name,
@@ -317,8 +353,8 @@ class MQ
317
353
  # bindings. If the exchange has queue bindings the server does not
318
354
  # delete it but raises a channel exception instead (MQ:Error).
319
355
  #
320
- def delete opts = {}
321
- @mq.callback{
356
+ def delete(opts = {})
357
+ @mq.callback {
322
358
  @mq.send Protocol::Exchange::Delete.new({ :exchange => name,
323
359
  :nowait => true }.merge(opts))
324
360
  @mq.exchanges.delete name
@@ -326,13 +362,34 @@ class MQ
326
362
  nil
327
363
  end
328
364
 
365
+
366
+ def durable?
367
+ !!@opts[:durable]
368
+ end # durable?
369
+
370
+ def transient?
371
+ !self.durable?
372
+ end # transient?
373
+
374
+ def auto_deleted?
375
+ !!@opts[:auto_delete]
376
+ end # auto_deleted?
377
+ alias auto_deletable? auto_deleted?
378
+
379
+
329
380
  def reset
330
381
  @deferred_status = nil
331
382
  initialize @mq, @type, @name, @opts
332
383
  end
333
384
 
334
- def receive_response response
385
+
386
+
387
+ #
388
+ # Implementation
389
+ #
390
+
391
+ def receive_response(response)
335
392
  self.callback && self.callback.call(self)
336
- end
337
- end
338
- end
393
+ end # receive_response
394
+ end # Exchange
395
+ end # MQ
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  class Header
3
5
  include AMQP
@@ -9,20 +11,24 @@ class MQ
9
11
 
10
12
  # Acknowledges the receipt of this message with the server.
11
13
  def ack
12
- @mq.callback{
14
+ @mq.callback {
13
15
  @mq.send Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag])
14
16
  }
15
17
  end
16
18
 
17
19
  # Reject this message (XXX currently unimplemented in rabbitmq)
18
20
  # * :requeue => true | false (default false)
19
- def reject opts = {}
20
- @mq.callback{
21
- @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
22
- }
21
+ def reject(opts = {})
22
+ if @mq.broker.server_properties[:product] == "RabbitMQ"
23
+ raise NotImplementedError.new("RabbitMQ doesn't implement the Basic.Reject method\nSee http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2009-February/002853.html")
24
+ else
25
+ @mq.callback {
26
+ @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
27
+ }
28
+ end
23
29
  end
24
30
 
25
- def method_missing meth, *args, &blk
31
+ def method_missing(meth, *args, &blk)
26
32
  @header.send meth, *args, &blk
27
33
  end
28
34
 
@@ -1,6 +1,8 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  class Logger
3
- def initialize *args, &block
5
+ def initialize(*args, &block)
4
6
  opts = args.pop if args.last.is_a? Hash
5
7
  opts ||= {}
6
8
 
@@ -13,7 +15,7 @@ class MQ
13
15
  attr_reader :prop
14
16
  alias :base :prop
15
17
 
16
- def log severity, *args
18
+ def log(severity, *args)
17
19
  opts = args.pop if args.last.is_a? Hash and args.size != 1
18
20
  opts ||= {}
19
21
  opts = @prop.clone.update(opts)
@@ -56,7 +58,7 @@ class MQ
56
58
  end
57
59
  alias :method_missing :log
58
60
 
59
- def print data = nil, &block
61
+ def print(data = nil, &block)
60
62
  if block
61
63
  @printer = block
62
64
  elsif data.is_a? Proc
@@ -68,7 +70,7 @@ class MQ
68
70
  end
69
71
  end
70
72
  alias :printer :print
71
-
73
+
72
74
  def self.printer &block
73
75
  @printer = block if block
74
76
  @printer
@@ -77,13 +79,13 @@ class MQ
77
79
  def self.disabled?
78
80
  !!@disabled
79
81
  end
80
-
82
+
81
83
  def self.enable
82
84
  @disabled = false
83
85
  end
84
-
86
+
85
87
  def self.disable
86
88
  @disabled = true
87
89
  end
88
90
  end
89
- end
91
+ end
@@ -1,7 +1,13 @@
1
+ # encoding: utf-8
2
+
1
3
  class MQ
2
4
  class Queue
3
5
  include AMQP
4
6
 
7
+ def self.add_default_options(name, opts, block)
8
+ { :queue => name, :nowait => block.nil? }.merge(opts)
9
+ end
10
+
5
11
  # Queues store and forward messages. Queues can be configured in the server
6
12
  # or created at runtime. Queues must be attached to at least one exchange
7
13
  # in order to receive messages from publishers.
@@ -15,8 +21,8 @@ class MQ
15
21
  #
16
22
  # == Options
17
23
  # * :passive => true | false (default false)
18
- # If set, the server will not create the exchange if it does not
19
- # already exist. The client can use this to check whether an exchange
24
+ # If set, the server will not create the queue if it does not
25
+ # already exist. The client can use this to check whether the queue
20
26
  # exists without modifying the server state.
21
27
  #
22
28
  # * :durable => true | false (default false)
@@ -51,7 +57,7 @@ class MQ
51
57
  #
52
58
  # The server waits for a short period of time before
53
59
  # determining the queue is unused to give time to the client code
54
- # to bind an exchange to it.
60
+ # to bind a queue to it.
55
61
  #
56
62
  # If the queue has been previously declared, this option is ignored
57
63
  # on subsequent declarations.
@@ -61,9 +67,9 @@ class MQ
61
67
  # not wait for a reply method. If the server could not complete the
62
68
  # method it will raise a channel or connection exception.
63
69
  #
64
- def initialize mq, name, opts = {}, &block
70
+ def initialize(mq, name, opts = {}, &block)
65
71
  @mq = mq
66
- @opts = { :queue => name, :nowait => block.nil? }.merge(opts)
72
+ @opts = self.class.add_default_options(name, opts, block)
67
73
  @bindings ||= {}
68
74
  @name = name unless name.empty?
69
75
  @status = @opts[:nowait] ? :unknown : :unfinished
@@ -74,7 +80,7 @@ class MQ
74
80
  self.callback = block
75
81
  end
76
82
 
77
- attr_reader :name
83
+ attr_reader :name, :sync_bind
78
84
  attr_accessor :opts, :callback, :bind_callback
79
85
 
80
86
  # This method binds a queue to an exchange. Until a queue is
@@ -110,12 +116,13 @@ class MQ
110
116
  # not wait for a reply method. If the server could not complete the
111
117
  # method it will raise a channel or connection exception.
112
118
  #
113
- def bind exchange, opts = {}, &block
119
+ def bind(exchange, opts = {}, &block)
114
120
  @status = :unbound
121
+ @sync_bind = ! opts[:nowait]
115
122
  exchange = exchange.respond_to?(:name) ? exchange.name : exchange
116
123
  @bindings[exchange] = opts
117
124
 
118
- @mq.callback{
125
+ @mq.callback {
119
126
  @mq.send Protocol::Queue::Bind.new({ :queue => name,
120
127
  :exchange => exchange,
121
128
  :routing_key => opts[:key],
@@ -139,11 +146,11 @@ class MQ
139
146
  # not wait for a reply method. If the server could not complete the
140
147
  # method it will raise a channel or connection exception.
141
148
  #
142
- def unbind exchange, opts = {}
149
+ def unbind(exchange, opts = {})
143
150
  exchange = exchange.respond_to?(:name) ? exchange.name : exchange
144
151
  @bindings.delete exchange
145
152
 
146
- @mq.callback{
153
+ @mq.callback {
147
154
  @mq.send Protocol::Queue::Unbind.new({ :queue => name,
148
155
  :exchange => exchange,
149
156
  :routing_key => opts[:key],
@@ -172,8 +179,8 @@ class MQ
172
179
  # not wait for a reply method. If the server could not complete the
173
180
  # method it will raise a channel or connection exception.
174
181
  #
175
- def delete opts = {}
176
- @mq.callback{
182
+ def delete(opts = {})
183
+ @mq.callback {
177
184
  @mq.send Protocol::Queue::Delete.new({ :queue => name,
178
185
  :nowait => true }.merge(opts))
179
186
  }
@@ -183,8 +190,8 @@ class MQ
183
190
 
184
191
  # Purge all messages from the queue.
185
192
  #
186
- def purge opts = {}
187
- @mq.callback{
193
+ def purge(opts = {})
194
+ @mq.callback {
188
195
  @mq.send Protocol::Queue::Purge.new({ :queue => name,
189
196
  :nowait => true }.merge(opts))
190
197
  }
@@ -245,14 +252,14 @@ class MQ
245
252
  # not wait for a reply method. If the server could not complete the
246
253
  # method it will raise a channel or connection exception.
247
254
  #
248
- def pop opts = {}, &blk
255
+ def pop(opts = {}, &blk)
249
256
  if blk
250
257
  @on_pop = blk
251
258
  @on_pop_opts = opts
252
259
  end
253
260
 
254
- @mq.callback{
255
- @mq.get_queue{ |q|
261
+ @mq.callback {
262
+ @mq.get_queue { |q|
256
263
  q.push(self)
257
264
  @mq.send Protocol::Basic::Get.new({ :queue => name,
258
265
  :consumer_tag => name,
@@ -318,7 +325,7 @@ class MQ
318
325
  # automatically set :nowait => false. This is required for the server
319
326
  # to send a confirmation.
320
327
  #
321
- def subscribe opts = {}, &blk
328
+ def subscribe(opts = {}, &blk)
322
329
  @consumer_tag = "#{name}-#{Kernel.rand(999_999_999_999)}"
323
330
  @mq.consumers[@consumer_tag] = self
324
331
 
@@ -328,7 +335,7 @@ class MQ
328
335
  @on_msg_opts = opts
329
336
  opts[:nowait] = false if (@on_confirm_subscribe = opts[:confirm])
330
337
 
331
- @mq.callback{
338
+ @mq.callback {
332
339
  @mq.send Protocol::Basic::Consume.new({ :queue => name,
333
340
  :consumer_tag => @consumer_tag,
334
341
  :no_ack => !opts[:ack],
@@ -358,15 +365,15 @@ class MQ
358
365
  # not wait for a reply method. If the server could not complete the
359
366
  # method it will raise a channel or connection exception.
360
367
  #
361
- def unsubscribe opts = {}, &blk
368
+ def unsubscribe(opts = {}, &blk)
362
369
  @on_cancel = blk
363
- @mq.callback{
370
+ @mq.callback {
364
371
  @mq.send Protocol::Basic::Cancel.new({ :consumer_tag => @consumer_tag }.merge(opts))
365
372
  }
366
373
  self
367
374
  end
368
375
 
369
- def publish data, opts = {}
376
+ def publish(data, opts = {})
370
377
  exchange.publish(data, opts)
371
378
  end
372
379
 
@@ -390,7 +397,7 @@ class MQ
390
397
  # See AMQP::Protocol::Header for the hash properties available from
391
398
  # the headers parameter. See #pop or #subscribe for a code example.
392
399
  #
393
- def receive headers, body
400
+ def receive(headers, body)
394
401
  headers = MQ::Header.new(@mq, headers) unless headers.nil?
395
402
 
396
403
  if cb = (@on_msg || @on_pop)
@@ -400,27 +407,32 @@ class MQ
400
407
 
401
408
  # Get the number of messages and consumers on a queue.
402
409
  #
403
- # MQ.queue('name').status{ |num_messages, num_consumers|
410
+ # MQ.queue('name').status { |num_messages, num_consumers|
404
411
  # puts num_messages
405
412
  # }
406
413
  #
407
- def status opts = {}, &blk
414
+ def status(opts = {}, &blk)
408
415
  return @status if opts.empty? && blk.nil?
409
416
 
410
417
  @on_status = blk
411
- @mq.callback{
418
+ @mq.callback {
412
419
  @mq.send Protocol::Queue::Declare.new({ :queue => name,
413
420
  :passive => true }.merge(opts))
414
421
  }
415
422
  self
416
423
  end
417
424
 
418
- def receive_status declare_ok
425
+ def receive_status(declare_ok)
419
426
  @name = declare_ok.queue
420
427
  @status = :finished
421
428
 
422
429
  if self.callback
423
- self.callback.call(self, declare_ok.message_count, declare_ok.consumer_count)
430
+ # compatibility for a common case when callback only takes one argument
431
+ if self.callback.arity == 1
432
+ self.callback.call(self)
433
+ else
434
+ self.callback.call(self, declare_ok.message_count, declare_ok.consumer_count)
435
+ end
424
436
  end
425
437
 
426
438
  if @on_status
@@ -430,7 +442,7 @@ class MQ
430
442
  end
431
443
  end
432
444
 
433
- def after_bind bind_ok
445
+ def after_bind(bind_ok)
434
446
  @status = :bound
435
447
  if self.bind_callback
436
448
  self.bind_callback.call(self)
@@ -456,7 +468,7 @@ class MQ
456
468
 
457
469
  binds = @bindings
458
470
  @bindings = {}
459
- binds.each{|ex,opts| bind(ex, opts) }
471
+ binds.each { |ex, opts| bind(ex, opts) }
460
472
 
461
473
  if blk = @on_msg
462
474
  @on_msg = nil