amqp-client 1.1.7 → 1.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +1 -1
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/main.yml +34 -9
- data/.github/workflows/release.yml +1 -1
- data/.rubocop.yml +5 -3
- data/CHANGELOG.md +6 -1
- data/CODEOWNERS +1 -1
- data/README.md +63 -4
- data/Rakefile +155 -4
- data/amqp-client.gemspec +3 -3
- data/lib/amqp/client/channel.rb +3 -3
- data/lib/amqp/client/connection.rb +67 -14
- data/lib/amqp/client/frame_bytes.rb +10 -1
- data/lib/amqp/client/version.rb +1 -1
- data/lib/amqp/client.rb +1 -2
- metadata +6 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0abeedd581cdc09eac6afaf690fb8a50fbaef0146e4c22be43d7cad90d8d9b6
|
4
|
+
data.tar.gz: 16161d756ce7d68ba24debc60eda941243bb951cd17262f1c148ef1039595e88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 272c099f3158572304b30bbf67227343ce5456b90e5b4a08ccaf9c2da0f9b3a42ffb02913431b7d98cc0b7f1d5d36ef2c61bd492cdabf4411b0d2f941c616f04
|
7
|
+
data.tar.gz: a2000478882d0e630d88fb67e09675b087981498ac9a5b7f7a80f69a092c082415530156136cca1fa2e1bf88874468437fd3dbc600ee354110fb0c6af59e1373
|
data/.github/workflows/docs.yml
CHANGED
data/.github/workflows/main.yml
CHANGED
@@ -19,21 +19,22 @@ jobs:
|
|
19
19
|
matrix:
|
20
20
|
sudo: [true]
|
21
21
|
ruby:
|
22
|
-
- "2.6"
|
23
|
-
- "2.7"
|
24
|
-
- "3.0"
|
25
|
-
- "3.1"
|
26
22
|
- "3.2"
|
27
23
|
- "3.3"
|
24
|
+
- "3.4"
|
28
25
|
include:
|
29
|
-
- { ruby: jruby, allow-failure:
|
30
|
-
- { ruby: truffleruby, allow-failure:
|
26
|
+
- { ruby: jruby, allow-failure: false, sudo: false }
|
27
|
+
- { ruby: truffleruby, allow-failure: false, sudo: false }
|
31
28
|
steps:
|
29
|
+
- name: Configure dpkg to skip building man pages
|
30
|
+
run: |
|
31
|
+
echo 'path-exclude /usr/share/doc/*' | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
|
32
|
+
echo 'path-exclude /usr/share/man/*' | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
|
32
33
|
- name: Install RabbitMQ
|
33
34
|
run: sudo apt-get update && sudo apt-get install -y rabbitmq-server
|
34
35
|
- name: Verify RabbitMQ started correctly
|
35
36
|
run: while true; do sudo rabbitmq-diagnostics status 2>/dev/null && break; echo -n .; sleep 2; done
|
36
|
-
- uses: actions/checkout@
|
37
|
+
- uses: actions/checkout@v5
|
37
38
|
- uses: ruby/setup-ruby@v1
|
38
39
|
with:
|
39
40
|
bundler-cache: true
|
@@ -55,6 +56,10 @@ jobs:
|
|
55
56
|
- "jruby"
|
56
57
|
- "truffleruby"
|
57
58
|
steps:
|
59
|
+
- name: Configure dpkg to skip building man pages
|
60
|
+
run: |
|
61
|
+
echo 'path-exclude /usr/share/doc/*' | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
|
62
|
+
echo 'path-exclude /usr/share/man/*' | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
|
58
63
|
- name: Install RabbitMQ
|
59
64
|
run: sudo apt-get update && sudo apt-get install -y rabbitmq-server
|
60
65
|
- name: Stop RabbitMQ
|
@@ -81,12 +86,20 @@ jobs:
|
|
81
86
|
run: sudo systemctl start rabbitmq-server
|
82
87
|
- name: Verify RabbitMQ started correctly
|
83
88
|
run: while true; do sudo rabbitmq-diagnostics status 2>/dev/null && break; echo -n .; sleep 2; done
|
84
|
-
- uses: actions/checkout@
|
89
|
+
- uses: actions/checkout@v5
|
85
90
|
- uses: ruby/setup-ruby@v1
|
86
91
|
with:
|
87
92
|
bundler-cache: true
|
88
93
|
ruby-version: ${{ matrix.ruby }}
|
94
|
+
- name: Run TLS tests (JRuby)
|
95
|
+
if: ${{ matrix.ruby == 'jruby' }}
|
96
|
+
run: bundle exec rake
|
97
|
+
env:
|
98
|
+
JAVA_OPTS: "-Djava.net.preferIPv4Stack=true"
|
99
|
+
TEST_AMQP_HOST: "localhost"
|
100
|
+
TESTOPTS: --name=/_tls$/
|
89
101
|
- name: Run TLS tests
|
102
|
+
if: ${{ matrix.ruby != 'jruby' }}
|
90
103
|
run: bundle exec rake
|
91
104
|
env:
|
92
105
|
TESTOPTS: --name=/_tls$/
|
@@ -104,7 +117,7 @@ jobs:
|
|
104
117
|
run: brew install rabbitmq
|
105
118
|
- name: Start RabbitMQ
|
106
119
|
run: brew services start rabbitmq
|
107
|
-
- uses: actions/checkout@
|
120
|
+
- uses: actions/checkout@v5
|
108
121
|
- uses: ruby/setup-ruby@v1
|
109
122
|
with:
|
110
123
|
bundler-cache: true
|
@@ -113,3 +126,15 @@ jobs:
|
|
113
126
|
run: while true; do rabbitmq-diagnostics status 2>/dev/null && break; echo -n .; sleep 2; done
|
114
127
|
- name: Run tests (excluding TLS tests)
|
115
128
|
run: bundle exec rake
|
129
|
+
|
130
|
+
lint:
|
131
|
+
runs-on: ubuntu-latest
|
132
|
+
timeout-minutes: 10
|
133
|
+
steps:
|
134
|
+
- uses: actions/checkout@v5
|
135
|
+
- uses: ruby/setup-ruby@v1
|
136
|
+
with:
|
137
|
+
bundler-cache: true
|
138
|
+
ruby-version: ruby
|
139
|
+
- name: Run RuboCop
|
140
|
+
run: bundle exec rake rubocop
|
data/.rubocop.yml
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
2
|
+
plugins:
|
3
|
+
- rubocop-minitest
|
2
4
|
|
3
5
|
AllCops:
|
4
6
|
NewCops: disable
|
5
|
-
TargetRubyVersion: 2
|
7
|
+
TargetRubyVersion: 3.2
|
6
8
|
SuggestExtensions: false
|
7
9
|
|
8
10
|
Style/StringLiterals:
|
@@ -18,11 +20,11 @@ Layout/LineLength:
|
|
18
20
|
|
19
21
|
Naming/FileName:
|
20
22
|
Exclude:
|
21
|
-
-
|
23
|
+
- "lib/amqp-client.rb"
|
22
24
|
|
23
25
|
Metrics/PerceivedComplexity:
|
24
26
|
Exclude:
|
25
|
-
-
|
27
|
+
- "lib/amqp/client/properties.rb"
|
26
28
|
|
27
29
|
Metrics/ParameterLists:
|
28
30
|
Max: 8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.2.0] - 2025-09-10
|
4
|
+
|
5
|
+
- Fixed: `Connection#channel` wasn't thread-safe
|
6
|
+
- Added: Support for heartbeats
|
7
|
+
|
3
8
|
## [1.1.7] - 2024-05-12
|
4
9
|
|
5
10
|
- Support for Connection.update-secret
|
@@ -48,7 +53,7 @@
|
|
48
53
|
|
49
54
|
## [1.0.1] - 2021-09-06
|
50
55
|
|
51
|
-
- The API is fully documented! https://cloudamqp.github.io/amqp-client.rb
|
56
|
+
- The API is fully documented! <https://cloudamqp.github.io/amqp-client.rb/>
|
52
57
|
- Fixed: Socket writing is now thread-safe
|
53
58
|
- Change: Block while waiting for basic_cancel by default
|
54
59
|
- Added: Can specify channel_max, heartbeat and frame_max as options to the Client/Connection
|
data/CODEOWNERS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* @
|
1
|
+
* @carlhoerberg @spuun @dentarg @baelter @walro
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ It's safe by default, messages are published as persistent, and is waiting for c
|
|
8
8
|
|
9
9
|
## Support
|
10
10
|
|
11
|
-
The library is fully supported by [CloudAMQP](https://www.cloudamqp.com), the largest RabbitMQ hosting provider in the world. Open [an issue](https://github.com/cloudamqp/amqp-client.rb/issues) or [email our support](mailto:support@cloudamqp.com) if you have problems or questions.
|
11
|
+
The library is fully supported by [CloudAMQP](https://www.cloudamqp.com), the largest LavinMQ and RabbitMQ hosting provider in the world. Open [an issue](https://github.com/cloudamqp/amqp-client.rb/issues) or [email our support](mailto:support@cloudamqp.com) if you have problems or questions.
|
12
12
|
|
13
13
|
## Documentation
|
14
14
|
|
@@ -113,17 +113,76 @@ gem 'amqp-client'
|
|
113
113
|
|
114
114
|
And then execute:
|
115
115
|
|
116
|
-
|
116
|
+
bundle install
|
117
117
|
|
118
118
|
Or install it yourself as:
|
119
119
|
|
120
|
-
|
120
|
+
gem install amqp-client
|
121
121
|
|
122
122
|
## Development
|
123
123
|
|
124
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.
|
125
125
|
|
126
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
126
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
127
|
+
|
128
|
+
### Release Process
|
129
|
+
|
130
|
+
The gem uses rake tasks to automate the release process. Make sure your working directory is clean before starting a release.
|
131
|
+
|
132
|
+
#### Quick Release (Patch Version)
|
133
|
+
|
134
|
+
```bash
|
135
|
+
rake release:full
|
136
|
+
```
|
137
|
+
|
138
|
+
This will:
|
139
|
+
|
140
|
+
1. Run tests and RuboCop to ensure code quality
|
141
|
+
2. Bump the patch version (e.g., 1.2.0 → 1.2.1)
|
142
|
+
3. Update the CHANGELOG.md with the new version and current date
|
143
|
+
4. Create a git commit and tag for the release
|
144
|
+
5. Build and push the gem to RubyGems
|
145
|
+
6. Push commits and tags to the remote repository
|
146
|
+
|
147
|
+
#### Custom Version Bump
|
148
|
+
|
149
|
+
For minor or major version bumps:
|
150
|
+
|
151
|
+
```bash
|
152
|
+
# Minor version bump (e.g., 1.2.0 → 1.3.0)
|
153
|
+
rake release:full[minor]
|
154
|
+
|
155
|
+
# Major version bump (e.g., 1.2.0 → 2.0.0)
|
156
|
+
rake release:full[major]
|
157
|
+
```
|
158
|
+
|
159
|
+
#### Individual Release Steps
|
160
|
+
|
161
|
+
You can also run individual steps if needed:
|
162
|
+
|
163
|
+
```bash
|
164
|
+
# Bump version only
|
165
|
+
rake release:bump[patch] # or [minor] or [major]
|
166
|
+
|
167
|
+
# Update changelog with current version
|
168
|
+
rake release:changelog
|
169
|
+
|
170
|
+
# Create git tag
|
171
|
+
rake release:tag
|
172
|
+
|
173
|
+
# Build and push to RubyGems
|
174
|
+
rake release:push
|
175
|
+
```
|
176
|
+
|
177
|
+
#### Manual Release Steps
|
178
|
+
|
179
|
+
If you prefer manual control:
|
180
|
+
|
181
|
+
1. Update the version number in `lib/amqp/client/version.rb`
|
182
|
+
2. Update the CHANGELOG.md with the new version and release notes
|
183
|
+
3. Commit your changes: `git add . && git commit -m "Release X.Y.Z"`
|
184
|
+
4. Create and push a tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
|
185
|
+
5. Build and push: `rake build && gem push amqp-client-X.Y.Z.gem`
|
127
186
|
|
128
187
|
## Contributing
|
129
188
|
|
data/Rakefile
CHANGED
@@ -18,12 +18,163 @@ end
|
|
18
18
|
|
19
19
|
require "rubocop/rake_task"
|
20
20
|
|
21
|
-
RuboCop::RakeTask.new
|
22
|
-
task.requires << "rubocop-minitest"
|
23
|
-
end
|
21
|
+
RuboCop::RakeTask.new
|
24
22
|
|
25
23
|
require "yard"
|
26
24
|
|
27
25
|
YARD::Rake::YardocTask.new
|
28
26
|
|
29
|
-
|
27
|
+
# Release helper methods
|
28
|
+
def current_version
|
29
|
+
version_file = "lib/amqp/client/version.rb"
|
30
|
+
content = File.read(version_file)
|
31
|
+
content.match(/VERSION = "(.+)"/)[1]
|
32
|
+
end
|
33
|
+
|
34
|
+
def bump_version(version_type)
|
35
|
+
unless %w[major minor patch].include?(version_type)
|
36
|
+
puts "Invalid version type. Use: major, minor, or patch"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
|
40
|
+
version_file = "lib/amqp/client/version.rb"
|
41
|
+
content = File.read(version_file)
|
42
|
+
|
43
|
+
current_version = content.match(/VERSION = "(.+)"/)[1]
|
44
|
+
major, minor, patch = current_version.split(".").map(&:to_i)
|
45
|
+
|
46
|
+
case version_type
|
47
|
+
when "major"
|
48
|
+
major += 1
|
49
|
+
minor = 0
|
50
|
+
patch = 0
|
51
|
+
when "minor"
|
52
|
+
minor += 1
|
53
|
+
patch = 0
|
54
|
+
when "patch"
|
55
|
+
patch += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
new_version = "#{major}.#{minor}.#{patch}"
|
59
|
+
new_content = content.gsub(/VERSION = ".+"/, %(VERSION = "#{new_version}"))
|
60
|
+
|
61
|
+
File.write(version_file, new_content)
|
62
|
+
puts "Bumped version from #{current_version} to #{new_version}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_changelog
|
66
|
+
version = current_version
|
67
|
+
date = Time.now.strftime("%Y-%m-%d")
|
68
|
+
|
69
|
+
changelog = File.read("CHANGELOG.md")
|
70
|
+
|
71
|
+
if changelog.include?("## [#{version}]")
|
72
|
+
puts "Version #{version} already exists in CHANGELOG.md"
|
73
|
+
else
|
74
|
+
updated_changelog = changelog.sub(
|
75
|
+
"## [Unreleased]",
|
76
|
+
"## [Unreleased]\n\n## [#{version}] - #{date}"
|
77
|
+
)
|
78
|
+
|
79
|
+
File.write("CHANGELOG.md", updated_changelog)
|
80
|
+
puts "Updated CHANGELOG.md with version #{version}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_git_tag
|
85
|
+
version = current_version
|
86
|
+
|
87
|
+
system("git add .")
|
88
|
+
system("git commit -m 'Release #{version}'")
|
89
|
+
|
90
|
+
# Check if tag already exists and remove it if it does
|
91
|
+
if system("git tag -l v#{version} | grep -q v#{version}")
|
92
|
+
puts "Tag v#{version} already exists, removing it..."
|
93
|
+
system("git tag -d v#{version}")
|
94
|
+
end
|
95
|
+
|
96
|
+
system("git tag v#{version}")
|
97
|
+
|
98
|
+
puts "Created git tag v#{version}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def push_gem_to_rubygems
|
102
|
+
version = current_version
|
103
|
+
|
104
|
+
# Look for gem file in both current directory and pkg directory
|
105
|
+
gem_file = "amqp-client-#{version}.gem"
|
106
|
+
pkg_gem_file = "pkg/amqp-client-#{version}.gem"
|
107
|
+
|
108
|
+
if File.exist?(pkg_gem_file)
|
109
|
+
system("gem push #{pkg_gem_file}")
|
110
|
+
puts "Pushed #{pkg_gem_file} to RubyGems"
|
111
|
+
elsif File.exist?(gem_file)
|
112
|
+
system("gem push #{gem_file}")
|
113
|
+
puts "Pushed #{gem_file} to RubyGems"
|
114
|
+
else
|
115
|
+
puts "Gem file #{gem_file} not found in current directory or pkg/. Make sure to build first."
|
116
|
+
exit 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def full_release_process(version_type)
|
121
|
+
puts "Starting release process..."
|
122
|
+
|
123
|
+
# Ensure working directory is clean
|
124
|
+
unless system("git diff --quiet && git diff --cached --quiet")
|
125
|
+
puts "Working directory is not clean. Please commit or stash changes first."
|
126
|
+
exit 1
|
127
|
+
end
|
128
|
+
|
129
|
+
# Bump version
|
130
|
+
Rake::Task["release:bump"].invoke(version_type)
|
131
|
+
Rake::Task["release:bump"].reenable
|
132
|
+
|
133
|
+
# Update changelog
|
134
|
+
Rake::Task["release:changelog"].invoke
|
135
|
+
Rake::Task["release:changelog"].reenable
|
136
|
+
|
137
|
+
# Create tag and push
|
138
|
+
Rake::Task["release:tag"].invoke
|
139
|
+
Rake::Task["release:tag"].reenable
|
140
|
+
|
141
|
+
# Build and push gem
|
142
|
+
Rake::Task["release:push"].invoke
|
143
|
+
Rake::Task["release:push"].reenable
|
144
|
+
|
145
|
+
# Push to git
|
146
|
+
system("git push origin")
|
147
|
+
system("git push origin --tags")
|
148
|
+
|
149
|
+
version = current_version
|
150
|
+
puts "Successfully released version #{version}!"
|
151
|
+
end
|
152
|
+
|
153
|
+
namespace :release do
|
154
|
+
desc "Bump version (usage: rake release:bump[major|minor|patch])"
|
155
|
+
task :bump, [:type] do |_t, args|
|
156
|
+
bump_version(args[:type] || "patch")
|
157
|
+
end
|
158
|
+
|
159
|
+
desc "Update changelog with current version"
|
160
|
+
task :changelog do
|
161
|
+
update_changelog
|
162
|
+
end
|
163
|
+
|
164
|
+
desc "Create git tag for current version"
|
165
|
+
task :tag do
|
166
|
+
create_git_tag
|
167
|
+
end
|
168
|
+
|
169
|
+
desc "Build and push gem to RubyGems"
|
170
|
+
task push: :build do
|
171
|
+
push_gem_to_rubygems
|
172
|
+
end
|
173
|
+
|
174
|
+
desc "Full release process (bump version, update changelog, tag, build and push)"
|
175
|
+
task :full, [:type] => %i[test rubocop] do |_t, args|
|
176
|
+
full_release_process(args[:type] || "patch")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
task default: [:test, *(:rubocop if ENV["CI"] != "true")]
|
data/amqp-client.gemspec
CHANGED
@@ -5,14 +5,14 @@ require_relative "lib/amqp/client/version"
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "amqp-client"
|
7
7
|
spec.version = AMQP::Client::VERSION
|
8
|
-
spec.authors = ["
|
9
|
-
spec.email = ["
|
8
|
+
spec.authors = ["CloudAMQP"]
|
9
|
+
spec.email = ["team@cloudamqp.com"]
|
10
10
|
|
11
11
|
spec.summary = "AMQP 0-9-1 client"
|
12
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
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
|
data/lib/amqp/client/channel.rb
CHANGED
@@ -251,7 +251,7 @@ module AMQP
|
|
251
251
|
# @option properties [Hash<String, Object>] headers Custom headers
|
252
252
|
# @option properties [Integer] delivery_mode 2 for persisted message, transient messages for all other values
|
253
253
|
# @option properties [Integer] priority A priority of the message (between 0 and 255)
|
254
|
-
# @option properties [
|
254
|
+
# @option properties [String] correlation_id A correlation id, most often used used for RPC communication
|
255
255
|
# @option properties [String] reply_to Queue to reply RPC responses to
|
256
256
|
# @option properties [Integer, String] expiration Number of seconds the message will stay in the queue
|
257
257
|
# @option properties [String] message_id Can be used to uniquely identify the message, e.g. for deduplication
|
@@ -345,8 +345,8 @@ module AMQP
|
|
345
345
|
end
|
346
346
|
|
347
347
|
# Specify how many messages to prefetch for consumers with `no_ack: false`
|
348
|
-
# @param prefetch_count [Integer] Number of messages to
|
349
|
-
# @param prefetch_size [Integer] Number of bytes to
|
348
|
+
# @param prefetch_count [Integer] Number of messages to maximum keep in flight
|
349
|
+
# @param prefetch_size [Integer] Number of bytes to maximum keep in flight
|
350
350
|
# @param global [Boolean] If true the limit will apply to channel rather than the consumer
|
351
351
|
# @return [nil]
|
352
352
|
def basic_qos(prefetch_count, prefetch_size: 0, global: false)
|
@@ -44,6 +44,7 @@ module AMQP
|
|
44
44
|
@frame_max = frame_max
|
45
45
|
@heartbeat = heartbeat
|
46
46
|
@channels = {}
|
47
|
+
@channels_lock = Mutex.new
|
47
48
|
@closed = nil
|
48
49
|
@replies = ::Queue.new
|
49
50
|
@write_lock = Mutex.new
|
@@ -51,6 +52,9 @@ module AMQP
|
|
51
52
|
@on_blocked = ->(reason) { warn "AMQP-Client blocked by broker: #{reason}" }
|
52
53
|
@on_unblocked = -> { warn "AMQP-Client unblocked by broker" }
|
53
54
|
|
55
|
+
# Only used with heartbeats
|
56
|
+
@last_activity_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
57
|
+
|
54
58
|
Thread.new { read_loop } if read_loop_thread
|
55
59
|
end
|
56
60
|
|
@@ -89,15 +93,17 @@ module AMQP
|
|
89
93
|
raise ArgumentError, "Channel ID cannot be 0" if id&.zero?
|
90
94
|
raise ArgumentError, "Channel ID higher than connection's channel max #{@channel_max}" if id && id > @channel_max
|
91
95
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
96
|
+
ch = @channels_lock.synchronize do
|
97
|
+
if id
|
98
|
+
@channels[id] ||= Channel.new(self, id)
|
99
|
+
else
|
100
|
+
1.upto(@channel_max) do |i|
|
101
|
+
break id = i unless @channels.key? i
|
102
|
+
end
|
103
|
+
raise Error, "Max channels reached" if id.nil?
|
99
104
|
|
100
|
-
|
105
|
+
@channels[id] = Channel.new(self, id)
|
106
|
+
end
|
101
107
|
end
|
102
108
|
ch.open
|
103
109
|
end
|
@@ -175,6 +181,7 @@ module AMQP
|
|
175
181
|
def write_bytes(*bytes)
|
176
182
|
@write_lock.synchronize do
|
177
183
|
@socket.write(*bytes)
|
184
|
+
update_last_activity
|
178
185
|
end
|
179
186
|
rescue *READ_EXCEPTIONS => e
|
180
187
|
raise Error::ConnectionClosed.new(*@closed) if @closed
|
@@ -206,6 +213,7 @@ module AMQP
|
|
206
213
|
|
207
214
|
# parse the frame, will return false if a close frame was received
|
208
215
|
parse_frame(type, channel_id, frame_buffer) || return
|
216
|
+
update_last_activity
|
209
217
|
end
|
210
218
|
nil
|
211
219
|
rescue *READ_EXCEPTIONS => e
|
@@ -232,7 +240,7 @@ module AMQP
|
|
232
240
|
READ_EXCEPTIONS = [IOError, OpenSSL::OpenSSLError, SystemCallError,
|
233
241
|
RUBY_ENGINE == "jruby" ? java.lang.NullPointerException : nil].compact.freeze
|
234
242
|
|
235
|
-
def parse_frame(type, channel_id, buf) # rubocop:disable Metrics/MethodLength
|
243
|
+
def parse_frame(type, channel_id, buf) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
236
244
|
channel = @channels[channel_id]
|
237
245
|
case type
|
238
246
|
when 1 # method frame
|
@@ -277,11 +285,11 @@ module AMQP
|
|
277
285
|
reply_code, reply_text_len = buf.unpack("@4 S> C")
|
278
286
|
reply_text = buf.byteslice(7, reply_text_len).force_encoding("utf-8")
|
279
287
|
classid, methodid = buf.byteslice(7 + reply_text_len, 4).unpack("S> S>")
|
280
|
-
channel = @channels.delete(channel_id)
|
288
|
+
channel = @channels_lock.synchronize { @channels.delete(channel_id) }
|
281
289
|
channel.closed!(:channel, reply_code, reply_text, classid, methodid)
|
282
290
|
write_bytes FrameBytes.channel_close_ok(channel_id)
|
283
291
|
when 41 # channel#close-ok
|
284
|
-
channel = @channels.delete(channel_id)
|
292
|
+
channel = @channels_lock.synchronize { @channels.delete(channel_id) }
|
285
293
|
channel.reply [:channel_close_ok]
|
286
294
|
else raise Error::UnsupportedMethodFrame, class_id, method_id
|
287
295
|
end
|
@@ -406,17 +414,61 @@ module AMQP
|
|
406
414
|
channel.header_delivered body_size, properties
|
407
415
|
when 3 # body
|
408
416
|
channel.body_delivered buf
|
417
|
+
when 8 # heartbeat
|
418
|
+
handle_server_heartbeat(channel_id)
|
409
419
|
else raise Error::UnsupportedFrameType, type
|
410
420
|
end
|
411
421
|
true
|
412
422
|
end
|
413
423
|
|
424
|
+
def update_last_activity
|
425
|
+
return unless @heartbeat&.positive?
|
426
|
+
|
427
|
+
@last_activity_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
428
|
+
end
|
429
|
+
|
430
|
+
def handle_server_heartbeat(channel_id)
|
431
|
+
return if channel_id.zero?
|
432
|
+
|
433
|
+
raise Error::ConnectionClosed.new(501, "Heartbeat frame received on non-zero channel #{channel_id}")
|
434
|
+
end
|
435
|
+
|
436
|
+
# Start the heartbeat background thread (called from connection#tune)
|
437
|
+
def start_heartbeats(period)
|
438
|
+
Thread.new do
|
439
|
+
Thread.current.abort_on_exception = true # Raising an unhandled exception is a bug
|
440
|
+
interval = period / 2.0
|
441
|
+
loop do
|
442
|
+
sleep interval
|
443
|
+
break if @closed
|
444
|
+
next if @socket.nil?
|
445
|
+
|
446
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
447
|
+
# If we haven't sent anything recently, send a heartbeat
|
448
|
+
next unless now - @last_activity_time >= interval
|
449
|
+
|
450
|
+
begin
|
451
|
+
send_heartbeat
|
452
|
+
rescue Error => e
|
453
|
+
warn "AMQP-Client heartbeat send failed: #{e.inspect}"
|
454
|
+
break
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def send_heartbeat
|
461
|
+
write_bytes FrameBytes.heartbeat
|
462
|
+
end
|
463
|
+
|
414
464
|
def expect(expected_frame_type)
|
415
465
|
frame_type, args = @replies.pop
|
416
466
|
if frame_type.nil?
|
417
467
|
return if expected_frame_type == :close_ok
|
418
468
|
|
419
|
-
raise
|
469
|
+
raise Error::ConnectionClosed.new(*@closed) if @closed
|
470
|
+
|
471
|
+
raise Error, "Connection closed while waiting for #{expected_frame_type}"
|
420
472
|
end
|
421
473
|
frame_type == expected_frame_type || raise(Error::UnexpectedFrame.new(expected_frame_type, frame_type))
|
422
474
|
args
|
@@ -454,7 +506,7 @@ module AMQP
|
|
454
506
|
channel_max, frame_max, heartbeat = nil
|
455
507
|
socket.write "AMQP\x00\x00\x09\x01"
|
456
508
|
buf = String.new(capacity: 4096)
|
457
|
-
loop do
|
509
|
+
loop do # rubocop:disable Metrics/BlockLength
|
458
510
|
begin
|
459
511
|
socket.readpartial(4096, buf)
|
460
512
|
rescue *READ_EXCEPTIONS => e
|
@@ -483,6 +535,7 @@ module AMQP
|
|
483
535
|
channel_max = [channel_max, options.fetch(:channel_max, 2048).to_i].min
|
484
536
|
frame_max = [frame_max, options.fetch(:frame_max, 131_072).to_i].min
|
485
537
|
heartbeat = [heartbeat, options.fetch(:heartbeat, 0).to_i].min
|
538
|
+
start_heartbeats(heartbeat) if heartbeat.positive?
|
486
539
|
socket.write FrameBytes.connection_tune_ok(channel_max, frame_max, heartbeat)
|
487
540
|
socket.write FrameBytes.connection_open(vhost)
|
488
541
|
when 41 # connection#open-ok
|
@@ -508,7 +561,7 @@ module AMQP
|
|
508
561
|
raise e
|
509
562
|
end
|
510
563
|
|
511
|
-
# Enable TCP keepalive, which is
|
564
|
+
# Enable TCP keepalive, which is preferred to heartbeats
|
512
565
|
# @return [void]
|
513
566
|
def enable_tcp_keepalive(socket, idle = 60, interval = 10, count = 3)
|
514
567
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
@@ -9,7 +9,7 @@ module AMQP
|
|
9
9
|
# Each frame type implemented as a method
|
10
10
|
# Having a class for each frame type is more expensive in terms of CPU and memory
|
11
11
|
# @api private
|
12
|
-
module FrameBytes
|
12
|
+
module FrameBytes # rubocop:disable Metrics/ModuleLength
|
13
13
|
def self.connection_start_ok(response, properties)
|
14
14
|
prop_tbl = Table.encode(properties)
|
15
15
|
[
|
@@ -542,6 +542,15 @@ module AMQP
|
|
542
542
|
206 # frame end
|
543
543
|
].pack("C S> L> S> S> C")
|
544
544
|
end
|
545
|
+
|
546
|
+
def self.heartbeat
|
547
|
+
[
|
548
|
+
8, # type: heartbeat
|
549
|
+
0, # channel id
|
550
|
+
0, # frame size
|
551
|
+
206 # frame end
|
552
|
+
].pack("C S> L> C")
|
553
|
+
end
|
545
554
|
end
|
546
555
|
end
|
547
556
|
end
|
data/lib/amqp/client/version.rb
CHANGED
data/lib/amqp/client.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "set"
|
4
3
|
require_relative "client/version"
|
5
4
|
require_relative "client/connection"
|
6
5
|
require_relative "client/exchange"
|
@@ -52,7 +51,7 @@ module AMQP
|
|
52
51
|
def start
|
53
52
|
@stopped = false
|
54
53
|
Thread.new(connect(read_loop_thread: false)) do |conn|
|
55
|
-
Thread.abort_on_exception = true # Raising an unhandled exception is a bug
|
54
|
+
Thread.current.abort_on_exception = true # Raising an unhandled exception is a bug
|
56
55
|
loop do
|
57
56
|
break if @stopped
|
58
57
|
|
metadata
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amqp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- CloudAMQP
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: Modern AMQP 0-9-1 Ruby client
|
14
13
|
email:
|
15
|
-
-
|
14
|
+
- team@cloudamqp.com
|
16
15
|
executables: []
|
17
16
|
extensions: []
|
18
17
|
extra_rdoc_files: []
|
@@ -53,7 +52,6 @@ metadata:
|
|
53
52
|
homepage_uri: https://github.com/cloudamqp/amqp-client.rb
|
54
53
|
source_code_uri: https://github.com/cloudamqp/amqp-client.rb.git
|
55
54
|
changelog_uri: https://github.com/cloudamqp/amqp-client.rb/blob/main/CHANGELOG.md
|
56
|
-
post_install_message:
|
57
55
|
rdoc_options: []
|
58
56
|
require_paths:
|
59
57
|
- lib
|
@@ -61,15 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
59
|
requirements:
|
62
60
|
- - ">="
|
63
61
|
- !ruby/object:Gem::Version
|
64
|
-
version: 2.
|
62
|
+
version: 3.2.0
|
65
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
64
|
requirements:
|
67
65
|
- - ">="
|
68
66
|
- !ruby/object:Gem::Version
|
69
67
|
version: '0'
|
70
68
|
requirements: []
|
71
|
-
rubygems_version: 3.
|
72
|
-
signing_key:
|
69
|
+
rubygems_version: 3.6.9
|
73
70
|
specification_version: 4
|
74
71
|
summary: AMQP 0-9-1 client
|
75
72
|
test_files: []
|