amqp-client 1.1.4 → 1.1.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e287ef0dee4c9ec581690733c12c132f74c595ceddead1ba5b95b765138e11c
4
- data.tar.gz: e5462c8cd2a4f7232a51e17d9ec93f4fb2ea88a922bb9b5cc58242cced78d61e
3
+ metadata.gz: 10119835607b70ee73dd581f9bef3211a26fc18bb6fb7fa2f6e6bfe913076b16
4
+ data.tar.gz: a30989dab30c2dea294c0310bbfd211a36fa7453d935200c6b7783553f5e816c
5
5
  SHA512:
6
- metadata.gz: 923fb64c7234fb6718f7dbc5eb33c1f8de03a9cf12d669539935cc6ef83f4ca387afc80c1e56fcc2ed167dc14381cda1d7b6a67673c6516818909cb6a84c287d
7
- data.tar.gz: 14c4d9157cee844609eeb5beed70ee2560ba3442c5afb857fed3e0945c443910f7bd4a281e327d2cb5ee0f0896facf4c8e693e5d3ecf1152c07474e20f8a724f
6
+ metadata.gz: 2658a0f4e151ab9fd0a56d1096e8d6b191b5e5164e27eadb8dc922651664440149fd1eef7b9d410fbcdf781eba3ef046b141c017b3b5d317b241ec7afa2d8150
7
+ data.tar.gz: 73695493c9f72619279a5552bdea275f6aa781da3da1dadebb788eaff249f68e088b3eb9fc5cc7c3afab1d6df0b44bd83e18ce2aa3605ace83a00035dfd84890
@@ -0,0 +1,41 @@
1
+ name: "CodeQL"
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ # The branches below must be a subset of the branches above
8
+ branches: [ main ]
9
+ schedule:
10
+ - cron: '23 22 * * 2'
11
+
12
+ jobs:
13
+ analyze:
14
+ name: Analyze
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ actions: read
18
+ contents: read
19
+ security-events: write
20
+
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ language: [ 'ruby' ]
25
+
26
+ steps:
27
+ - name: Checkout repository
28
+ uses: actions/checkout@v4
29
+
30
+ # Initializes the CodeQL tools for scanning.
31
+ - name: Initialize CodeQL
32
+ uses: github/codeql-action/init@v3
33
+ with:
34
+ languages: ${{ matrix.language }}
35
+ # If you wish to specify custom queries, you can do so here or in a config file.
36
+ # By default, queries listed here will override any specified in a config file.
37
+ # Prefix the list here with "+" to use these queries and those in the config file.
38
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
39
+
40
+ - name: Perform CodeQL Analysis
41
+ uses: github/codeql-action/analyze@v3
@@ -12,17 +12,17 @@ jobs:
12
12
  docs:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v2
15
+ - uses: actions/checkout@v4
16
16
  - name: Setup Ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: 3.0
19
+ ruby-version: ruby
20
20
  - name: Install yard
21
21
  run: gem install yard
22
22
  - name: Generate docs
23
23
  run: yard doc
24
24
  - name: Deploy docs
25
- uses: JamesIves/github-pages-deploy-action@4.1.5
25
+ uses: JamesIves/github-pages-deploy-action@v4.5.0
26
26
  with:
27
27
  branch: gh-pages
28
28
  folder: doc
@@ -1,33 +1,40 @@
1
1
  name: Ruby
2
2
 
3
- on: [push,pull_request]
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
4
10
 
5
11
  jobs:
6
12
  tests:
13
+ name: >-
14
+ ${{ matrix.ruby }} (sudo: ${{ matrix.sudo }})
7
15
  runs-on: ubuntu-latest
8
- timeout-minutes: 5
9
- services:
10
- rabbitmq:
11
- image: rabbitmq:latest
12
- ports:
13
- - 5672/tcp
14
- # needed because the rabbitmq container does not provide a healthcheck
15
- options: >-
16
- --health-cmd "rabbitmqctl node_health_check"
17
- --health-interval 10s
18
- --health-timeout 5s
19
- --health-retries 5
16
+ timeout-minutes: 10
20
17
  strategy:
21
18
  fail-fast: false
22
19
  matrix:
23
- ruby: ['2.6', '2.7', '3.0', '3.1']
20
+ sudo: [true]
21
+ ruby:
22
+ - "2.6"
23
+ - "2.7"
24
+ - "3.0"
25
+ - "3.1"
26
+ - "3.2"
27
+ - "3.3"
24
28
  include:
25
- - { ruby: jruby, allow-failure: true }
26
- - { ruby: truffleruby, allow-failure: true }
29
+ - { ruby: jruby, allow-failure: true, sudo: false }
30
+ - { ruby: truffleruby, allow-failure: true, sudo: false }
27
31
  steps:
28
- - uses: actions/checkout@v2
29
- - name: Set up Ruby
30
- uses: ruby/setup-ruby@v1
32
+ - name: Install RabbitMQ
33
+ run: sudo apt-get update && sudo apt-get install -y rabbitmq-server
34
+ - name: Verify RabbitMQ started correctly
35
+ run: while true; do sudo rabbitmq-diagnostics status 2>/dev/null && break; echo -n .; sleep 2; done
36
+ - uses: actions/checkout@v4
37
+ - uses: ruby/setup-ruby@v1
31
38
  with:
32
39
  bundler-cache: true
33
40
  ruby-version: ${{ matrix.ruby }}
@@ -35,20 +42,25 @@ jobs:
35
42
  continue-on-error: ${{ matrix.allow-failure || false }}
36
43
  run: bundle exec rake
37
44
  env:
38
- AMQP_PORT: ${{ job.services.rabbitmq.ports[5672] }}
45
+ RUN_SUDO_TESTS: ${{ matrix.sudo }}
46
+
39
47
  tls:
40
48
  runs-on: ubuntu-latest
41
- timeout-minutes: 5
49
+ timeout-minutes: 10
42
50
  strategy:
43
51
  fail-fast: false
44
52
  matrix:
45
- ruby: ['3.0', 'jruby', 'truffleruby']
53
+ ruby:
54
+ - "ruby" # latest stable release
55
+ - "jruby"
56
+ - "truffleruby"
46
57
  steps:
47
58
  - name: Install RabbitMQ
48
59
  run: sudo apt-get update && sudo apt-get install -y rabbitmq-server
49
60
  - name: Stop RabbitMQ
50
61
  run: sudo systemctl stop rabbitmq-server
51
-
62
+ - name: Set up Homebrew
63
+ uses: Homebrew/actions/setup-homebrew@master
52
64
  - name: Install github.com/FiloSottile/mkcert
53
65
  run: brew install mkcert
54
66
  - name: Create local CA
@@ -69,10 +81,8 @@ jobs:
69
81
  run: sudo systemctl start rabbitmq-server
