async-nats 0.1.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.
data/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # Async::NATS
2
+
3
+ A high-performance, fiber-based NATS client for Ruby built on top of the [async](https://github.com/socketry/async) framework. This project provides a fully asynchronous NATS client that maintains API compatibility with [nats-pure](https://github.com/nats-io/nats-pure.rb) while delivering superior performance through fiber-based concurrency.
4
+
5
+ ## ⚠️ AI-Generated Code Notice
6
+
7
+ **This entire project was developed by the Windsurf AI code agent.** While the code has been tested and benchmarked, it should be considered experimental. Use in production at your own risk and conduct thorough testing for your specific use case.
8
+
9
+ ## Features
10
+
11
+ - ✅ **Fully Async** - All I/O operations use fibers instead of threads
12
+ - ✅ **API Compatible** - Drop-in replacement for nats-pure with async support
13
+ - ✅ **High Performance** - 4-8% faster publish throughput than nats-pure
14
+ - ✅ **Efficient Drain** - 100x faster drain operations using async primitives
15
+ - ✅ **Lower Resource Usage** - Fiber-based concurrency uses less memory than threads
16
+ - ✅ **Custom Async Executor** - Worker pool pattern for subscription handling
17
+ - ✅ **Context Detection** - Works in both async and non-async contexts
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'async-nats'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ bundle install
31
+ ```
32
+
33
+ Or install it yourself as:
34
+
35
+ ```bash
36
+ gem install async-nats
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Basic Publishing and Subscribing
42
+
43
+ ```ruby
44
+ require 'async'
45
+ require 'async/nats'
46
+
47
+ Async do
48
+ # Create and connect to NATS server
49
+ nats = Async::NATS::Client.new
50
+ nats.connect(servers: ["nats://localhost:4222"])
51
+
52
+ # Subscribe to a subject
53
+ nats.subscribe("hello") do |msg|
54
+ puts "Received: #{msg.data}"
55
+ end
56
+
57
+ # Publish a message
58
+ nats.publish("hello", "World!")
59
+
60
+ # Wait a bit for message delivery
61
+ Async::Task.current.sleep(0.1)
62
+
63
+ # Gracefully drain and close
64
+ nats.drain.wait
65
+ end
66
+ ```
67
+
68
+ ### Request-Reply Pattern
69
+
70
+ ```ruby
71
+ Async do
72
+ nats = Async::NATS::Client.new
73
+ nats.connect
74
+
75
+ # Responder
76
+ nats.subscribe("help") do |msg|
77
+ nats.publish(msg.reply, "I can help!")
78
+ end
79
+
80
+ # Requester
81
+ response = nats.request("help", "please", timeout: 1.0)
82
+ puts "Response: #{response.data}"
83
+
84
+ nats.close
85
+ end
86
+ ```
87
+
88
+ ### Multiple Subscriptions
89
+
90
+ ```ruby
91
+ Async do
92
+ nats = Async::NATS::Client.new
93
+ nats.connect
94
+
95
+ # Subscribe to multiple subjects
96
+ nats.subscribe("events.>") do |msg|
97
+ puts "Event: #{msg.subject} - #{msg.data}"
98
+ end
99
+
100
+ nats.subscribe("logs.*") do |msg|
101
+ puts "Log: #{msg.subject} - #{msg.data}"
102
+ end
103
+
104
+ # Publish to different subjects
105
+ nats.publish("events.user.login", "user123")
106
+ nats.publish("logs.error", "Something went wrong")
107
+
108
+ Async::Task.current.sleep(0.1)
109
+ nats.close
110
+ end
111
+ ```
112
+
113
+ ### Async Drain with Timeout
114
+
115
+ ```ruby
116
+ Async do |task|
117
+ nats = Async::NATS::Client.new
118
+ nats.connect
119
+
120
+ # ... use connection ...
121
+
122
+ # Drain with custom timeout
123
+ begin
124
+ task.with_timeout(5.0) do
125
+ nats.drain.wait
126
+ end
127
+ puts "Drain completed successfully"
128
+ rescue Async::TimeoutError
129
+ puts "Drain timed out, forcing close"
130
+ nats.close
131
+ end
132
+ end
133
+ ```
134
+
135
+ ### Queue Groups
136
+
137
+ ```ruby
138
+ Async do
139
+ nats = Async::NATS::Client.new
140
+ nats.connect
141
+
142
+ # Create queue group subscribers
143
+ 3.times do |i|
144
+ nats.subscribe("work", queue: "workers") do |msg|
145
+ puts "Worker #{i} processing: #{msg.data}"
146
+ end
147
+ end
148
+
149
+ # Publish work items - distributed across queue group
150
+ 10.times do |i|
151
+ nats.publish("work", "task-#{i}")
152
+ end
153
+
154
+ Async::Task.current.sleep(0.5)
155
+ nats.close
156
+ end
157
+ ```
158
+
159
+ ## Performance
160
+
161
+ async-nats delivers excellent performance compared to nats-pure:
162
+
163
+ ### Publish Performance (100k messages)
164
+ - **nats-pure**: 947,460 msgs/sec
165
+ - **async-nats**: 989,994 msgs/sec
166
+ - **Result**: **+4.5% faster** ✅
167
+
168
+ ### Pub/Sub Performance (10k messages)
169
+ - **nats-pure**: 19,982 msgs/sec
170
+ - **async-nats**: 19,017 msgs/sec
171
+ - **Result**: Essentially identical ✅
172
+
173
+ ### Drain Performance
174
+ - **async-nats**: ~1ms drain latency
175
+ - **Result**: **100x faster** than thread-based polling ✅
176
+
177
+ See [BENCHMARK_SUMMARY.md](BENCHMARK_SUMMARY.md) and [BENCHMARK_RESULTS.md](BENCHMARK_RESULTS.md) for detailed performance analysis.
178
+
179
+ ## Architecture
180
+
181
+ async-nats is built on top of nats-pure and overrides key components to use async primitives:
182
+
183
+ ### Key Components
184
+
185
+ 1. **Async::NATS::Socket** - Replaces blocking I/O with async operations using `io-stream`
186
+ 2. **Async::NATS::Client** - Overrides connection and threading to use async tasks
187
+ 3. **Async::NATS::Executor** - Custom worker pool for subscription callbacks
188
+ 4. **Async Drain** - Event-driven drain using `Async::Barrier`
189
+
190
+ ### Concurrency Model
191
+
192
+ ```
193
+ ┌─────────────────────────────────────────┐
194
+ │ Async::NATS::Client │
195
+ │ │
196
+ │ ┌─────────────┐ ┌─────────────┐ │
197
+ │ │ Read Loop │ │ Flusher │ │
198
+ │ │ (Async Task)│ │ (Async Task)│ │
199
+ │ └─────────────┘ └─────────────┘ │
200
+ │ │
201
+ │ ┌─────────────────────────────────┐ │
202
+ │ │ Async::NATS::Executor │ │
203
+ │ │ (Worker Pool for Callbacks) │ │
204
+ │ │ ┌────┐ ┌────┐ ┌────┐ │ │
205
+ │ │ │ W1 │ │ W2 │ │... │ │ │
206
+ │ │ └────┘ └────┘ └────┘ │ │
207
+ │ └─────────────────────────────────┘ │
208
+ └─────────────────────────────────────────┘
209
+ ```
210
+
211
+ All concurrency uses fibers (via async tasks) instead of threads, resulting in:
212
+ - Lower memory footprint
213
+ - Better scalability
214
+ - Faster context switching
215
+ - More efficient resource usage
216
+
217
+ ## Relationship with nats-pure
218
+
219
+ async-nats is built **on top of** [nats-pure](https://github.com/nats-io/nats-pure.rb), not as a fork. It:
220
+
221
+ 1. **Inherits from** `NATS::IO::Client` to maintain API compatibility
222
+ 2. **Overrides** I/O and threading methods to use async primitives
223
+ 3. **Reuses** protocol handling, message parsing, and business logic
224
+ 4. **Extends** with async-specific features like async drain
225
+
226
+ This approach provides:
227
+ - ✅ Full API compatibility with nats-pure
228
+ - ✅ Automatic updates when nats-pure fixes bugs
229
+ - ✅ Minimal code duplication
230
+ - ✅ Easy migration path
231
+
232
+ ### Migration from nats-pure
233
+
234
+ Migration is straightforward:
235
+
236
+ ```ruby
237
+ # Before (nats-pure)
238
+ require 'nats/io/client'
239
+
240
+ nats = NATS::IO::Client.new
241
+ nats.connect
242
+ # ... use nats ...
243
+ nats.close
244
+
245
+ # After (async-nats)
246
+ require 'async'
247
+ require 'async/nats'
248
+
249
+ Async do
250
+ nats = Async::NATS::Client.new
251
+ nats.connect
252
+ # ... use nats (same API) ...
253
+ nats.close
254
+ end
255
+ ```
256
+
257
+ The main difference is wrapping your code in an `Async` block to provide the async context.
258
+
259
+ ## Development
260
+
261
+ This project was developed entirely by the **Windsurf AI code agent** through an iterative process:
262
+
263
+ 1. **Initial Port** - Created async socket wrapper and client override
264
+ 2. **Benchmarking** - Ported benchmarks from nats-pure and compared performance
265
+ 3. **Drain Fix** - Implemented context detection for mixed async/thread usage
266
+ 4. **Async Executor** - Replaced ThreadPoolExecutor with async worker pool
267
+ 5. **Async Drain** - Implemented event-driven drain using Async::Barrier
268
+ 6. **Performance Optimization** - Cached task references to eliminate overhead
269
+
270
+ ### Running Tests
271
+
272
+ ```bash
273
+ # Run RSpec tests
274
+ bundle exec rspec
275
+
276
+ # Run benchmarks
277
+ docker run -d --name nats-bench -p 4222:4222 nats:latest
278
+ bundle exec ruby benchmark/pub_perf.rb -n 100000
279
+ bundle exec ruby benchmark/pub_sub_simple.rb
280
+ docker stop nats-bench && docker rm nats-bench
281
+ ```
282
+
283
+ ### Project Structure
284
+
285
+ ```
286
+ lib/async/nats/
287
+ ├── client.rb # Main client with async overrides
288
+ ├── socket.rb # Async I/O wrapper
289
+ ├── executor.rb # Async worker pool for subscriptions
290
+ └── version.rb # Version information
291
+
292
+ benchmark/
293
+ ├── pub_perf.rb # Publish performance test
294
+ ├── pub_sub_perf.rb # Pub/Sub with drain test
295
+ └── pub_sub_simple.rb # Simple pub/sub test
296
+
297
+ BENCHMARK_SUMMARY.md # Performance summary
298
+ BENCHMARK_RESULTS.md # Detailed benchmark results
299
+ ```
300
+
301
+ ## Benchmarks
302
+
303
+ Comprehensive benchmarks are available:
304
+
305
+ - **[BENCHMARK_SUMMARY.md](BENCHMARK_SUMMARY.md)** - Quick overview of performance results
306
+ - **[BENCHMARK_RESULTS.md](BENCHMARK_RESULTS.md)** - Detailed analysis with tables and comparisons
307
+
308
+ To run benchmarks yourself:
309
+
310
+ ```bash
311
+ # Start NATS server
312
+ docker run -d --name nats-bench -p 4222:4222 nats:latest
313
+
314
+ # Run publish benchmark
315
+ bundle exec ruby benchmark/pub_perf.rb -n 100000
316
+
317
+ # Run pub/sub benchmark
318
+ bundle exec ruby benchmark/pub_sub_simple.rb
319
+
320
+ # Cleanup
321
+ docker stop nats-bench && docker rm nats-bench
322
+ ```
323
+
324
+ ## Limitations
325
+
326
+ - **Experimental** - This is an AI-generated project and should be thoroughly tested before production use
327
+ - **Requires Async Context** - Must be used within an `Async` block
328
+ - **Ruby 3.0+** - Requires modern Ruby with fiber support
329
+ - **Limited Testing** - While benchmarked, it lacks comprehensive test coverage
330
+
331
+ ## Contributing
332
+
333
+ Bug reports and pull requests are welcome on GitHub. This project is intended to demonstrate the capabilities of AI-assisted development and the async framework.
334
+
335
+ ## License
336
+
337
+ The gem is available as open source under the terms of the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
338
+
339
+ ## Credits
340
+
341
+ - **Development**: Entirely created by the Windsurf AI code agent
342
+ - **Based on**: [nats-pure](https://github.com/nats-io/nats-pure.rb) by the NATS.io team
343
+ - **Async Framework**: [async](https://github.com/socketry/async) by Samuel Williams
344
+ - **NATS**: [NATS.io](https://nats.io/) messaging system
345
+
346
+ ## Disclaimer
347
+
348
+ This project was developed completely autonomously by an AI code agent as an experiment in AI-assisted software development. While functional and benchmarked, it has not undergone the same level of scrutiny as human-developed production software. Use at your own risk and conduct thorough testing for your specific use case.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2016-2018 The NATS Authors
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "bundler/setup"
18
+ require "optparse"
19
+ require "async"
20
+
21
+ $:.unshift File.expand_path("../../lib", __FILE__)
22
+ require "async/nats"
23
+
24
+ $count = 100000
25
+ $batch = 100
26
+
27
+ $delay = 0.00001
28
+ $dmin = 0.00001
29
+
30
+ TRIP = (2 * 1024 * 1024)
31
+ TSIZE = 4 * 1024
32
+
33
+ $sub = "test"
34
+ $data_size = 16
35
+
36
+ $hash = 2500
37
+
38
+ $stdout.sync = true
39
+
40
+ parser = OptionParser.new do |opts|
41
+ opts.banner = "Usage: pub_perf [options]"
42
+
43
+ opts.separator ""
44
+ opts.separator "options:"
45
+
46
+ opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i }
47
+ opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i }
48
+ opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub }
49
+ opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i }
50
+ end
51
+
52
+ parser.parse(ARGV)
53
+
54
+ trap("TERM") { exit! }
55
+ trap("INT") { exit! }
56
+
57
+ $data = Array.new($data_size) { "%01x" % rand(16) }.join("").freeze
58
+
59
+ Async do
60
+ nats = Async::NATS::Client.new
61
+ nats.connect
62
+
63
+ $batch = 10 if $data_size >= TSIZE
64
+ $start = Time.now
65
+ $to_send = $count
66
+
67
+ puts "Sending #{$count} messages of size #{$data.size} bytes on [#{$sub}]"
68
+
69
+ loop do
70
+ (0..$batch).each do
71
+ $to_send -= 1
72
+ nats.publish($sub, $data)
73
+ if $to_send == 0
74
+ nats.flush
75
+
76
+ elapsed = Time.now - $start
77
+ mbytes = sprintf("%.1f", (($data_size * $count) / elapsed) / (1024 * 1024))
78
+ puts "\nTest completed : #{($count / elapsed).ceil} sent/received msgs/sec (#{mbytes} MB/sec)\n"
79
+ exit
80
+ end
81
+ Async::Task.current.sleep($delay) if $to_send.modulo(1000) == 0
82
+ printf("#") if $to_send.modulo($hash) == 0
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2016-2018 The NATS Authors
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "bundler/setup"
18
+ require "optparse"
19
+ require "async"
20
+
21
+ $:.unshift File.expand_path("../../lib", __FILE__)
22
+ require "async/nats"
23
+
24
+ $count = 100000
25
+ $batch = 100
26
+
27
+ $subscriptions = 1
28
+ $concurrency = 1
29
+
30
+ $delay = 0.00001
31
+
32
+ TSIZE = 4 * 1024
33
+
34
+ $sub = "test"
35
+ $data_size = 16
36
+
37
+ $stdout.sync = true
38
+
39
+ parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: pub_perf [options]"
41
+
42
+ opts.separator ""
43
+ opts.separator "options:"
44
+
45
+ opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i }
46
+ opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i }
47
+ opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub }
48
+ opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i }
49
+ opts.on("-c SUBSCRIPTIONS", "Subscription number (default: (#{$subscriptions})") { |subscriptions| $subscriptions = subscriptions.to_i }
50
+ opts.on("-t CONCURRENCY", "Subscription processing concurrency (default: (#{$concurrency})") { |concurrency| $concurrency = concurrency.to_i }
51
+ end
52
+
53
+ parser.parse(ARGV)
54
+
55
+ $data = Array.new($data_size) { "%01x" % rand(16) }.join("").freeze
56
+
57
+ puts "Sending #{$count} messages of size #{$data.size} bytes on [#{$sub}], receiving each in #{$subscriptions} subscriptions"
58
+
59
+ def results
60
+ elapsed = Time.now - $start
61
+ mbytes = sprintf("%.1f", (($data_size * $received) / elapsed) / (1024 * 1024))
62
+ <<~MSG
63
+
64
+ Test completed: #{($received / elapsed).ceil} received msgs/sec (#{mbytes} MB/sec)
65
+ Received #{$received} messages in #{elapsed} seconds
66
+ MSG
67
+ end
68
+
69
+ trap("TERM") {
70
+ puts results
71
+ exit!
72
+ }
73
+ trap("INT") {
74
+ puts results
75
+ exit!
76
+ }
77
+
78
+ Async do
79
+ nats = Async::NATS::Client.new
80
+ nats.connect
81
+
82
+ $batch = 10 if $data_size >= TSIZE
83
+
84
+ $received = 0
85
+
86
+ $subscriptions.times do
87
+ subscription = nats.subscribe($sub) {
88
+ $received += 1
89
+ printf("#") if $received.modulo(2500) == 0
90
+ }
91
+ subscription.processing_concurrency = $concurrency
92
+ end
93
+
94
+ $start = Time.now
95
+ $to_send = $count
96
+
97
+ loop do
98
+ (0..$batch).each do
99
+ $to_send -= 1
100
+ nats.publish($sub, $data)
101
+
102
+ break if $to_send.zero?
103
+
104
+ Async::Task.current.sleep($delay) if $to_send.modulo(1000) == 0
105
+ end
106
+ break if $to_send.zero?
107
+ end
108
+
109
+ # Finish and let client to process everything
110
+ nats.flush(300)
111
+ nats.drain
112
+
113
+ # Wait a bit for drain to complete
114
+ Async::Task.current.sleep(1)
115
+
116
+ puts results
117
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Simple pub/sub benchmark without drain
5
+
6
+ require "bundler/setup"
7
+ require "async"
8
+ require "monitor"
9
+
10
+ $:.unshift File.expand_path("../../lib", __FILE__)
11
+ require "async/nats"
12
+
13
+ COUNT = 10000
14
+
15
+ puts "Simple pub/sub benchmark: #{COUNT} messages"
16
+
17
+ Async do
18
+ nats = Async::NATS::Client.new
19
+ nats.connect(servers: ["nats://127.0.0.1:4222"])
20
+
21
+ # Thread-safe counters
22
+ received = 0
23
+ start_time = nil
24
+ lock = Monitor.new
25
+
26
+ # Subscribe
27
+ nats.subscribe("bench.test") do |msg|
28
+ lock.synchronize do
29
+ received += 1
30
+ start_time ||= Time.now
31
+
32
+ print "#" if received % 2500 == 0
33
+ end
34
+ end
35
+
36
+ # Give subscription time to register
37
+ Async::Task.current.sleep(0.1)
38
+
39
+ # Publish messages
40
+ puts "Publishing #{COUNT} messages..."
41
+ COUNT.times do |i|
42
+ nats.publish("bench.test", "test message #{i}")
43
+ end
44
+
45
+ nats.flush(5.0)
46
+
47
+ # Wait for messages to be received
48
+ puts "\nWaiting for messages..."
49
+ 10.times do
50
+ Async::Task.current.sleep(0.5)
51
+ count = lock.synchronize { received }
52
+ if count == COUNT
53
+ break
54
+ end
55
+ end
56
+
57
+ # Calculate results
58
+ final_count = lock.synchronize { received }
59
+ final_start = lock.synchronize { start_time }
60
+
61
+ if final_count == COUNT && final_start
62
+ elapsed = Time.now - final_start
63
+ msgs_per_sec = (COUNT / elapsed).to_i
64
+ mb_per_sec = ((16 * COUNT) / elapsed / (1024 * 1024)).round(1)
65
+
66
+ puts "\nTest completed: #{msgs_per_sec} msgs/sec (#{mb_per_sec} MB/sec)"
67
+ puts "Received #{COUNT} messages in #{elapsed.round(3)} seconds"
68
+ else
69
+ puts "\nTimeout or incomplete"
70
+ puts "Received #{final_count}/#{COUNT} messages"
71
+ end
72
+
73
+ nats.close
74
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2016-2018 The NATS Authors
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "bundler/setup"
18
+ require "optparse"
19
+ require "async"
20
+
21
+ $:.unshift File.expand_path("../../lib", __FILE__)
22
+ require "async/nats"
23
+
24
+ $expected = 100000
25
+ $hash = 2500
26
+ $sub = "test"
27
+
28
+ $stdout.sync = true
29
+
30
+ parser = OptionParser.new do |opts|
31
+ opts.banner = "Usage: sub_perf [options]"
32
+
33
+ opts.separator ""
34
+ opts.separator "options:"
35
+
36
+ opts.on("-n COUNT", "Messages to expect (default: #{$expected})") { |count| $expected = count.to_i }
37
+ opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |sub| $sub = sub }
38
+ end
39
+
40
+ parser.parse(ARGV)
41
+
42
+ trap("TERM") { exit! }
43
+ trap("INT") { exit! }
44
+
45
+ Async do
46
+ nats = Async::NATS::Client.new
47
+
48
+ nats.connect
49
+ done = nats.new_cond
50
+ received = 1
51
+ nats.subscribe($sub) do
52
+ ($start = Time.now and puts "Started Receiving!") if received == 1
53
+ if (received += 1) == $expected
54
+ puts "\nTest completed : #{($expected / (Time.now - $start)).ceil} msgs/sec.\n"
55
+ nats.synchronize do
56
+ done.signal
57
+ end
58
+ end
59
+ printf("+") if received.modulo($hash) == 0
60
+ end
61
+
62
+ puts "Waiting for #{$expected} messages on [#{$sub}]"
63
+ nats.synchronize do
64
+ done.wait
65
+ end
66
+ end