70
82
  - name: Verify RabbitMQ started correctly
71
83
  run: while true; do sudo rabbitmq-diagnostics status 2>/dev/null && break; echo -n .; sleep 2; done
72
-
73
- - uses: actions/checkout@v2
74
- - name: Set up Ruby
75
- uses: ruby/setup-ruby@v1
84
+ - uses: actions/checkout@v4
85
+ - uses: ruby/setup-ruby@v1
76
86
  with:
77
87
  bundler-cache: true
78
88
  ruby-version: ${{ matrix.ruby }}
@@ -80,21 +90,22 @@ jobs:
80
90
  run: bundle exec rake
81
91
  env:
82
92
  TESTOPTS: --name=/_tls$/
93
+
83
94
  macos:
84
95
  runs-on: macos-latest
85
- timeout-minutes: 5
96
+ timeout-minutes: 10
86
97
  strategy:
87
98
  fail-fast: false
88
99
  matrix:
89
- ruby: ['3.0']
100
+ ruby: # enough to test one Ruby on macOS
101
+ - "ruby" # latest stable release
90
102
  steps:
91
103
  - name: Install RabbitMQ
92
104
  run: brew install rabbitmq
93
105
  - name: Start RabbitMQ
94
106
  run: brew services start rabbitmq
95
- - uses: actions/checkout@v2
96
- - name: Set up Ruby
97
- uses: ruby/setup-ruby@v1
107
+ - uses: actions/checkout@v4
108
+ - uses: ruby/setup-ruby@v1
98
109
  with:
99
110
  bundler-cache: true
100
111
  ruby-version: ${{ matrix.ruby }}
@@ -0,0 +1,26 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - v*
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ release:
11
+ if: github.repository == 'cloudamqp/amqp-client.rb'
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ id-token: write # for trusted publishing
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ bundler-cache: true
20
+ ruby-version: ruby
21
+ - uses: rubygems/configure-rubygems-credentials@v1.0.0
22
+ - run: ruby -v
23
+ # ensure gem can be built and installed, push to rubygems.org
24
+ - run: gem build *.gemspec
25
+ - run: gem install *.gem
26
+ - run: gem push *.gem
data/.rubocop.yml CHANGED
@@ -1,7 +1,9 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
+ NewCops: disable
4
5
  TargetRubyVersion: 2.6
6
+ SuggestExtensions: false
5
7
 
6
8
  Style/StringLiterals:
7
9
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.6] - 2024-03-26
4
+
5
+ - Fixed: Channel#wait_for_confirms now waits for all confirms, in a thread safe way
6
+ - Changed: When server sends Connection.blocked the client isn't write blocked anymore, and can continue consume for instance. However, the on_blocked/unblocked callbacks should be used and manually stop publishing as the server otherwise will stop reading from the client socket.
7
+
8
+ ## [1.1.5] - 2024-03-15
9
+
10
+ - Fixed: Correctly reference the `UnexpectedFrameEnd` exception
11
+
3
12
  ## [1.1.4] - 2021-12-27
4
13
 
5
14
  - Fixed: Ruby 3.1.0 compability, StringIO have to be required manually
data/Gemfile CHANGED
@@ -11,6 +11,8 @@ gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.7"
13
13
 
14
+ gem "rubocop-minitest", require: false
15
+
14
16
  gem "yard", require: false
15
17
 
16
- gem "rubocop-minitest", require: false
18
+ gem "redcarpet", require: false, platforms: :ruby
data/README.md CHANGED
@@ -49,6 +49,10 @@ puts msg.body
49
49
  The library provides a high-level API that is a bit easier to get started with, and also handles reconnection automatically.
50
50
 
51
51
  ```ruby
52
+ require "amqp-client"
53
+ require "json"
54
+ require "zlib"
55
+
52
56
  # Start the client, it will connect and once connected it will reconnect if that connection is lost
53
57
  # Operation pending when the connection is lost will raise an exception (not timeout)
54
58
  amqp = AMQP::Client.new("amqp://localhost").start
@@ -62,9 +66,10 @@ myqueue.bind("amq.topic", "my.events.*")
62
66
  # The message will be reprocessed if the client loses connection to the broker
63
67
  # between message arrival and when the message was supposed to be ack'ed.
64
68
  myqueue.subscribe(prefetch: 20) do |msg|
65
- process(JSON.parse(msg.body))
69
+ puts JSON.parse(msg.body)
66
70
  msg.ack
67
- rescue
71
+ rescue => e
72
+ puts e.full_message
68
73
  msg.reject(requeue: false)
69
74
  end
70
75
 
@@ -76,16 +81,27 @@ amqp.publish("my message", "amq.topic", "topic.foo", headers: { foo: 'bar' })
76
81
  amqp.publish(Zlib.gzip("an event"), "amq.topic", "my.event", content_encoding: 'gzip')
77
82
  ```
78
83
 
84
+ ## Benchmark
85
+
86
+ 1 byte messages:
87
+
88
+ | Client | Publish rate | Consume rate | Memory usage |
89
+ | ------ | ------------ | ------------ | ------------ |
90
+ | amqp-client.rb | 237.000 msgs/s | 154.000 msgs/s | 23 MB |
91
+ | bunny | 39.000 msgs/s | 44.000 msgs/s | 31 MB |
92
+
93
+ Gem comparison:
94
+
95
+ | Client | Runtime dependencies | [Lines of code](https://github.com/AlDanial/cloc) |
96
+ | --- | --- | --- |
97
+ | amqp-client.rb | 0 | 1876 |
98
+ | bunny | 2 | 4003 |
99
+
79
100
  ## Supported Ruby versions
80
101
 
81
102
  All maintained Ruby versions are supported.
82
103
 
83
- - 3.1
84
- - 3.0
85
- - 2.7
86
- - 2.6
87
- - jruby
88
- - truffleruby
104
+ See the [CI workflow](https://github.com/cloudamqp/amqp-client.rb/blob/main/.github/workflows/main.yml) for the exact versions.
89
105
 
90
106
  ## Installation
91
107
 
@@ -107,7 +123,7 @@ Or install it yourself as:
107
123
 
108
124
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
109
125
 
110
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
126
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the changelog and version number in `version.rb`, make a commit, and then run `bundle exec rake release:source_control_push`, which will create a git tag for the version, push git commits and the created tag. GitHub Actions will then push the `.gem` file to [rubygems.org](https://rubygems.org/gems/amqp-client).
111
127
 
112
128
  ## Contributing
113
129
 
data/Rakefile CHANGED
@@ -22,4 +22,8 @@ RuboCop::RakeTask.new do |task|
22
22
  task.requires << "rubocop-minitest"
23
23
  end
24
24
 
25
+ require "yard"
26
+
27
+ YARD::Rake::YardocTask.new
28
+
25
29
  task default: [:test, *(:rubocop if RUBY_ENGINE == "ruby")]
data/amqp-client.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ["carl@cloudamqp.com"]
10
10
 
11
11
  spec.summary = "AMQP 0-9-1 client"
12
- spec.description = "Work in progress"
12
+ spec.description = "Modern AMQP 0-9-1 Ruby client"
13
13
  spec.homepage = "https://github.com/cloudamqp/amqp-client.rb"
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
@@ -26,10 +26,4 @@ Gem::Specification.new do |spec|
26
26
  spec.bindir = "exe"
27
27
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
-
30
- # Uncomment to register a new dependency of your gem
31
- # spec.add_dependency "example-gem", "~> 1.0"
32
-
33
- # For more information and examples about making a new gem, checkout our
34
- # guide at: https://bundler.io/guides/creating_gem.html
35
29
  end
@@ -22,8 +22,9 @@ module AMQP
22
22
  @open = false
23
23
  @on_return = nil
24
24
  @confirm = nil
25
- @unconfirmed = ::Queue.new
26
- @unconfirmed_empty = ::Queue.new
25
+ @unconfirmed = []
26
+ @unconfirmed_lock = Mutex.new
27
+ @unconfirmed_empty = ConditionVariable.new
27
28
  @basic_gets = ::Queue.new
28
29
  end
29
30
 
@@ -60,7 +61,7 @@ module AMQP
60
61
  expect :channel_close_ok
61
62
  @replies.close
62
63
  @basic_gets.close
63
- @unconfirmed_empty.close
64
+ @unconfirmed_lock.synchronize { @unconfirmed_empty.broadcast }
64
65
  @consumers.each_value(&:close)
65
66
  nil
66
67
  end
@@ -73,8 +74,9 @@ module AMQP
73
74
  @closed = [level, code, reason, classid, methodid]
74
75
  @replies.close
75
76
  @basic_gets.close
76
- @unconfirmed_empty.close
77
+ @unconfirmed_lock.synchronize { @unconfirmed_empty.broadcast }
77
78
  @consumers.each_value(&:close)
79
+ @consumers.each_value(&:clear) # empty the queues too, messages can't be acked anymore
78
80
  nil
79
81
  end
80
82
 
@@ -200,11 +202,12 @@ module AMQP
200
202
  # Purge a queue
201
203
  # @param name [String] Name of the queue
202
204
  # @param no_wait [Boolean] Don't wait for a broker confirmation if true
203
- # @return [nil]
205
+ # @return [Integer] Number of messages in queue when purged
206
+ # @return [nil] If no_wait was set true
204
207
  def queue_purge(name, no_wait: false)
205
208
  write_bytes FrameBytes.queue_purge(@id, name, no_wait)
206
- expect :queue_purge_ok unless no_wait
207
- nil
209
+ message_count, = expect :queue_purge_ok unless no_wait
210
+ message_count
208
211
  end
209
212
 
210
213
  # Unbind a queue from an exchange
@@ -265,12 +268,15 @@ module AMQP
265
268
  when true then properties[:delivery_mode] = 2
266
269
  when false then properties[:delivery_mode] = 1
267
270
  end
268
-
271
+ if @confirm
272
+ @unconfirmed_lock.synchronize do
273
+ @unconfirmed.push @confirm += 1
274
+ end
275
+ end
269
276
  if body.bytesize.between?(1, body_max)
270
277
  write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
271
278
  FrameBytes.header(id, body.bytesize, properties),
272
279
  FrameBytes.body(id, body)
273
- @unconfirmed.push @confirm += 1 if @confirm
274
280
  return
275
281
  end
276
282
 
@@ -283,7 +289,6 @@ module AMQP
283
289
  write_bytes FrameBytes.body(id, body_part)
284
290
  pos += len
285
291
  end
286
- @unconfirmed.push @confirm += 1 if @confirm
287
292
  nil
288
293
  end
289
294
 
@@ -310,22 +315,16 @@ module AMQP
310
315
  # @yield [Message] Delivered message from the queue
311
316
  # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
312
317
  # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
313
- def basic_consume(queue, tag: "", no_ack: true, exclusive: false, arguments: {}, worker_threads: 1)
318
+ def basic_consume(queue, tag: "", no_ack: true, exclusive: false, arguments: {}, worker_threads: 1, &blk)
314
319
  write_bytes FrameBytes.basic_consume(@id, queue, tag, no_ack, exclusive, arguments)
315
320
  tag, = expect(:basic_consume_ok)
316
- q = @consumers[tag] = ::Queue.new
321
+ @consumers[tag] = q = ::Queue.new
317
322
  if worker_threads.zero?
318
- loop do
319
- yield (q.pop || break)
320
- end
323
+ consume_loop(q, tag, &blk)
321
324
  nil
322
325
  else
323
326
  threads = Array.new(worker_threads) do
324
- Thread.new do
325
- loop do
326
- yield (q.pop || break)
327
- end
328
- end
327
+ Thread.new { consume_loop(q, tag, &blk) }
329
328
  end
330
329
  [tag, threads]
331
330
  end
@@ -400,42 +399,44 @@ module AMQP
400
399
  # @param no_wait [Boolean] If false the method will block until the broker has confirmed the request
401
400
  # @return [nil]
402
401
  def confirm_select(no_wait: false)
403
- return if @confirm
402
+ return if @confirm # fast path
404
403
 
405
- write_bytes FrameBytes.confirm_select(@id, no_wait)
406
- expect :confirm_select_ok unless no_wait
407
- @confirm = 0
404
+ @unconfirmed_lock.synchronize do
405
+ # check again in case another thread already did this while we waited for the lock
406
+ return if @confirm
407
+
408
+ write_bytes FrameBytes.confirm_select(@id, no_wait)
409
+ expect :confirm_select_ok unless no_wait
410
+ @confirm = 0
411
+ end
408
412
  nil
409
413
  end
410
414
 
411
415
  # Block until all publishes messages are confirmed
412
- # @return [Boolean] True if all message where positivly acknowledged, false if not
416
+ # @return nil
413
417
  def wait_for_confirms
414
- return true if @unconfirmed.empty?
415
-
416
- ok = @unconfirmed_empty.pop
417
- raise Error::Closed.new(@id, *@closed) if ok.nil?
418
-
419
- ok
418
+ @unconfirmed_lock.synchronize do
419
+ until @unconfirmed.empty?
420
+ @unconfirmed_empty.wait(@unconfirmed_lock)
421
+ raise Error::Closed.new(@id, *@closed) if @closed
422
+ end
423
+ end
420
424
  end
421
425
 
422
426
  # Called by Connection when received ack/nack from broker
423
427
  # @api private
424
428
  def confirm(args)
425
- ack_or_nack, delivery_tag, multiple = *args
426
- loop do
427
- tag = @unconfirmed.pop(true)
428
- break if tag == delivery_tag
429
- next if multiple && tag < delivery_tag
430
-
431
- @unconfirmed << tag # requeue
432
- rescue ThreadError
433
- break
429
+ _ack_or_nack, delivery_tag, multiple = *args
430
+ @unconfirmed_lock.synchronize do
431
+ case multiple
432
+ when true
433
+ idx = @unconfirmed.index(delivery_tag) || raise("Delivery tag not found")
434
+ @unconfirmed.shift(idx + 1)
435
+ when false
436
+ @unconfirmed.delete(delivery_tag) || raise("Delivery tag not found")
437
+ end
438
+ @unconfirmed_empty.broadcast if @unconfirmed.empty?
434
439
  end
435
- return unless @unconfirmed.empty?
436
-
437
- ok = ack_or_nack == :ack
438
- @unconfirmed_empty.push(ok) until @unconfirmed_empty.num_waiting.zero?
439
440
  end
440
441
 
441
442
  # @!endgroup
@@ -547,6 +548,21 @@ module AMQP
547
548
 
548
549
  args
549
550
  end
551
+
552
+ def consume_loop(queue, tag)
553
+ while (msg = queue.pop)
554
+ begin
555
+ yield msg
556
+ rescue StandardError # cancel the consumer if an uncaught exception is raised
557
+ begin
558
+ close("Unexpected exception in consumer #{tag} thread", 500)
559
+ rescue StandardError # ignore sockets errors while canceling
560
+ nil
561
+ end
562
+ raise # reraise original exception
563
+ end
564
+ end
565
+ end
550
566
  end
551
567
  end
552
568
  end
@@ -48,9 +48,22 @@ module AMQP
48
48
  @replies = ::Queue.new
49
49
  @write_lock = Mutex.new
50
50
  @blocked = nil
51
+ @on_blocked = ->(reason) { warn "AMQP-Client blocked by broker: #{reason}" }
52
+ @on_unblocked = -> { warn "AMQP-Client unblocked by broker" }
53
+
51
54
  Thread.new { read_loop } if read_loop_thread
52
55
  end
53
56
 
57
+ # Indicates that the server is blocking publishes.
58
+ # If the client keeps publishing the server will stop reading from the socket.
59
+ # Use the #on_blocked callback to get notified when the server is resource constrained.
60
+ # @see #on_blocked
61
+ # @see #on_unblocked
62
+ # @return [Bool]
63
+ def blocked?
64
+ !@blocked.nil?
65
+ end
66
+
54
67
  # Alias for {#initialize}
55
68
  # @see #initialize
56
69
  # @deprecated
@@ -125,20 +138,33 @@ module AMQP
125
138
  !@closed.nil?
126
139
  end
127
140
 
141
+ # @!group Callbacks
142
+
143
+ # Callback called when client is blocked by the broker
144
+ # @yield [String] reason to why the connection is being blocked
145
+ # @return [nil]
146
+ def on_blocked(&blk)
147
+ @on_blocked = blk
148
+ nil
149
+ end
150
+
151
+ # Callback called when client is unblocked by the broker
152
+ # @yield
153
+ # @return [nil]
154
+ def on_unblocked(&blk)
155
+ @on_unblocked = blk
156
+ nil
157
+ end
158
+
159
+ # @!endgroup
160
+
128
161
  # Write byte array(s) directly to the socket (thread-safe)
129
162
  # @param bytes [String] One or more byte arrays
130
163
  # @return [Integer] number of bytes written
131
164
  # @api private
132
165
  def write_bytes(*bytes)
133
- blocked = @blocked
134
- warn "AMQP-Client blocked by broker: #{blocked}" if blocked
135
166
  @write_lock.synchronize do
136
- warn "AMQP-Client unblocked by broker" if blocked
137
- if RUBY_ENGINE == "truffleruby"
138
- bytes.each { |b| @socket.write b }
139
- else
140
- @socket.write(*bytes)
141
- end
167
+ @socket.write(*bytes)
142
168
  end
143
169
  rescue *READ_EXCEPTIONS => e
144
170
  raise Error::ConnectionClosed.new(*@closed) if @closed
@@ -166,7 +192,7 @@ module AMQP
166
192
 
167
193
  # make sure that the frame end is correct
168
194
  frame_end = socket.readchar.ord
169
- raise UnexpectedFrameEnd, frame_end if frame_end != 206
195
+ raise Error::UnexpectedFrameEnd, frame_end if frame_end != 206
170
196
 
171
197
  # parse the frame, will return false if a close frame was received
172
198
  parse_frame(type, channel_id, frame_buffer) || return
@@ -179,8 +205,12 @@ module AMQP
179
205
  @closed ||= [400, "unknown"]
180
206
  @replies.close
181
207
  begin
182
- @write_lock.synchronize do
208
+ if @write_lock.owned? # if connection is blocked
183
209
  @socket.close
210
+ else
211
+ @write_lock.synchronize do
212
+ @socket.close
213
+ end
184
214
  end
185
215
  rescue *READ_EXCEPTIONS
186
216
  nil
@@ -192,7 +222,8 @@ module AMQP
192
222
  READ_EXCEPTIONS = [IOError, OpenSSL::OpenSSLError, SystemCallError,
193
223
  RUBY_ENGINE == "jruby" ? java.lang.NullPointerException : nil].compact.freeze
194
224
 
195
- def parse_frame(type, channel_id, buf)
225
+ def parse_frame(type, channel_id, buf) # rubocop:disable Metrics/MethodLength
226
+ channel = @channels[channel_id]
196
227
  case type
197
228
  when 1 # method frame
198
229
  class_id, method_id = buf.unpack("S> S>")
@@ -220,16 +251,16 @@ module AMQP
220
251
  reason_len = buf.getbyte(4)
221
252
  reason = buf.byteslice(5, reason_len).force_encoding("utf-8")
222
253
  @blocked = reason
223
- @write_lock.lock
254
+ @on_blocked.call(reason)
224
255
  when 61 # connection#unblocked
225
256
  @blocked = nil
226
- @write_lock.unlock
257
+ @on_unblocked.call
227
258
  else raise Error::UnsupportedMethodFrame, class_id, method_id
228
259
  end
229
260
  when 20 # channel
230
261
  case method_id
231
262
  when 11 # channel#open-ok
232
- @channels[channel_id].reply [:channel_open_ok]
263
+ channel.reply [:channel_open_ok]
233
264
  when 40 # channel#close
234
265
  reply_code, reply_text_len = buf.unpack("@4 S> C")
235
266
  reply_text = buf.byteslice(7, reply_text_len).force_encoding("utf-8")
@@ -245,13 +276,13 @@ module AMQP
245
276
  when 40 # exchange
246
277
  case method_id
247
278
  when 11 # declare-ok
248
- @channels[channel_id].reply [:exchange_declare_ok]
279
+ channel.reply [:exchange_declare_ok]
249
280
  when 21 # delete-ok
250
- @channels[channel_id].reply [:exchange_delete_ok]
281
+ channel.reply [:exchange_delete_ok]
251
282
  when 31 # bind-ok
252
- @channels[channel_id].reply [:exchange_bind_ok]
283
+ channel.reply [:exchange_bind_ok]
253
284
  when 51 # unbind-ok
254
- @channels[channel_id].reply [:exchange_unbind_ok]
285
+ channel.reply [:exchange_unbind_ok]
255
286
  else raise Error::UnsupportedMethodFrame, class_id, method_id
256
287
  end
257
288
  when 50 # queue
@@ -260,36 +291,37 @@ module AMQP
260
291
  queue_name_len = buf.getbyte(4)
261
292
  queue_name = buf.byteslice(5, queue_name_len).force_encoding("utf-8")
262
293
  message_count, consumer_count = buf.byteslice(5 + queue_name_len, 8).unpack("L> L>")
263
- @channels[channel_id].reply [:queue_declare_ok, queue_name, message_count, consumer_count]
294
+ channel.reply [:queue_declare_ok, queue_name, message_count, consumer_count]
264
295
  when 21 # bind-ok
265
- @channels[channel_id].reply [:queue_bind_ok]
296
+ channel.reply [:queue_bind_ok]
266
297
  when 31 # purge-ok
267
- @channels[channel_id].reply [:queue_purge_ok]
298
+ message_count = buf.unpack1("@4 L>")
299
+ channel.reply [:queue_purge_ok, message_count]
268
300
  when 41 # delete-ok
269
301
  message_count = buf.unpack1("@4 L>")
270
- @channels[channel_id].reply [:queue_delete, message_count]
302
+ channel.reply [:queue_delete, message_count]
271
303
  when 51 # unbind-ok
272
- @channels[channel_id].reply [:queue_unbind_ok]
304
+ channel.reply [:queue_unbind_ok]
273
305
  else raise Error::UnsupportedMethodFrame.new class_id, method_id
274
306
  end
275
307
  when 60 # basic
276
308
  case method_id
277
309
  when 11 # qos-ok
278
- @channels[channel_id].reply [:basic_qos_ok]
310
+ channel.reply [:basic_qos_ok]
279
311
  when 21 # consume-ok
280
312
  tag_len = buf.getbyte(4)
281
313
  tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
282
- @channels[channel_id].reply [:basic_consume_ok, tag]
314
+ channel.reply [:basic_consume_ok, tag]
283
315
  when 30 # cancel
284
316
  tag_len = buf.getbyte(4)
285
317
  tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
286
318
  no_wait = buf.getbyte(5 + tag_len) == 1
287
- @channels[channel_id].close_consumer(tag)
319
+ channel.close_consumer(tag)
288
320
  write_bytes FrameBytes.basic_cancel_ok(@id, tag) unless no_wait
289
321
  when 31 # cancel-ok
290
322
  tag_len = buf.getbyte(4)
291
323
  tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
292
- @channels[channel_id].reply [:basic_cancel_ok, tag]
324
+ channel.reply [:basic_cancel_ok, tag]
293
325
  when 50 # return
294
326
  reply_code, reply_text_len = buf.unpack("@4 S> C")
295
327
  pos = 7
@@ -302,7 +334,7 @@ module AMQP
302
334
  routing_key_len = buf.getbyte(pos)
303
335
  pos += 1
304
336
  routing_key = buf.byteslice(pos, routing_key_len).force_encoding("utf-8")
305
- @channels[channel_id].message_returned(reply_code, reply_text, exchange, routing_key)
337
+ channel.message_returned(reply_code, reply_text, exchange, routing_key)
306
338
  when 60 # deliver
307
339
  ctag_len = buf.getbyte(4)
308
340
  consumer_tag = buf.byteslice(5, ctag_len).force_encoding("utf-8")
@@ -314,7 +346,7 @@ module AMQP
314
346
  rk_len = buf.getbyte(pos)
315
347
  pos += 1
316
348
  routing_key = buf.byteslice(pos, rk_len).force_encoding("utf-8")
317
- @channels[channel_id].message_delivered(consumer_tag, delivery_tag, redelivered == 1, exchange, routing_key)
349
+ channel.message_delivered(consumer_tag, delivery_tag, redelivered == 1, exchange, routing_key)
318
350
  when 71 # get-ok
319
351
  delivery_tag, redelivered, exchange_len = buf.unpack("@4 Q> C C")
320
352
  pos = 14
@@ -325,33 +357,33 @@ module AMQP
325
357
  routing_key = buf.byteslice(pos, routing_key_len).force_encoding("utf-8")
326
358
  # pos += routing_key_len
327
359
  # message_count = buf.byteslice(pos, 4).unpack1("L>")
328
- @channels[channel_id].message_delivered(nil, delivery_tag, redelivered == 1, exchange, routing_key)
360
+ channel.message_delivered(nil, delivery_tag, redelivered == 1, exchange, routing_key)
329
361
  when 72 # get-empty
330
- @channels[channel_id].basic_get_empty
362
+ channel.basic_get_empty
331
363
  when 80 # ack
332
364
  delivery_tag, multiple = buf.unpack("@4 Q> C")
333
- @channels[channel_id].confirm [:ack, delivery_tag, multiple == 1]
365
+ channel.confirm [:ack, delivery_tag, multiple == 1]
334
366
  when 111 # recover-ok
335
- @channels[channel_id].reply [:basic_recover_ok]
367
+ channel.reply [:basic_recover_ok]
336
368
  when 120 # nack
337
369
  delivery_tag, multiple, requeue = buf.unpack("@4 Q> C C")
338
- @channels[channel_id].confirm [:nack, delivery_tag, multiple == 1, requeue == 1]
370
+ channel.confirm [:nack, delivery_tag, multiple == 1, requeue == 1]
339
371
  else raise Error::UnsupportedMethodFrame.new class_id, method_id
340
372
  end
341
373
  when 85 # confirm
342
374
  case method_id
343
375
  when 11 # select-ok
344
- @channels[channel_id].reply [:confirm_select_ok]
376
+ channel.reply [:confirm_select_ok]
345
377
  else raise Error::UnsupportedMethodFrame.new class_id, method_id
346
378
  end
347
379
  when 90 # tx
348
380
  case method_id
349
381
  when 11 # select-ok
350
- @channels[channel_id].reply [:tx_select_ok]
382
+ channel.reply [:tx_select_ok]
351
383
  when 21 # commit-ok
352
- @channels[channel_id].reply [:tx_commit_ok]
384
+ channel.reply [:tx_commit_ok]
353
385
  when 31 # rollback-ok
354
- @channels[channel_id].reply [:tx_rollback_ok]
386
+ channel.reply [:tx_rollback_ok]
355
387
  else raise Error::UnsupportedMethodFrame.new class_id, method_id
356
388
  end
357
389
  else raise Error::UnsupportedMethodFrame.new class_id, method_id
@@ -359,9 +391,9 @@ module AMQP
359
391
  when 2 # header
360
392
  body_size = buf.unpack1("@4 Q>")
361
393
  properties = Properties.decode(buf, 12)
362
- @channels[channel_id].header_delivered body_size, properties
394
+ channel.header_delivered body_size, properties
363
395
  when 3 # body
364
- @channels[channel_id].body_delivered buf
396
+ channel.body_delivered buf
365
397
  else raise Error::UnsupportedFrameType, type
366
398
  end
367
399
  true
@@ -419,7 +451,7 @@ module AMQP
419
451
 
420
452
  type, channel_id, frame_size = buf.unpack("C S> L>")
421
453
  frame_end = buf.getbyte(frame_size + 7)
422
- raise UnexpectedFrameEndError, frame_end if frame_end != 206
454
+ raise Error::UnexpectedFrameEnd, frame_end if frame_end != 206
423
455
 
424
456
  case type
425
457
  when 1 # method frame
@@ -3,6 +3,6 @@
3
3
  module AMQP
4
4
  class Client
5
5
  # Version of the client library
6
- VERSION = "1.1.4"
6
+ VERSION = "1.1.6"
7
7
  end
8
8
  end
data/lib/amqp/client.rb CHANGED
@@ -26,7 +26,6 @@ module AMQP
26
26
  def initialize(uri = "", **options)
27
27
  @uri = uri
28
28
  @options = options
29
-
30
29
  @queues = {}
31
30
  @exchanges = {}
32
31
  @subscriptions = Set.new
@@ -172,15 +171,16 @@ module AMQP
172
171
 
173
172
  # Consume messages from a queue
174
173
  # @param queue [String] Name of the queue to subscribe to
175
- # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
176
- # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
177
- # @param worker_threads [Integer] Number of threads processing messages,
178
- # 0 means that the thread calling this method will be blocked
174
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected) (default: false)
175
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false (default: 1)
176
+ # @param worker_threads [Integer] Number of threads processing messages (default: 1)
179
177
  # @param arguments [Hash] Custom arguments to the consumer
180
178
  # @yield [Message] Delivered message from the queue
181
179
  # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
182
- # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
180
+ # @return [nil]
183
181
  def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
182
+ raise ArgumentError, "worker_threads have to be > 0" if worker_threads <= 0
183
+
184
184
  @subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
185
185
 
186
186
  with_connection do |conn|
metadata CHANGED
@@ -1,24 +1,26 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amqp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Hörberg
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-27 00:00:00.000000000 Z
11
+ date: 2024-03-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Work in progress
13
+ description: Modern AMQP 0-9-1 Ruby client
14
14
  email:
15
15
  - carl@cloudamqp.com
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".github/workflows/codeql-analysis.yml"
20
21
  - ".github/workflows/docs.yml"
21
22
  - ".github/workflows/main.yml"
23
+ - ".github/workflows/release.yml"
22
24
  - ".gitignore"
23
25
  - ".rubocop.yml"
24
26
  - ".rubocop_todo.yml"
@@ -43,7 +45,6 @@ files:
43
45
  - lib/amqp/client/queue.rb
44
46
  - lib/amqp/client/table.rb
45
47
  - lib/amqp/client/version.rb
46
- - sig/amqp-client.rbs
47
48
  homepage: https://github.com/cloudamqp/amqp-client.rb
48
49
  licenses:
49
50
  - MIT
@@ -51,7 +52,7 @@ metadata:
51
52
  homepage_uri: https://github.com/cloudamqp/amqp-client.rb
52
53
  source_code_uri: https://github.com/cloudamqp/amqp-client.rb.git
53
54
  changelog_uri: https://github.com/cloudamqp/amqp-client.rb/blob/main/CHANGELOG.md
54
- post_install_message:
55
+ post_install_message:
55
56
  rdoc_options: []
56
57
  require_paths:
57
58
  - lib
@@ -66,8 +67,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
67
  - !ruby/object:Gem::Version
67
68
  version: '0'
68
69
  requirements: []
69
- rubygems_version: 3.3.3
70
- signing_key:
70
+ rubygems_version: 3.5.3
71
+ signing_key:
71
72
  specification_version: 4
72
73
  summary: AMQP 0-9-1 client
73
74
  test_files: []
data/sig/amqp-client.rbs DELETED
@@ -1,264 +0,0 @@
1
- # TypeProf 0.15.3
2
-
3
- # Classes
4
- module AMQP
5
- class Client
6
- VERSION: String
7
- @uri: String
8
- @options: Hash[Symbol, (String | Integer | bool)]
9
- @queues: Hash[String, Queue]
10
- @exchanges: Hash[String, Exchange]
11
- @subscriptions: Set[[String, bool, Integer, Integer, Hash[Symbol, untyped], nil]]
12
- @connq: Thread::SizedQueue
13
- @stopped: bool
14
-
15
- def initialize: (?String uri, **untyped) -> void
16
- def connect: (?read_loop_thread: bool) -> Connection
17
- def start: -> Client
18
- def stop: -> nil
19
- def queue: (String name, ?durable: bool, ?auto_delete: bool, ?arguments: Hash[Symbol | String, (String | Integer | bool)]) -> Queue
20
- def exchange: (String name, String `type`, ?durable: bool, ?auto_delete: bool, ?internal: bool, ?arguments: Hash[Symbol | String, untyped]) -> Exchange
21
- def publish: (String body, String exchange, String routing_key, **untyped) -> bool
22
- def publish_and_forget: (String body, String exchange, String routing_key, **untyped) -> nil
23
- def wait_for_confirms: -> bool
24
- def subscribe: (String queue, ?no_ack: bool, ?prefetch: Integer, ?worker_threads: Integer, ?arguments: Hash[Symbol | String, untyped]) { (Message) -> void } -> [String, Array[Thread]]?
25
- def bind: (String queue, String exchange, String binding_key, ?arguments: Hash[Symbol | String, untyped]) -> nil
26
- def unbind: (String queue, String exchange, String binding_key, ?arguments: Hash[Symbol | String, untyped]) -> nil
27
- def purge: (String queue) -> nil
28
- def delete_queue: (String name, ?if_unused: bool, ?if_empty: bool) -> Integer?
29
- def exchange_bind: (String destination, String source, String binding_key, ?arguments: Hash[Symbol | String, untyped]) -> nil
30
- def exchange_unbind: (String destination, String source, String binding_key, ?arguments: Hash[Symbol | String, untyped]) -> nil
31
- def delete_exchange: (String name) -> nil
32
- def with_connection: { (Connection) -> void } -> void
33
-
34
- module Table
35
- def self.encode: (Hash[Symbol | String, untyped] hash) -> String
36
- def self.decode: (String bytes) -> Hash[String, untyped]
37
- end
38
-
39
- class Properties
40
- attr_accessor content_type(): String?
41
- attr_accessor content_encoding(): String?
42
- attr_accessor headers(): Hash[String | Symbol, untyped]?
43
- attr_accessor delivery_mode(): Integer?
44
- attr_accessor priority(): Integer?
45
- attr_accessor correlation_id(): String?
46
- attr_accessor reply_to(): String?
47
- attr_accessor expiration(): (Integer | String)?
48
- attr_accessor message_id(): String?
49
- attr_accessor timestamp(): Time?
50
- attr_accessor type(): String?
51
- attr_accessor user_id(): String?
52
- attr_accessor app_id(): String?
53
- end
54
-
55
- module FrameBytes
56
- def self.connection_start_ok: (String response, Hash[untyped, untyped] properties) -> String
57
- def self.connection_tune_ok: ((Float | Integer | String)? channel_max, (Float | Integer | String)? frame_max, (Float | Integer | String)? heartbeat) -> String
58
- def self.connection_open: (String vhost) -> String
59
- def self.connection_close: (Integer code, String reason) -> String
60
- def self.connection_close_ok: -> String
61
- def self.channel_open: (Integer id) -> String
62
- def self.channel_close: (Integer id, String reason, Integer code) -> String
63
- def self.channel_close_ok: ((Float | Integer | String)? id) -> String
64
- def self.exchange_declare: (Integer id, untyped name, untyped `type`, bool passive, bool durable, bool auto_delete, bool internal, Hash[untyped, untyped] arguments) -> String
65
- def self.exchange_delete: (Integer id, untyped name, bool if_unused, bool no_wait) -> String
66
- def self.exchange_bind: (Integer id, untyped destination, untyped source, untyped binding_key, bool no_wait, Hash[untyped, untyped] arguments) -> String
67
- def self.exchange_unbind: (Integer id, untyped destination, untyped source, untyped binding_key, bool no_wait, Hash[untyped, untyped] arguments) -> String
68
- def self.queue_declare: (Integer id, String name, bool passive, bool durable, bool exclusive, bool auto_delete, Hash[untyped, untyped] arguments) -> String
69
- def self.queue_delete: (Integer id, untyped name, bool if_unused, bool if_empty, bool no_wait) -> String
70
- def self.queue_bind: (Integer id, untyped queue, untyped exchange, untyped binding_key, bool no_wait, Hash[untyped, untyped] arguments) -> String
71
- def self.queue_unbind: (Integer id, untyped queue, untyped exchange, untyped binding_key, Hash[untyped, untyped] arguments) -> String
72
- def self.queue_purge: (Integer id, untyped queue, bool no_wait) -> String
73
- def self.basic_get: (Integer id, untyped queue, bool no_ack) -> String
74
- def self.basic_publish: (Integer id, untyped exchange, untyped routing_key, bool mandatory) -> String
75
- def self.header: (Integer id, untyped body_size, Hash[(Symbol | String), untyped] properties) -> String
76
- def self.body: (Integer id, untyped body_part) -> String
77
- def self.basic_consume: (Integer id, String queue, String? tag, bool? no_ack, bool? exclusive, Hash[untyped, untyped]? arguments) -> String
78
- def self.basic_cancel: (Integer id, untyped consumer_tag, ?no_wait: bool) -> String
79
- def self.basic_cancel_ok: (Integer id, String consumer_tag) -> String
80
- def self.basic_ack: (Integer id, untyped delivery_tag, bool multiple) -> String
81
- def self.basic_nack: (Integer id, untyped delivery_tag, bool multiple, bool requeue) -> String
82
- def self.basic_reject: (Integer id, untyped delivery_tag, bool requeue) -> String
83
- def self.basic_qos: (Integer id, Integer? prefetch_size, Integer? prefetch_count, bool? global) -> String
84
- def self.basic_recover: (Integer id, untyped requeue) -> String
85
- def self.confirm_select: (Integer id, bool no_wait) -> String
86
- def self.tx_select: (Integer id) -> String
87
- def self.tx_commit: (Integer id) -> String
88
- def self.tx_rollback: (Integer id) -> String
89
- end
90
-
91
- class Message
92
- attr_reader channel(): Connection::Channel
93
- attr_reader delivery_tag(): Integer
94
- attr_reader exchange(): String
95
- attr_reader routing_key(): String
96
- attr_reader redelivered(): bool
97
- attr_reader consumer_tag(): String?
98
- attr_accessor properties(): Properties
99
- attr_accessor body(): String
100
- end
101
-
102
- class ReturnMessage
103
- attr_reader reply_code(): Integer
104
- attr_reader reply_text(): String
105
- attr_reader exchange(): String
106
- attr_reader routing_key(): String
107
- attr_accessor properties(): Properties
108
- attr_accessor body(): String
109
- end
110
-
111
- class Connection
112
- CLIENT_PROPERTIES: Hash[Symbol | String, untyped]
113
- @socket: (IO | untyped)
114
- @channel_max: Integer
115
- @heartbeat: Integer
116
- @channels: Hash[Integer, Channel]
117
- @closed: Array[untyped]?
118
- @replies: Thread::Queue
119
- @write_lock: Thread::Mutex
120
- @blocked: String?
121
- @id: Integer
122
-
123
- def initialize: (?String uri, ?read_loop_thread: bool, **untyped) -> void
124
- def self.connect: (?String uri, ?read_loop_thread: bool, **untyped) -> Connection
125
- attr_reader frame_max: Integer
126
- def inspect: -> String
127
- def channel: (?Integer? id) -> Channel
128
- def with_channel: { (Channel) -> void } -> void
129
- def close: (?reason: String, ?code: Integer) -> nil
130
- def closed?: -> bool
131
- def write_bytes: (*String bytes) -> Integer
132
- def read_loop: -> nil
133
-
134
- private
135
- def parse_frame: (Integer `type`, Integer channel_id, String buf) -> bool
136
- def expect: (:close_ok expected_frame_type) -> untyped
137
- def open_socket: (String host, Integer port, bool tls, Hash[Symbol, untyped] options) -> (IO | untyped)
138
- def establish: ((IO | untyped) socket, String user, String password, String vhost, Hash[Symbol, untyped] options) -> [Integer, Integer, Integer]
139
- def enable_tcp_keepalive: ((IO | untyped) socket) -> void
140
- def port_from_env: -> Integer?
141
-
142
- class Channel
143
- @connection: Connection
144
- @replies: Thread::Queue
145
- @consumers: Hash[String, Thread::Queue]
146
- @closed: Array[(:channel | :connection | Integer | String)]?
147
- @open: bool
148
- @on_return: nil
149
- @confirm: Integer?
150
- @unconfirmed: Thread::Queue
151
- @unconfirmed_empty: Thread::Queue
152
- @basic_gets: Thread::Queue
153
- @next_msg: (Message | ReturnMessage)?
154
- @next_body: StringIO?
155
- @next_body_size: (Float | Integer | String)?
156
-
157
- def initialize: (Connection connection, Integer? id) -> void
158
- def inspect: -> String
159
- attr_reader id: Integer?
160
- def open: -> Channel
161
- def close: (?reason: String, ?code: Integer) -> nil
162
- def closed!: (:channel | :connection level, (Float | Integer | String)? code, String reason, (Float | Integer | String)? classid, (Float | Integer | String)? methodid) -> nil
163
- def on_return: { (ReturnMessage) -> void } -> void
164
- def exchange_declare: (String name, String `type`, ?passive: bool, ?durable: bool, ?auto_delete: bool, ?internal: bool, ?arguments: Hash[untyped, untyped]) -> nil
165
- def exchange_delete: (String name, ?if_unused: bool, ?no_wait: bool) -> nil
166
- def exchange_bind: (String destination, String source, String binding_key, ?arguments: Hash[untyped, untyped]) -> nil
167
- def exchange_unbind: (String destination, String source, String binding_key, ?arguments: Hash[untyped, untyped]) -> nil
168
- def queue_declare: (?String name, ?passive: bool, ?durable: bool, ?exclusive: bool, ?auto_delete: bool, ?arguments: Hash[untyped, untyped]) -> QueueOk
169
- def queue_delete: (String name, ?if_unused: bool, ?if_empty: bool, ?no_wait: bool) -> Integer?
170
- def queue_bind: (String name, String exchange, String binding_key, ?arguments: Hash[untyped, untyped]) -> nil
171
- def queue_purge: (String name, ?no_wait: bool) -> nil
172
- def queue_unbind: (String name, String exchange, String binding_key, ?arguments: Hash[untyped, untyped]) -> nil
173
- def basic_get: (String queue_name, ?no_ack: bool) -> Message?
174
- def basic_publish: (String body, String exchange, String routing_key, **untyped) -> nil
175
- def basic_publish_confirm: (String body, String exchange, String routing_key, **untyped) -> bool
176
- def basic_consume: (String queue, ?tag: String, ?no_ack: bool?, ?exclusive: bool, ?arguments: Hash[untyped, untyped]?, ?worker_threads: Integer?) { (Message) -> void } -> [String, Array[Thread]]?
177
- def basic_cancel: (String consumer_tag, ?no_wait: bool) -> nil
178
- def basic_qos: (Integer prefetch_count, ?prefetch_size: Integer, ?global: bool) -> nil
179
- def basic_ack: (Integer delivery_tag, ?multiple: bool) -> nil
180
- def basic_nack: (Integer delivery_tag, ?multiple: bool, ?requeue: bool) -> nil
181
- def basic_reject: (Integer delivery_tag, ?requeue: bool) -> nil
182
- def basic_recover: (?requeue: bool) -> nil
183
- def confirm_select: (?no_wait: bool) -> nil
184
- def wait_for_confirms: -> bool
185
- def tx_select: -> nil
186
- def tx_commit: -> nil
187
- def tx_rollback: -> nil
188
- def reply: (Array[untyped] args) -> void
189
- def confirm: (Array[(:ack | :nack | Integer | bool)] args) -> nil
190
- def message_returned: ((Float | Integer | String)? reply_code, String reply_text, String exchange, String routing_key) -> ReturnMessage
191
- def message_delivered: (String? consumer_tag, (Float | Integer | String)? delivery_tag, bool redelivered, String exchange, String routing_key) -> Message
192
- def basic_get_empty: -> void
193
- def header_delivered: (Integer body_size, Properties properties) -> void
194
- def body_delivered: (String body_part) -> void
195
- def close_consumer: (String tag) -> nil
196
-
197
- private
198
- def next_message_finished!: -> void
199
- def write_bytes: (*String bytes) -> Integer
200
- def expect: (Symbol expected_frame_type) -> Array[untyped]
201
-
202
- class QueueOk < Struct[untyped]
203
- attr_accessor queue_name(): String
204
- attr_accessor message_count(): Integer
205
- attr_accessor consumer_count(): Integer
206
- end
207
- end
208
- end
209
-
210
- class Error < StandardError
211
- class UnexpectedFrame < Error
212
- def initialize: (Symbol expected, Symbol actual) -> void
213
- end
214
-
215
- class UnexpectedFrameEnd < Error
216
- def initialize: (untyped actual) -> void
217
- end
218
-
219
- class UnsupportedFrameType < Error
220
- def initialize: (untyped `type`) -> void
221
- end
222
-
223
- class UnsupportedMethodFrame < Error
224
- def initialize: (Integer class_id, Integer method_id) -> void
225
- end
226
-
227
- class Closed < Error
228
- def self.new: (Integer id, (:channel | :connection) level, Integer code, String reason, ?Integer classid, ?Integer methodid) -> (ChannelClosed | ConnectionClosed)
229
- end
230
-
231
- class ChannelClosed < Error
232
- def initialize: (Integer id, Integer code, String reason, ?Integer classid, ?Integer methodid) -> void
233
- end
234
-
235
- class ConnectionClosed < Error
236
- def initialize: (Integer code, String reason, ?Integer classid, ?Integer methodid) -> void
237
- end
238
- end
239
-
240
- class Exchange
241
- @client: Client
242
- @name: String
243
-
244
- def initialize: (Client client, String name) -> void
245
- def publish: (String body, String routing_key, **untyped) -> Exchange
246
- def bind: (String exchange, String binding_key, ?arguments: Hash[String | Symbol, untyped]) -> Exchange
247
- def unbind: (String exchange, String binding_key, ?arguments: Hash[String | Symbol, untyped]) -> Exchange
248
- def delete: -> nil
249
- end
250
-
251
- class Queue
252
- @client: Client
253
- @name: String
254
-
255
- def initialize: (Client client, String name) -> void
256
- def publish: (String body, **untyped) -> Queue
257
- def subscribe: (?no_ack: bool, ?prefetch: Integer, ?worker_threads: Integer, ?arguments: Hash[String | Symbol, untyped]) { (Message) -> void } -> Queue
258
- def bind: (String exchange, String binding_key, ?arguments: Hash[String | Symbol, untyped]) -> Queue
259
- def unbind: (String exchange, String binding_key, ?arguments: Hash[String | Symbol, untyped]) -> Queue
260
- def purge: -> Queue
261
- def delete: -> nil
262
- end
263
- end
264
- end