From abe484c588d2cfd7fbb118e7a0a5fd14c18efa20 Mon Sep 17 00:00:00 2001 From: Vladimir Dementyev Date: Wed, 8 Jan 2025 12:53:43 -0800 Subject: [PATCH 1/2] * enforce rubocop --- .github/workflows/rubocop.yml | 9 +- .rubocop.yml | 2 + README.md | 2 +- Rakefile | 3 +- benchmark/latency_perf.rb | 30 +- benchmark/nuid_perf.rb | 10 +- benchmark/pub_perf.rb | 42 +- benchmark/pub_sub_perf.rb | 51 +- benchmark/sub_perf.rb | 24 +- examples/basic-tls.rb | 16 +- examples/basic-usage.rb | 16 +- examples/basic.rb | 6 +- examples/clustered.rb | 9 +- lib/nats.rb | 14 +- lib/nats/client.rb | 8 +- lib/nats/io/client.rb | 529 ++++++++++----------- lib/nats/io/errors.rb | 2 + lib/nats/io/jetstream.rb | 209 ++++---- lib/nats/io/jetstream/api.rb | 101 ++-- lib/nats/io/jetstream/errors.rb | 21 +- lib/nats/io/jetstream/js.rb | 18 +- lib/nats/io/jetstream/js/config.rb | 12 +- lib/nats/io/jetstream/js/header.rb | 24 +- lib/nats/io/jetstream/js/status.rb | 16 +- lib/nats/io/jetstream/js/sub.rb | 4 +- lib/nats/io/jetstream/manager.rb | 200 ++++---- lib/nats/io/jetstream/msg.rb | 10 +- lib/nats/io/jetstream/msg/ack.rb | 24 +- lib/nats/io/jetstream/msg/ack_methods.rb | 43 +- lib/nats/io/jetstream/msg/metadata.rb | 16 +- lib/nats/io/jetstream/pull_subscription.rb | 21 +- lib/nats/io/jetstream/push_subscription.rb | 4 +- lib/nats/io/kv.rb | 44 +- lib/nats/io/kv/api.rb | 9 +- lib/nats/io/kv/bucket_status.rb | 2 + lib/nats/io/kv/errors.rb | 7 +- lib/nats/io/kv/manager.rb | 16 +- lib/nats/io/msg.rb | 32 +- lib/nats/io/parser.rb | 46 +- lib/nats/io/rails.rb | 2 + lib/nats/io/subscription.rb | 43 +- lib/nats/io/version.rb | 4 +- lib/nats/io/websocket.rb | 18 +- lib/nats/nuid.rb | 55 ++- nats-pure.gemspec | 18 +- spec/auth_spec.rb | 67 +-- spec/client_cluster_reconnect_spec.rb | 120 ++--- spec/client_cluster_tls_reconnect_spec.rb | 119 +++-- spec/client_delayed_connection_spec.rb | 23 +- spec/client_drain_spec.rb | 29 +- spec/client_errors_spec.rb | 138 +++--- spec/client_fork_spec.rb | 64 +-- spec/client_nkeys_connect_spec.rb | 94 ++-- spec/client_reconnect_spec.rb | 109 +++-- spec/client_spec.rb | 136 +++--- spec/client_threadsafe_spec.rb | 104 ++-- spec/client_tls_spec.rb | 162 +++---- spec/client_v2_spec.rb | 205 ++++---- spec/client_ws_spec.rb | 62 +-- spec/integration/rails_spec.rb | 29 +- spec/js_features_spec.rb | 148 +++--- spec/js_spec.rb | 387 +++++++-------- spec/kv_spec.rb | 190 ++++---- spec/nuid_spec.rb | 30 +- spec/spec_helper.rb | 7 +- spec/support/dns.rb | 12 +- spec/support/nats_server_helper.rb | 41 +- spec/support/utils.rb | 4 +- 68 files changed, 1965 insertions(+), 2107 deletions(-) diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 405d52f..4993434 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -2,11 +2,10 @@ name: Lint Ruby on: workflow_dispatch: - # TODO: Autocorrect first - # push: - # branches: - # - main - # pull_request: + push: + branches: + - main + pull_request: jobs: rubocop: diff --git a/.rubocop.yml b/.rubocop.yml index eb283d6..2be2b32 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,6 +17,8 @@ AllCops: - 'lib/.rbnext/**/*' - 'lib/generators/**/templates/*.rb' - '.github/**/*' + - 'examples/**/*' + - 'benchmark/**/*' DisplayCopNames: true SuggestExtensions: false NewCops: disable diff --git a/README.md b/README.md index d3830ba..b6a34df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Gem Version](https://badge.fury.io/rb/nats-pure.svg)](https://rubygems.org/gems/nats-pure) [![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) -[![Build](https://github.com/nats-io/nats-pure/workflows/Build/badge.svg)](https://github.com/nats-io/nats-pure/actions) +[![Build](https://github.com/nats-io/nats-pure.rb/workflows/Build/badge.svg)](https://github.com/nats-io/nats-pure.rb/actions) [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://docs.nats.io/) # NATS: Pure Ruby Client diff --git a/Rakefile b/Rakefile index 6781b46..f63e43f 100644 --- a/Rakefile +++ b/Rakefile @@ -12,5 +12,4 @@ rescue LoadError task(:rubocop) {} end -# TODO: Add rubocop as soon as we fix the style issues -task default: %w[spec] +task default: %w[rubocop spec] diff --git a/benchmark/latency_perf.rb b/benchmark/latency_perf.rb index f3a5a24..31f13e3 100644 --- a/benchmark/latency_perf.rb +++ b/benchmark/latency_perf.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,14 +14,14 @@ # limitations under the License. # -require 'optparse' +require "optparse" -$LOAD_PATH << File.expand_path('../../lib', __FILE__) -require 'nats/io/client' +$LOAD_PATH << File.expand_path("../../lib", __FILE__) +require "nats/io/client" $loop = 10000 $hash = 250 -$sub = 'test' +$sub = "test" $subscriptions = 1 $concurrency = 1 @@ -33,7 +35,7 @@ opts.separator "" opts.separator "options:" - opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |sub| $sub = sub } + opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |sub| $sub = sub } opts.on("-n ITERATIONS", "iterations to expect (default: #{$loop})") { |iter| $loop = iter.to_i } opts.on("-c SUBSCRIPTIONS", "Subscription number (default: (#{$subscriptions})") { |subscriptions| $subscriptions = subscriptions.to_i } opts.on("-t CONCURRENCY", "Subscription processing concurrency (default: (#{$concurrency})") { |concurrency| $concurrency = concurrency.to_i } @@ -44,7 +46,7 @@ $drain = $loop trap("TERM") { exit! } -trap("INT") { exit! } +trap("INT") { exit! } nats = NATS::IO::Client.new @@ -61,19 +63,19 @@ puts "nats: reconnected!" end -nats.connect(:max_reconnect => 10) +nats.connect(max_reconnect: 10) if $queue $subscriptions.times do sub = nats.subscribe($sub, queue: $queue) do |msg| - msg.respond("OK:"+msg.data) + msg.respond("OK:" + msg.data) end sub.processing_concurrency = $concurrency end else $subscriptions.times do sub = nats.subscribe($sub) do |msg| - msg.respond("OKOK:"+msg.data) + msg.respond("OKOK:" + msg.data) end sub.processing_concurrency = $concurrency end @@ -87,17 +89,17 @@ loop do begin nats.request($sub, "AAA-#{$drain}", timeout: 2) - rescue NATS::IO::Timeout => e + rescue NATS::IO::Timeout timeouts += 1 end - $drain-=1 + $drain -= 1 if $drain == 0 - ms = "%.2f" % (((Time.now-$start)/$loop)*1000.0) + ms = "%.2f" % (((Time.now - $start) / $loop) * 1000.0) puts "\nTest completed : #{ms} ms avg request/response latency\n" puts "Timeouts: #{timeouts}" if timeouts > 0 exit! - else - printf('#') if $drain.modulo($hash) == 0 + elsif $drain.modulo($hash) == 0 + printf("#") end end diff --git a/benchmark/nuid_perf.rb b/benchmark/nuid_perf.rb index 1482ffb..47e7a15 100644 --- a/benchmark/nuid_perf.rb +++ b/benchmark/nuid_perf.rb @@ -1,6 +1,8 @@ -require 'securerandom' -require 'nats/nuid' -require 'benchmark/ips' +# frozen_string_literal: true + +require "securerandom" +require "nats/nuid" +require "benchmark/ips" Benchmark.ips do |x| x.report "NUID based inboxes with locked instance" do |t| @@ -15,6 +17,6 @@ x.report "SecureRandom based inboxes" do |t| t.times { "_INBOX.#{::SecureRandom.hex(11)}" } end - + x.compare! end diff --git a/benchmark/pub_perf.rb b/benchmark/pub_perf.rb index 49aaf91..c6366d2 100644 --- a/benchmark/pub_perf.rb +++ b/benchmark/pub_perf.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,24 +14,24 @@ # limitations under the License. # -require 'optparse' +require "optparse" -$:.unshift File.expand_path('../../lib', __FILE__) -require 'nats/io/client' +$:.unshift File.expand_path("../../lib", __FILE__) +require "nats/io/client" $count = 100000 $batch = 100 $delay = 0.00001 -$dmin = 0.00001 +$dmin = 0.00001 -TRIP = (2*1024*1024) -TSIZE = 4*1024 +TRIP = (2 * 1024 * 1024) +TSIZE = 4 * 1024 -$sub = 'test' +$sub = "test" $data_size = 16 -$hash = 2500 +$hash = 2500 $stdout.sync = true @@ -39,24 +41,24 @@ opts.separator "" opts.separator "options:" - opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i } - opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i } - opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub } - opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i } + opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i } + opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i } + opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub } + opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i } end parser.parse(ARGV) trap("TERM") { exit! } -trap("INT") { exit! } +trap("INT") { exit! } -$data = Array.new($data_size) { "%01x" % rand(16) }.join('').freeze +$data = Array.new($data_size) { "%01x" % rand(16) }.join("").freeze nats = NATS::IO::Client.new nats.connect $batch = 10 if $data_size >= TSIZE -$start = Time.now +$start = Time.now $to_send = $count puts "Sending #{$count} messages of size #{$data.size} bytes on [#{$sub}]" @@ -64,18 +66,16 @@ loop do (0..$batch).each do $to_send -= 1 + nats.publish($sub, $data) if $to_send == 0 - nats.publish($sub, $data) nats.flush elapsed = Time.now - $start - mbytes = sprintf("%.1f", (($data_size*$count)/elapsed)/(1024*1024)) - puts "\nTest completed : #{($count/elapsed).ceil} sent/received msgs/sec (#{mbytes} MB/sec)\n" + mbytes = sprintf("%.1f", (($data_size * $count) / elapsed) / (1024 * 1024)) + puts "\nTest completed : #{($count / elapsed).ceil} sent/received msgs/sec (#{mbytes} MB/sec)\n" exit - else - nats.publish($sub, $data) end sleep $delay if $to_send.modulo(1000) == 0 - printf('#') if $to_send.modulo($hash) == 0 + printf("#") if $to_send.modulo($hash) == 0 end end diff --git a/benchmark/pub_sub_perf.rb b/benchmark/pub_sub_perf.rb index 55e113c..d8d38ae 100644 --- a/benchmark/pub_sub_perf.rb +++ b/benchmark/pub_sub_perf.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,12 +14,12 @@ # limitations under the License. # -require 'optparse' -require 'concurrent' -require 'ruby-progressbar' +require "optparse" +require "concurrent" +require "ruby-progressbar" -$:.unshift File.expand_path('../../lib', __FILE__) -require 'nats/io/client' +$:.unshift File.expand_path("../../lib", __FILE__) +require "nats/io/client" $count = 100000 $batch = 100 @@ -27,9 +29,9 @@ $delay = 0.00001 -TSIZE = 4*1024 +TSIZE = 4 * 1024 -$sub = 'test' +$sub = "test" $data_size = 16 $stdout.sync = true @@ -40,34 +42,40 @@ opts.separator "" opts.separator "options:" - opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i } - opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i } - opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub } - opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i } + opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i } + opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i } + opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub } + opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i } opts.on("-c SUBSCRIPTIONS", "Subscription number (default: (#{$subscriptions})") { |subscriptions| $subscriptions = subscriptions.to_i } opts.on("-t CONCURRENCY", "Subscription processing concurrency (default: (#{$concurrency})") { |concurrency| $concurrency = concurrency.to_i } end parser.parse(ARGV) -$data = Array.new($data_size) { "%01x" % rand(16) }.join('').freeze +$data = Array.new($data_size) { "%01x" % rand(16) }.join("").freeze puts "Sending #{$count} messages of size #{$data.size} bytes on [#{$sub}], receiving each in #{$subscriptions} subscriptions" -$progressbar = ProgressBar.create(title: "Received", total: $count*$subscriptions, format: '%t: |%B| %p%% %a %e', autofinish: false, throttle_rate: 0.1) +$progressbar = ProgressBar.create(title: "Received", total: $count * $subscriptions, format: "%t: |%B| %p%% %a %e", autofinish: false, throttle_rate: 0.1) def results - elapsed = Time.now - $start - mbytes = sprintf("%.1f", (($data_size*$received)/elapsed)/(1024*1024)) + elapsed = Time.now - $start + mbytes = sprintf("%.1f", (($data_size * $received) / elapsed) / (1024 * 1024)) <<~MSG - Test completed: #{($received/elapsed).ceil} received msgs/sec (#{mbytes} MB/sec) + Test completed: #{($received / elapsed).ceil} received msgs/sec (#{mbytes} MB/sec) Received #{$received} messages in #{elapsed} seconds MSG end -trap("TERM") { puts results; exit! } -trap("INT") { puts results; exit! } +trap("TERM") { + puts results + exit! +} +trap("INT") { + puts results + exit! +} nats = NATS::IO::Client.new nats.connect @@ -77,11 +85,14 @@ def results $received = 0 $subscriptions.times do - subscription = nats.subscribe($sub) { $received += 1; $progressbar.progress = $received } + subscription = nats.subscribe($sub) { + $received += 1 + $progressbar.progress = $received + } subscription.processing_concurrency = $concurrency end -$start = Time.now +$start = Time.now $to_send = $count loop do diff --git a/benchmark/sub_perf.rb b/benchmark/sub_perf.rb index ea3bea1..e7ae839 100644 --- a/benchmark/sub_perf.rb +++ b/benchmark/sub_perf.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,15 +14,15 @@ # limitations under the License. # -require 'optparse' -require 'monitor' +require "optparse" +require "monitor" -$:.unshift File.expand_path('../../lib', __FILE__) -require 'nats/io/client' +$:.unshift File.expand_path("../../lib", __FILE__) +require "nats/io/client" $expected = 100000 $hash = 2500 -$sub = 'test' +$sub = "test" $stdout.sync = true @@ -31,13 +33,13 @@ opts.separator "options:" opts.on("-n COUNT", "Messages to expect (default: #{$expected})") { |count| $expected = count.to_i } - opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |sub| $sub = sub } + opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |sub| $sub = sub } end parser.parse(ARGV) trap("TERM") { exit! } -trap("INT") { exit! } +trap("INT") { exit! } nats = NATS::IO::Client.new @@ -45,14 +47,14 @@ done = nats.new_cond received = 1 nats.subscribe($sub) do - ($start = Time.now and puts "Started Receiving!") if (received == 1) - if ((received += 1) == $expected) - puts "\nTest completed : #{($expected/(Time.now-$start)).ceil} msgs/sec.\n" + ($start = Time.now and puts "Started Receiving!") if received == 1 + if (received += 1) == $expected + puts "\nTest completed : #{($expected / (Time.now - $start)).ceil} msgs/sec.\n" nats.synchronize do done.signal end end - printf('+') if received.modulo($hash) == 0 + printf("+") if received.modulo($hash) == 0 end puts "Waiting for #{$expected} messages on [#{$sub}]" diff --git a/examples/basic-tls.rb b/examples/basic-tls.rb index 03d136e..a3e9530 100644 --- a/examples/basic-tls.rb +++ b/examples/basic-tls.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +14,8 @@ # limitations under the License. # -require 'nats/io/client' -require 'openssl' +require "nats/io/client" +require "openssl" nats = NATS::IO::Client.new @@ -33,20 +35,20 @@ # Set the RootCAs tls_context.cert_store = OpenSSL::X509::Store.new ca_file = File.read("./spec/configs/certs/nats-service.localhost/ca.pem") -tls_context.cert_store.add_cert(OpenSSL::X509::Certificate.new ca_file) +tls_context.cert_store.add_cert(OpenSSL::X509::Certificate.new(ca_file)) # The server is setup to use a wildcard certificate for: -# +# # *.clients.nats-service.localhost # # so given the options above, having a wrong domain would # make the client connection fail with an error -# +# # Error: SSL_connect returned=1 errno=0 state=error: certificate verify failed (error number 1) -# +# server = "nats://server-A.clients.nats-service.localhost:4222" -nats.connect(servers: [server], tls: { context: tls_context }) +nats.connect(servers: [server], tls: {context: tls_context}) puts "Connected to #{nats.connected_server}" nats.subscribe(">") do |msg, reply, subject| diff --git a/examples/basic-usage.rb b/examples/basic-usage.rb index 0e55c6e..4cd7f86 100644 --- a/examples/basic-usage.rb +++ b/examples/basic-usage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +14,7 @@ # limitations under the License. # -require 'nats/io/client' +require "nats/io/client" nats = NATS::IO::Client.new @@ -23,31 +25,31 @@ nats.subscribe("foo.>") { |msg, reply, subject| puts "[Received] on '#{subject}': '#{msg}'" } # Simple Publisher -nats.publish('foo.bar.baz', 'Hello World!') +nats.publish("foo.bar.baz", "Hello World!") # Unsubscribing -sub = nats.subscribe('bar') { |msg| puts "Received : '#{msg}'" } +sub = nats.subscribe("bar") { |msg| puts "Received : '#{msg}'" } sub.unsubscribe(2) # Subscribers which reply to requests -nats.subscribe('help') do |msg, reply, subject| +nats.subscribe("help") do |msg, reply, subject| puts "[Received] on '#{subject}' #{reply}: '#{msg}'" nats.publish(reply, "I'll help!") if reply end -nats.subscribe('>') do |msg, reply, subject| +nats.subscribe(">") do |msg, reply, subject| puts "[Received] via wildcard on '#{subject}' #{reply}: '#{msg}'" nats.publish(reply, "Hi") if reply end # Requests happens asynchronously if given a callback -nats.request('help', 'world', max: 2) do |response| +nats.request("help", "world", max: 2) do |response| puts "[Response] to '#{response.subject}': #{response.data}'" end # Request without a callback waits for the response or times out. begin - msg = nats.request('help', 'please', timeout: 1.0) + msg = nats.request("help", "please", timeout: 1.0) puts "[Response] '#{msg.subject}': #{msg.data}" rescue NATS::IO::Timeout puts "nats: request timed out" diff --git a/examples/basic.rb b/examples/basic.rb index 4bb0167..ff1218a 100644 --- a/examples/basic.rb +++ b/examples/basic.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,11 +14,11 @@ # limitations under the License. # -require 'nats/io/client' +require "nats/io/client" nats = NATS::IO::Client.new -nats.connect(:servers => ["nats://127.0.0.1:4222"]) +nats.connect(servers: ["nats://127.0.0.1:4222"]) puts "Connected to #{nats.connected_server}" nats.subscribe(">") do |msg, reply, subject| diff --git a/examples/clustered.rb b/examples/clustered.rb index 413cd05..737f840 100644 --- a/examples/clustered.rb +++ b/examples/clustered.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +14,7 @@ # limitations under the License. # -require 'nats/io/client' +require "nats/io/client" $stdout.sync = true nats = NATS::IO::Client.new @@ -55,7 +57,10 @@ bytes_sent = 0 bytes_received = 0 -nats.subscribe("hello") {|msg| msgs_received += 1; bytes_received += msg.data.size } +nats.subscribe("hello") { |msg| + msgs_received += 1 + bytes_received += msg.data.size +} Thread.new do loop do diff --git a/lib/nats.rb b/lib/nats.rb index 349e97d..56646ae 100644 --- a/lib/nats.rb +++ b/lib/nats.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +14,8 @@ # limitations under the License. # -require 'nats/io/client' -require 'nats/nuid' +require "nats/io/client" +require "nats/nuid" # A thread safe Ruby client for the NATS messaging system (https://nats.io). # @@ -25,15 +27,15 @@ # # resp = nc.request("foo") # puts "Received: #{msg.data}" -# -# +# +# # @example Stream example # nc = NATS.connect("demo.nats.io") # sub = nc.subscribe("foo") -# +# # nc.publish("foo") # msg = sub.next_msg # puts "Received: #{msg.data}" -# +# module NATS end diff --git a/lib/nats/client.rb b/lib/nats/client.rb index 3bf12b0..fc87a8b 100644 --- a/lib/nats/client.rb +++ b/lib/nats/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,6 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -require 'nats/io/client' -require 'nats/io/rails' if defined?(Rails::Engine) -require 'nats/nuid' +require "nats/io/client" +require "nats/io/rails" if defined?(Rails::Engine) +require "nats/nuid" diff --git a/lib/nats/io/client.rb b/lib/nats/io/client.rb index bcafde4..2b640a7 100644 --- a/lib/nats/io/client.rb +++ b/lib/nats/io/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,21 +14,20 @@ # limitations under the License. # -require_relative 'parser' -require_relative 'version' -require_relative 'errors' -require_relative 'msg' -require_relative 'subscription' -require_relative 'jetstream' - -require 'nats/nuid' -require 'thread' -require 'socket' -require 'json' -require 'monitor' -require 'uri' -require 'securerandom' -require 'concurrent' +require_relative "parser" +require_relative "version" +require_relative "errors" +require_relative "msg" +require_relative "subscription" +require_relative "jetstream" + +require "nats/nuid" +require "socket" +require "json" +require "monitor" +require "uri" +require "securerandom" +require "concurrent" begin require "openssl" @@ -46,7 +47,7 @@ class << self # nc.publish("hello", "world") # nc.close # - def connect(uri=nil, opts={}) + def connect(uri = nil, opts = {}) nc = NATS::Client.new nc.connect(uri, opts) @@ -100,25 +101,25 @@ class Client include MonitorMixin include Status - attr_reader :status, :server_info, :server_pool, :options, :connected_server, :stats, :uri, :subscription_executor, :reloader + attr_reader :status, :server_info, :server_pool, :options, :stats, :uri, :subscription_executor, :reloader - DEFAULT_PORT = { nats: 4222, ws: 80, wss: 443 }.freeze - DEFAULT_URI = ("nats://localhost:#{DEFAULT_PORT[:nats]}".freeze) + DEFAULT_PORT = {nats: 4222, ws: 80, wss: 443}.freeze + DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT[:nats]}".freeze - CR_LF = ("\r\n".freeze) - CR_LF_SIZE = (CR_LF.bytesize) + CR_LF = "\r\n" + CR_LF_SIZE = CR_LF.bytesize - PING_REQUEST = ("PING#{CR_LF}".freeze) - PONG_RESPONSE = ("PONG#{CR_LF}".freeze) + PING_REQUEST = "PING#{CR_LF}".freeze + PONG_RESPONSE = "PONG#{CR_LF}".freeze - NATS_HDR_LINE = ("NATS/1.0#{CR_LF}".freeze) + NATS_HDR_LINE = "NATS/1.0#{CR_LF}".freeze STATUS_MSG_LEN = 3 - STATUS_HDR = ("Status".freeze) - DESC_HDR = ("Description".freeze) - NATS_HDR_LINE_SIZE = (NATS_HDR_LINE.bytesize) + STATUS_HDR = "Status" + DESC_HDR = "Description" + NATS_HDR_LINE_SIZE = NATS_HDR_LINE.bytesize - SUB_OP = ('SUB'.freeze) - EMPTY_MSG = (''.freeze) + SUB_OP = "SUB" + EMPTY_MSG = "" INSTANCES = ObjectSpace::WeakMap.new # tracks all alive client instances private_constant :INSTANCES @@ -172,7 +173,7 @@ def initialize(uri = nil, opts = {}) @ping_interval_thread = nil # Info that we get from the server - @server_info = { } + @server_info = {} # URI from server to which we are currently connected @uri = nil @@ -181,7 +182,7 @@ def initialize(uri = nil, opts = {}) @status = nil # Subscriptions - @subs = { } + @subs = {} @ssid = 0 # Ping interval @@ -204,10 +205,10 @@ def initialize(uri = nil, opts = {}) @last_err = nil # Async callbacks, no ops by default. - @err_cb = proc { } - @close_cb = proc { } - @disconnect_cb = proc { } - @reconnect_cb = proc { } + @err_cb = proc {} + @close_cb = proc {} + @disconnect_cb = proc {} + @reconnect_cb = proc {} # Secure TLS options @tls = nil @@ -251,7 +252,7 @@ def initialize(uri = nil, opts = {}) end # Prepare connecting to NATS, but postpone real connection until first usage. - def connect(uri=nil, opts={}) + def connect(uri = nil, opts = {}) if uri || opts.any? @initial_uri = uri @initial_options = opts @@ -295,8 +296,8 @@ def connect(uri=nil, opts={}) when String # Initialize TLS defaults in case any url is using it. srvs = opts[:servers] = process_uri(uri) - if srvs.any? {|u| %w[tls wss].include? u.scheme } and !opts[:tls] - opts[:tls] = { context: tls_context } + if srvs.any? { |u| %w[tls wss].include? u.scheme } && !opts[:tls] + opts[:tls] = {context: tls_context} end @single_url_connect_used = true if srvs.size == 1 when Hash @@ -314,14 +315,14 @@ def connect(uri=nil, opts={}) opts[:max_outstanding_pings] = NATS::IO::DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil? # Override with ENV - opts[:verbose] = ENV['NATS_VERBOSE'].downcase == 'true' unless ENV['NATS_VERBOSE'].nil? - opts[:pedantic] = ENV['NATS_PEDANTIC'].downcase == 'true' unless ENV['NATS_PEDANTIC'].nil? - opts[:reconnect] = ENV['NATS_RECONNECT'].downcase == 'true' unless ENV['NATS_RECONNECT'].nil? - opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil? - opts[:ignore_discovered_urls] = ENV['NATS_IGNORE_DISCOVERED_URLS'].downcase == 'true' unless ENV['NATS_IGNORE_DISCOVERED_URLS'].nil? - opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil? - opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil? - opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil? + opts[:verbose] = ENV["NATS_VERBOSE"].downcase == "true" unless ENV["NATS_VERBOSE"].nil? + opts[:pedantic] = ENV["NATS_PEDANTIC"].downcase == "true" unless ENV["NATS_PEDANTIC"].nil? + opts[:reconnect] = ENV["NATS_RECONNECT"].downcase == "true" unless ENV["NATS_RECONNECT"].nil? + opts[:reconnect_time_wait] = ENV["NATS_RECONNECT_TIME_WAIT"].to_i unless ENV["NATS_RECONNECT_TIME_WAIT"].nil? + opts[:ignore_discovered_urls] = ENV["NATS_IGNORE_DISCOVERED_URLS"].downcase == "true" unless ENV["NATS_IGNORE_DISCOVERED_URLS"].nil? + opts[:max_reconnect_attempts] = ENV["NATS_MAX_RECONNECT_ATTEMPTS"].to_i unless ENV["NATS_MAX_RECONNECT_ATTEMPTS"].nil? + opts[:ping_interval] = ENV["NATS_PING_INTERVAL"].to_i unless ENV["NATS_PING_INTERVAL"].nil? + opts[:max_outstanding_pings] = ENV["NATS_MAX_OUTSTANDING_PINGS"].to_i unless ENV["NATS_MAX_OUTSTANDING_PINGS"].nil? opts[:connect_timeout] ||= NATS::IO::DEFAULT_CONNECT_TIMEOUT opts[:drain_timeout] ||= NATS::IO::DEFAULT_DRAIN_TIMEOUT @options = opts @@ -331,14 +332,14 @@ def connect(uri=nil, opts={}) uris.shuffle! unless @options[:dont_randomize_servers] uris.each do |u| nats_uri = case u - when URI - u.dup - else - URI.parse(u) - end + when URI + u.dup + else + URI.parse(u) + end @server_pool << { - :uri => nats_uri, - :hostname => nats_uri.hostname + uri: nats_uri, + hostname: nats_uri.hostname } end @@ -355,7 +356,7 @@ class << self; alias_method :request, :old_request; end @user_credentials ||= opts[:user_credentials] @nkeys_seed ||= opts[:nkeys_seed] - setup_nkeys_connect if @user_credentials or @nkeys_seed + setup_nkeys_connect if @user_credentials || @nkeys_seed # Tokens, if set will take preference over the user@server uri token @auth_token ||= opts[:auth_token] @@ -378,7 +379,7 @@ class << self; alias_method :request, :old_request; end srv = select_next_server # Use the hostname from the server for TLS hostname verification. - if client_using_secure_connection? and single_url_connect_used? + if client_using_secure_connection? && single_url_connect_used? # Always reuse the original hostname used to connect. @hostname ||= srv[:hostname] else @@ -451,8 +452,8 @@ class << self; alias_method :request, :old_request; end self end - def publish(subject, msg=EMPTY_MSG, opt_reply=nil, **options, &blk) - raise NATS::IO::BadSubject if !subject or subject.empty? + def publish(subject, msg = EMPTY_MSG, opt_reply = nil, **options, &blk) + raise NATS::IO::BadSubject if !subject || subject.empty? if options[:header] return publish_msg(NATS::Msg.new(subject: subject, data: msg, reply: opt_reply, header: options[:header])) end @@ -469,10 +470,10 @@ def publish(subject, msg=EMPTY_MSG, opt_reply=nil, **options, &blk) # Publishes a NATS::Msg that may include headers. def publish_msg(msg) raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg) - raise NATS::IO::BadSubject if !msg.subject or msg.subject.empty? + raise NATS::IO::BadSubject if !msg.subject || msg.subject.empty? - msg.reply ||= '' - msg.data ||= '' + msg.reply ||= "".dup + msg.data ||= "".dup msg_size = msg.data.bytesize # Accounting @@ -480,7 +481,7 @@ def publish_msg(msg) @stats[:out_bytes] += msg_size if msg.header - hdr = '' + hdr = "".dup hdr << NATS_HDR_LINE msg.header.each do |k, v| hdr << "#{k}: #{v}#{CR_LF}" @@ -498,7 +499,7 @@ def publish_msg(msg) # Create subscription which is dispatched asynchronously # messages to a callback. - def subscribe(subject, opts={}, &callback) + def subscribe(subject, opts = {}, &callback) raise NATS::IO::ConnectionDrainingError.new("nats: connection draining") if draining? sid = nil @@ -509,7 +510,7 @@ def subscribe(subject, opts={}, &callback) sub.nc = self sub.sid = sid end - opts[:pending_msgs_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT + opts[:pending_msgs_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT opts[:pending_bytes_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT sub.subject = subject @@ -517,7 +518,7 @@ def subscribe(subject, opts={}, &callback) sub.received = 0 sub.queue = opts[:queue] if opts[:queue] sub.max = opts[:max] if opts[:max] - sub.pending_msgs_limit = opts[:pending_msgs_limit] + sub.pending_msgs_limit = opts[:pending_msgs_limit] sub.pending_bytes_limit = opts[:pending_bytes_limit] sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit) sub.processing_concurrency = opts[:processing_concurrency] if opts.key?(:processing_concurrency) @@ -541,8 +542,8 @@ def subscribe(subject, opts={}, &callback) # It times out in case the request is not retrieved within the # specified deadline. # If given a callback, then the request happens asynchronously. - def request(subject, payload="", **opts, &blk) - raise NATS::IO::BadSubject if !subject or subject.empty? + def request(subject, payload = "", **opts, &blk) + raise NATS::IO::BadSubject if !subject || subject.empty? # If a block was given then fallback to method using auto unsubscribe. return old_request(subject, payload, opts, &blk) if blk @@ -573,7 +574,7 @@ def request(subject, payload="", **opts, &blk) # Publish request and wait for reply. publish(subject, payload, inbox) begin - MonotonicTime::with_nats_timeout(timeout) do + MonotonicTime.with_nats_timeout(timeout) do @resp_sub.synchronize do future.wait(timeout) end @@ -590,7 +591,7 @@ def request(subject, payload="", **opts, &blk) @resp_map.delete(token) end - if response and response.header + if response&.header status = response.header[STATUS_HDR] raise NATS::IO::NoRespondersError if status == "503" end @@ -601,7 +602,7 @@ def request(subject, payload="", **opts, &blk) # request_msg makes a NATS request using a NATS::Msg that may include headers. def request_msg(msg, **opts) raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg) - raise NATS::IO::BadSubject if !msg.subject or msg.subject.empty? + raise NATS::IO::BadSubject if !msg.subject || msg.subject.empty? token = nil inbox = nil @@ -621,13 +622,13 @@ def request_msg(msg, **opts) @resp_map[token][:future] = future end msg.reply = inbox - msg.data ||= '' - msg_size = msg.data.bytesize + msg.data ||= "" + msg.data.bytesize # Publish request and wait for reply. publish_msg(msg) begin - MonotonicTime::with_nats_timeout(timeout) do + MonotonicTime.with_nats_timeout(timeout) do @resp_sub.synchronize do future.wait(timeout) end @@ -644,7 +645,7 @@ def request_msg(msg, **opts) @resp_map.delete(token) end - if response and response.header + if response&.header status = response.header[STATUS_HDR] raise NATS::IO::NoRespondersError if status == "503" end @@ -656,7 +657,7 @@ def request_msg(msg, **opts) # expecting a single response or raising a timeout in case the request # is not retrieved within the specified deadline. # If given a callback, then the request happens asynchronously. - def old_request(subject, payload, opts={}, &blk) + def old_request(subject, payload, opts = {}, &blk) return unless subject inbox = new_inbox @@ -705,13 +706,13 @@ def old_request(subject, payload, opts={}, &blk) # Publish the request and then wait for the response... publish(subject, payload, inbox) - MonotonicTime::with_nats_timeout(timeout) do + MonotonicTime.with_nats_timeout(timeout) do future.wait(timeout) end end response = sub.response - if response and response.header + if response&.header status = response.header[STATUS_HDR] raise NATS::IO::NoRespondersError if status == "503" end @@ -720,7 +721,7 @@ def old_request(subject, payload, opts={}, &blk) end # Send a ping and wait for a pong back within a timeout. - def flush(timeout=10) + def flush(timeout = 10) # Schedule sending a PING, and block until we receive PONG back, # or raise a timeout in case the response is past the deadline. pong = @pongs.new_cond @@ -730,18 +731,18 @@ def flush(timeout=10) # Flush once pong future has been prepared @pending_queue << PING_REQUEST @flush_queue << :ping - MonotonicTime::with_nats_timeout(timeout) do + MonotonicTime.with_nats_timeout(timeout) do pong.wait(timeout) end end end - alias :servers :server_pool + alias_method :servers, :server_pool # discovered_servers returns the NATS Servers that have been discovered # via INFO protocol updates. def discovered_servers - servers.select {|s| s[:discovered] } + servers.select { |s| s[:discovered] } end # Close connection to NATS, flushing in case connection is alive @@ -782,7 +783,7 @@ def closed? end def draining? - if @status == DRAINING_PUBS or @status == DRAINING_SUBS + if (@status == DRAINING_PUBS) || (@status == DRAINING_SUBS) return true end @@ -835,7 +836,7 @@ def drain # @option params [String] :domain JetStream Domain to use for the requests. # @option params [Float] :timeout Default timeout to use for JS requests. # @return [NATS::JetStream] - def jetstream(opts={}) + def jetstream(opts = {}) ::NATS::JetStream.new(self, opts) end alias_method :JetStream, :jetstream @@ -857,10 +858,8 @@ def process_info(line) # so has to be done under the lock. synchronize do # Symbolize keys from parsed info line - @server_info = parsed_info.reduce({}) do |info, (k,v)| + @server_info = parsed_info.each_with_object({}) do |(k, v), info| info[k.to_sym] = v - - info end # Detect any announced server that we might not be aware of... @@ -879,7 +878,7 @@ def process_info(line) srv[:uri].hostname == u.hostname && srv[:uri].port == u.port end - if not present + if !present # Let explicit user and pass options set the credentials. u.user = options[:user] if options[:user] u.password = options[:pass] if options[:pass] @@ -891,7 +890,7 @@ def process_info(line) end # NOTE: Auto discovery won't work here when TLS host verification is enabled. - srv = { :uri => u, :reconnect_attempts => 0, :discovered => true, :hostname => u.hostname } + srv = {uri: u, reconnect_attempts: 0, discovered: true, hostname: u.hostname} srvs << srv end end @@ -914,13 +913,13 @@ def process_hdr(header) # Check if the first line has an inline status and description. if lines.count > 0 status_hdr = lines.first.rstrip - status = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN) + status = status_hdr.slice(NATS_HDR_LINE_SIZE - 1, STATUS_MSG_LEN) - if status and !status.empty? + if status && !status.empty? hdr[STATUS_HDR] = status - if NATS_HDR_LINE_SIZE+2 < status_hdr.bytesize - desc = status_hdr.slice(NATS_HDR_LINE_SIZE+STATUS_MSG_LEN, status_hdr.bytesize) + if NATS_HDR_LINE_SIZE + 2 < status_hdr.bytesize + desc = status_hdr.slice(NATS_HDR_LINE_SIZE + STATUS_MSG_LEN, status_hdr.bytesize) hdr[DESC_HDR] = desc unless desc.empty? end end @@ -933,7 +932,7 @@ def process_hdr(header) hdr[key] = value end rescue => e - err = e + e end end @@ -946,7 +945,7 @@ def process_pong # Take first pong wait and signal any flush in case there was one @pongs.synchronize do pong = @pongs.pop - pong.signal unless pong.nil? + pong&.signal end @pings_outstanding -= 1 @pongs_received += 1 @@ -966,10 +965,9 @@ def process_err(err) # while holding the lock. e = synchronize do current = server_pool.first - case - when err =~ /'Stale Connection'/ + if err =~ /'Stale Connection'/ @last_err = NATS::IO::StaleConnectionError.new(err) - when current && current[:auth_required] + elsif current && current[:auth_required] # We cannot recover from auth errors so mark it to avoid # retrying to unecessarily next time. current[:error_received] = true @@ -1019,8 +1017,8 @@ def process_msg(subject, sid, reply, data, header) elsif sub.pending_queue # Async subscribers use a sized queue for processing # and should be able to consume messages in parallel. - if sub.pending_queue.size >= sub.pending_msgs_limit \ - or sub.pending_size >= sub.pending_bytes_limit then + if (sub.pending_queue.size >= sub.pending_msgs_limit) \ + || (sub.pending_size >= sub.pending_bytes_limit) err = NATS::IO::SlowConsumer.new("nats: slow consumer, messages dropped") else hdr = process_hdr(header) @@ -1034,10 +1032,12 @@ def process_msg(subject, sid, reply, data, header) end end - synchronize do - @last_err = err - err_cb_call(self, err, sub) if @err_cb - end if err + if err + synchronize do + @last_err = err + err_cb_call(self, err, sub) if @err_cb + end + end end def select_next_server @@ -1103,7 +1103,7 @@ def send_command(command) # Auto unsubscribes the server by sending UNSUB command and throws away # subscription in case already present and has received enough messages. - def unsubscribe(sub, opt_max=nil) + def unsubscribe(sub, opt_max = nil) sid = nil closed = nil sub.synchronize do @@ -1120,7 +1120,7 @@ def unsubscribe(sub, opt_max=nil) return unless sub synchronize do sub.max = opt_max - @subs.delete(sid) unless (sub.max && (sub.received < sub.max)) + @subs.delete(sid) unless sub.max && (sub.received < sub.max) end sub.synchronize do @@ -1141,7 +1141,7 @@ def drain_sub(sub) @flush_queue << :drain synchronize { sub = @subs[sid] } - return unless sub + nil unless sub end def do_drain @@ -1158,16 +1158,16 @@ def do_drain force_flush! # Wait until all subs have no pending messages. - drain_timeout = MonotonicTime::now + @options[:drain_timeout] + drain_timeout = MonotonicTime.now + @options[:drain_timeout] to_delete = [] loop do - break if MonotonicTime::now > drain_timeout + break if MonotonicTime.now > drain_timeout sleep 0.1 # Wait until all subs are done. @subs.each do |_, sub| - if sub != @resp_sub and sub.pending_queue.size == 0 + if (sub != @resp_sub) && (sub.pending_queue.size == 0) to_delete << sub end end @@ -1180,7 +1180,7 @@ def do_drain # Wait until only the resp mux is remaining or there are no subscriptions. if @subs.count == 1 - sid, sub = @subs.first + _, sub = @subs.first if sub == @resp_sub break end @@ -1192,7 +1192,7 @@ def do_drain subscription_executor.shutdown subscription_executor.wait_for_termination(@options[:drain_timeout]) - if MonotonicTime::now > drain_timeout + if MonotonicTime.now > drain_timeout e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out") err_cb_call(self, e, nil) if @err_cb end @@ -1229,27 +1229,26 @@ def auth_connection? def connect_command cs = { - :verbose => @options[:verbose], - :pedantic => @options[:pedantic], - :lang => NATS::IO::LANG, - :version => NATS::IO::VERSION, - :protocol => NATS::IO::PROTOCOL + verbose: @options[:verbose], + pedantic: @options[:pedantic], + lang: NATS::IO::LANG, + version: NATS::IO::VERSION, + protocol: NATS::IO::PROTOCOL } cs[:name] = @options[:name] if @options[:name] - case - when auth_connection? + if auth_connection? if @uri.password cs[:user] = @uri.user cs[:pass] = @uri.password else cs[:auth_token] = @uri.user end - when @user_jwt_cb && @signature_cb + elsif @user_jwt_cb && @signature_cb nonce = @server_info[:nonce] cs[:jwt] = @user_jwt_cb.call cs[:sig] = @signature_cb.call(nonce) - when @user_nkey_cb && @signature_cb + elsif @user_nkey_cb && @signature_cb nonce = @server_info[:nonce] cs[:nkey] = @user_nkey_cb.call cs[:sig] = @signature_cb.call(nonce) @@ -1260,10 +1259,10 @@ def connect_command if @server_info[:headers] cs[:headers] = @server_info[:headers] cs[:no_responders] = if @options[:no_responders] == false - @options[:no_responders] - else - @server_info[:headers] - end + @options[:no_responders] + else + @server_info[:headers] + end end "CONNECT #{cs.to_json}#{CR_LF}" @@ -1283,9 +1282,9 @@ def process_op_error(e) # If we were connected and configured to reconnect, # then trigger disconnect and start reconnection logic - if connected? and should_reconnect? + if connected? && should_reconnect? @status = RECONNECTING - @io.close if @io + @io&.close @io = nil # TODO: Reconnecting pending buffer? @@ -1293,18 +1292,16 @@ def process_op_error(e) # Do reconnect under a different thread than the one # in which we got the error. Thread.new do - begin - # Abort currently running reads in case they're around - # FIXME: There might be more graceful way here... - @read_loop_thread.exit if @read_loop_thread.alive? - @flusher_thread.exit if @flusher_thread.alive? - @ping_interval_thread.exit if @ping_interval_thread.alive? - - attempt_reconnect - rescue NATS::IO::NoServersError => e - @last_err = e - close - end + # Abort currently running reads in case they're around + # FIXME: There might be more graceful way here... + @read_loop_thread.exit if @read_loop_thread.alive? + @flusher_thread.exit if @flusher_thread.alive? + @ping_interval_thread.exit if @ping_interval_thread.alive? + + attempt_reconnect + rescue NATS::IO::NoServersError => e + @last_err = e + close end Thread.exit @@ -1322,26 +1319,24 @@ def process_op_error(e) # Gathers data from the socket and sends it to the parser. def read_loop loop do - begin - should_bail = synchronize do - # FIXME: In case of reconnect as well? - @status == CLOSED or @status == RECONNECTING - end - if !@io or @io.closed? or should_bail - return - end - - # TODO: Remove timeout and just wait to be ready - data = @io.read(NATS::IO::MAX_SOCKET_READ_BYTES) - @parser.parse(data) if data - rescue Errno::ETIMEDOUT - # FIXME: We do not really need a timeout here... - retry - rescue => e - # In case of reading/parser errors, trigger - # reconnection logic in case desired. - process_op_error(e) + should_bail = synchronize do + # FIXME: In case of reconnect as well? + @status == CLOSED or @status == RECONNECTING end + if !@io || @io.closed? || should_bail + return + end + + # TODO: Remove timeout and just wait to be ready + data = @io.read(NATS::IO::MAX_SOCKET_READ_BYTES) + @parser.parse(data) if data + rescue Errno::ETIMEDOUT + # FIXME: We do not really need a timeout here... + retry + rescue => e + # In case of reading/parser errors, trigger + # reconnection logic in case desired. + process_op_error(e) end end @@ -1353,7 +1348,7 @@ def flusher_loop @flush_queue.pop should_bail = synchronize do - (@status != CONNECTED && !draining? ) || @status == CONNECTING + (@status != CONNECTED && !draining?) || @status == CONNECTING end return if should_bail @@ -1374,17 +1369,19 @@ def force_flush! # until reaching the max pending queue size. cmds = [] cmds << @pending_queue.pop until @pending_queue.empty? - begin - @io.write(cmds.join) unless cmds.empty? - rescue => e - synchronize do - @last_err = e - err_cb_call(self, e, nil) if @err_cb - end + if @io + begin + @io.write(cmds.join) unless cmds.empty? + rescue => e + synchronize do + @last_err = e + err_cb_call(self, e, nil) if @err_cb + end - process_op_error(e) - return - end if @io + process_op_error(e) + nil + end + end end def ping_interval_loop @@ -1410,27 +1407,26 @@ def ping_interval_loop def process_connect_init # FIXME: Can receive PING as well here in recent versions. line = @io.read_line(options[:connect_timeout]) - if !line or line.empty? + if !line || line.empty? raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received") end - if match = line.match(NATS::Protocol::INFO) + if (match = line.match(NATS::Protocol::INFO)) info_json = match.captures.first process_info(info_json) else raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not valid") end - case - when (server_using_secure_connection? and client_using_secure_connection?) + if server_using_secure_connection? && client_using_secure_connection? @io.setup_tls! # Server > v2.9.19 returns tls_required regardless of no_tls for WebSocket config being used so need to check URI. - when (server_using_secure_connection? and !client_using_secure_connection? and @uri.scheme != "ws") - raise NATS::IO::ConnectError.new('TLS/SSL required by server') + elsif server_using_secure_connection? && !client_using_secure_connection? && (@uri.scheme != "ws") + raise NATS::IO::ConnectError.new("TLS/SSL required by server") # Server < v2.9.19 requiring TLS/SSL over websocket but not requiring it over standard protocol # doesn't send `tls_required` in its INFO so we need to check the URI scheme for WebSocket. - when (client_using_secure_connection? and !server_using_secure_connection? and @uri.scheme != "wss") - raise NATS::IO::ConnectError.new('TLS/SSL not supported by server') + elsif client_using_secure_connection? && !server_using_secure_connection? && (@uri.scheme != "wss") + raise NATS::IO::ConnectError.new("TLS/SSL not supported by server") else # Otherwise, use a regular connection. end @@ -1451,6 +1447,7 @@ def process_connect_init case next_op when NATS::Protocol::PONG + # do nothing when NATS::Protocol::ERR if @server_info[:auth_required] raise NATS::IO::AuthError.new($1) @@ -1464,7 +1461,7 @@ def process_connect_init # Reconnect logic, this is done while holding the lock. def attempt_reconnect - @disconnect_cb.call(@last_err) if @disconnect_cb + @disconnect_cb&.call(@last_err) # Clear sticky error @last_err = nil @@ -1475,7 +1472,7 @@ def attempt_reconnect srv = select_next_server # Set hostname to use for TLS hostname verification - if client_using_secure_connection? and single_url_connect_used? + if client_using_secure_connection? && single_url_connect_used? # Reuse original hostname name in case of using TLS. @hostname ||= srv[:hostname] else @@ -1541,10 +1538,10 @@ def attempt_reconnect # Dispatch the reconnected callback while holding lock # which we should have already - @reconnect_cb.call if @reconnect_cb + @reconnect_cb&.call end - def close_connection(conn_status, do_cbs=true) + def close_connection(conn_status, do_cbs = true) synchronize do @connect_called = false if @status == CLOSED @@ -1559,15 +1556,15 @@ def close_connection(conn_status, do_cbs=true) # FIXME: More graceful way of handling the following? # Ensure ping interval and flusher are not running anymore - if @ping_interval_thread and @ping_interval_thread.alive? + if @ping_interval_thread&.alive? @ping_interval_thread.exit end - if @flusher_thread and @flusher_thread.alive? + if @flusher_thread&.alive? @flusher_thread.exit end - if @read_loop_thread and @read_loop_thread.alive? + if @read_loop_thread&.alive? @read_loop_thread.exit end @@ -1582,24 +1579,26 @@ def close_connection(conn_status, do_cbs=true) # Try to write any pending flushes in case # we have a connection then close it. - should_flush = (@pending_queue && @io && @io.socket && !@io.closed?) - begin - cmds = [] - cmds << @pending_queue.pop until @pending_queue.empty? - - # FIXME: Fails when empty on TLS connection? - @io.write(cmds.join) unless cmds.empty? - rescue => e - @last_err = e - err_cb_call(self, e, nil) if @err_cb - end if should_flush + should_flush = @pending_queue && @io && @io.socket && !@io.closed? + if should_flush + begin + cmds = [] + cmds << @pending_queue.pop until @pending_queue.empty? + + # FIXME: Fails when empty on TLS connection? + @io.write(cmds.join) unless cmds.empty? + rescue => e + @last_err = e + err_cb_call(self, e, nil) if @err_cb + end + end # Destroy any remaining subscriptions. @subs.clear if do_cbs - @disconnect_cb.call(@last_err) if @disconnect_cb - @close_cb.call if @close_cb + @disconnect_cb&.call(@last_err) + @close_cb&.call end @status = conn_status @@ -1631,11 +1630,11 @@ def start_threads! # Subscription handling thread pool @subscription_executor = Concurrent::ThreadPoolExecutor.new( - name: 'nats:subscription', # threads will be given names like nats:subscription-worker-1 + name: "nats:subscription", # threads will be given names like nats:subscription-worker-1 max_threads: NATS::IO::DEFAULT_TOTAL_SUB_CONCURRENCY, # JRuby has a bug on certain Java version of not creating new threads: # https://github.com/ruby-concurrency/concurrent-ruby/issues/864 - min_threads: defined?(JRUBY_VERSION) ? 2 : 0, + min_threads: defined?(JRUBY_VERSION) ? 2 : 0 ) end @@ -1643,7 +1642,7 @@ def start_threads! # for the new style request response. def start_resp_mux_sub! @resp_sub_prefix = new_inbox - @resp_map = Hash.new { |h,k| h[k] = { }} + @resp_map = Hash.new { |h, k| h[k] = {} } @resp_sub = Subscription.new @resp_sub.subject = "#{@resp_sub_prefix}.*" @@ -1657,7 +1656,7 @@ def start_resp_mux_sub! @resp_sub.callback = proc do |msg| # Pick the token and signal the request under the mutex # from the subscription itself. - token = msg.subject.split('.').last + token = msg.subject.split(".").last future = nil synchronize do future = @resp_map[token][:future] @@ -1689,7 +1688,7 @@ def can_reuse_server?(server) return false if server[:error_received] # We will retry a number of times to reconnect to a server. - return server[:reconnect_attempts] <= @options[:max_reconnect_attempts] + server[:reconnect_attempts] <= @options[:max_reconnect_attempts] end def should_delay_connect?(server) @@ -1706,35 +1705,34 @@ def should_reconnect? def create_socket socket_class = case @uri.scheme - when "nats", "tls" - NATS::IO::Socket - when "ws", "wss" - require_relative 'websocket' - NATS::IO::WebSocket - else - raise NotImplementedError, "#{@uri.scheme} protocol is not supported, check NATS cluster URL spelling" - end + when "nats", "tls" + NATS::IO::Socket + when "ws", "wss" + require_relative "websocket" + NATS::IO::WebSocket + else + raise NotImplementedError, "#{@uri.scheme} protocol is not supported, check NATS cluster URL spelling" + end socket_class.new( uri: @uri, - tls: { context: tls_context, hostname: @hostname }, - connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT, + tls: {context: tls_context, hostname: @hostname}, + connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT ) end def setup_nkeys_connect begin - require 'nkeys' - require 'base64' + require "nkeys" + require "base64" rescue LoadError raise(Error, "nkeys is not installed") end - case - when @nkeys_seed + if @nkeys_seed @user_nkey_cb = nkey_cb_for_nkey_file(@nkeys_seed) @signature_cb = signature_cb_for_nkey_file(@nkeys_seed) - when @user_credentials + elsif @user_credentials # When the credentials are within a single decorated file. @user_jwt_cb = jwt_cb_for_creds_file(@user_credentials) @signature_cb = signature_cb_for_creds_file(@user_credentials) @@ -1744,18 +1742,18 @@ def setup_nkeys_connect def signature_cb_for_nkey_file(nkey) proc { |nonce| seed = File.read(nkey).chomp - kp = NKEYS::from_seed(seed) + kp = NKEYS.from_seed(seed) raw_signed = kp.sign(nonce) kp.wipe! encoded = Base64.urlsafe_encode64(raw_signed) - encoded.gsub('=', '') + encoded.gsub("=", "") } end def nkey_cb_for_nkey_file(nkey) proc { seed = File.read(nkey).chomp - kp = NKEYS::from_seed(seed) + kp = NKEYS.from_seed(seed) # Take a copy since original will be gone with the wipe. pub_key = kp.public_key.dup @@ -1767,21 +1765,20 @@ def nkey_cb_for_nkey_file(nkey) def jwt_cb_for_creds_file(creds) proc { - jwt_start = "BEGIN NATS USER JWT".freeze + jwt_start = "BEGIN NATS USER JWT" found = false jwt = nil File.readlines(creds).each do |line| - case - when found + if found jwt = line.chomp break - when line.include?(jwt_start) + elsif line.include?(jwt_start) found = true end end - raise(Error, "No JWT found in #{creds}") if not found + raise(Error, "No JWT found in #{creds}") if !found jwt } @@ -1789,23 +1786,22 @@ def jwt_cb_for_creds_file(creds) def signature_cb_for_creds_file(creds) proc { |nonce| - seed_start = "BEGIN USER NKEY SEED".freeze + seed_start = "BEGIN USER NKEY SEED" found = false seed = nil File.readlines(creds).each do |line| - case - when found + if found seed = line.chomp break - when line.include?(seed_start) + elsif line.include?(seed_start) found = true end end - raise(Error, "No nkey user seed found in #{creds}") if not found + raise(Error, "No nkey user seed found in #{creds}") if !found - kp = NKEYS::from_seed(seed) + kp = NKEYS.from_seed(seed) raw_signed = kp.sign(nonce) # seed is a reference so also cleared when doing wipe, @@ -1814,14 +1810,12 @@ def signature_cb_for_creds_file(creds) encoded = Base64.urlsafe_encode64(raw_signed) # Remove padding - encoded.gsub('=', '') + encoded.gsub("=", "") } end def process_uri(uris) - uris.split(',').map do |uri| - opts = {} - + uris.split(",").map do |uri| # Scheme uri = "nats://#{uri}" if !uri.include?("://") @@ -1865,7 +1859,7 @@ module IO DEFAULT_DRAIN_TIMEOUT = 30 # Default Pending Limits - DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536 + DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536 DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024 DEFAULT_TOTAL_SUB_CONCURRENCY = 24 @@ -1875,7 +1869,7 @@ module IO class Socket attr_accessor :socket - def initialize(options={}) + def initialize(options = {}) @uri = options[:uri] @connect_timeout = options[:connect_timeout] @write_timeout = options[:write_timeout] @@ -1887,13 +1881,11 @@ def initialize(options={}) def connect addrinfo = ::Socket.getaddrinfo(@uri.hostname, nil, ::Socket::AF_UNSPEC, ::Socket::SOCK_STREAM) addrinfo.each_with_index do |ai, i| - begin - @socket = connect_addrinfo(ai, @uri.port, @connect_timeout) - break - rescue SystemCallError => e - # Give up if no more available - raise e if addrinfo.length == i+1 - end + @socket = connect_addrinfo(ai, @uri.port, @connect_timeout) + break + rescue SystemCallError => e + # Give up if no more available + raise e if addrinfo.length == i + 1 end # Set TCP no delay by default @@ -1916,7 +1908,7 @@ def setup_tls! @socket = tls_socket end - def read_line(deadline=nil) + def read_line(deadline = nil) # FIXME: Should accumulate and read in a non blocking way instead unless ::IO.select([@socket], nil, nil, deadline) raise NATS::IO::SocketTimeoutError @@ -1924,10 +1916,9 @@ def read_line(deadline=nil) @socket.gets end - def read(max_bytes, deadline=nil) - + def read(max_bytes, deadline = nil) begin - return @socket.read_nonblock(max_bytes) + @socket.read_nonblock(max_bytes) rescue ::IO::WaitReadable if ::IO.select([@socket], nil, nil, deadline) retry @@ -1942,7 +1933,7 @@ def read(max_bytes, deadline=nil) end end rescue EOFError => e - if RUBY_ENGINE == 'jruby' and e.message == 'No message available' + if (RUBY_ENGINE == "jruby") && (e.message == "No message available") # FIXME: can happen in jruby # even though seems it is temporary and eventually possible # to read from socket. @@ -1951,32 +1942,29 @@ def read(max_bytes, deadline=nil) raise Errno::ECONNRESET end - def write(data, deadline=nil) + def write(data, deadline = nil) length = data.bytesize total_written = 0 loop do - begin - written = @socket.write_nonblock(data) - - total_written += written - break total_written if total_written >= length - data = data.byteslice(written..-1) - rescue ::IO::WaitWritable - if ::IO.select(nil, [@socket], nil, deadline) - retry - else - raise NATS::IO::SocketTimeoutError - end - rescue ::IO::WaitReadable - if ::IO.select([@socket], nil, nil, deadline) - retry - else - raise NATS::IO::SocketTimeoutError - end + written = @socket.write_nonblock(data) + + total_written += written + break total_written if total_written >= length + data = data.byteslice(written..-1) + rescue ::IO::WaitWritable + if ::IO.select(nil, [@socket], nil, deadline) + retry + else + raise NATS::IO::SocketTimeoutError + end + rescue ::IO::WaitReadable + if ::IO.select([@socket], nil, nil, deadline) + retry + else + raise NATS::IO::SocketTimeoutError end end - rescue EOFError raise Errno::ECONNRESET end @@ -2021,14 +2009,13 @@ class MonotonicTime # Implementation of MonotonicTime adapted from # https://github.com/ruby-concurrency/concurrent-ruby/ class << self - case - when defined?(Process::CLOCK_MONOTONIC) + if defined?(Process::CLOCK_MONOTONIC) def now Process.clock_gettime(Process::CLOCK_MONOTONIC) end - when RUBY_ENGINE == 'jruby' + elsif RUBY_ENGINE == "jruby" def now - java.lang.System.nanoTime() / 1_000_000_000.0 + java.lang.System.nanoTime / 1_000_000_000.0 end else def now diff --git a/lib/nats/io/errors.rb b/lib/nats/io/errors.rb index 441e1c3..f977efb 100644 --- a/lib/nats/io/errors.rb +++ b/lib/nats/io/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/nats/io/jetstream.rb b/lib/nats/io/jetstream.rb index 606b3a8..10e051f 100644 --- a/lib/nats/io/jetstream.rb +++ b/lib/nats/io/jetstream.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,17 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # -require_relative 'msg' -require_relative 'client' -require_relative 'errors' -require_relative 'kv' -require_relative 'jetstream/api' -require_relative 'jetstream/errors' -require_relative 'jetstream/js' -require_relative 'jetstream/manager' -require_relative 'jetstream/msg' -require_relative 'jetstream/pull_subscription' -require_relative 'jetstream/push_subscription' +require_relative "msg" +require_relative "client" +require_relative "errors" +require_relative "kv" +require_relative "jetstream/api" +require_relative "jetstream/errors" +require_relative "jetstream/js" +require_relative "jetstream/manager" +require_relative "jetstream/msg" +require_relative "jetstream/pull_subscription" +require_relative "jetstream/push_subscription" module NATS # JetStream returns a context with a similar API as the NATS::Client @@ -40,15 +42,15 @@ class JetStream # @option params [String] :prefix JetStream API prefix to use for the requests. # @option params [String] :domain JetStream Domain to use for the requests. # @option params [Float] :timeout Default timeout to use for JS requests. - def initialize(conn, params={}) + def initialize(conn, params = {}) @nc = conn @prefix = if params[:prefix] - params[:prefix] - elsif params[:domain] - "$JS.#{params[:domain]}.API" - else - JS::DefaultAPIPrefix - end + params[:prefix] + elsif params[:domain] + "$JS.#{params[:domain]}.API" + else + JS::DefaultAPIPrefix + end @opts = params @opts[:timeout] ||= 5 # seconds params[:prefix] = @prefix @@ -80,7 +82,7 @@ def initialize(conn, params={}) # @option params [String] :stream Expected Stream to which the message is being published. # @raise [NATS::Timeout] When it takes too long to receive an ack response. # @return [PubAck] The pub ack response. - def publish(subject, payload="", **params) + def publish(subject, payload = "", **params) params[:timeout] ||= @opts[:timeout] if params[:stream] params[:header] ||= {} @@ -89,8 +91,8 @@ def publish(subject, payload="", **params) # Send message with headers. msg = NATS::Msg.new(subject: subject, - data: payload, - header: params[:header]) + data: payload, + header: params[:header]) begin resp = @nc.request_msg(msg, **params) @@ -113,53 +115,49 @@ def publish(subject, payload="", **params) # @option params [String] :durable Consumer durable name from where the messages will be fetched. # @option params [Hash] :config Configuration for the consumer. # @return [NATS::JetStream::PushSubscription] - def subscribe(subject, params={}, &cb) + def subscribe(subject, params = {}, &cb) params[:consumer] ||= params[:durable] params[:consumer] ||= params[:name] - multi_filter = case - when (subject.is_a?(Array) and subject.size == 1) - subject = subject.first - false - when (subject.is_a?(Array) and subject.size > 1) - true - end + multi_filter = if subject.is_a?(Array) && (subject.size == 1) + subject = subject.first + false + elsif subject.is_a?(Array) && (subject.size > 1) + true + end - # stream = if params[:stream].nil? - if multi_filter - # Use the first subject to try to find the stream. - streams = subject.map do |s| - begin - find_stream_name_by_subject(s) - rescue NATS::JetStream::Error::NotFound - raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'") - end - end + if multi_filter + # Use the first subject to try to find the stream. + streams = subject.map do |s| + find_stream_name_by_subject(s) + rescue NATS::JetStream::Error::NotFound + raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'") + end - # Ensure that the filter subjects are not ambiguous. - streams.uniq! - if streams.count > 1 - raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}") - end + # Ensure that the filter subjects are not ambiguous. + streams.uniq! + if streams.count > 1 + raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}") + end - streams.first - else - find_stream_name_by_subject(subject) - end - else - params[:stream] - end + streams.first + else + find_stream_name_by_subject(subject) + end + else + params[:stream] + end queue = params[:queue] durable = params[:durable] - flow_control = params[:flow_control] + params[:flow_control] manual_ack = params[:manual_ack] idle_heartbeat = params[:idle_heartbeat] flow_control = params[:flow_control] config = params[:config] if queue - if durable and durable != queue + if durable && (durable != queue) raise NATS::JetStream::Error.new("nats: cannot create queue subscription '#{queue}' to consumer '#{durable}'") else durable = queue @@ -170,7 +168,7 @@ def subscribe(subject, params={}, &cb) consumer_found = false should_create = false - if not durable + if !durable should_create = true else begin @@ -185,18 +183,16 @@ def subscribe(subject, params={}, &cb) end if consumer_found - if not config.deliver_group + if !config.deliver_group if queue raise NATS::JetStream::Error.new("nats: cannot create a queue subscription for a consumer without a deliver group") elsif cinfo.push_bound raise NATS::JetStream::Error.new("nats: consumer is already bound to a subscription") end - else - if not queue - raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}") - elsif queue != config.deliver_group - raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}") - end + elsif !queue + raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}") + elsif queue != config.deliver_group + raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}") end elsif should_create # Auto-create consumer if none found. @@ -209,8 +205,8 @@ def subscribe(subject, params={}, &cb) raise NATS::JetStream::Error.new("nats: invalid ConsumerConfig") end - config.durable_name = durable if not config.durable_name - config.deliver_group = queue if not config.deliver_group + config.durable_name = durable if !config.durable_name + config.deliver_group = queue if !config.deliver_group # Create inbox for push consumer. deliver = @nc.new_inbox @@ -225,9 +221,9 @@ def subscribe(subject, params={}, &cb) # Heartbeats / FlowControl config.flow_control = flow_control - if idle_heartbeat or config.idle_heartbeat + if idle_heartbeat || config.idle_heartbeat idle_heartbeat = config.idle_heartbeat if config.idle_heartbeat - idle_heartbeat = idle_heartbeat * ::NATS::NANOSECONDS + idle_heartbeat *= ::NATS::NANOSECONDS config.idle_heartbeat = idle_heartbeat end @@ -237,11 +233,15 @@ def subscribe(subject, params={}, &cb) end # Enable auto acking for async callbacks unless disabled. - if cb and not manual_ack + if cb && !manual_ack ocb = cb new_cb = proc do |msg| ocb.call(msg) - msg.ack rescue JetStream::Error::MsgAlreadyAckd + begin + msg.ack + rescue + JetStream::Error::MsgAlreadyAckd + end end cb = new_cb end @@ -250,7 +250,7 @@ def subscribe(subject, params={}, &cb) sub.jsi = JS::Sub.new( js: self, stream: stream, - consumer: consumer, + consumer: consumer ) sub end @@ -265,57 +265,54 @@ def subscribe(subject, params={}, &cb) # @option params [String] :name Name of the Consumer to which the PullSubscription will be bound. # @option params [Hash] :config Configuration for the consumer. # @return [NATS::JetStream::PullSubscription] - def pull_subscribe(subject, durable, params={}) - if (!durable or durable.empty?) && !(params[:consumer] or params[:name]) + def pull_subscribe(subject, durable, params = {}) + if (!durable || durable.empty?) && !(params[:consumer] || params[:name]) raise JetStream::Error::InvalidDurableName.new("nats: invalid durable name") end - multi_filter = case - when (subject.is_a?(Array) and subject.size == 1) - subject = subject.first - false - when (subject.is_a?(Array) and subject.size > 1) - true - end + multi_filter = if subject.is_a?(Array) && (subject.size == 1) + subject = subject.first + false + elsif subject.is_a?(Array) && (subject.size > 1) + true + end params[:consumer] ||= durable params[:consumer] ||= params[:name] stream = if params[:stream].nil? - if multi_filter - # Use the first subject to try to find the stream. - streams = subject.map do |s| - begin - find_stream_name_by_subject(s) - rescue NATS::JetStream::Error::NotFound - raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'") - end - end + if multi_filter + # Use the first subject to try to find the stream. + streams = subject.map do |s| + find_stream_name_by_subject(s) + rescue NATS::JetStream::Error::NotFound + raise NATS::JetStream::Error.new("nats: could not find stream matching filter subject '#{s}'") + end - # Ensure that the filter subjects are not ambiguous. - streams.uniq! - if streams.count > 1 - raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}") - end + # Ensure that the filter subjects are not ambiguous. + streams.uniq! + if streams.count > 1 + raise NATS::JetStream::Error.new("nats: multiple streams matched filter subjects: #{streams}") + end - streams.first - else - find_stream_name_by_subject(subject) - end - else - params[:stream] - end + streams.first + else + find_stream_name_by_subject(subject) + end + else + params[:stream] + end begin consumer_info(stream, params[:consumer]) rescue NATS::JetStream::Error::NotFound => e # If attempting to bind, then this is a hard error. - raise e if params[:stream] and !multi_filter + raise e if params[:stream] && !multi_filter - config = if not params[:config] - JetStream::API::ConsumerConfig.new - elsif params[:config].is_a?(JetStream::API::ConsumerConfig) - params[:config] - else - JetStream::API::ConsumerConfig.new(params[:config]) - end + config = if !(params[:config]) + JetStream::API::ConsumerConfig.new + elsif params[:config].is_a?(JetStream::API::ConsumerConfig) + params[:config] + else + JetStream::API::ConsumerConfig.new(params[:config]) + end config[:durable_name] = durable config[:ack_policy] ||= JS::Config::AckExplicit if multi_filter diff --git a/lib/nats/io/jetstream/api.rb b/lib/nats/io/jetstream/api.rb index e544608..be9e2f6 100644 --- a/lib/nats/io/jetstream/api.rb +++ b/lib/nats/io/jetstream/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +14,9 @@ # limitations under the License. # -require_relative 'errors' -require 'base64' -require 'time' +require_relative "errors" +require "base64" +require "time" module NATS class JetStream @@ -29,12 +31,12 @@ module API # @!attribute stream_seq # @return [Integer] The stream sequence. SequenceInfo = Struct.new(:consumer_seq, :stream_seq, :last_active, - keyword_init: true) do - def initialize(opts={}) + keyword_init: true) do + def initialize(opts = {}) # Filter unrecognized fields and freeze. rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super freeze end end @@ -64,11 +66,11 @@ def initialize(opts={}) # @!attribute cluster # @return [Hash] ConsumerInfo = Struct.new(:type, :stream_name, :name, :created, - :config, :delivered, :ack_floor, - :num_ack_pending, :num_redelivered, :num_waiting, - :num_pending, :cluster, :push_bound, - keyword_init: true) do - def initialize(opts={}) + :config, :delivered, :ack_floor, + :num_ack_pending, :num_redelivered, :num_waiting, + :num_pending, :cluster, :push_bound, + keyword_init: true) do + def initialize(opts = {}) opts[:created] = Time.parse(opts[:created]) opts[:ack_floor] = SequenceInfo.new(opts[:ack_floor]) opts[:delivered] = SequenceInfo.new(opts[:delivered]) @@ -78,7 +80,7 @@ def initialize(opts={}) # Filter unrecognized fields just in case. rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super freeze end end @@ -102,32 +104,30 @@ def initialize(opts={}) # @!attribute max_ack_pending # @return [Integer] ConsumerConfig = Struct.new(:name, :durable_name, :description, - :deliver_policy, :opt_start_seq, :opt_start_time, - :ack_policy, :ack_wait, :max_deliver, :backoff, - :filter_subject, :replay_policy, :rate_limit_bps, - :sample_freq, :max_waiting, :max_ack_pending, - :flow_control, :idle_heartbeat, :headers_only, - - # Pull based options - :max_batch, :max_expires, - # Push based consumers - :deliver_subject, :deliver_group, - # Ephemeral inactivity threshold - :inactive_threshold, - # Generally inherited by parent stream and other markers, - # now can be configured directly. - :num_replicas, - # Force memory storage - :mem_storage, - - # NATS v2.10 features - :metadata, :filter_subjects, :max_bytes, - keyword_init: true) do - def initialize(opts={}) + :deliver_policy, :opt_start_seq, :opt_start_time, + :ack_policy, :ack_wait, :max_deliver, :backoff, + :filter_subject, :replay_policy, :rate_limit_bps, + :sample_freq, :max_waiting, :max_ack_pending, + :flow_control, :idle_heartbeat, :headers_only, + # Pull based options + :max_batch, :max_expires, + # Push based consumers + :deliver_subject, :deliver_group, + # Ephemeral inactivity threshold + :inactive_threshold, + # Generally inherited by parent stream and other markers, + # now can be configured directly. + :num_replicas, + # Force memory storage + :mem_storage, + # NATS v2.10 features + :metadata, :filter_subjects, :max_bytes, + keyword_init: true) do + def initialize(opts = {}) # Filter unrecognized fields just in case. rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super end end @@ -196,12 +196,13 @@ def initialize(opts={}) :allow_direct, :mirror_direct, :metadata, - keyword_init: true) do - def initialize(opts={}) + keyword_init: true + ) do + def initialize(opts = {}) # Filter unrecognized fields just in case. rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super end end @@ -218,8 +219,8 @@ def initialize(opts={}) # @!attribute domain # @return [String] StreamInfo = Struct.new(:type, :config, :created, :state, :domain, - keyword_init: true) do - def initialize(opts={}) + keyword_init: true) do + def initialize(opts = {}) opts[:config] = StreamConfig.new(opts[:config]) opts[:state] = StreamState.new(opts[:state]) opts[:created] = ::Time.parse(opts[:created]) @@ -227,7 +228,7 @@ def initialize(opts={}) # Filter fields and freeze. rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super freeze end end @@ -245,12 +246,12 @@ def initialize(opts={}) # @!attribute consumer_count # @return [Integer] StreamState = Struct.new(:messages, :bytes, :first_seq, :first_ts, - :last_seq, :last_ts, :consumer_count, - keyword_init: true) do - def initialize(opts={}) + :last_seq, :last_ts, :consumer_count, + keyword_init: true) do + def initialize(opts = {}) rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super end end @@ -267,13 +268,13 @@ def initialize(opts={}) # @!attribute did_create # @return [Boolean] StreamCreateResponse = Struct.new(:type, :config, :created, :state, :did_create, - keyword_init: true) do - def initialize(opts={}) + keyword_init: true) do + def initialize(opts = {}) rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } opts[:config] = StreamConfig.new(opts[:config]) opts[:state] = StreamState.new(opts[:state]) - super(opts) + super freeze end end @@ -297,11 +298,11 @@ def initialize(opts) # Filter out members not present. rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super end def sequence - self.seq + seq end end end diff --git a/lib/nats/io/jetstream/errors.rb b/lib/nats/io/jetstream/errors.rb index 29200ba..a351858 100644 --- a/lib/nats/io/jetstream/errors.rb +++ b/lib/nats/io/jetstream/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +18,6 @@ module NATS class JetStream # Error is any error that may arise when interacting with JetStream. class Error < NATS::IO::Error - # When there is a NATS::IO::NoResponders error after making a publish request. class NoStreamResponse < Error; end @@ -43,7 +44,7 @@ class InvalidConsumerName < Error; end class APIError < Error attr_reader :code, :err_code, :description, :stream, :seq - def initialize(params={}) + def initialize(params = {}) @code = params[:code] @err_code = params[:err_code] @description = params[:description] @@ -61,8 +62,8 @@ def to_s # running in cluster mode. # This condition is represented with a message that has 503 status code header. class ServiceUnavailable < APIError - def initialize(params={}) - super(params) + def initialize(params = {}) + super @code ||= 503 end end @@ -70,8 +71,8 @@ def initialize(params={}) # When there is a hard failure in the JetStream. # This condition is represented with a message that has 500 status code header. class ServerError < APIError - def initialize(params={}) - super(params) + def initialize(params = {}) + super @code ||= 500 end end @@ -79,8 +80,8 @@ def initialize(params={}) # When a JetStream object was not found. # This condition is represented with a message that has 404 status code header. class NotFound < APIError - def initialize(params={}) - super(params) + def initialize(params = {}) + super @code ||= 404 end end @@ -94,8 +95,8 @@ class ConsumerNotFound < NotFound; end # When the JetStream client makes an invalid request. # This condition is represented with a message that has 400 status code header. class BadRequest < APIError - def initialize(params={}) - super(params) + def initialize(params = {}) + super @code ||= 400 end end diff --git a/lib/nats/io/jetstream/js.rb b/lib/nats/io/jetstream/js.rb index 8a49389..2cc6f8d 100644 --- a/lib/nats/io/jetstream/js.rb +++ b/lib/nats/io/jetstream/js.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,17 +14,17 @@ # limitations under the License. # -require_relative 'js/config' -require_relative 'js/header' -require_relative 'js/status' -require_relative 'js/sub' +require_relative "js/config" +require_relative "js/header" +require_relative "js/status" +require_relative "js/sub" module NATS class JetStream # Misc internal functions to support JS API. # @private module JS - DefaultAPIPrefix = ("$JS.API".freeze) + DefaultAPIPrefix = "$JS.API" # rubocop:disable Naming/ConstantName class << self def next_req_to_json(next_req) @@ -34,14 +36,14 @@ def next_req_to_json(next_req) end def is_status_msg(msg) - return (!msg.nil? and (!msg.header.nil? and msg.header[Header::Status])) + (!msg.nil? and (!msg.header.nil? and msg.header[Header::Status])) end # check_503_error raises exception when a NATS::Msg has a 503 status header. # @param msg [NATS::Msg] The message with status headers. # @raise [NATS::JetStream::Error::ServiceUnavailable] def check_503_error(msg) - return if msg.nil? or msg.header.nil? + return if msg.nil? || msg.header.nil? if msg.header[Header::Status] == Status::ServiceUnavailable raise ::NATS::JetStream::Error::ServiceUnavailable end @@ -59,7 +61,7 @@ def from_msg(msg) check_503_error(msg) code = msg.header[JS::Header::Status] desc = msg.header[JS::Header::Desc] - return ::NATS::JetStream::API::Error.new({code: code, description: desc}) + ::NATS::JetStream::API::Error.new({code: code, description: desc}) end # from_error takes an API response that errored and maps the error diff --git a/lib/nats/io/jetstream/js/config.rb b/lib/nats/io/jetstream/js/config.rb index 1eeddda..a8f559f 100644 --- a/lib/nats/io/jetstream/js/config.rb +++ b/lib/nats/io/jetstream/js/config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,10 +18,14 @@ module NATS class JetStream module JS module Config + # rubocop:disable Naming/ConstantName + # AckPolicy - AckExplicit = ("explicit".freeze) - AckAll = ("all".freeze) - AckNone = ("none".freeze) + AckExplicit = "explicit" + AckAll = "all" + AckNone = "none" + + # rubocop:enable Naming/ConstantName end end end diff --git a/lib/nats/io/jetstream/js/header.rb b/lib/nats/io/jetstream/js/header.rb index fd5c637..6a67098 100644 --- a/lib/nats/io/jetstream/js/header.rb +++ b/lib/nats/io/jetstream/js/header.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,15 +18,19 @@ module NATS class JetStream module JS module Header - Status = ("Status".freeze) - Desc = ("Description".freeze) - MsgID = ("Nats-Msg-Id".freeze) - ExpectedStream = ("Nats-Expected-Stream".freeze) - ExpectedLastSeq = ("Nats-Expected-Last-Sequence".freeze) - ExpectedLastSubjSeq = ("Nats-Expected-Last-Subject-Sequence".freeze) - ExpectedLastMsgID = ("Nats-Expected-Last-Msg-Id".freeze) - LastConsumerSeq = ("Nats-Last-Consumer".freeze) - LastStreamSeq = ("Nats-Last-Stream".freeze) + # rubocop:disable Naming/ConstantName + + Status = "Status" + Desc = "Description" + MsgID = "Nats-Msg-Id" + ExpectedStream = "Nats-Expected-Stream" + ExpectedLastSeq = "Nats-Expected-Last-Sequence" + ExpectedLastSubjSeq = "Nats-Expected-Last-Subject-Sequence" + ExpectedLastMsgID = "Nats-Expected-Last-Msg-Id" + LastConsumerSeq = "Nats-Last-Consumer" + LastStreamSeq = "Nats-Last-Stream" + + # rubocop:enable Naming/ConstantName end end end diff --git a/lib/nats/io/jetstream/js/status.rb b/lib/nats/io/jetstream/js/status.rb index aedd07e..1e11064 100644 --- a/lib/nats/io/jetstream/js/status.rb +++ b/lib/nats/io/jetstream/js/status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,11 +18,15 @@ module NATS class JetStream module JS module Status - CtrlMsg = ("100".freeze) - NoMsgs = ("404".freeze) - NotFound = ("404".freeze) - RequestTimeout = ("408".freeze) - ServiceUnavailable = ("503".freeze) + # rubocop:disable Naming/ConstantName + + CtrlMsg = "100" + NoMsgs = "404" + NotFound = "404" + RequestTimeout = "408" + ServiceUnavailable = "503" + + # rubocop:enable Naming/ConstantName end end end diff --git a/lib/nats/io/jetstream/js/sub.rb b/lib/nats/io/jetstream/js/sub.rb index 0c9cb67..b9df157 100644 --- a/lib/nats/io/jetstream/js/sub.rb +++ b/lib/nats/io/jetstream/js/sub.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +20,7 @@ module JS class Sub attr_reader :js, :stream, :consumer, :nms - def initialize(opts={}) + def initialize(opts = {}) @js = opts[:js] @stream = opts[:stream] @consumer = opts[:consumer] diff --git a/lib/nats/io/jetstream/manager.rb b/lib/nats/io/jetstream/manager.rb index 16cb09f..063b78b 100644 --- a/lib/nats/io/jetstream/manager.rb +++ b/lib/nats/io/jetstream/manager.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -31,15 +33,15 @@ module Manager # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::StreamCreateResponse] The result of creating a Stream. - def add_stream(config, params={}) - config = if not config.is_a?(JetStream::API::StreamConfig) - JetStream::API::StreamConfig.new(config) - else - config - end + def add_stream(config, params = {}) + config = if !config.is_a?(JetStream::API::StreamConfig) + JetStream::API::StreamConfig.new(config) + else + config + end stream = config[:name] raise ArgumentError.new(":name is required to create streams") unless stream - raise ArgumentError.new("Spaces, tabs, period (.), greater than (>) or asterisk (*) are prohibited in stream names") if stream =~ /(\s|\.|\>|\*)/ + raise ArgumentError.new("Spaces, tabs, period (.), greater than (>) or asterisk (*) are prohibited in stream names") if stream =~ /(\s|\.|>|\*)/ req_subject = "#{@prefix}.STREAM.CREATE.#{stream}" cfg = config.to_h.compact @@ -52,11 +54,11 @@ def add_stream(config, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::StreamInfo] The latest StreamInfo of the stream. - def stream_info(stream, params={}) - raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty? + def stream_info(stream, params = {}) + raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? || stream.empty? req_subject = "#{@prefix}.STREAM.INFO.#{stream}" - result = api_request(req_subject, '', params) + result = api_request(req_subject, "", params) JetStream::API::StreamInfo.new(result) end @@ -65,15 +67,15 @@ def stream_info(stream, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::StreamCreateResponse] The result of creating a Stream. - def update_stream(config, params={}) - config = if not config.is_a?(JetStream::API::StreamConfig) - JetStream::API::StreamConfig.new(config) - else - config - end + def update_stream(config, params = {}) + config = if !config.is_a?(JetStream::API::StreamConfig) + JetStream::API::StreamConfig.new(config) + else + config + end stream = config[:name] raise ArgumentError.new(":name is required to create streams") unless stream - raise ArgumentError.new("Spaces, tabs, period (.), greater than (>) or asterisk (*) are prohibited in stream names") if stream =~ /(\s|\.|\>|\*)/ + raise ArgumentError.new("Spaces, tabs, period (.), greater than (>) or asterisk (*) are prohibited in stream names") if stream =~ /(\s|\.|>|\*)/ req_subject = "#{@prefix}.STREAM.UPDATE.#{stream}" cfg = config.to_h.compact result = api_request(req_subject, cfg.to_json, params) @@ -85,11 +87,11 @@ def update_stream(config, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [Boolean] - def delete_stream(stream, params={}) - raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty? + def delete_stream(stream, params = {}) + raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? || stream.empty? req_subject = "#{@prefix}.STREAM.DELETE.#{stream}" - result = api_request(req_subject, '', params) + result = api_request(req_subject, "", params) result[:success] end @@ -99,46 +101,45 @@ def delete_stream(stream, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::ConsumerInfo] The result of creating a Consumer. - def add_consumer(stream, config, params={}) - raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty? - config = if not config.is_a?(JetStream::API::ConsumerConfig) - JetStream::API::ConsumerConfig.new(config) - else - config - end + def add_consumer(stream, config, params = {}) + raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? || stream.empty? + config = if !config.is_a?(JetStream::API::ConsumerConfig) + JetStream::API::ConsumerConfig.new(config) + else + config + end config[:name] ||= config[:durable_name] - req_subject = case - when config[:name] - ############################################################################### - # # - # Using names is the supported way of creating consumers (NATS +v2.9.0. # - # # - ############################################################################### - if config[:filter_subject] && config[:filter_subject] != ">" - "#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}.#{config[:filter_subject]}" - else - ############################################################################## - # # - # Endpoint to support creating ANY consumer with multi-filters (NATS +v2.10) # - # # - ############################################################################## - "#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}" - end - when config[:durable_name] - ############################################################################### - # # - # Endpoint to support creating DURABLES before NATS v2.9.0. # - # # - ############################################################################### - "#{@prefix}.CONSUMER.DURABLE.CREATE.#{stream}.#{config[:durable_name]}" - else - ############################################################################### - # # - # Endpoint to support creating EPHEMERALS before NATS v2.9.0. # - # # - ############################################################################### - "#{@prefix}.CONSUMER.CREATE.#{stream}" - end + req_subject = if config[:name] + ############################################################################### + # # + # Using names is the supported way of creating consumers (NATS +v2.9.0. # + # # + ############################################################################### + if config[:filter_subject] && config[:filter_subject] != ">" + "#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}.#{config[:filter_subject]}" + else + ############################################################################## + # # + # Endpoint to support creating ANY consumer with multi-filters (NATS +v2.10) # + # # + ############################################################################## + "#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}" + end + elsif config[:durable_name] + ############################################################################### + # # + # Endpoint to support creating DURABLES before NATS v2.9.0. # + # # + ############################################################################### + "#{@prefix}.CONSUMER.DURABLE.CREATE.#{stream}.#{config[:durable_name]}" + else + ############################################################################### + # # + # Endpoint to support creating EPHEMERALS before NATS v2.9.0. # + # # + ############################################################################### + "#{@prefix}.CONSUMER.CREATE.#{stream}" + end config[:ack_policy] ||= JS::Config::AckExplicit # Check if have to normalize ack wait so that it is in nanoseconds for Go compat. @@ -167,12 +168,12 @@ def add_consumer(stream, config, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer. - def consumer_info(stream, consumer, params={}) - raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty? - raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? or consumer.empty? + def consumer_info(stream, consumer, params = {}) + raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? || stream.empty? + raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? || consumer.empty? req_subject = "#{@prefix}.CONSUMER.INFO.#{stream}.#{consumer}" - result = api_request(req_subject, '', params) + result = api_request(req_subject, "", params) JetStream::API::ConsumerInfo.new(result) end @@ -182,12 +183,12 @@ def consumer_info(stream, consumer, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [Boolean] - def delete_consumer(stream, consumer, params={}) - raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? or stream.empty? - raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? or consumer.empty? + def delete_consumer(stream, consumer, params = {}) + raise JetStream::Error::InvalidStreamName.new("nats: invalid stream name") if stream.nil? || stream.empty? + raise JetStream::Error::InvalidConsumerName.new("nats: invalid consumer name") if consumer.nil? || consumer.empty? req_subject = "#{@prefix}.CONSUMER.DELETE.#{stream}.#{consumer}" - result = api_request(req_subject, '', params) + result = api_request(req_subject, "", params) result[:success] end @@ -197,9 +198,9 @@ def delete_consumer(stream, consumer, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [String] The name of the JetStream stream for the subject. - def find_stream_name_by_subject(subject, params={}) + def find_stream_name_by_subject(subject, params = {}) req_subject = "#{@prefix}.STREAM.NAMES" - req = { subject: subject } + req = {subject: subject} result = api_request(req_subject, req.to_json, params) raise JetStream::Error::NotFound unless result[:streams] @@ -213,23 +214,22 @@ def find_stream_name_by_subject(subject, params={}) # @option seq [Integer] Sequence number of a message. # @option subject [String] Subject of the message. # @option direct [Boolean] Use direct mode to for faster access (requires NATS v2.9.0) - def get_msg(stream_name, params={}) + def get_msg(stream_name, params = {}) req = {} - case - when params[:next] + if params[:next] req[:seq] = params[:seq] req[:next_by_subj] = params[:subject] - when params[:seq] + elsif params[:seq] req[:seq] = params[:seq] - when params[:subject] + elsif params[:subject] req[:last_by_subj] = params[:subject] end data = req.to_json if params[:direct] - if params[:subject] and not params[:seq] + if params[:subject] && !(params[:seq]) # last_by_subject type request requires no payload. - data = '' + data = "" req_subject = "#{@prefix}.DIRECT.GET.#{stream_name}.#{params[:subject]}" else req_subject = "#{@prefix}.DIRECT.GET.#{stream_name}" @@ -238,16 +238,14 @@ def get_msg(stream_name, params={}) req_subject = "#{@prefix}.STREAM.MSG.GET.#{stream_name}" end resp = api_request(req_subject, data, direct: params[:direct]) - msg = if params[:direct] - _lift_msg_to_raw_msg(resp) - else - JetStream::API::RawStreamMsg.new(resp[:message]) - end - - msg + if params[:direct] + _lift_msg_to_raw_msg(resp) + else + JetStream::API::RawStreamMsg.new(resp[:message]) + end end - def get_last_msg(stream_name, subject, params={}) + def get_last_msg(stream_name, subject, params = {}) params[:subject] = subject get_msg(stream_name, params) end @@ -258,20 +256,20 @@ def account_info private - def api_request(req_subject, req="", params={}) + def api_request(req_subject, req = "", params = {}) params[:timeout] ||= @opts[:timeout] msg = begin - @nc.request(req_subject, req, **params) - rescue NATS::IO::NoRespondersError - raise JetStream::Error::ServiceUnavailable - end + @nc.request(req_subject, req, **params) + rescue NATS::IO::NoRespondersError + raise JetStream::Error::ServiceUnavailable + end result = if params[:direct] - msg - else - JSON.parse(msg.data, symbolize_names: true) - end - if result.is_a?(Hash) and result[:error] + msg + else + JSON.parse(msg.data, symbolize_names: true) + end + if result.is_a?(Hash) && result[:error] raise JS.from_error(result[:error]) end @@ -279,21 +277,21 @@ def api_request(req_subject, req="", params={}) end def _lift_msg_to_raw_msg(msg) - if msg.header and msg.header['Status'] - status = msg.header['Status'] - if status == '404' + if msg.header && msg.header["Status"] + status = msg.header["Status"] + if status == "404" raise ::NATS::JetStream::Error::NotFound.new else raise JS.from_msg(msg) end end - subject = msg.header['Nats-Subject'] - seq = msg.header['Nats-Sequence'] + subject = msg.header["Nats-Subject"] + seq = msg.header["Nats-Sequence"] raw_msg = JetStream::API::RawStreamMsg.new( subject: subject, seq: seq, - headers: msg.header, - ) + headers: msg.header + ) raw_msg.data = msg.data raw_msg diff --git a/lib/nats/io/jetstream/msg.rb b/lib/nats/io/jetstream/msg.rb index 25ab490..bd5748d 100644 --- a/lib/nats/io/jetstream/msg.rb +++ b/lib/nats/io/jetstream/msg.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +14,9 @@ # limitations under the License. # -require_relative 'msg/ack' -require_relative 'msg/ack_methods' -require_relative 'msg/metadata' +require_relative "msg/ack" +require_relative "msg/ack_methods" +require_relative "msg/metadata" module NATS class JetStream @@ -23,4 +25,4 @@ class JetStream module Msg end end -end \ No newline at end of file +end diff --git a/lib/nats/io/jetstream/msg/ack.rb b/lib/nats/io/jetstream/msg/ack.rb index f65b063..a195bd4 100644 --- a/lib/nats/io/jetstream/msg/ack.rb +++ b/lib/nats/io/jetstream/msg/ack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,19 +18,21 @@ module NATS class JetStream module Msg module Ack + # rubocop:disable Naming/ConstantName + # Ack types - Ack = ("+ACK".freeze) - Nak = ("-NAK".freeze) - Progress = ("+WPI".freeze) - Term = ("+TERM".freeze) + Ack = "+ACK" + Nak = "-NAK" + Progress = "+WPI" + Term = "+TERM" - Empty = (''.freeze) - DotSep = ('.'.freeze) - NoDomainName = ('_'.freeze) + Empty = "" + DotSep = "." + NoDomainName = "_" # Position - Prefix0 = ('$JS'.freeze) - Prefix1 = ('ACK'.freeze) + Prefix0 = "$JS" + Prefix1 = "ACK" Domain = 2 AccHash = 3 Stream = 4 @@ -50,6 +54,8 @@ module Ack V2TokenCounts = 12 SequencePair = Struct.new(:stream, :consumer) + + # rubocop:enable Naming/ConstantName end private_constant :Ack end diff --git a/lib/nats/io/jetstream/msg/ack_methods.rb b/lib/nats/io/jetstream/msg/ack_methods.rb index 06bb4e8..e792bee 100644 --- a/lib/nats/io/jetstream/msg/ack_methods.rb +++ b/lib/nats/io/jetstream/msg/ack_methods.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,10 +22,10 @@ def ack(**params) ensure_is_acked_once! resp = if params[:timeout] - @nc.request(@reply, Ack::Ack, **params) - else - @nc.publish(@reply, Ack::Ack) - end + @nc.request(@reply, Ack::Ack, **params) + else + @nc.publish(@reply, Ack::Ack) + end @sub.synchronize { @ackd = true } resp @@ -42,15 +44,15 @@ def ack_sync(**params) def nak(**params) ensure_is_acked_once! payload = if params[:delay] - payload = "#{Ack::Nak} #{{ delay: params[:delay] }.to_json}" - else - Ack::Nak - end + "#{Ack::Nak} #{{delay: params[:delay]}.to_json}" + else + Ack::Nak + end resp = if params[:timeout] - @nc.request(@reply, payload, **params) - else - @nc.publish(@reply, payload) - end + @nc.request(@reply, payload, **params) + else + @nc.publish(@reply, payload) + end @sub.synchronize { @ackd = true } resp @@ -60,10 +62,10 @@ def term(**params) ensure_is_acked_once! resp = if params[:timeout] - @nc.request(@reply, Ack::Term, **params) - else - @nc.publish(@reply, Ack::Term) - end + @nc.request(@reply, Ack::Term, **params) + else + @nc.publish(@reply, Ack::Term) + end @sub.synchronize { @ackd = true } resp @@ -91,15 +93,14 @@ def parse_metadata(reply) tokens = reply.split(Ack::DotSep) n = tokens.count - case - when n < Ack::V1TokenCounts || (n > Ack::V1TokenCounts and n < Ack::V2TokenCounts) + if n < Ack::V1TokenCounts || ((n > Ack::V1TokenCounts) && (n < Ack::V2TokenCounts)) raise NotJSMessage.new("nats: not a jetstream message") - when tokens[0] != Ack::Prefix0 || tokens[1] != Ack::Prefix1 + elsif tokens[0] != Ack::Prefix0 || tokens[1] != Ack::Prefix1 raise NotJSMessage.new("nats: not a jetstream message") - when n == Ack::V1TokenCounts + elsif n == Ack::V1TokenCounts tokens.insert(Ack::Domain, Ack::Empty) tokens.insert(Ack::AccHash, Ack::Empty) - when tokens[Ack::Domain] == Ack::NoDomainName + elsif tokens[Ack::Domain] == Ack::NoDomainName tokens[Ack::Domain] = Ack::Empty end diff --git a/lib/nats/io/jetstream/msg/metadata.rb b/lib/nats/io/jetstream/msg/metadata.rb index 10350f1..866a5fe 100644 --- a/lib/nats/io/jetstream/msg/metadata.rb +++ b/lib/nats/io/jetstream/msg/metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +14,7 @@ # limitations under the License. # -require 'time' +require "time" module NATS class JetStream @@ -21,13 +23,13 @@ class Metadata attr_reader :sequence, :num_delivered, :num_pending, :timestamp, :stream, :consumer, :domain def initialize(opts) - @sequence = Ack::SequencePair.new(opts[Ack::StreamSeq].to_i, opts[Ack::ConsumerSeq].to_i) - @domain = opts[Ack::Domain] + @sequence = Ack::SequencePair.new(opts[Ack::StreamSeq].to_i, opts[Ack::ConsumerSeq].to_i) + @domain = opts[Ack::Domain] @num_delivered = opts[Ack::NumDelivered].to_i - @num_pending = opts[Ack::NumPending].to_i - @timestamp = Time.at((opts[Ack::Timestamp].to_i / 1_000_000_000.0)) - @stream = opts[Ack::Stream] - @consumer = opts[Ack::Consumer] + @num_pending = opts[Ack::NumPending].to_i + @timestamp = Time.at((opts[Ack::Timestamp].to_i / 1_000_000_000.0)) + @stream = opts[Ack::Stream] + @consumer = opts[Ack::Consumer] # TODO: Not exposed in Go client either right now. # account = opts[Ack::AccHash] end diff --git a/lib/nats/io/jetstream/pull_subscription.rb b/lib/nats/io/jetstream/pull_subscription.rb index c0330f0..d50bc0d 100644 --- a/lib/nats/io/jetstream/pull_subscription.rb +++ b/lib/nats/io/jetstream/pull_subscription.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +14,7 @@ # limitations under the License. # -require_relative 'errors' +require_relative "errors" module NATS class JetStream @@ -39,7 +41,7 @@ class JetStream module PullSubscription # next_msg is not available for pull based subscriptions. # @raise [NATS::JetStream::Error] - def next_msg(params={}) + def next_msg(params = {}) raise ::NATS::JetStream::Error.new("nats: pull subscription cannot use next_msg") end @@ -49,7 +51,7 @@ def next_msg(params={}) # @param params [Hash] Options to customize the fetch request. # @option params [Float] :timeout Duration of the fetch request before it expires. # @return [Array] - def fetch(batch=1, params={}) + def fetch(batch = 1, params = {}) if batch < 1 raise ::NATS::JetStream::Error.new("nats: invalid batch size") end @@ -80,7 +82,7 @@ def fetch(batch=1, params={}) if JS.is_status_msg(msg) case msg.header["Status"] when JS::Status::NoMsgs - msg = nil + nil when JS::Status::RequestTimeout # Skip else @@ -194,26 +196,25 @@ def fetch(batch=1, params={}) # Check if have not received yet a single message. duration = MonotonicTime.since(start_time) - if msgs.empty? and duration > timeout + if msgs.empty? && (duration > timeout) raise NATS::Timeout.new("nats: fetch timeout") end needed = batch - msgs.count - while needed > 0 and MonotonicTime.since(start_time) < timeout + while (needed > 0) && (MonotonicTime.since(start_time) < timeout) duration = MonotonicTime.since(start_time) # Wait for the rest of the messages. synchronize do - # Wait until there is a message delivered. if @pending_queue.empty? deadline = timeout - duration - wait_start = MonotonicTime.now + MonotonicTime.now wait_for_msgs_cond.wait(deadline) if deadline > 0 duration = MonotonicTime.since(start_time) - if msgs.empty? && @pending_queue.empty? and duration > timeout + if msgs.empty? && @pending_queue.empty? && (duration > timeout) raise NATS::Timeout.new("nats: fetch timeout") end end @@ -256,7 +257,7 @@ def fetch(batch=1, params={}) # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer. - def consumer_info(params={}) + def consumer_info(params = {}) @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params) end end diff --git a/lib/nats/io/jetstream/push_subscription.rb b/lib/nats/io/jetstream/push_subscription.rb index 5bb911d..824375a 100644 --- a/lib/nats/io/jetstream/push_subscription.rb +++ b/lib/nats/io/jetstream/push_subscription.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,7 +35,7 @@ module PushSubscription # @param params [Hash] Options to customize API request. # @option params [Float] :timeout Time to wait for response. # @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer. - def consumer_info(params={}) + def consumer_info(params = {}) @jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params) end end diff --git a/lib/nats/io/kv.rb b/lib/nats/io/kv.rb index faba2a4..b76ca72 100644 --- a/lib/nats/io/kv.rb +++ b/lib/nats/io/kv.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,10 +14,10 @@ # limitations under the License. # -require_relative 'kv/api' -require_relative 'kv/bucket_status' -require_relative 'kv/errors' -require_relative 'kv/manager' +require_relative "kv/api" +require_relative "kv/bucket_status" +require_relative "kv/errors" +require_relative "kv/manager" module NATS class KeyValue @@ -26,7 +28,7 @@ class KeyValue MSG_ROLLUP_ALL = "all" ROLLUP = "Nats-Rollup" - def initialize(opts={}) + def initialize(opts = {}) @name = opts[:name] @stream = opts[:stream] @pre = opts[:pre] @@ -35,7 +37,7 @@ def initialize(opts={}) end # get returns the latest value for the key. - def get(key, params={}) + def get(key, params = {}) entry = nil begin entry = _get(key, params) @@ -46,19 +48,19 @@ def get(key, params={}) entry end - def _get(key, params={}) + def _get(key, params = {}) msg = nil subject = "#{@pre}#{key}" - if params[:revision] - msg = @js.get_msg(@stream, - seq: params[:revision], - direct: @direct) + msg = if params[:revision] + @js.get_msg(@stream, + seq: params[:revision], + direct: @direct) else - msg = @js.get_msg(@stream, - subject: subject, - seq: params[:revision], - direct: @direct) + @js.get_msg(@stream, + subject: subject, + seq: params[:revision], + direct: @direct) end entry = Entry.new(bucket: @name, key: key, value: msg.data, revision: msg.seq) @@ -70,9 +72,9 @@ def _get(key, params={}) ) end - if not msg.headers.nil? + if !msg.headers.nil? op = msg.headers[KV_OP] - if op == KV_DEL or op == KV_PURGE + if (op == KV_DEL) || (op == KV_PURGE) raise KeyDeletedError.new(entry: entry, op: op) end end @@ -122,7 +124,7 @@ def create(key, value) EXPECTED_LAST_SUBJECT_SEQUENCE = "Nats-Expected-Last-Subject-Sequence" # update will update the value iff the latest revision matches. - def update(key, value, params={}) + def update(key, value, params = {}) hdrs = {} last = (params[:last] ||= 0) hdrs[EXPECTED_LAST_SUBJECT_SEQUENCE] = last.to_s @@ -141,7 +143,7 @@ def update(key, value, params={}) end # delete will place a delete marker and remove all previous revisions. - def delete(key, params={}) + def delete(key, params = {}) hdrs = {} hdrs[KV_OP] = KV_DEL last = (params[:last] ||= 0) @@ -168,10 +170,10 @@ def status end Entry = Struct.new(:bucket, :key, :value, :revision, :delta, :created, :operation, keyword_init: true) do - def initialize(opts={}) + def initialize(opts = {}) rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super end end end diff --git a/lib/nats/io/kv/api.rb b/lib/nats/io/kv/api.rb index 9b36c17..5de9841 100644 --- a/lib/nats/io/kv/api.rb +++ b/lib/nats/io/kv/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -27,11 +29,12 @@ module API :placement, :republish, :direct, - keyword_init: true) do - def initialize(opts={}) + keyword_init: true + ) do + def initialize(opts = {}) rem = opts.keys - members opts.delete_if { |k| rem.include?(k) } - super(opts) + super end end end diff --git a/lib/nats/io/kv/bucket_status.rb b/lib/nats/io/kv/bucket_status.rb index 56069bc..671b39c 100644 --- a/lib/nats/io/kv/bucket_status.rb +++ b/lib/nats/io/kv/bucket_status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/nats/io/kv/errors.rb b/lib/nats/io/kv/errors.rb index 3900bfb..62a8440 100644 --- a/lib/nats/io/kv/errors.rb +++ b/lib/nats/io/kv/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +14,7 @@ # limitations under the License. # -require_relative '../errors' +require_relative "../errors" module NATS class KeyValue @@ -21,7 +23,7 @@ class Error < NATS::Error; end # When a key is not found. class KeyNotFoundError < Error attr_reader :entry, :op - def initialize(params={}) + def initialize(params = {}) @entry = params[:entry] @op = params[:op] @message = params[:message] @@ -52,6 +54,7 @@ class KeyWrongLastSequenceError < Error def initialize(msg) @msg = msg end + def to_s "nats: #{@msg}" end diff --git a/lib/nats/io/kv/manager.rb b/lib/nats/io/kv/manager.rb index 439c69d..b79dfda 100644 --- a/lib/nats/io/kv/manager.rb +++ b/lib/nats/io/kv/manager.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -36,11 +38,11 @@ def key_value(bucket) end def create_key_value(config) - config = if not config.is_a?(KeyValue::API::KeyValueConfig) - KeyValue::API::KeyValueConfig.new(config) - else - config - end + config = if !config.is_a?(KeyValue::API::KeyValueConfig) + KeyValue::API::KeyValueConfig.new(config) + else + config + end config.history ||= 1 config.replicas ||= 1 duplicate_window = 2 * 60 # 2 minutes @@ -68,8 +70,8 @@ def create_key_value(config) max_msgs_per_subject: config.history, num_replicas: config.replicas, storage: config.storage, - republish: config.republish, - ) + republish: config.republish + ) si = add_stream(stream) KeyValue.new( diff --git a/lib/nats/io/msg.rb b/lib/nats/io/msg.rb index 59126a7..5324347 100644 --- a/lib/nats/io/msg.rb +++ b/lib/nats/io/msg.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -require_relative 'jetstream' +require_relative "jetstream" module NATS class Msg @@ -20,26 +22,26 @@ class Msg # Enhance it with ack related methods from JetStream to ack msgs. include JetStream::Msg::AckMethods - def initialize(opts={}) + def initialize(opts = {}) @subject = opts[:subject] - @reply = opts[:reply] - @data = opts[:data] - @header = opts[:header] - @nc = opts[:nc] - @sub = opts[:sub] - @ackd = false - @meta = nil + @reply = opts[:reply] + @data = opts[:data] + @header = opts[:header] + @nc = opts[:nc] + @sub = opts[:sub] + @ackd = false + @meta = nil end - def respond(data='') + def respond(data = "") return unless @nc - if self.header - dmsg = self.dup - dmsg.subject = self.reply + if header + dmsg = dup + dmsg.subject = reply dmsg.data = data @nc.publish_msg(dmsg) else - @nc.publish(self.reply, data) + @nc.publish(reply, data) end end @@ -50,7 +52,7 @@ def respond_msg(msg) def inspect hdr = ", header=#{@header}" if @header - dot = '...' if @data.length > 10 + dot = "..." if @data.length > 10 dat = "#{data.slice(0, 10)}#{dot}" "#" end diff --git a/lib/nats/io/parser.rb b/lib/nats/io/parser.rb index 01dfe83..079cb27 100644 --- a/lib/nats/io/parser.rb +++ b/lib/nats/io/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,27 +16,26 @@ module NATS module Protocol - - MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i - HMSG = /\AHMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([\d]+)\s+(\d+)\r\n/i - OK = /\A\+OK\s*\r\n/i - ERR = /\A-ERR\s+('.+')?\r\n/i - PING = /\APING\s*\r\n/i - PONG = /\APONG\s*\r\n/i - INFO = /\AINFO\s+([^\r\n]+)\r\n/i - UNKNOWN = /\A(.*)\r\n/ + MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i + HMSG = /\AHMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([\d]+)\s+(\d+)\r\n/i + OK = /\A\+OK\s*\r\n/i + ERR = /\A-ERR\s+('.+')?\r\n/i + PING = /\APING\s*\r\n/i + PONG = /\APONG\s*\r\n/i + INFO = /\AINFO\s+([^\r\n]+)\r\n/i + UNKNOWN = /\A(.*)\r\n/ AWAITING_CONTROL_LINE = 1 - AWAITING_MSG_PAYLOAD = 2 + AWAITING_MSG_PAYLOAD = 2 - CR_LF = ("\r\n".freeze) - CR_LF_SIZE = (CR_LF.bytesize) + CR_LF = "\r\n" + CR_LF_SIZE = CR_LF.bytesize - PING_REQUEST = ("PING#{CR_LF}".freeze) - PONG_RESPONSE = ("PONG#{CR_LF}".freeze) + PING_REQUEST = "PING#{CR_LF}".freeze + PONG_RESPONSE = "PONG#{CR_LF}".freeze - SUB_OP = ('SUB'.freeze) - EMPTY_MSG = (''.freeze) + SUB_OP = "SUB" + EMPTY_MSG = "" class Parser def initialize(nc) @@ -55,7 +56,7 @@ def reset! def parse(data) @buf = @buf ? @buf << data : data - while (@buf) + while @buf case @parse_state when AWAITING_CONTROL_LINE case @buf @@ -91,23 +92,22 @@ def parse(data) # If we are here we do not have a complete line yet that we understand. return end - @buf = nil if (@buf && @buf.empty?) + @buf = nil if @buf && @buf.empty? when AWAITING_MSG_PAYLOAD - return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE)) + return unless @needed && @buf.bytesize >= (@needed + CR_LF_SIZE) if @header_needed hbuf = @buf.slice(0, @header_needed) - payload = @buf.slice(@header_needed, (@needed-@header_needed)) + payload = @buf.slice(@header_needed, (@needed - @header_needed)) @nc.send(:process_msg, @sub, @sid, @reply, payload, hbuf) - @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize) else @nc.send(:process_msg, @sub, @sid, @reply, @buf.slice(0, @needed), nil) - @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize) end + @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize) @sub = @sid = @reply = @needed = @header_needed = nil @parse_state = AWAITING_CONTROL_LINE - @buf = nil if (@buf && @buf.empty?) + @buf = nil if @buf && @buf.empty? end end end diff --git a/lib/nats/io/rails.rb b/lib/nats/io/rails.rb index 9d762c8..f0cf11c 100644 --- a/lib/nats/io/rails.rb +++ b/lib/nats/io/rails.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails" module NATS diff --git a/lib/nats/io/subscription.rb b/lib/nats/io/subscription.rb index 34bbc14..52cb8da 100644 --- a/lib/nats/io/subscription.rb +++ b/lib/nats/io/subscription.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2021 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,22 +15,21 @@ # module NATS - # A Subscription represents interest in a given subject. - # + # # @example Create NATS subscription with callback. # require 'nats/client' - # + # # nc = NATS.connect("demo.nats.io") # sub = nc.subscribe("foo") do |msg| # puts "Received [#{msg.subject}]: #{}" # end - # + # class Subscription include MonitorMixin attr_accessor :subject, :queue, :future, :callback, :response, :received, :max, :pending, :sid - attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond, :concurrency_semaphore + attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond attr_accessor :pending_msgs_limit, :pending_bytes_limit attr_accessor :nc attr_accessor :jsi @@ -36,22 +37,22 @@ class Subscription def initialize(**opts) super() # required to initialize monitor - @subject = '' - @queue = nil - @future = nil + @subject = "" + @queue = nil + @future = nil @callback = nil @response = nil @received = 0 - @max = nil - @pending = nil - @sid = nil - @nc = nil - @closed = nil + @max = nil + @pending = nil + @sid = nil + @nc = nil + @closed = nil # State from async subscriber messages delivery - @pending_queue = nil - @pending_size = 0 - @pending_msgs_limit = nil + @pending_queue = nil + @pending_size = 0 + @pending_msgs_limit = nil @pending_bytes_limit = nil # Sync subscriber @@ -78,22 +79,22 @@ def concurrency_semaphore # Auto unsubscribes the server by sending UNSUB command and throws away # subscription in case already present and has received enough messages. - def unsubscribe(opt_max=nil) + def unsubscribe(opt_max = nil) @nc.send(:unsubscribe, self, opt_max) end # next_msg blocks and waiting for the next message to be received. - def next_msg(opts={}) + def next_msg(opts = {}) timeout = opts[:timeout] ||= 0.5 synchronize do - return @pending_queue.pop if not @pending_queue.empty? + return @pending_queue.pop if !@pending_queue.empty? # Wait for a bit until getting a signal. - MonotonicTime::with_nats_timeout(timeout) do + MonotonicTime.with_nats_timeout(timeout) do wait_for_msgs_cond.wait(timeout) end - if not @pending_queue.empty? + if !@pending_queue.empty? return @pending_queue.pop else raise NATS::Timeout diff --git a/lib/nats/io/version.rb b/lib/nats/io/version.rb index 2a9c905..433c163 100644 --- a/lib/nats/io/version.rb +++ b/lib/nats/io/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2022 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +17,7 @@ module NATS module IO # VERSION is the version of the client announced on CONNECT to the server. - VERSION = "2.4.0".freeze + VERSION = "2.4.0" # LANG is the lang runtime of the client announced on CONNECT to the server. LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze diff --git a/lib/nats/io/websocket.rb b/lib/nats/io/websocket.rb index 22cb3f4..e9a0132 100644 --- a/lib/nats/io/websocket.rb +++ b/lib/nats/io/websocket.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + begin - require 'websocket' + require "websocket" rescue LoadError raise LoadError, "Please add `websocket` gem to your Gemfile to connect to NATS via WebSocket." end @@ -15,7 +17,7 @@ class HandshakeError < RuntimeError; end attr_accessor :socket - def initialize(options={}) + def initialize(options = {}) super end @@ -44,31 +46,31 @@ def setup_tls! super end - def read(max_bytes=MAX_SOCKET_READ_BYTES, deadline=nil) + def read(max_bytes = MAX_SOCKET_READ_BYTES, deadline = nil) data = super @frame << data result = [] - while msg = @frame.next + while (msg = @frame.next) result << msg end result.join end - def read_line(deadline=nil) + def read_line(deadline = nil) data = super @frame << data result = [] - while msg = @frame.next + while (msg = @frame.next) result << msg end result.join end - def write(data, deadline=nil) + def write(data, deadline = nil) raise HandshakeError, "Attempted to write to socket while WebSocket handshake is in progress" unless @handshaked frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: :binary, version: @handshake.version) - super frame.to_s + super(frame.to_s) end end end diff --git a/lib/nats/nuid.rb b/lib/nats/nuid.rb index 52bc6db..2c9d669 100644 --- a/lib/nats/nuid.rb +++ b/lib/nats/nuid.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright 2016-2018 The NATS Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,27 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. # -require 'securerandom' +require "securerandom" module NATS class NUID - DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('') - BASE = 62 + DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".chars + BASE = 62 PREFIX_LENGTH = 12 - SEQ_LENGTH = 10 - TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH - MAX_SEQ = BASE**10 - MIN_INC = 33 - MAX_INC = 333 + SEQ_LENGTH = 10 + TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH + MAX_SEQ = BASE**10 + MIN_INC = 33 + MAX_INC = 333 INC = MAX_INC - MIN_INC Ractor.make_shareable(DIGITS) if defined?(Ractor) def initialize - @prand = Random.new - @seq = @prand.rand(MAX_SEQ) - @inc = MIN_INC + @prand.rand(INC) - @prefix = '' + @prand = Random.new + @seq = @prand.rand(MAX_SEQ) + @inc = MIN_INC + @prand.rand(INC) + @prefix = "" randomize_prefix! end @@ -46,22 +48,31 @@ def next # Do this inline 10 times to avoid even more extra allocs, # then use string interpolation of everything which works # faster for doing concat. - s_10 = DIGITS[l % BASE]; + s_10 = DIGITS[l % BASE] # Ugly, but parallel assignment is slightly faster here... - s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = \ - (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]),\ - (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]),\ - (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]) + s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = + (l /= BASE + DIGITS[l % BASE]), (l /= BASE + DIGITS[l % BASE]), (l /= BASE + DIGITS[l % BASE]), + (l /= BASE + DIGITS[l % BASE]), (l /= BASE + DIGITS[l % BASE]), (l /= BASE + DIGITS[l % BASE]), + (l /= BASE + DIGITS[l % BASE]), (l /= BASE + DIGITS[l % BASE]), (l /= BASE + DIGITS[l % BASE]) "#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}" end def randomize_prefix! - @prefix = \ - SecureRandom.random_bytes(PREFIX_LENGTH).each_byte - .reduce('') do |prefix, n| - prefix << DIGITS[n % BASE] - end + @prefix = + SecureRandom.random_bytes(PREFIX_LENGTH).each_byte + .reduce("".dup) do |prefix, n| + prefix << DIGITS[n % BASE] + end end private diff --git a/nats-pure.gemspec b/nats-pure.gemspec index 6eb8a7a..6c896f6 100644 --- a/nats-pure.gemspec +++ b/nats-pure.gemspec @@ -14,18 +14,18 @@ # limitations under the License. # -require_relative 'lib/nats/io/version' +require_relative "lib/nats/io/version" Gem::Specification.new do |s| - s.name = 'nats-pure' + s.name = "nats-pure" s.version = NATS::IO::VERSION - s.summary = 'NATS is an open-source, high-performance, lightweight cloud messaging system.' - s.homepage = 'https://nats.io' - s.description = 'NATS is an open-source, high-performance, lightweight cloud messaging system.' - s.licenses = ['Apache-2.0'] + s.summary = "NATS is an open-source, high-performance, lightweight cloud messaging system." + s.homepage = "https://nats.io" + s.description = "NATS is an open-source, high-performance, lightweight cloud messaging system." + s.licenses = ["Apache-2.0"] - s.authors = ['Waldemar Quevedo'] - s.email = ['wally@synadia.com'] + s.authors = ["Waldemar Quevedo"] + s.email = ["wally@synadia.com"] s.metadata = { "bug_tracker_uri" => "https://github.com/nats-io/nats-pure.rb/issues", @@ -37,7 +37,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 3.0" - s.require_paths = ['lib'] + s.require_paths = ["lib"] s.files = Dir.glob("lib/**/*.rb") + Dir.glob("sig/**/*.rbs") + %w[README.md LICENSE CHANGELOG.md] diff --git a/spec/auth_spec.rb b/spec/auth_spec.rb index 59bbca3..59e8a03 100644 --- a/spec/auth_spec.rb +++ b/spec/auth_spec.rb @@ -1,88 +1,69 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +# frozen_string_literal: true -require 'spec_helper' -require 'fileutils' +describe "Client - Authorization" do + let(:auth_server) { "nats://secret:password@127.0.0.1:9222" } + let(:auth_server_pid) { "/tmp/nats_authorization.pid" } + let(:auth_server_no_cred) { "nats://127.0.0.1:9222" } -describe 'Client - Authorization' do + let(:another_auth_server) { "nats://secret:secret@127.0.0.1:9223" } + let(:another_auth_server_pid) { "/tmp/nats_another_authorization.pid" } - USER = 'secret' - PASS = 'password' + let(:token_auth_server) { "nats://secret@127.0.0.1:9222" } + let(:wrong_token_auth_server) { "nats://other@127.0.0.1:9222" } - TEST_AUTH_SERVER = "nats://#{USER}:#{PASS}@127.0.0.1:9222" - TEST_AUTH_SERVER_PID = '/tmp/nats_authorization.pid' - TEST_AUTH_SERVER_NO_CRED = 'nats://127.0.0.1:9222' - - TEST_ANOTHER_AUTH_SERVER = "nats://#{USER}:secret@127.0.0.1:9223" - TEST_ANOTHER_AUTH_SERVER_PID = '/tmp/nats_another_authorization.pid' - - TEST_TOKEN_AUTH_SERVER = "nats://#{USER}@127.0.0.1:9222" - TEST_WRONG_TOKEN_AUTH_SERVER = "nats://other@127.0.0.1:9222" - - after (:each) do + after do @server_control.kill_server - FileUtils.rm_f TEST_AUTH_SERVER_PID + FileUtils.rm_f auth_server_pid end - it 'should connect to an authorized server with proper credentials' do - @server_control = NatsServerControl.new(TEST_AUTH_SERVER, TEST_AUTH_SERVER_PID) + it "should connect to an authorized server with proper credentials" do + @server_control = NatsServerControl.new(auth_server, auth_server_pid) @server_control.start_server nats = NATS::IO::Client.new expect do - nats.connect(:servers => [TEST_AUTH_SERVER], :reconnect => false) + nats.connect(servers: [auth_server], reconnect: false) nats.flush end.to_not raise_error nats.close end - it 'should connect to an authorized server with token' do - @server_control = NatsServerControl.new(TEST_TOKEN_AUTH_SERVER, TEST_AUTH_SERVER_PID) + it "should connect to an authorized server with token" do + @server_control = NatsServerControl.new(token_auth_server, auth_server_pid) @server_control.start_server nats = NATS::IO::Client.new expect do - nats.connect(:servers => [TEST_TOKEN_AUTH_SERVER], :reconnect => false) + nats.connect(servers: [token_auth_server], reconnect: false) nats.flush end.to_not raise_error nats.close expect do - nc = NATS.connect(TEST_TOKEN_AUTH_SERVER, reconnect: false) + nc = NATS.connect(token_auth_server, reconnect: false) nc.flush nc.close end.to_not raise_error expect do - nc = NATS.connect(TEST_AUTH_SERVER_NO_CRED, reconnect: false) + nc = NATS.connect(auth_server_no_cred, reconnect: false) nc.flush nc.close end.to raise_error(NATS::IO::AuthError) expect do - nc = NATS.connect(TEST_AUTH_SERVER_NO_CRED, reconnect: false, auth_token: 'secret') + nc = NATS.connect(auth_server_no_cred, reconnect: false, auth_token: "secret") nc.flush nc.close end.to_not raise_error expect do - nc = NATS.connect(TEST_WRONG_TOKEN_AUTH_SERVER, reconnect: false, auth_token: 'secret') + nc = NATS.connect(wrong_token_auth_server, reconnect: false, auth_token: "secret") nc.flush nc.close end.to_not raise_error end - it 'should fail to connect to an authorized server without proper credentials' do - @server_control = NatsServerControl.new(TEST_AUTH_SERVER, TEST_AUTH_SERVER_PID) + it "should fail to connect to an authorized server without proper credentials" do + @server_control = NatsServerControl.new(auth_server, auth_server_pid) @server_control.start_server nats = NATS::IO::Client.new errors = [] @@ -95,7 +76,7 @@ errors << e end nats.connect({ - servers: [TEST_AUTH_SERVER_NO_CRED], + servers: [auth_server_no_cred], reconnect: false }) end.to raise_error(NATS::IO::AuthError) diff --git a/spec/client_cluster_reconnect_spec.rb b/spec/client_cluster_reconnect_spec.rb index bd70107..26500ce 100644 --- a/spec/client_cluster_reconnect_spec.rb +++ b/spec/client_cluster_reconnect_spec.rb @@ -1,60 +1,45 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -describe 'Client - Cluster reconnect' do +# frozen_string_literal: true +describe "Client - Cluster reconnect" do before(:all) do auth_options = { - 'user' => 'secret', - 'password' => 'password', - 'token' => 'asdf', - 'timeout' => 5 + "user" => "secret", + "password" => "password", + "token" => "asdf", + "timeout" => 5 } s1_config_opts = { - 'pid_file' => '/tmp/nats_cluster_s1.pid', - 'authorization' => auth_options, - 'host' => '127.0.0.1', - 'port' => 4242, - 'cluster_port' => 6222 + "pid_file" => "/tmp/nats_cluster_s1.pid", + "authorization" => auth_options, + "host" => "127.0.0.1", + "port" => 4242, + "cluster_port" => 6222 } s2_config_opts = { - 'pid_file' => '/tmp/nats_cluster_s2.pid', - 'authorization' => auth_options, - 'host' => '127.0.0.1', - 'port' => 4243, - 'cluster_port' => 6223 + "pid_file" => "/tmp/nats_cluster_s2.pid", + "authorization" => auth_options, + "host" => "127.0.0.1", + "port" => 4243, + "cluster_port" => 6223 } s3_config_opts = { - 'pid_file' => '/tmp/nats_cluster_s3.pid', - 'authorization' => auth_options, - 'host' => '127.0.0.1', - 'port' => 4244, - 'cluster_port' => 6224 + "pid_file" => "/tmp/nats_cluster_s3.pid", + "authorization" => auth_options, + "host" => "127.0.0.1", + "port" => 4244, + "cluster_port" => 6224 } nodes = [] configs = [s1_config_opts, s2_config_opts, s3_config_opts] configs.each do |config_opts| - nodes << NatsServerControl.init_with_config_from_string(%Q( - host: '#{config_opts['host']}' - port: #{config_opts['port']} - pid_file: '#{config_opts['pid_file']}' + nodes << NatsServerControl.init_with_config_from_string(%( + host: '#{config_opts["host"]}' + port: #{config_opts["port"]} + pid_file: '#{config_opts["pid_file"]}' authorization { user: '#{auth_options["user"]}' password: '#{auth_options["password"]}' @@ -62,8 +47,8 @@ } cluster { name: "TEST" - host: '#{config_opts['host']}' - port: #{config_opts['cluster_port']} + host: '#{config_opts["host"]}' + port: #{config_opts["cluster_port"]} authorization { user: foo @@ -72,7 +57,7 @@ } routes = [ - 'nats-route://foo:bar@127.0.0.1:#{s1_config_opts['cluster_port']}' + 'nats-route://foo:bar@127.0.0.1:#{s1_config_opts["cluster_port"]}' ] } ), config_opts) @@ -81,26 +66,26 @@ @s1, @s2, @s3 = nodes end - context 'with cluster fully assembled when client connects' do - before(:each) do + context "with cluster fully assembled when client connects" do + before do [@s1, @s2, @s3].each do |s| s.start_server(true) end end - after(:each) do + after do [@s1, @s2, @s3].each do |s| s.kill_server end end - it 'should connect to another server if possible before reconnect' do + it "should connect to another server if possible before reconnect" do @s3.kill_server mon = Monitor.new reconnected = mon.new_cond - nats = NATS.connect(:servers => [@s1.uri, @s2.uri], :dont_randomize_servers => true) + nats = NATS.connect(servers: [@s1.uri, @s2.uri], dont_randomize_servers: true) disconnects = 0 nats.on_disconnect do @@ -145,14 +130,14 @@ expect(closes).to eql(1) end - it 'should connect to another server if possible before reconnect using multiple uris' do + it "should connect to another server if possible before reconnect using multiple uris" do @s3.kill_server mon = Monitor.new reconnected = mon.new_cond nats = NATS::IO::Client.new - nats.connect("nats://secret:password@127.0.0.1:4242,nats://secret:password@127.0.0.1:4243", :dont_randomize_servers => true) + nats.connect("nats://secret:password@127.0.0.1:4242,nats://secret:password@127.0.0.1:4243", dont_randomize_servers: true) disconnects = 0 nats.on_disconnect do @@ -197,7 +182,7 @@ expect(closes).to eql(1) end - it 'should gracefully reconnect to another available server while publishing' do + it "should gracefully reconnect to another available server while publishing" do @s3.kill_server mon = Monitor.new @@ -242,15 +227,14 @@ msg_payload = "A" * 10_000 1000.times do |n| # Receive 100 messages initially and then failover - case - when n == 100 + if n == 100 nats.flush # Wait a bit for all messages sleep 0.5 expect(msgs.count).to eql(100) @s1.kill_server - when (n % 100 == 0) + elsif n % 100 == 0 # yield a millisecond sleep 0.001 end @@ -276,17 +260,17 @@ end end - context 'with auto discovery using seed node' do - before(:each) do + context "with auto discovery using seed node" do + before do # Only start initial seed node @s1.start_server(true) end - after(:each) do + after do @s1.kill_server end - it 'should reconnect to nodes discovered from seed server' do + it "should reconnect to nodes discovered from seed server" do # Nodes join to cluster before we try to connect [@s2, @s3].each do |s| s.start_server(true) @@ -321,7 +305,7 @@ end # Connect to first server only and trigger reconnect - nats.connect(:servers => [@s1.uri], :dont_randomize_servers => true, :reconnect => true) + nats.connect(servers: [@s1.uri], dont_randomize_servers: true, reconnect: true) expect(nats.connected_server).to eql(@s1.uri) @s1.kill_server sleep 0.2 @@ -349,8 +333,8 @@ end end - it 'should reconnect to nodes discovered from seed server with single uri' do - skip 'FIXME: flaky test' + it "should reconnect to nodes discovered from seed server with single uri" do + skip "FIXME: flaky test" # Nodes join to cluster before we try to connect [@s2, @s3].each do |s| @@ -386,7 +370,7 @@ end # Connect to first server only and trigger reconnect - nats.connect("nats://secret:password@127.0.0.1:4242", :dont_randomize_servers => true, :reconnect => true, :reconnect_time_wait => 0.5) + nats.connect("nats://secret:password@127.0.0.1:4242", dont_randomize_servers: true, reconnect: true, reconnect_time_wait: 0.5) expect(nats.connected_server.to_s).to eql(@s1.uri.to_s) @s1.kill_server sleep 0.1 @@ -414,7 +398,7 @@ end end - it 'should reconnect to nodes discovered in the cluster after first connect' do + it "should reconnect to nodes discovered in the cluster after first connect" do mon = Monitor.new reconnected = mon.new_cond @@ -444,11 +428,11 @@ # Connect to first server only and trigger reconnect nats.connect({ - :servers => [@s1.uri], - :dont_randomize_servers => true, - :user => 'secret', - :pass => 'password', - :max_reconnect_attempts => 10 + servers: [@s1.uri], + dont_randomize_servers: true, + user: "secret", + pass: "password", + max_reconnect_attempts: 10 }) expect(nats.connected_server).to eql(@s1.uri) diff --git a/spec/client_cluster_tls_reconnect_spec.rb b/spec/client_cluster_tls_reconnect_spec.rb index b250cc6..43b5077 100644 --- a/spec/client_cluster_tls_reconnect_spec.rb +++ b/spec/client_cluster_tls_reconnect_spec.rb @@ -1,63 +1,48 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -describe 'Client - Cluster TLS reconnect' do +# frozen_string_literal: true +describe "Client - Cluster TLS reconnect" do before(:all) do s1_config_opts = { - 'pid_file' => '/tmp/nats_cluster_s1.pid', - 'host' => '127.0.0.1', - 'port' => 4232, - 'cluster_port' => 6232, - 'wsport' => 8232, - 'advertise' => 'server-A.clients.nats-service.localhost:8232', - 'name' => 'server-A' + "pid_file" => "/tmp/nats_cluster_s1.pid", + "host" => "127.0.0.1", + "port" => 4232, + "cluster_port" => 6232, + "wsport" => 8232, + "advertise" => "server-A.clients.nats-service.localhost:8232", + "name" => "server-A" } s2_config_opts = { - 'pid_file' => '/tmp/nats_cluster_s2.pid', - 'host' => '127.0.0.1', - 'port' => 4233, - 'cluster_port' => 6233, - 'wsport' => 8233, - 'advertise' => 'server-B.clients.nats-service.localhost:8233', - 'name' => 'server-B' + "pid_file" => "/tmp/nats_cluster_s2.pid", + "host" => "127.0.0.1", + "port" => 4233, + "cluster_port" => 6233, + "wsport" => 8233, + "advertise" => "server-B.clients.nats-service.localhost:8233", + "name" => "server-B" } s3_config_opts = { - 'pid_file' => '/tmp/nats_cluster_s3.pid', - 'host' => '127.0.0.1', - 'port' => 4234, - 'cluster_port' => 6234, - 'wsport' => 8234, - 'advertise' => 'server-C.clients.nats-service.localhost:8234', - 'name' => 'server-C' + "pid_file" => "/tmp/nats_cluster_s3.pid", + "host" => "127.0.0.1", + "port" => 4234, + "cluster_port" => 6234, + "wsport" => 8234, + "advertise" => "server-C.clients.nats-service.localhost:8234", + "name" => "server-C" } nodes = [] configs = [s1_config_opts, s2_config_opts, s3_config_opts] configs.each do |config_opts| - nodes << NatsServerControl.init_with_config_from_string(%Q( - host: '#{config_opts['host']}' - port: #{config_opts['port']} - pid_file: '#{config_opts['pid_file']}' - server_name: '#{config_opts['name']}' + nodes << NatsServerControl.init_with_config_from_string(%( + host: '#{config_opts["host"]}' + port: #{config_opts["port"]} + pid_file: '#{config_opts["pid_file"]}' + server_name: '#{config_opts["name"]}' websocket { - port: #{config_opts['wsport']} + port: #{config_opts["wsport"]} tls { cert_file: "./spec/configs/certs/nats-service.localhost/server.pem" key_file: "./spec/configs/certs/nats-service.localhost/server-key.pem" @@ -65,7 +50,7 @@ timeout: 10 } # NOTE: Force to reconnect using any other hostname other than the initial one. - advertise: '#{config_opts['advertise']}' + advertise: '#{config_opts["advertise"]}' } tls { @@ -77,8 +62,8 @@ cluster { name: "TEST" - host: '#{config_opts['host']}' - port: #{config_opts['cluster_port']} + host: '#{config_opts["host"]}' + port: #{config_opts["cluster_port"]} authorization { user: foo @@ -87,7 +72,7 @@ } routes = [ - 'nats-route://foo:bar@127.0.0.1:#{s1_config_opts['cluster_port']}' + 'nats-route://foo:bar@127.0.0.1:#{s1_config_opts["cluster_port"]}' ] } ), config_opts) @@ -96,17 +81,17 @@ @s1, @s2, @s3 = nodes end - context 'with auto discovery using seed node' do - before(:each) do + context "with auto discovery using seed node" do + before do # Only start initial seed node @s1.start_server(true) end - after(:each) do + after do @s1.kill_server end - it 'should reconnect to nodes discovered from seed server' do + it "should reconnect to nodes discovered from seed server" do # Nodes join to cluster before we try to connect [@s2, @s3].each do |s| s.start_server(true) @@ -148,14 +133,15 @@ ctx.verify_hostname = true nats.connect("tls://server-A.clients.nats-service.localhost:4232", { - dont_randomize_servers: true, reconnect: true, tls: { - context: ctx - }}) + dont_randomize_servers: true, reconnect: true, tls: { + context: ctx + } + }) expect(nats.connected_server.to_s).to eql("tls://server-A.clients.nats-service.localhost:4232") - nats.subscribe("hello") { |msg, reply| nats.publish(reply, '') } + nats.subscribe("hello") { |msg, reply| nats.publish(reply, "") } nats.flush - nats.request("hello", 'world') + nats.request("hello", "world") @s1.kill_server sleep 0.1 @@ -168,7 +154,7 @@ expect(nats.connected_server.to_s).to_not eql("") expect(["tls://127.0.0.1:4233", "tls://127.0.0.1:4234"].include?(nats.connected_server.to_s)).to eql(true) - nats.request("hello", 'world', timeout: 1) + nats.request("hello", "world", timeout: 1) expect(reconnects).to eql(1) expect(disconnects).to eql(1) @@ -184,8 +170,8 @@ end end - it 'should reconnect to nodes discovered from seed server with WebSockets' do - skip 'flaky' + it "should reconnect to nodes discovered from seed server with WebSockets" do + skip "flaky" # Nodes join to cluster before we try to connect [@s2, @s3].each do |s| s.start_server(true) @@ -227,14 +213,15 @@ ctx.verify_hostname = true nats.connect("wss://server-A.clients.nats-service.localhost:8232", { - dont_randomize_servers: true, reconnect: true, tls: { - context: ctx - }}) + dont_randomize_servers: true, reconnect: true, tls: { + context: ctx + } + }) expect(nats.connected_server.to_s).to eql("wss://server-A.clients.nats-service.localhost:8232") - nats.subscribe("hello") { |msg, reply| nats.publish(reply, '') } + nats.subscribe("hello") { |msg, reply| nats.publish(reply, "") } nats.flush - nats.request("hello", 'world') + nats.request("hello", "world") expect(nats.connected?).to eql(true) @s1.kill_server @@ -250,7 +237,7 @@ expect(nats.instance_variable_get("@hostname")).to eql("server-A.clients.nats-service.localhost") expect(nats.connected_server.to_s).to_not eql("") expect(["wss://server-B.clients.nats-service.localhost:8233", "wss://server-C.clients.nats-service.localhost:8234"].include?(nats.connected_server.to_s)).to eql(true) - nats.request("hello", 'world', timeout: 1) + nats.request("hello", "world", timeout: 1) expect(reconnects).to eql(1) expect(disconnects).to eql(1) diff --git a/spec/client_delayed_connection_spec.rb b/spec/client_delayed_connection_spec.rb index 291af57..1104603 100644 --- a/spec/client_delayed_connection_spec.rb +++ b/spec/client_delayed_connection_spec.rb @@ -1,34 +1,19 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +# frozen_string_literal: true -require 'spec_helper' -require 'monitor' - -describe 'Client - Delayed Connection' do +describe "Client - Delayed Connection" do let(:server) do NatsServerControl.new("nats://127.0.0.1:4522", "/tmp/test-nats.pid", "--cluster nats://127.0.0.1:4248 --cluster_name test-cluster") end context "when server isn't available" do - it 'should not raise error on client init' do + it "should not raise error on client init" do expect do nc = NATS::Client.new(servers: [server.uri]) nc.close end.to_not raise_error end - it 'should raise error on connect' do + it "should raise error on connect" do expect do nc = NATS::Client.new nc.connect(servers: [server.uri]) diff --git a/spec/client_drain_spec.rb b/spec/client_drain_spec.rb index e90e609..18e2f7a 100644 --- a/spec/client_drain_spec.rb +++ b/spec/client_drain_spec.rb @@ -1,8 +1,6 @@ -require 'spec_helper' -require 'monitor' - -describe 'Client - Drain' do +# frozen_string_literal: true +describe "Client - Drain" do before(:all) do @s = NatsServerControl.new @s.start_server(true) @@ -12,7 +10,7 @@ @s.kill_server end - it 'should gracefully drain a connection' do + it "should gracefully drain a connection" do nc = NATS.connect(drain_timeout: 5) nc2 = NATS.connect @@ -31,10 +29,10 @@ reqs_started = Queue.new wait_reqs = Future.new - t = Thread.new do + Thread.new do wait_subs.wait_for(2) 40.times do |i| - ('a'..'b').each do + ("a".."b").each do payload = "REQ:#{_1}:#{i}" nc2.publish(_1, payload * 128) sleep 0.01 @@ -43,11 +41,11 @@ wait_pubs.set_result(:ok) - ('a'..'b').map do |sub| + ("a".."b").map do |sub| Thread.new do reqs_started << sub payload = "REQ:#{sub}" - msg = nc2.request(sub, payload) + nc2.request(sub, payload) end end.each(&:join) @@ -57,7 +55,7 @@ # A queue to control the speed of processing messages sub_queue = Queue.new subs = [] - ('a'..'b').each do |subject| + ("a".."b").each do |subject| sub = nc.subscribe(subject) do |msg| ft = sub_queue.pop msg.respond("OK:#{msg.data}") if msg.reply @@ -80,7 +78,8 @@ wait_pubs.wait_for(2) - reqs_started.pop; reqs_started.pop + reqs_started.pop + reqs_started.pop # Start draining process asynchronously. nc.drain @@ -92,7 +91,7 @@ expect(wait_reqs.wait_for(2)).to eql(:ok) end - it 'should report drain timeout error' do + it "should report drain timeout error" do nc = NATS.connect(drain_timeout: 0.5) nc2 = NATS.connect @@ -107,10 +106,10 @@ wait_subs = Future.new wait_pubs = Future.new - t = Thread.new do + Thread.new do wait_subs.wait_for(2) 10.times do |i| - ('a'..'b').each do + ("a".."b").each do payload = "REQ:#{_1}:#{i}" nc2.publish(_1, payload * 128) sleep 0.01 @@ -124,7 +123,7 @@ # A queue to control the speed of processing messages sub_queue = Queue.new subs = [] - ('a'..'b').each do |subject| + ("a".."b").each do |subject| sub = nc.subscribe(subject) do |msg| ft = sub_queue.pop sleep 0.01 diff --git a/spec/client_errors_spec.rb b/spec/client_errors_spec.rb index 0da50c7..eda61ef 100644 --- a/spec/client_errors_spec.rb +++ b/spec/client_errors_spec.rb @@ -1,21 +1,6 @@ -# Copyright 2016-2021 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -describe 'Client - Errors' do +# frozen_string_literal: true +describe "Client - Errors" do before(:all) do @s = NatsServerControl.new @s.start_server(true) @@ -25,7 +10,7 @@ @s.kill_server end - it 'should process errors from server' do + it "should process errors from server" do nats = NATS::IO::Client.new nats.connect(reconnect: false) @@ -54,7 +39,11 @@ # FIXME: This can fail due to timeout because # disconnection may have already occurred. - nats.flush(1) rescue nil + begin + nats.flush(1) + rescue + nil + end nats.close mon.synchronize { done.wait(3) } @@ -66,7 +55,7 @@ expect(nats.closed?).to eql(true) end - it 'should handle unknown errors in the protocol' do + it "should handle unknown errors in the protocol" do mon = Monitor.new done = mon.new_cond @@ -106,7 +95,7 @@ expect(nats.closed?).to eql(true) end - it 'should handle as async errors uncaught exceptions from callbacks' do + it "should handle as async errors uncaught exceptions from callbacks" do nats = NATS::IO::Client.new nats.connect(reconnect: false) @@ -131,7 +120,7 @@ # Trigger invalid subject server error which the client # detects so that it will disconnect - class CustomError < StandardError; end + custom_error = Class.new(StandardError) n = 0 msgs = [] @@ -139,7 +128,7 @@ class CustomError < StandardError; end n += 1 if n == 2 - raise CustomError.new("NG!") + raise custom_error.new("NG!") end msgs << payload @@ -148,7 +137,11 @@ class CustomError < StandardError; end 5.times do nats.publish("hello") end - nats.flush(1) rescue nil + begin + nats.flush(1) + rescue + nil + end # Wait for messages to be received sleep 2 @@ -158,14 +151,14 @@ class CustomError < StandardError; end expect(msgs.count).to eql(4) expect(errors.count).to eql(1) - expect(errors.first).to be_a(CustomError) + expect(errors.first).to be_a(custom_error) expect(disconnects.count).to eql(1) expect(disconnects.first).to be_nil expect(closes).to eql(1) expect(nats.closed?).to eql(true) end - it 'should handle subscriptions with slow consumers as async errors when over pending msgs limit' do + it "should handle subscriptions with slow consumers as async errors when over pending msgs limit" do nats = NATS::IO::Client.new nats.connect(reconnect: false) @@ -197,14 +190,22 @@ class CustomError < StandardError; end 20.times do |n| nats.publish("hello", "ng-#{n}") end - nats.flush(1) rescue nil + begin + nats.flush(1) + rescue + nil + end # Wait a bit for subscriber to recover sleep 2 3.times do |n| nats.publish("hello", "ok-#{n}") end - nats.flush(1) rescue nil + begin + nats.flush(1) + rescue + nil + end # Wait a bit to receive final messages sleep 0.5 @@ -223,7 +224,7 @@ class CustomError < StandardError; end expect(nats.closed?).to eql(true) end - it 'should handle subscriptions with slow consumers as async errors when over pending bytes limit' do + it "should handle subscriptions with slow consumers as async errors when over pending bytes limit" do nats = NATS::IO::Client.new nats.connect(reconnect: false) @@ -246,22 +247,30 @@ class CustomError < StandardError; end mon.synchronize { done.signal } end - data = '' + data = "" nats.subscribe("hello", pending_bytes_limit: 10) do |msg| data += msg.data sleep 2 if data.size == 10 end 20.times do - nats.publish("hello", 'A') + nats.publish("hello", "A") + end + begin + nats.flush(1) + rescue + nil end - nats.flush(1) rescue nil sleep 2 3.times do |n| - nats.publish("hello", 'B') + nats.publish("hello", "B") + end + begin + nats.flush(1) + rescue + nil end - nats.flush(1) rescue nil # Wait a bit to receive final messages sleep 0.5 @@ -277,7 +286,7 @@ class CustomError < StandardError; end expect(nats.closed?).to eql(true) end - context 'against a server which is idle' do + context "against a server which is idle" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4555 @@ -295,8 +304,7 @@ class CustomError < StandardError; end @fake_nats_server.close end - it 'should fail due to timeout errors during connect' do - msgs = [] + it "should fail due to timeout errors during connect" do errors = [] closes = 0 reconnects = 0 @@ -324,12 +332,12 @@ class CustomError < StandardError; end end expect do - nats.connect({ - :servers => ["nats://127.0.0.1:4555"], - :max_reconnect_attempts => 1, - :reconnect_time_wait => 1, - :connect_timeout => 1 - }) + nats.connect({ + servers: ["nats://127.0.0.1:4555"], + max_reconnect_attempts: 1, + reconnect_time_wait: 1, + connect_timeout: 1 + }) end.to raise_error(NATS::IO::SocketTimeoutError) expect(disconnects.count).to eql(1) @@ -348,16 +356,15 @@ class CustomError < StandardError; end end end - context 'against a server with a custom INFO line' do + context "against a server with a custom INFO line" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4556 @fake_nats_server_th = Thread.new do - loop do # Wait for a client to connect and linger client = @fake_nats_server.accept - client.puts %Q(INFO {"version":"1.3.0 foo bar","max_payload": 1048576}\r\n) + client.puts %(INFO {"version":"1.3.0 foo bar","max_payload": 1048576}\r\n) client.puts "PONG\r\n" rescue IOError # ignore client disconnects end @@ -369,12 +376,8 @@ class CustomError < StandardError; end @fake_nats_server.close end - it 'should be able to connect' do - msgs = [] + it "should be able to connect" do errors = [] - closes = 0 - reconnects = 0 - disconnects = [] nc = NATS::IO::Client.new mon = Monitor.new @@ -389,11 +392,11 @@ class CustomError < StandardError; end end expect do - nc.connect({ - :servers => ["nats://127.0.0.1:4556"], - :reconnect => false, - :connect_timeout => 1 - }) + nc.connect({ + servers: ["nats://127.0.0.1:4556"], + reconnect: false, + connect_timeout: 1 + }) end.to_not raise_error nc.close @@ -402,17 +405,16 @@ class CustomError < StandardError; end end end - context 'against a server with a custom malformed INFO line' do + context "against a server with a custom malformed INFO line" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4556 @fake_nats_server_th = Thread.new do - loop do # Wait for a client to connect and linger client = @fake_nats_server.accept begin - client.puts %Q(INFO {foo) + client.puts %(INFO {foo) ensure client.close end @@ -426,12 +428,8 @@ class CustomError < StandardError; end @fake_nats_server.close end - it 'should fail to connect' do - msgs = [] + it "should fail to connect" do errors = [] - closes = 0 - reconnects = 0 - disconnects = [] nc = NATS::IO::Client.new mon = Monitor.new @@ -446,12 +444,12 @@ class CustomError < StandardError; end end expect do - nc.connect({ - :servers => ["nats://127.0.0.1:4556"], - :reconnect => false, - :connect_timeout => 1 - }) - end.to raise_error (NATS::IO::ConnectError) + nc.connect({ + servers: ["nats://127.0.0.1:4556"], + reconnect: false, + connect_timeout: 1 + }) + end.to raise_error(NATS::IO::ConnectError) nc.close mon.synchronize { done.wait(3) } diff --git a/spec/client_fork_spec.rb b/spec/client_fork_spec.rb index 385bae3..ac54441 100644 --- a/spec/client_fork_spec.rb +++ b/spec/client_fork_spec.rb @@ -1,25 +1,10 @@ -# Copyright 2016-2023 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +# frozen_string_literal: true return unless Process.respond_to?(:fork) # Skip if fork is not supported (Windows, JRuby, etc) return unless Process.respond_to?(:_fork) # Skip if around fork callbacks are not supported (before Ruby 3.1) -require 'spec_helper' - -describe 'Client - Fork detection' do - - before(:each) do +describe "Client - Fork detection" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream-fork") @s = NatsServerControl.new("nats://127.0.0.1:4524", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @s.start_server(true) @@ -36,15 +21,15 @@ end end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - let(:options) { { } } + let(:options) { {} } let!(:nats) { NATS.connect("nats://127.0.0.1:4524", options) } - it 'should be able to publish messages from child process after forking' do + it "should be able to publish messages from child process after forking" do received = nil nats.subscribe("forked-topic") do |msg| received = msg.data @@ -61,47 +46,47 @@ nats.close end - it 'should be able to make requests messages from child process after forking' do - received = nil + it "should be able to make requests messages from child process after forking" do nats.subscribe("service") do |msg| msg.respond("pong") end resp = nats.request("service", "ping") expect(resp.data).to eq("pong") - expect(nats.stats).to eq({:in_msgs=>2, :out_msgs=>2, :in_bytes=>8, :out_bytes=>8, :reconnects=>0}) + expect(nats.stats).to eq({in_msgs: 2, out_msgs: 2, in_bytes: 8, out_bytes: 8, reconnects: 0}) pid = fork do - expect(nats.stats).to eq({:in_msgs=>0, :out_msgs=>0, :in_bytes=>0, :out_bytes=>0, :reconnects=>0}) + expect(nats.stats).to eq({in_msgs: 0, out_msgs: 0, in_bytes: 0, out_bytes: 0, reconnects: 0}) resp = nats.request("service", "ping") expect(resp.data).to eq("pong") - expect(nats.stats).to eq({:in_msgs=>1, :out_msgs=>1, :in_bytes=>4, :out_bytes=>4, :reconnects=>0}) + expect(nats.stats).to eq({in_msgs: 1, out_msgs: 1, in_bytes: 4, out_bytes: 4, reconnects: 0}) nats.publish("dev.null") - expect(nats.stats).to eq({:in_msgs=>1, :out_msgs=>2, :in_bytes=>4, :out_bytes=>4, :reconnects=>0}) - subs = nats.instance_variable_get('@subs') + expect(nats.stats).to eq({in_msgs: 1, out_msgs: 2, in_bytes: 4, out_bytes: 4, reconnects: 0}) + subs = nats.instance_variable_get("@subs") expect(subs.count).to eq(1) - spool = nats.instance_variable_get('@server_pool') + spool = nats.instance_variable_get("@server_pool") expect(spool.count).to eql(1) nats.close end Process.wait(pid) expect($?.exitstatus).to be_zero - expect(nats.stats).to eq({:in_msgs=>3, :out_msgs=>3, :in_bytes=>12, :out_bytes=>12, :reconnects=>0}) - subs = nats.instance_variable_get('@subs') + expect(nats.stats).to eq({in_msgs: 3, out_msgs: 3, in_bytes: 12, out_bytes: 12, reconnects: 0}) + subs = nats.instance_variable_get("@subs") expect(subs.count).to eq(2) - spool = nats.instance_variable_get('@server_pool') + spool = nats.instance_variable_get("@server_pool") expect(spool.count).to eql(1) nats.close end - it 'should be able to receive messages from parent process after forking' do + it "should be able to receive messages from parent process after forking" do from_child, to_parent = IO.pipe from_parent, to_child = IO.pipe pid = fork do # child process - to_child.close; from_child.close # close unused ends + to_child.close + from_child.close # close unused ends received = false nats.subscribe("forked-topic") do |msg| @@ -124,12 +109,14 @@ to_parent.write("timed out to receieve message") unless received ensure - to_parent.close; from_parent.close + to_parent.close + from_parent.close nats.close end # parent process - to_parent.close; from_parent.close # close unused ends + to_parent.close + from_parent.close # close unused ends from_child.gets nats.publish("forked-topic", "hey from the parent process") @@ -140,7 +127,8 @@ result = from_child.read expect(result).to eq("hey from the parent process") - to_child.close; from_child.close + to_child.close + from_child.close Process.wait(pid) end @@ -172,7 +160,7 @@ end context "when reconnection is disabled" do - let(:options) { { reconnect: false } } + let(:options) { {reconnect: false} } it "raises an error in child process after fork is detected" do callback_error = nil diff --git a/spec/client_nkeys_connect_spec.rb b/spec/client_nkeys_connect_spec.rb index 33dc69f..f0e32d7 100644 --- a/spec/client_nkeys_connect_spec.rb +++ b/spec/client_nkeys_connect_spec.rb @@ -1,20 +1,19 @@ -require 'spec_helper' +# frozen_string_literal: true -describe 'Client - NATS v2 Auth' do - - context 'with NKEYS and JWT' do - before(:each) do +describe "Client - NATS v2 Auth" do + context "with NKEYS and JWT" do + before do config_opts = { - 'pid_file' => '/tmp/nats_nkeys_jwt.pid', - 'host' => '127.0.0.1', - 'port' => 4722, + "pid_file" => "/tmp/nats_nkeys_jwt.pid", + "host" => "127.0.0.1", + "port" => 4722 } - @s = NatsServerControl.init_with_config_from_string(%Q( + @s = NatsServerControl.init_with_config_from_string(%( authorization { timeout: 2 } - port = #{config_opts['port']} + port = #{config_opts["port"]} operator = "./spec/configs/nkeys/op.jwt" # This is for account resolution. @@ -32,11 +31,11 @@ @s.start_server(true) end - after(:each) do + after do @s.kill_server end - it 'should connect to server and publish messages' do + it "should connect to server and publish messages" do mon = Monitor.new done = mon.new_cond @@ -46,15 +45,15 @@ nats.on_error do |e| errors << e end - nats.connect(servers: ['nats://127.0.0.1:4722'], - reconnect: false, - user_credentials: "./spec/configs/nkeys/foo-user.creds") + nats.connect(servers: ["nats://127.0.0.1:4722"], + reconnect: false, + user_credentials: "./spec/configs/nkeys/foo-user.creds") nats.subscribe("hello") do |msg| msgs << msg done.signal end nats.flush - nats.publish("hello", 'world') + nats.publish("hello", "world") mon.synchronize do done.wait(1) @@ -63,7 +62,7 @@ expect(msgs.count).to eql(1) end - it 'should support user supplied credential callbacks' do + it "should support user supplied credential callbacks" do mon = Monitor.new done = mon.new_cond @@ -83,13 +82,13 @@ user_jwt_called = false jwt_cb = proc { user_jwt_called = true - nats.send(:jwt_cb_for_creds_file, "./spec/configs/nkeys/foo-user.creds").call() + nats.send(:jwt_cb_for_creds_file, "./spec/configs/nkeys/foo-user.creds").call } - nats.connect(servers: ['nats://127.0.0.1:4722'], - reconnect: false, - user_signature_cb: sig_cb, - user_jwt_cb: jwt_cb) + nats.connect(servers: ["nats://127.0.0.1:4722"], + reconnect: false, + user_signature_cb: sig_cb, + user_jwt_cb: jwt_cb) expect(user_sig_called).to be(true) expect(user_jwt_called).to be(true) @@ -99,7 +98,7 @@ done.signal end nats.flush - nats.publish("hello", 'world') + nats.publish("hello", "world") mon.synchronize do done.wait(1) @@ -108,39 +107,38 @@ expect(msgs.count).to eql(1) end - it 'should fail with auth error if no user credentials present' do + it "should fail with auth error if no user credentials present" do mon = Monitor.new - done = mon.new_cond + mon.new_cond errors = [] - msgs = [] nats = NATS::IO::Client.new nats.on_error do |e| errors << e end expect do - nats.connect(servers: ['nats://127.0.0.1:4722'], - reconnect: false) + nats.connect(servers: ["nats://127.0.0.1:4722"], + reconnect: false) end.to raise_error(NATS::IO::AuthError) expect(errors.count).to eql(1) end end - context 'with NKEYS only' do - before(:each) do + context "with NKEYS only" do + before do config_opts = { - 'pid_file' => '/tmp/nats_nkeys.pid', - 'host' => '127.0.0.1', - 'port' => 4723, + "pid_file" => "/tmp/nats_nkeys.pid", + "host" => "127.0.0.1", + "port" => 4723 } - @s = NatsServerControl.init_with_config_from_string(%Q( + @s = NatsServerControl.init_with_config_from_string(%( authorization { timeout: 2 } - port = #{config_opts['port']} + port = #{config_opts["port"]} accounts { acme { @@ -165,11 +163,11 @@ @s.start_server(true) end - after(:each) do + after do @s.kill_server end - it 'should connect to the server and publish messages' do + it "should connect to the server and publish messages" do mon = Monitor.new done = mon.new_cond @@ -179,15 +177,15 @@ nats.on_error do |e| errors << e end - nats.connect(servers: ['nats://127.0.0.1:4723'], - reconnect: false, - nkeys_seed: "./spec/configs/nkeys/foo-user.nk") + nats.connect(servers: ["nats://127.0.0.1:4723"], + reconnect: false, + nkeys_seed: "./spec/configs/nkeys/foo-user.nk") nats.subscribe("hello") do |msg| msgs << msg done.signal end nats.flush - nats.publish("hello", 'world') + nats.publish("hello", "world") mon.synchronize do done.wait(1) @@ -196,7 +194,7 @@ expect(msgs.count).to eql(1) end - it 'should support user supplied nkey callbacks' do + it "should support user supplied nkey callbacks" do mon = Monitor.new done = mon.new_cond @@ -210,7 +208,7 @@ user_nkey_called = false user_nkey_cb = proc { user_nkey_called = true - nats.send(:nkey_cb_for_nkey_file, "./spec/configs/nkeys/foo-user.nk").call() + nats.send(:nkey_cb_for_nkey_file, "./spec/configs/nkeys/foo-user.nk").call } user_sig_called = false @@ -219,10 +217,10 @@ nats.send(:signature_cb_for_nkey_file, "./spec/configs/nkeys/foo-user.nk").call(nonce) } - nats.connect(servers: ['nats://127.0.0.1:4723'], - reconnect: false, - user_nkey_cb: user_nkey_cb, - user_signature_cb: sig_cb) + nats.connect(servers: ["nats://127.0.0.1:4723"], + reconnect: false, + user_nkey_cb: user_nkey_cb, + user_signature_cb: sig_cb) expect(user_sig_called).to be(true) expect(user_nkey_called).to be(true) @@ -232,7 +230,7 @@ done.signal end nats.flush - nats.publish("hello", 'world') + nats.publish("hello", "world") mon.synchronize do done.wait(1) diff --git a/spec/client_reconnect_spec.rb b/spec/client_reconnect_spec.rb index 7579555..7f0a70d 100644 --- a/spec/client_reconnect_spec.rb +++ b/spec/client_reconnect_spec.rb @@ -1,18 +1,16 @@ -require 'spec_helper' -require 'monitor' +# frozen_string_literal: true -describe 'Client - Reconnect' do - - before(:each) do +describe "Client - Reconnect" do + before do @s = NatsServerControl.new @s.start_server(true) end - after(:each) do + after do @s.kill_server end - it 'should process errors from a server and reconnect' do + it "should process errors from a server and reconnect" do nats = NATS::IO::Client.new nats.connect({ reconnect: true, @@ -45,7 +43,11 @@ # FIXME: This can fail due to timeout because # disconnection may have already occurred. - nats.flush(1) rescue nil + begin + nats.flush(1) + rescue + nil + end # Should have a connection closed at this without reconnecting. mon.synchronize { done.wait(3) } @@ -59,7 +61,7 @@ expect(nats.closed?).to eql(true) end - it 'should reconnect to server and replay all subscriptions' do + it "should reconnect to server and replay all subscriptions" do msgs = [] errors = [] closes = 0 @@ -126,7 +128,7 @@ nats.close end - it 'should abort reconnecting if disabled' do + it "should abort reconnecting if disabled" do msgs = [] errors = [] closes = 0 @@ -154,7 +156,7 @@ mon.synchronize { done.signal } end - nats.connect(:reconnect => false) + nats.connect(reconnect: false) nats.subscribe("foo") do |msg| msgs << msg @@ -181,8 +183,7 @@ nats.close end - it 'should give up connecting if no servers available' do - msgs = [] + it "should give up connecting if no servers available" do errors = [] closes = 0 reconnects = 0 @@ -211,9 +212,9 @@ expect do nats.connect({ - :servers => ["nats://127.0.0.1:4229"], - :max_reconnect_attempts => 2, - :reconnect_time_wait => 1 + servers: ["nats://127.0.0.1:4229"], + max_reconnect_attempts: 2, + reconnect_time_wait: 1 }) end.to raise_error(Errno::ECONNREFUSED) @@ -226,7 +227,7 @@ expect(nats.status).to eql(NATS::IO::DISCONNECTED) end - it 'should give up reconnecting if no servers available' do + it "should give up reconnecting if no servers available" do msgs = [] errors = [] closes = 0 @@ -255,9 +256,9 @@ end nats.connect({ - :servers => ["nats://127.0.0.1:4222"], - :max_reconnect_attempts => 1, - :reconnect_time_wait => 1 + servers: ["nats://127.0.0.1:4222"], + max_reconnect_attempts: 1, + reconnect_time_wait: 1 }) nats.subscribe("foo") do |msg| @@ -292,7 +293,7 @@ expect(nats.status).to eql(NATS::IO::CLOSED) end - context 'against a server which is idle during connect' do + context "against a server which is idle during connect" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4444 @@ -310,8 +311,7 @@ @fake_nats_server.close end - it 'should give up reconnecting if no servers available due to timeout errors during connect' do - msgs = [] + it "should give up reconnecting if no servers available due to timeout errors during connect" do errors = [] closes = 0 reconnects = 0 @@ -339,11 +339,11 @@ end nats.connect({ - :servers => ["nats://127.0.0.1:4222", "nats://127.0.0.1:4444"], - :max_reconnect_attempts => 1, - :reconnect_time_wait => 1, - :dont_randomize_servers => true, - :connect_timeout => 1 + servers: ["nats://127.0.0.1:4222", "nats://127.0.0.1:4444"], + max_reconnect_attempts: 1, + reconnect_time_wait: 1, + dont_randomize_servers: true, + connect_timeout: 1 }) # Trigger reconnect logic @@ -363,7 +363,7 @@ end end - context 'against a server which becomes idle after being connected' do + context "against a server which becomes idle after being connected" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4445 @@ -375,7 +375,7 @@ client.puts "INFO {}\r\n" # Read and ignore CONNECT command send by the client - connect_op = client.gets + client.gets # Reply to any pending pings client may have sent sleep 0.1 @@ -395,8 +395,7 @@ @fake_nats_server.close end - it 'should reconnect to a healthy server if connection becomes stale' do - msgs = [] + it "should reconnect to a healthy server if connection becomes stale" do errors = [] closes = 0 reconnects = 0 @@ -424,12 +423,12 @@ end nats.connect({ - :servers => ["nats://127.0.0.1:4445","nats://127.0.0.1:4222"], - :max_reconnect_attempts => -1, - :reconnect_time_wait => 2, - :dont_randomize_servers => true, - :connect_timeout => 1, - :ping_interval => 2 + servers: ["nats://127.0.0.1:4445", "nats://127.0.0.1:4222"], + max_reconnect_attempts: -1, + reconnect_time_wait: 2, + dont_randomize_servers: true, + connect_timeout: 1, + ping_interval: 2 }) mon.synchronize { done.wait(7) } @@ -446,7 +445,7 @@ end end - context 'against a server which stops following protocol after being connected' do + context "against a server which stops following protocol after being connected" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4446 @@ -458,7 +457,7 @@ client.puts "INFO {}\r\n" # Read and ignore CONNECT command send by the client - connect_op = client.gets + client.gets # Reply to any pending pings client may have sent sleep 0.1 @@ -479,8 +478,7 @@ @fake_nats_server.close end - it 'should reconnect to a healthy server after unknown protocol error' do - msgs = [] + it "should reconnect to a healthy server after unknown protocol error" do errors = [] closes = 0 reconnects = 0 @@ -508,11 +506,11 @@ end nats.connect({ - :servers => ["nats://127.0.0.1:4446","nats://127.0.0.1:4222"], - :max_reconnect_attempts => -1, - :reconnect_time_wait => 2, - :dont_randomize_servers => true, - :connect_timeout => 1 + servers: ["nats://127.0.0.1:4446", "nats://127.0.0.1:4222"], + max_reconnect_attempts: -1, + reconnect_time_wait: 2, + dont_randomize_servers: true, + connect_timeout: 1 }) # Wait for disconnect due to the unknown protocol error mon.synchronize { done.wait(7) } @@ -533,7 +531,7 @@ end end - context 'against a server to which we have a stale connection after being connected' do + context "against a server to which we have a stale connection after being connected" do before(:all) do # Start a fake tcp server @fake_nats_server = TCPServer.new 4447 @@ -545,7 +543,7 @@ client.puts "INFO {}\r\n" # Read and ignore CONNECT command send by the client - connect_op = client.gets + client.gets # Reply to any pending pings client may have sent sleep 0.5 @@ -566,8 +564,7 @@ @fake_nats_server.close end - it 'should try to reconnect after receiving stale connection error' do - msgs = [] + it "should try to reconnect after receiving stale connection error" do errors = [] closes = 0 reconnects = 0 @@ -595,11 +592,11 @@ end nats.connect({ - :servers => ["nats://127.0.0.1:4447"], - :max_reconnect_attempts => 1, - :reconnect_time_wait => 2, - :dont_randomize_servers => true, - :connect_timeout => 2 + servers: ["nats://127.0.0.1:4447"], + max_reconnect_attempts: 1, + reconnect_time_wait: 2, + dont_randomize_servers: true, + connect_timeout: 2 }) # Wait for disconnect due to the unknown protocol error diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 5c42044..87cb649 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -1,22 +1,6 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'monitor' - -describe 'Client - Specification' do +# frozen_string_literal: true +describe "Client - Specification" do before(:all) do @s = NatsServerControl.new("nats://127.0.0.1:4522", "/tmp/test-nats.pid", "--cluster nats://127.0.0.1:4248 --cluster_name test-cluster") @s.start_server(true) @@ -26,7 +10,7 @@ @s.kill_server end - it 'should connect' do + it "should connect" do expect do nc = NATS::Client.new nc.connect(servers: [@s.uri]) @@ -63,7 +47,7 @@ end.to_not raise_error end - it 'supports new server announcement discovery' do + it "supports new server announcement discovery" do s = NatsServerControl.new("nats://127.0.0.1:5223", "/tmp/test-nats-2.pid", "--cluster nats://127.0.0.1:5248 --cluster_name test-cluster --routes nats://127.0.0.1:4248") s.start_server(true) @@ -76,7 +60,7 @@ nc.close end - it 'supports skipping new server announcement discovery' do + it "supports skipping new server announcement discovery" do s = NatsServerControl.new("nats://127.0.0.1:5223", "/tmp/test-nats-2.pid", "--cluster nats://127.0.0.1:5248 --cluster_name test-cluster --routes nats://127.0.0.1:4248") s.start_server(true) @@ -89,35 +73,35 @@ nc.close end - it 'should support custom inbox prefixes' do - ["x.>", "x.*",".x.", ".x", "x."].each do |p| + it "should support custom inbox prefixes" do + ["x.>", "x.*", ".x.", ".x", "x."].each do |p| expect do nc = NATS::Client.new - nc.connect(:servers => [@s.uri], :custom_inbox_prefix => p) + nc.connect(servers: [@s.uri], custom_inbox_prefix: p) end.to raise_error(NATS::IO::ClientError) end ["x.y", "x", "_X", "_X_"].each do |p| expect do nc = NATS::Client.new - nc.connect(:servers => [@s.uri], :custom_inbox_prefix => p) + nc.connect(servers: [@s.uri], custom_inbox_prefix: p) end.to_not raise_error end nc = NATS::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) expect(nc.new_inbox).to start_with("_INBOX") expect(nc.new_inbox.length).to eq(29) nc = NATS::Client.new - nc.connect(:servers => [@s.uri], :custom_inbox_prefix => "custom_prefix") + nc.connect(servers: [@s.uri], custom_inbox_prefix: "custom_prefix") expect(nc.new_inbox).to start_with("custom_prefix") expect(nc.new_inbox.length).to eq(36) end - it 'should received a message when subscribed to a topic' do + it "should received a message when subscribed to a topic" do nc = NATS::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) msgs = [] nc.subscribe("hello") do |msg| @@ -132,13 +116,13 @@ sleep 0.1 # Let other threads to process messages. expect(msgs.count).to eql(5) - expect(msgs.first.data).to eql('world-1') - expect(msgs.last.data).to eql('world-5') + expect(msgs.first.data).to eql("world-1") + expect(msgs.last.data).to eql("world-5") nc.close end - it 'should be able to receive requests synchronously with a timeout' do + it "should be able to receive requests synchronously with a timeout" do nc = NATS.connect(@s.uri) received = [] @@ -149,21 +133,21 @@ nc.flush responses = [] - responses << nc.request("help", 'please', timeout: 1) - responses << nc.request("help", 'again', timeout: 1) + responses << nc.request("help", "please", timeout: 1) + responses << nc.request("help", "again", timeout: 1) expect(responses.count).to eql(2) - expect(responses.first.data).to eql('reply.1') - expect(responses.last.data).to eql('reply.2') + expect(responses.first.data).to eql("reply.1") + expect(responses.last.data).to eql("reply.2") nc.close end - it 'should be able to receive limited requests asynchronously' do + it "should be able to receive limited requests asynchronously" do mon = Monitor.new done = mon.new_cond nc = NATS::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) received = [] nc.subscribe("help") do |msg, reply, subject| @@ -197,9 +181,9 @@ expect(responses[1].data).to eql("back") end - it 'should be able to unsubscribe' do + it "should be able to unsubscribe" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri], :reconnect => false) + nc.connect(servers: [@s.uri], reconnect: false) msgs = [] sub = nc.subscribe("foo") do |msg| @@ -223,13 +207,13 @@ nc.close end - it 'should be able to create many subscriptions' do + it "should be able to create many subscriptions" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) max_subs = 50 max_messages = 50 - msgs = { } + msgs = {} 1.upto(max_subs).each do |n| sub = nc.subscribe("quux.#{n}") do |msg, reply, subject| msgs[subject] << msg @@ -267,12 +251,12 @@ nc.close end - it 'should raise timeout error if timed request does not get response' do + it "should raise timeout error if timed request does not get response" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) # Have interest but do not respond - nc.subscribe("hi") { } + nc.subscribe("hi") {} expect do nc.request("hi", "timeout", timeout: 1) @@ -281,12 +265,12 @@ nc.close end - it 'should close connection gracefully' do + it "should close connection gracefully" do mon = Monitor.new test_is_done = mon.new_cond nats = NATS::IO::Client.new - nats.connect(:servers => [@s.uri], :reconnect => false) + nats.connect(servers: [@s.uri], reconnect: false) errors = [] disconnects = 0 @@ -339,7 +323,7 @@ end it "should support distributed queues" do - conns = Hash.new { |h,k| h[k] = {}} + conns = Hash.new { |h, k| h[k] = {} } 5.times do |n| nc = NATS::IO::Client.new conns[n][:nats] = nc @@ -350,7 +334,7 @@ servers: [@s.uri], reconnect: false }) - nc.subscribe("foo", queue: 'bar') do |msg| + nc.subscribe("foo", queue: "bar") do |msg| conns[n][:msgs] << msg end nc.flush @@ -359,7 +343,7 @@ # Publish messages on using the first connection nc = conns[0][:nats] 1000.times do |n| - nc.publish("foo", 'hi') + nc.publish("foo", "hi") end nc.flush @@ -375,10 +359,10 @@ expect(total).to eql(1000) end - context 'using new style request response' do - it 'should be able to receive requests synchronously with a timeout' do + context "using new style request response" do + it "should be able to receive requests synchronously with a timeout" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri], :old_style_request => false) + nc.connect(servers: [@s.uri], old_style_request: false) received = [] nc.subscribe("help") do |msg, reply, subject| @@ -388,20 +372,18 @@ nc.flush responses = [] - responses << nc.request("help", 'please', timeout: 1) - responses << nc.request("help", 'again', timeout: 1) + responses << nc.request("help", "please", timeout: 1) + responses << nc.request("help", "again", timeout: 1) expect(responses.count).to eql(2) - expect(responses.first.data).to eql('reply.1') - expect(responses.last.data).to eql('reply.2') + expect(responses.first.data).to eql("reply.1") + expect(responses.last.data).to eql("reply.2") nc.close end - it 'should be able to receive requests synchronously in parallel' do + it "should be able to receive requests synchronously in parallel" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri], :old_style_request => false) - - received = [] + nc.connect(servers: [@s.uri], old_style_request: false) nc.subscribe("help") do |payload, reply, subject| # Echoes the same data back. nc.publish(reply, payload) @@ -411,7 +393,7 @@ a_future = Future.new total = 100 - t_a = Thread.new do + Thread.new do sleep 0.2 responses = [] total.times do |n| @@ -421,7 +403,7 @@ end b_future = Future.new - t_b = Thread.new do + Thread.new do responses = [] sleep 0.2 @@ -432,7 +414,7 @@ end c_future = Future.new - t_c = Thread.new do + Thread.new do responses = [] sleep 0.2 @@ -472,14 +454,14 @@ end context "using old style request" do - it 'should be able to receive responses' do + it "should be able to receive responses" do mon = Monitor.new subscribed_done = mon.new_cond test_done = mon.new_cond another_thread = Thread.new do nats = NATS::IO::Client.new - nats.connect(:servers => [@s.uri], :reconnect => false) + nats.connect(servers: [@s.uri], reconnect: false) nats.subscribe("help") do |msg| nats.publish(msg.reply, "response to req:#{msg.data}") end @@ -496,7 +478,7 @@ end nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) mon.synchronize do subscribed_done.wait(1) end @@ -504,16 +486,14 @@ responses = [] expect do 3.times do |n| - response = nil - - response = nc.request("help", "#{n}", timeout: 1, old_style: true) + response = nc.request("help", n.to_s, timeout: 1, old_style: true) responses << response end end.to_not raise_error expect(responses.count).to eql(3) # A new subscription would have the next sid for this client. - sub = nc.subscribe("hello"){ } + sub = nc.subscribe("hello") {} expect(sub.sid).to eql(4) mon.synchronize do @@ -529,7 +509,7 @@ end end - context 'with default port' do + context "with default port" do before(:all) do @s4222 = NatsServerControl.new("nats://127.0.0.1:4222", "/tmp/test-nats.pid-4222") @s4222.start_server(true) @@ -539,14 +519,14 @@ @s4222.kill_server end - it 'should connect' do + it "should connect" do expect do nc = NATS.connect("localhost") sub = nc.subscribe("foo") nc.flush nc.publish("foo", "hello world") msg = sub.next_msg - expect(msg.data).to eql('hello world') + expect(msg.data).to eql("hello world") end.to_not raise_error expect do @@ -555,7 +535,7 @@ nc.flush nc.publish("foo", "hello world") msg = sub.next_msg - expect(msg.data).to eql('hello world') + expect(msg.data).to eql("hello world") end.to_not raise_error expect do @@ -564,7 +544,7 @@ nc.flush nc.publish("foo", "hello world") msg = sub.next_msg - expect(msg.data).to eql('hello world') + expect(msg.data).to eql("hello world") end.to_not raise_error expect do @@ -573,7 +553,7 @@ nc.flush nc.publish("foo", "hello world") msg = sub.next_msg - expect(msg.data).to eql('hello world') + expect(msg.data).to eql("hello world") end.to_not raise_error end end diff --git a/spec/client_threadsafe_spec.rb b/spec/client_threadsafe_spec.rb index 3b38992..a484078 100644 --- a/spec/client_threadsafe_spec.rb +++ b/spec/client_threadsafe_spec.rb @@ -1,21 +1,6 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -describe 'Client - Thread safety' do +# frozen_string_literal: true +describe "Client - Thread safety" do before(:all) do @s = NatsServerControl.new @s.start_server(true) @@ -25,30 +10,15 @@ @s.kill_server end - class Component - attr_reader :nats, :options - attr_accessor :msgs - - def initialize(options={}) - @nats = NATS::IO::Client.new - @msgs = [] - @options = options - end - - def connect! - @nats.connect(@options) - end - end - - it 'should be able to send and receive messages from different threads' do - component = Component.new - component.connect! + it "should be able to send and receive messages from different threads" do + component = Struct.new(:nats, :msgs).new(NATS::IO::Client.new, []) + component.nats.connect q = Queue.new threads = [] thr_a = Thread.new do component.nats.subscribe("hello") do |data, reply, subject| - component.msgs << { :data => data, :subject => subject } + component.msgs << {data: data, subject: subject} end q << "hello subscribed" @@ -66,7 +36,7 @@ def connect! thr_b = Thread.new do component.nats.subscribe("world") do |data, reply, subject| - component.msgs << { :data => data, :subject => subject } + component.msgs << {data: data, subject: subject} end q << "world subscribed" @@ -81,11 +51,12 @@ def connect! end threads << thr_b - q.pop; q.pop + q.pop + q.pop thr_c = Thread.new do (1..100).step(2) do |n| - component.nats.publish("hello", "#{n}") + component.nats.publish("hello", n.to_s) end component.nats.flush end @@ -93,13 +64,14 @@ def connect! thr_d = Thread.new do (0..99).step(2) do |n| - component.nats.publish("world", "#{n}") + component.nats.publish("world", n.to_s) end component.nats.flush end threads << thr_d - q.pop; q.pop + q.pop + q.pop expect(component.msgs.count).to eql(100) result = component.msgs.select { |msg| msg[:subject] != "hello" && msg[:data].to_i % 2 == 1 } @@ -121,32 +93,32 @@ def connect! end end - it 'should allow async subscriptions to process messages in parallel' do - nc = NATS.connect(servers: ['nats://0.0.0.0:4222']) + it "should allow async subscriptions to process messages in parallel" do + nc = NATS.connect(servers: ["nats://0.0.0.0:4222"]) foo_msgs = [] - nc.subscribe('foo') do |payload| + nc.subscribe("foo") do |payload| foo_msgs << payload sleep 1 end bar_msgs = [] - nc.subscribe('bar') do |payload, reply| + nc.subscribe("bar") do |payload, reply| bar_msgs << payload - nc.publish(reply, 'OK!') + nc.publish(reply, "OK!") end quux_msgs = [] - nc.subscribe('quux') do |payload, reply| + nc.subscribe("quux") do |payload, reply| quux_msgs << payload end # Receive on message foo first which takes longer to process. - nc.publish('foo', 'hello') + nc.publish("foo", "hello") # Publish many messages to quux which should be able to consume fast. 1.upto(10).each do |n| - nc.publish('quux', "test-#{n}") + nc.publish("quux", "test-#{n}") end # Awaiting for the response happens on the same @@ -154,10 +126,10 @@ def connect! # the read loop thread is going to signal back. response = nil expect do - response = nc.request('bar', 'help', timeout: 0.5) + response = nc.request("bar", "help", timeout: 0.5) end.to_not raise_error - expect(response.data).to eql('OK!') + expect(response.data).to eql("OK!") # Wait a bit in case all of this happened too fast sleep 0.2 @@ -166,14 +138,14 @@ def connect! expect(quux_msgs.count).to eql(10) 1.upto(10).each do |n| - expect(quux_msgs[n-1]).to eql("test-#{n}") + expect(quux_msgs[n - 1]).to eql("test-#{n}") end nc.close end - it 'should connect once across threads' do + it "should connect once across threads" do nc = NATS.connect(@s.uri) - nc.subscribe(">") { } + nc.subscribe(">") {} nc.subscribe("help") do |msg, reply| nc.publish(reply, "OK!") end @@ -187,8 +159,8 @@ def connect! loop do begin - nc2.publish("foo", 'bar', 'quux') - rescue => e + nc2.publish("foo", "bar", "quux") + rescue break end sleep 0.01 @@ -220,26 +192,26 @@ def connect! end # Using pure-nats.rb in a Ractor requires URI 0.11.0 or greater due to URI Ractor support. - major_version, minor_version, _ = Gem.loaded_specs['uri'].version.to_s.split('.').map(&:to_i) if Gem.loaded_specs['uri'] + major_version, minor_version, _ = Gem.loaded_specs["uri"].version.to_s.split(".").map(&:to_i) if Gem.loaded_specs["uri"] if major_version && major_version >= 0 && minor_version >= 11 - it 'should be able to process messages in a Ractor' do + it "should be able to process messages in a Ractor" do pending "As of Rails 7.0 known to fail with errors about unshareable objects" if defined? Rails nc = NATS.connect(@s.uri) messages = [] - nc.subscribe('foo') do |msg| + nc.subscribe("foo") do |msg| messages << msg end r1 = Ractor.new(@s.uri) do |uri| r_nc = NATS.connect(uri) - r_nc.publish('foo', 'bar') + r_nc.publish("foo", "bar") r_nc.flush r_nc.close - 'r1 Finished' + "r1 Finished" end r1.take # wait for Ractor to finish sending messages Thread.pass # allow subscription thread to process messages @@ -249,22 +221,22 @@ def connect! r_nc = NATS.connect(uri) r_messages = [] - r_nc.subscribe('bar') do |payload, reply| - r_nc.publish(reply, 'OK!') + r_nc.subscribe("bar") do |payload, reply| + r_nc.publish(reply, "OK!") r_messages << payload end - Ractor.yield 'r2 Ready' + Ractor.yield "r2 Ready" sleep 0.01 while r_messages.empty? end r2.take # wait for Ractor to finish setup response = nil expect do - response = nc.request('bar', 'baz', timeout: 0.5) + response = nc.request("bar", "baz", timeout: 0.5) end.to_not raise_error - expect(response.data).to eql('OK!') + expect(response.data).to eql("OK!") end end end diff --git a/spec/client_tls_spec.rb b/spec/client_tls_spec.rb index a009303..7a6a5c5 100644 --- a/spec/client_tls_spec.rb +++ b/spec/client_tls_spec.rb @@ -1,22 +1,6 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'openssl' -require 'erb' - -DEFAULT_JRUBY_CIPHER_SUITE = %q( +# frozen_string_literal: true + +DEFAULT_JRUBY_CIPHER_SUITE = ' # JRuby is sensible to the ciphers being used # so we specify the ones that are available on it here. # See: https://github.com/jruby/jruby/issues/1738 @@ -28,18 +12,17 @@ "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA" ] -) - -describe 'Client - TLS spec' do +' - context 'when server requires TLS and no auth needed' do - before(:each) do +describe "Client - TLS spec" do + context "when server requires TLS and no auth needed" do + before do opts = { - 'pid_file' => '/tmp/test-nats-4444.pid', - 'host' => '127.0.0.1', - 'port' => 4444 + "pid_file" => "/tmp/test-nats-4444.pid", + "host" => "127.0.0.1", + "port" => 4444 } - config = ERB.new(%Q( + config = ERB.new(%( net: "<%= opts['host'] %>" port: <%= opts['port'] %> @@ -56,27 +39,26 @@ @tls_no_auth.start_server end - after(:each) do + after do @tls_no_auth.kill_server end - it 'should error if client does not set secure connection and server requires it' do + it "should error if client does not set secure connection and server requires it" do errors = [] closes = 0 - reconnects = 0 disconnects = 0 reconnects = 0 nats = NATS::IO::Client.new - nats.on_close { closes += 1 } - nats.on_reconnect { reconnects += 1 } + nats.on_close { closes += 1 } + nats.on_reconnect { reconnects += 1 } nats.on_disconnect { disconnects += 1 } nats.on_error do |e| errors << e end expect do - nats.connect(:servers => ['nats://127.0.0.1:4444'], :reconnect => false) + nats.connect(servers: ["nats://127.0.0.1:4444"], reconnect: false) end.to raise_error(NATS::IO::ConnectError) # Async handler also gets triggered since defined @@ -89,16 +71,15 @@ expect(disconnects).to eql(1) end - it 'should allow to connect client with secure connection if server requires it' do + it "should allow to connect client with secure connection if server requires it" do errors = [] closes = 0 - reconnects = 0 disconnects = 0 reconnects = 0 nats = NATS::IO::Client.new - nats.on_close { closes += 1 } - nats.on_reconnect { reconnects += 1 } + nats.on_close { closes += 1 } + nats.on_reconnect { reconnects += 1 } nats.on_disconnect { disconnects += 1 } nats.on_error do |e| errors << e @@ -109,7 +90,7 @@ tls_context.set_params tls_context.ca_file = "./spec/configs/certs/ca.pem" nats.connect({ - servers: ['tls://127.0.0.1:4444'], + servers: ["tls://127.0.0.1:4444"], reconnect: false, tls: { context: tls_context @@ -125,7 +106,7 @@ nats.flush # Send some messages... - 100.times {|n| nats.publish("hello.#{n}", "world") } + 100.times { |n| nats.publish("hello.#{n}", "world") } nats.flush sleep 0.5 @@ -140,25 +121,27 @@ expect(disconnects).to eql(1) end - - it 'should allow to client to try to connect securely using tls scheme' do + it "should allow to client to try to connect securely using tls scheme" do nats = NATS::IO::Client.new # Discard error since only want to confirm that TLS is setup due to scheme option. - nats.connect('tls://127.0.0.1:4444', reconnect:false) rescue nil + begin + nats.connect("tls://127.0.0.1:4444", reconnect: false) + rescue + nil + end expect(nats.options[:tls]).to_not be_nil end - it 'should allow custom secure connection contexts' do + it "should allow custom secure connection contexts" do errors = [] closes = 0 - reconnects = 0 disconnects = 0 reconnects = 0 nats = NATS::IO::Client.new - nats.on_close { closes += 1 } - nats.on_reconnect { reconnects += 1 } + nats.on_close { closes += 1 } + nats.on_reconnect { reconnects += 1 } nats.on_disconnect { disconnects += 1 } nats.on_error do |e| errors << e @@ -169,24 +152,24 @@ tls_context.ssl_version = :TLSv1 nats.connect({ - servers: ['tls://127.0.0.1:4444'], - reconnect: false, - tls: { - context: tls_context - } - }) + servers: ["tls://127.0.0.1:4444"], + reconnect: false, + tls: { + context: tls_context + } + }) end.to raise_error(OpenSSL::SSL::SSLError) end end - context 'when server requires TLS and certificates' do - before(:each) do + context "when server requires TLS and certificates" do + before do opts = { - 'pid_file' => '/tmp/test-nats-4555.pid', - 'host' => '127.0.0.1', - 'port' => 4555 + "pid_file" => "/tmp/test-nats-4555.pid", + "host" => "127.0.0.1", + "port" => 4555 } - config = ERB.new(%Q( + config = ERB.new(%( net: "<%= opts['host'] %>" port: <%= opts['port'] %> @@ -208,20 +191,20 @@ @tlsverify = NatsServerControl.init_with_config_from_string(config.result(binding), opts) @tlsverify.start_server end - after(:each) do + + after do @tlsverify.kill_server end - it 'should allow custom secure connection contexts' do + it "should allow custom secure connection contexts" do errors = [] closes = 0 - reconnects = 0 disconnects = 0 reconnects = 0 nats = NATS::IO::Client.new - nats.on_close { closes += 1 } - nats.on_reconnect { reconnects += 1 } + nats.on_close { closes += 1 } + nats.on_reconnect { reconnects += 1 } nats.on_disconnect { disconnects += 1 } nats.on_error do |e| errors << e @@ -235,7 +218,7 @@ tls_context.verify_mode = OpenSSL::SSL::VERIFY_PEER nats.connect({ - servers: ['tls://127.0.0.1:4555'], + servers: ["tls://127.0.0.1:4555"], reconnect: false, tls: { context: tls_context @@ -243,7 +226,7 @@ }) nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") @@ -252,7 +235,7 @@ end end - context 'when server requires TLS and client enables host verification', :tls_verify_hostname do + context "when server requires TLS and client enables host verification", :tls_verify_hostname do let(:tls_context) { ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = "./spec/configs/certs/nats-service.localhost/ca.pem" @@ -261,14 +244,15 @@ ctx } - before(:each) do + + before do @tls_verify_host_server_uri = URI.parse("nats://127.0.0.1:4556") opts = { - 'pid_file' => "/tmp/test-nats-#{@tls_verify_host_server_uri.port}.pid", - 'host' => '127.0.0.1', - 'port' => @tls_verify_host_server_uri.port + "pid_file" => "/tmp/test-nats-#{@tls_verify_host_server_uri.port}.pid", + "host" => "127.0.0.1", + "port" => @tls_verify_host_server_uri.port } - config = ERB.new(%Q( + config = ERB.new(%( net: "<%= opts['host'] %>" port: <%= opts['port'] %> @@ -291,11 +275,11 @@ @tls_verify_host.start_server end - after(:each) do + after do @tls_verify_host.kill_server end - it 'should be able to connect and verify server hostname' do + it "should be able to connect and verify server hostname" do expect do nats = NATS::IO::Client.new @@ -308,7 +292,7 @@ }) nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") @@ -316,7 +300,7 @@ end.to_not raise_error end - it 'should not be able to connect if using wrong server hostname' do + it "should not be able to connect if using wrong server hostname" do expect do nats = NATS::IO::Client.new @@ -329,7 +313,7 @@ }) nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") @@ -338,15 +322,15 @@ end end - context 'when bad server requires TLS', :tls_verify_hostname do - before(:each) do + context "when bad server requires TLS", :tls_verify_hostname do + before do @tls_verify_host_bad_server_uri = URI.parse("nats://127.0.0.1:4557") opts = { - 'pid_file' => "/tmp/test-nats-#{@tls_verify_host_bad_server_uri.port}.pid", - 'host' => '127.0.0.1', - 'port' => @tls_verify_host_bad_server_uri.port + "pid_file" => "/tmp/test-nats-#{@tls_verify_host_bad_server_uri.port}.pid", + "host" => "127.0.0.1", + "port" => @tls_verify_host_bad_server_uri.port } - config = ERB.new(%Q( + config = ERB.new(%( net: "<%= opts['host'] %>" port: <%= opts['port'] %> @@ -369,11 +353,11 @@ @tls_verify_bad_host.start_server end - after(:each) do + after do @tls_verify_bad_host.kill_server end - it 'should be able to connect if client does not verify server hostname' do + it "should be able to connect if client does not verify server hostname" do ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = "./spec/configs/certs/nats-service.localhost/ca.pem" ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -391,7 +375,7 @@ }) nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") @@ -399,7 +383,7 @@ end.to_not raise_error end - it 'should not be able to connect if client enabled hostname verification' do + it "should not be able to connect if client enabled hostname verification" do ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = "./spec/configs/certs/nats-service.localhost/ca.pem" ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -417,7 +401,7 @@ }) nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") @@ -425,7 +409,7 @@ end.to raise_error(OpenSSL::SSL::SSLError) end - it 'should not be able to connect if client enabled hostname verification using tls as the scheme' do + it "should not be able to connect if client enabled hostname verification using tls as the scheme" do ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = "./spec/configs/certs/nats-service.localhost/ca.pem" ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER diff --git a/spec/client_v2_spec.rb b/spec/client_v2_spec.rb index 7a93e31..7bcaab4 100644 --- a/spec/client_v2_spec.rb +++ b/spec/client_v2_spec.rb @@ -1,21 +1,6 @@ -# Copyright 2016-2021 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'monitor' - -describe 'Client - v2.2 features' do +# frozen_string_literal: true + +describe "Client - v2.2 features" do before(:all) do @tmpdir = Dir.mktmpdir("ruby-jetstream") @s = NatsServerControl.new("nats://127.0.0.1:4523", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @@ -27,13 +12,13 @@ FileUtils.remove_entry(@tmpdir) end - it 'should receive a message with headers' do + it "should receive a message with headers" do mon = Monitor.new done = mon.new_cond - done2 = mon.new_cond + mon.new_cond nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) msgs = [] @@ -51,7 +36,7 @@ # Single arity is now a NATS::Msg type msgs2 = [] - sub2 = nc.subscribe("hello") do |msg| + nc.subscribe("hello") do |msg| msgs2 << msg if msgs.count >= 5 @@ -63,13 +48,13 @@ nc.flush 1.upto(5) do |n| - data = "hello world-#{'A' * n}" - msg = NATS::Msg.new(subject: 'hello', - data: data, - header: { - 'foo': 'bar', - 'hello': "hello-#{n}" - }) + data = "hello world-#{"A" * n}" + msg = NATS::Msg.new(subject: "hello", + data: data, + header: { + foo: "bar", + hello: "hello-#{n}" + }) nc.publish_msg(msg) nc.flush end @@ -80,14 +65,14 @@ msgs.each_with_index do |msg, i| n = i + 1 - expect(msg.data).to eql("hello world-#{'A' * n}") - expect(msg.header).to eql({"foo"=>"bar", "hello"=>"hello-#{n}"}) + expect(msg.data).to eql("hello world-#{"A" * n}") + expect(msg.header).to eql({"foo" => "bar", "hello" => "hello-#{n}"}) end msgs2.each_with_index do |msg, i| n = i + 1 - expect(msg.data).to eql("hello world-#{'A' * n}") - expect(msg.header).to eql({"foo"=>"bar", "hello"=>"hello-#{n}"}) + expect(msg.data).to eql("hello world-#{"A" * n}") + expect(msg.header).to eql({"foo" => "bar", "hello" => "hello-#{n}"}) end sub1.unsubscribe @@ -97,10 +82,10 @@ nc.publish("quux", "first") # empty payload - nc.publish("quux", header: { "foo": "A"}) + nc.publish("quux", header: {foo: "A"}) # payload and header - nc.publish("quux", "third", header: { "foo": "B"}) + nc.publish("quux", "third", header: {foo: "B"}) nc.flush msg = sub3.next_msg @@ -115,15 +100,15 @@ nc.close end - it 'should make requests with headers' do + it "should make requests with headers" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) msgs = [] seq = 0 nc.subscribe("hello") do |data, reply, _, header| seq += 1 - header['response'] = seq + header["response"] = seq msg = NATS::Msg.new(data: data, subject: reply, header: header) msgs << msg @@ -131,17 +116,18 @@ end nc.flush - 1.upto(5) do |n|p - data = "hello world-#{'A' * n}" - msg = NATS::Msg.new(subject: 'hello', - data: data, - header: { - 'foo': 'bar', - 'hello': "hello-#{n}" - }) + 1.upto(5) do |n| + p + data = "hello world-#{"A" * n}" + msg = NATS::Msg.new(subject: "hello", + data: data, + header: { + foo: "bar", + hello: "hello-#{n}" + }) resp = nc.request_msg(msg, timeout: 1) - expect(resp.data).to eql("hello world-#{'A' * n}") - expect(resp.header).to eql({"foo" => "bar", "hello" => "hello-#{n}", "response" => "#{n}"}) + expect(resp.data).to eql("hello world-#{"A" * n}") + expect(resp.header).to eql({"foo" => "bar", "hello" => "hello-#{n}", "response" => n.to_s}) nc.flush end expect(msgs.count).to eql(5) @@ -155,8 +141,8 @@ end q2.push(1) - msg = nc.request("quux", timeout: 2, header: { "one": "1" }) - expect(msg.data).to eql('') + msg = nc.request("quux", timeout: 2, header: {one: "1"}) + expect(msg.data).to eql("") expect(msg.header).to eql({"one" => "1", "reply" => "ok"}) expect do @@ -164,13 +150,13 @@ end.to raise_error TypeError expect do - nc.request("quux", timeout: 0.1, header: { "one": "1" }) + nc.request("quux", timeout: 0.1, header: {one: "1"}) end.to raise_error NATS::Timeout q2.push(2) nc.close end - it 'should raise no responders error by default' do + it "should raise no responders error by default" do nc = NATS.connect(servers: [@s.uri]) expect do @@ -202,7 +188,7 @@ nc.close end - it 'should not raise no responders error if no responders disabled' do + it "should not raise no responders error if no responders disabled" do nc = NATS::IO::Client.new nc.connect(servers: [@s.uri], no_responders: false) @@ -215,12 +201,16 @@ # Timed out requests should be cleaned up. 50.times do - nc.request("hi", "timeout", timeout: 0.001) rescue nil + nc.request("hi", "timeout", timeout: 0.001) + rescue + nil end msg = NATS::Msg.new(subject: "hi") 50.times do - nc.request_msg(msg, timeout: 0.001) rescue nil + nc.request_msg(msg, timeout: 0.001) + rescue + nil end result = nc.instance_variable_get(:@resp_map) @@ -237,7 +227,7 @@ nc.close end - it 'should not raise no responders error if no responders disabled' do + it "should not raise no responders error if no responders disabled" do nc = NATS::IO::Client.new nc.connect(servers: [@s.uri], no_responders: false) @@ -259,7 +249,7 @@ nc.close end - it 'should handle responses with status and description headers' do + it "should handle responses with status and description headers" do nc = NATS::IO::Client.new nc.connect(servers: [@s.uri], no_responders: true) @@ -294,7 +284,7 @@ expect(resp).to_not be_nil # Get single message. - pull_req = { no_wait: true, batch: 1} + pull_req = {no_wait: true, batch: 1} resp = nc.request("$JS.API.CONSUMER.MSG.NEXT.foojs.sample", pull_req.to_json, old_style: true) expect(resp).to_not be_nil expect(resp.data).to eql("hello world") @@ -303,14 +293,14 @@ resp = nc.request("$JS.API.CONSUMER.MSG.NEXT.foojs.sample", pull_req.to_json, old_style: true) expect(resp).to_not be_nil expect(resp.header).to_not be_nil - expect(resp.header).to eql({"Status"=>"404", "Description"=>"No Messages"}) + expect(resp.header).to eql({"Status" => "404", "Description" => "No Messages"}) nc.close end - it 'should get a message with Subscription#next_msg' do + it "should get a message with Subscription#next_msg" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) sub = nc.subscribe("hello") msgs = [] @@ -319,13 +309,13 @@ end.to raise_error(NATS::IO::Timeout) 1.upto(5) do |n| - data = "hello world-#{'A' * n}" - msg = NATS::Msg.new(subject: 'hello', - data: data, - header: { - 'foo': 'bar', - 'hello': "hello-#{n}" - }) + data = "hello world-#{"A" * n}" + msg = NATS::Msg.new(subject: "hello", + data: data, + header: { + foo: "bar", + hello: "hello-#{n}" + }) nc.publish_msg(msg) nc.flush end @@ -335,13 +325,13 @@ subject: "hello", reply: nil, data: a_string_starting_with("hello world"), - header: {"foo"=>"bar", "hello"=>"hello-1"} + header: {"foo" => "bar", "hello" => "hello-1"} ) msgs.each_with_index do |msg, i| n = i + 1 - expect(msg.data).to eql("hello world-#{'A' * n}") - expect(msg.header).to eql({"foo"=>"bar", "hello"=>"hello-#{n}"}) + expect(msg.data).to eql("hello world-#{"A" * n}") + expect(msg.header).to eql({"foo" => "bar", "hello" => "hello-#{n}"}) end expect do @@ -351,9 +341,9 @@ nc.close end - it 'should support NATS::Msg#respond' do + it "should support NATS::Msg#respond" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) nc.on_error do |e| puts "Error: #{e}" puts e.backtrace @@ -369,13 +359,13 @@ nc.flush 1.upto(5) do |n| - data = "hello world-#{'A' * n}" - msg = NATS::Msg.new(subject: 'hello', - data: data, - header: { - 'foo': 'bar', - 'hello': "hello-#{n}" - }) + data = "hello world-#{"A" * n}" + msg = NATS::Msg.new(subject: "hello", + data: data, + header: { + foo: "bar", + hello: "hello-#{n}" + }) resp = nc.request_msg(msg, timeout: 1) expect(resp.data).to eql("hi!") nc.flush @@ -385,9 +375,9 @@ nc.close end - it 'should make responses with headers NATS::Msg#respond_msg' do + it "should make responses with headers NATS::Msg#respond_msg" do nc = NATS::IO::Client.new - nc.connect(:servers => [@s.uri]) + nc.connect(servers: [@s.uri]) nc.on_error do |e| puts "Error: #{e}" puts e.backtrace @@ -398,7 +388,7 @@ nc.subscribe("hello") do |msg| seq += 1 m = NATS::Msg.new(data: msg.data, subject: msg.reply, header: msg.header) - m.header['response'] = seq + m.header["response"] = seq msgs << m msg.respond_msg(m) @@ -406,16 +396,16 @@ nc.flush 1.upto(5) do |n| - data = "hello world-#{'A' * n}" - msg = NATS::Msg.new(subject: 'hello', - data: data, - header: { - 'foo': 'bar', - 'hello': "hello-#{n}" - }) + data = "hello world-#{"A" * n}" + msg = NATS::Msg.new(subject: "hello", + data: data, + header: { + foo: "bar", + hello: "hello-#{n}" + }) resp = nc.request_msg(msg, timeout: 1) - expect(resp.data).to eql("hello world-#{'A' * n}") - expect(resp.header).to eql({"foo" => "bar", "hello" => "hello-#{n}", "response" => "#{n}"}) + expect(resp.data).to eql("hello world-#{"A" * n}") + expect(resp.header).to eql({"foo" => "bar", "hello" => "hello-#{n}", "response" => n.to_s}) nc.flush end expect(msgs.count).to eql(5) @@ -426,22 +416,21 @@ it "should process inline status messages with headers" do nc = NATS::IO::Client.new tests = [ - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-1\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-1"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-1\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-1"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-2\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-2"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-2\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-2"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-3\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-3"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-3\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-3"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-4\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-4"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-4\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-4"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-5\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-5"}}, - {input: %Q(NATS/1.0\r\nfoo: bar\r\nhello: hello-5\r\n\r\n), expected: {"foo"=>"bar", "hello"=>"hello-5"}}, - {input: %Q(NATS/1.0 408 Request Timeout\r\nNats-Pending-Messages: 1\r\nNats-Pending-Bytes: 0\r\n\r\n), - expected: { "Status" => "408", "Nats-Pending-Messages" => "1", "Nats-Pending-Bytes" => "0", "Description" => "Request Timeout"} - }, - {input: %Q(NATS/1.0 404 No Messages\r\n\r\n), - expected: {"Status" => "404", "Description" => "No Messages"}} - ] + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-1\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-1"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-1\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-1"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-2\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-2"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-2\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-2"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-3\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-3"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-3\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-3"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-4\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-4"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-4\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-4"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-5\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-5"}}, + {input: %(NATS/1.0\r\nfoo: bar\r\nhello: hello-5\r\n\r\n), expected: {"foo" => "bar", "hello" => "hello-5"}}, + {input: %(NATS/1.0 408 Request Timeout\r\nNats-Pending-Messages: 1\r\nNats-Pending-Bytes: 0\r\n\r\n), + expected: {"Status" => "408", "Nats-Pending-Messages" => "1", "Nats-Pending-Bytes" => "0", "Description" => "Request Timeout"}}, + {input: %(NATS/1.0 404 No Messages\r\n\r\n), + expected: {"Status" => "404", "Description" => "No Messages"}} + ] tests.each do |test| result = nc.send(:process_hdr, test[:input]) expect(result).to eql(test[:expected]) diff --git a/spec/client_ws_spec.rb b/spec/client_ws_spec.rb index 8df75a9..ac969ef 100644 --- a/spec/client_ws_spec.rb +++ b/spec/client_ws_spec.rb @@ -1,38 +1,22 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +# frozen_string_literal: true -require 'spec_helper' -require 'openssl' -require 'erb' - -describe 'Client - WebSocket spec' do - before :each do +describe "Client - WebSocket spec" do + before do @natsctl = NatsServerControl.init_with_config_from_string(config.result(binding), opts) @natsctl.start_server(true) end - after :each do + after do @natsctl.kill_server end - context 'when server accepts websocket connections without TLS' do + context "when server accepts websocket connections without TLS" do let :opts do { - 'pid_file' => '/tmp/test-nats-8080.pid', - 'host' => '127.0.0.1', - 'port' => 4080, - 'wsport' => 8080, + "pid_file" => "/tmp/test-nats-8080.pid", + "host" => "127.0.0.1", + "port" => 4080, + "wsport" => 8080 } end let :config do @@ -46,11 +30,11 @@ CONF end - it 'should work' do + it "should work" do nats = NATS.connect("ws://localhost:8080") nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") @@ -58,13 +42,13 @@ end end - context 'when server requires TLS for websocket connections' do + context "when server requires TLS for websocket connections" do let :opts do { - 'pid_file' => '/tmp/test-nats-8443.pid', - 'host' => '127.0.0.1', - 'port' => 4443, - 'wsport' => 8443, + "pid_file" => "/tmp/test-nats-8443.pid", + "host" => "127.0.0.1", + "port" => 4443, + "wsport" => 8443 } end let :config do @@ -82,20 +66,20 @@ end # Flaky on JRuby due to NATS::IO::SocketTimeoutError - it 'should connect over TLS', skip: defined?(JRUBY_VERSION) do + it "should connect over TLS", skip: defined?(JRUBY_VERSION) do tls_context = OpenSSL::SSL::SSLContext.new tls_context.set_params tls_context.ca_file = "./spec/configs/certs/ca.pem" nats = NATS.connect( - servers: ['wss://localhost:8443'], - reconnect: false, - tls: { - context: tls_context - } + servers: ["wss://localhost:8443"], + reconnect: false, + tls: { + context: tls_context + } ) nats.subscribe("hello") do |msg, reply| - nats.publish(reply, 'ok') + nats.publish(reply, "ok") end response = nats.request("hello", "world") diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 3710b27..9e6a436 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -1,26 +1,27 @@ +# frozen_string_literal: true begin - require 'rails' - require 'nats/io/rails' - require 'rails/application' - require 'active_record' - require 'active_record/railtie' + require "rails" + require "nats/io/rails" + require "rails/application" + require "active_record" + require "active_record/railtie" rescue LoadError end -require 'spec_helper' +require "spec_helper" -describe 'Rails integration', :rails do +describe "Rails integration", :rails do before(:all) do - skip 'rails not installed' if not defined?(Rails) + skip "rails not installed" if !defined?(Rails) @serverctl = NatsServerControl.new.tap { |s| s.start_server(true) } end after(:all) do - @serverctl.kill_server if @serverctl + @serverctl&.kill_server end - around(:each) do |example| + around do |example| old_database_url = ENV["DATABASE_URL"] ENV["DATABASE_URL"] ||= "sqlite3::memory:?db_pool_size=#{db_pool_size}&checkout_timeout=#{checkout_timeout}" example.run @@ -33,13 +34,13 @@ let!(:application) do stub_const("TestApp", Class.new(Rails::Application) do - config.load_defaults Rails::VERSION::STRING.split('.').take(2).join('.') + config.load_defaults Rails::VERSION::STRING.split(".").take(2).join(".") config.eager_load = true - config.active_record.legacy_connection_handling = false if ActiveRecord::VERSION::STRING < '7.0.0' - end).tap { Rails.application.initialize!} + config.active_record.legacy_connection_handling = false if ActiveRecord::VERSION::STRING < "7.0.0" + end).tap { Rails.application.initialize! } end - it 'should give back implicitly checked out database connections' do + it "should give back implicitly checked out database connections" do nats = NATS.connect queue = Queue.new diff --git a/spec/js_features_spec.rb b/spec/js_features_spec.rb index 638b353..97f5e7b 100644 --- a/spec/js_features_spec.rb +++ b/spec/js_features_spec.rb @@ -1,36 +1,20 @@ -# Copyright 2016-2023 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'monitor' -require 'tmpdir' - -describe 'JetStream' do - describe 'NATS v2.10 Features' do - before(:each) do +# frozen_string_literal: true + +describe "JetStream" do + describe "NATS v2.10 Features" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream") @s = NatsServerControl.new("nats://127.0.0.1:4852", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - it 'should create pull subscribers with multiple filter subjects' do - skip 'requires v2.10' unless ENV['NATS_SERVER_VERSION'] == "main" + it "should create pull subscribers with multiple filter subjects" do + skip "requires v2.10" unless ENV["NATS_SERVER_VERSION"] == "main" nc = NATS.connect(@s.uri) js = nc.jetstream @@ -45,7 +29,7 @@ # Manually using add_consumer JS API to create an ephemeral. expect do - consumer = js.add_consumer("MULTI_FILTER", { + js.add_consumer("MULTI_FILTER", { name: "my-ephemeral", filter_subjects: ["foo.one.*", "foo.two.*"] }) @@ -56,15 +40,15 @@ msg.ack end expect(msgs.count).to eql(4) - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') - expect(msgs[3].subject).to eql('foo.one.3') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") + expect(msgs[3].subject).to eql("foo.one.3") end.to_not raise_error # Manually using add_consumer JS API to create a durable. expect do - consumer = js.add_consumer("MULTI_FILTER", { + js.add_consumer("MULTI_FILTER", { durable_name: "my-durable", filter_subjects: ["foo.three.*"] }) @@ -75,15 +59,15 @@ msg.ack end expect(msgs.count).to eql(1) - expect(msgs[0].subject).to eql('foo.three.3') + expect(msgs[0].subject).to eql("foo.three.3") end.to_not raise_error # Binding to stream explicitly. expect do sub = js.pull_subscribe(["foo.one.1", "foo.two.2"], "MULTI_FILTER_CONSUMER", stream: "MULTI_FILTER") info = sub.consumer_info - expect(info.name).to eql('MULTI_FILTER_CONSUMER') - expect(info.config.durable_name).to eql('MULTI_FILTER_CONSUMER') + expect(info.name).to eql("MULTI_FILTER_CONSUMER") + expect(info.config.durable_name).to eql("MULTI_FILTER_CONSUMER") expect(info.config.max_waiting).to eql(512) expect(info.num_pending).to eql(3) @@ -92,10 +76,10 @@ msg.ack end expect(msgs.count).to eql(3) - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') - expect(msgs[2].data).to eql('22') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") + expect(msgs[2].data).to eql("22") end.to_not raise_error # Creating a single filter consumer using an Array. @@ -110,7 +94,7 @@ msgs.each do |msg| msg.ack end - expect(msgs[0].subject).to eql('foo.one.1') + expect(msgs[0].subject).to eql("foo.one.1") expect(msgs.count).to eql(1) end.to_not raise_error @@ -124,9 +108,9 @@ msgs.each do |msg| msg.ack end - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") expect(msgs.count).to eql(3) end.to_not raise_error @@ -140,9 +124,9 @@ msgs.each do |msg| msg.ack end - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") expect(msgs.count).to eql(3) end.to raise_error(NATS::JetStream::Error) @@ -156,15 +140,15 @@ msgs.each do |msg| msg.ack end - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") expect(msgs.count).to eql(3) end.to raise_error(NATS::JetStream::Error) end - it 'should create push subscribers with multiple filter subjects' do - skip 'requires v2.10' unless ENV['NATS_SERVER_VERSION'] == "main" + it "should create push subscribers with multiple filter subjects" do + skip "requires v2.10" unless ENV["NATS_SERVER_VERSION"] == "main" nc = NATS.connect(@s.uri) js = nc.jetstream @@ -181,39 +165,39 @@ expect do sub = js.subscribe(["foo.one.1", "foo.two.2"], durable: "MULTI_FILTER_CONSUMER", stream: "MULTI_FILTER") info = sub.consumer_info - expect(info.name).to eql('MULTI_FILTER_CONSUMER') - expect(info.config.durable_name).to eql('MULTI_FILTER_CONSUMER') + expect(info.name).to eql("MULTI_FILTER_CONSUMER") + expect(info.config.durable_name).to eql("MULTI_FILTER_CONSUMER") expect(info.config.max_waiting).to eql(nil) expect(info.num_pending).to eql(3) msgs = [] 3.times do - msg = sub.next_msg() + msg = sub.next_msg msg.ack msgs << msg end expect(msgs.count).to eql(3) - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') - expect(msgs[2].data).to eql('22') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") + expect(msgs[2].data).to eql("22") end.to_not raise_error # Creating a single filter consumer using an Array. expect do sub = js.subscribe(["foo.one.1"], config: {name: "foo"}) info = sub.consumer_info - expect(info.name).to eql('foo') + expect(info.name).to eql("foo") expect(info.num_pending).to eql(1) - msg = sub.next_msg() - expect(msg.subject).to eql('foo.one.1') + msg = sub.next_msg + expect(msg.subject).to eql("foo.one.1") end.to_not raise_error # Auto creating a consumer via a loookup. expect do sub = js.subscribe(["foo.one.1", "foo.two.2"], config: {name: "psub3"}) info = sub.consumer_info - expect(info.name).to eql('psub3') + expect(info.name).to eql("psub3") expect(info.num_pending).to eql(3) msgs = [] @@ -222,9 +206,9 @@ msg.ack msgs << msg end - expect(msgs[0].subject).to eql('foo.one.1') - expect(msgs[1].subject).to eql('foo.two.2') - expect(msgs[2].subject).to eql('foo.two.2') + expect(msgs[0].subject).to eql("foo.one.1") + expect(msgs[1].subject).to eql("foo.two.2") + expect(msgs[2].subject).to eql("foo.two.2") expect(msgs.count).to eql(3) end.to_not raise_error @@ -239,38 +223,38 @@ end.to raise_error(NATS::JetStream::Error) end - it 'should create streams and customers with metadata' do - skip 'requires v2.10' unless ENV['NATS_SERVER_VERSION'] == "main" + it "should create streams and customers with metadata" do + skip "requires v2.10" unless ENV["NATS_SERVER_VERSION"] == "main" nc = NATS.connect(@s.uri) js = nc.jetstream stream = js.add_stream({ - :name => "WITH_METADATA", - :metadata => { - 'foo': 'bar', - 'hello': 'world' + name: "WITH_METADATA", + metadata: { + foo: "bar", + hello: "world" } }) - expect(stream[:config][:metadata][:foo]).to eql('bar') - expect(stream[:config][:metadata][:hello]).to eql('world') + expect(stream[:config][:metadata][:foo]).to eql("bar") + expect(stream[:config][:metadata][:hello]).to eql("world") stream = js.stream_info("WITH_METADATA") - expect(stream[:config][:metadata][:foo]).to eql('bar') - expect(stream[:config][:metadata][:hello]).to eql('world') + expect(stream[:config][:metadata][:foo]).to eql("bar") + expect(stream[:config][:metadata][:hello]).to eql("world") consumer = js.add_consumer("WITH_METADATA", { - :name => "wm", - :metadata => { - 'hoge': 'fuga', - 'quux': 'uqbar' - } + name: "wm", + metadata: { + hoge: "fuga", + quux: "uqbar" + } }) - expect(consumer[:config][:metadata][:hoge]).to eql('fuga') - expect(consumer[:config][:metadata][:quux]).to eql('uqbar') + expect(consumer[:config][:metadata][:hoge]).to eql("fuga") + expect(consumer[:config][:metadata][:quux]).to eql("uqbar") consumer = js.consumer_info("WITH_METADATA", "wm") - expect(consumer[:config][:metadata][:hoge]).to eql('fuga') - expect(consumer[:config][:metadata][:quux]).to eql('uqbar') + expect(consumer[:config][:metadata][:hoge]).to eql("fuga") + expect(consumer[:config][:metadata][:quux]).to eql("uqbar") end end end diff --git a/spec/js_spec.rb b/spec/js_spec.rb index 588e30b..f6dd4ac 100644 --- a/spec/js_spec.rb +++ b/spec/js_spec.rb @@ -1,35 +1,19 @@ -# Copyright 2016-2023 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'monitor' -require 'tmpdir' - -describe 'JetStream' do - describe 'Publish' do - before(:each) do +# frozen_string_literal: true + +describe "JetStream" do + describe "Publish" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream") @s = NatsServerControl.new("nats://127.0.0.1:4524", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - it 'should publish messages to a stream' do + it "should publish messages to a stream" do nc = NATS.connect(@s.uri) # Create sample Stream and pull based consumer from JetStream @@ -56,7 +40,7 @@ # Assert stream name. expect do - ack = js.publish("foo.js", "hello world", stream: "bar") + js.publish("foo.js", "hello world", stream: "bar") end.to raise_error(NATS::JetStream::API::Error) begin @@ -73,19 +57,19 @@ end end - describe 'Pull Subscribe' do - before(:each) do + describe "Pull Subscribe" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream") @s = NatsServerControl.new("nats://127.0.0.1:4524", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - before(:each) do + before do nc = NATS.connect(@s.uri) stream_req = { name: "test", @@ -96,7 +80,7 @@ nc.close end - after(:each) do + after do nc = NATS.connect(@s.uri) stream_req = { name: "test", @@ -117,7 +101,7 @@ js.publish("world", "2") js.publish("hello.world", "3") - sub = js.pull_subscribe("hello", "psub", config: { max_waiting: 30 }) + sub = js.pull_subscribe("hello", "psub", config: {max_waiting: 30}) info = sub.consumer_info expect(info.config.max_waiting).to eql(30) expect(info.num_pending).to eql(1) @@ -143,7 +127,7 @@ expect(info.num_pending).to eql(0) end - it 'should find the pull subscription by subject' do + it "should find the pull subscription by subject" do nc = NATS.connect(@s.uri) js = nc.jetstream @@ -165,7 +149,7 @@ sub.unsubscribe end - it 'should pull subscribe and fetch messages' do + it "should pull subscribe and fetch messages" do nc = NATS.connect(@s.uri) js = nc.jetstream @@ -233,14 +217,14 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info).to include({ - num_waiting: 0, - num_ack_pending: 0, - num_pending: 8, - }) + num_waiting: 0, + num_ack_pending: 0, + num_pending: 8 + }) expect(info[:delivered]).to include({ - consumer_seq: 2, - stream_seq: 2 - }) + consumer_seq: 2, + stream_seq: 2 + }) expect(info[:delivered][:consumer_seq]).to eql(2) expect(info[:delivered][:stream_seq]).to eql(2) @@ -273,9 +257,9 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info[:delivered]).to include({ - consumer_seq: 10, - stream_seq: 10 - }) + consumer_seq: 10, + stream_seq: 10 + }) expect(sub.pending_queue.size).to eql(0) # Publish 5 more messages. @@ -285,9 +269,9 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info[:delivered]).to include({ - consumer_seq: 10, - stream_seq: 10 - }) + consumer_seq: 10, + stream_seq: 10 + }) # Only 5 messages will be received, with 2 pending though won't be delivered yet. # This should take as long as the timeout but should not throw an exception since @@ -306,9 +290,9 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info[:delivered]).to include({ - consumer_seq: 15, - stream_seq: 15 - }) + consumer_seq: 15, + stream_seq: 15 + }) # 10 more messages 16.upto(25) { |n| js.publish("test", "hello: #{n}") } @@ -321,9 +305,9 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info[:delivered]).to include({ - consumer_seq: 15, - stream_seq: 15 - }) + consumer_seq: 15, + stream_seq: 15 + }) # Get 10 messages which are the total (25). msgs = sub.fetch(10) @@ -344,14 +328,14 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info).to include({ - num_waiting: 0, - num_ack_pending: 0, - num_pending: 0, - }) + num_waiting: 0, + num_ack_pending: 0, + num_pending: 0 + }) expect(info[:delivered]).to include({ - consumer_seq: 25, - stream_seq: 25 - }) + consumer_seq: 25, + stream_seq: 25 + }) # expect(sub.pending_queue.size).to eql(0) expect(i).to eql(26) @@ -364,28 +348,26 @@ resp = nc.request("$JS.API.CONSUMER.INFO.test.test") info = JSON.parse(resp.data, symbolize_names: true) expect(info).to include({ - num_waiting: 0, - num_ack_pending: 0, - num_pending: 0, - }) + num_waiting: 0, + num_ack_pending: 0, + num_pending: 0 + }) expect(info[:delivered]).to include({ - consumer_seq: 25, - stream_seq: 25 - }) + consumer_seq: 25, + stream_seq: 25 + }) # Make a lot of requests to get a request timeout error. ts = [] errors = [] 3.times do ts << Thread.new do - begin - msgs = sub.fetch(2, timeout: 0.2) - rescue => e - errors << e - end + msgs = sub.fetch(2, timeout: 0.2) + rescue => e + errors << e end end - ts.each {|t| t.join } + ts.each { |t| t.join } expect(errors.count > 0).to eql(true) e = errors.first @@ -412,12 +394,10 @@ ts = [] 5.times do ts << Thread.new do - begin - msgs = sub.fetch(1, timeout: 0.5) - expect(msgs).to be_empty - rescue => e - errors << e - end + msgs = sub.fetch(1, timeout: 0.5) + expect(msgs).to be_empty + rescue => e + errors << e end end ts.each do |t| @@ -430,7 +410,7 @@ nc.close end - it 'should unsubscribe' do + it "should unsubscribe" do nc = NATS.connect(@s.uri) js = nc.jetstream @@ -450,7 +430,7 @@ sub = js.pull_subscribe("test", "delsub", stream: "test") expect do - msgs = sub.fetch(1, timeout: 0.5) + sub.fetch(1, timeout: 0.5) end.to raise_error(NATS::IO::Timeout) ack = js.publish("test", "hello") @@ -472,7 +452,7 @@ end.to raise_error(NATS::IO::BadSubscription) end - it 'should account pending data' do + it "should account pending data" do nc = NATS.connect(@s.uri) nc2 = NATS.connect(@s.uri) js = nc.jetstream @@ -486,7 +466,7 @@ # Continuously send messages until reaching pending bytes limit. t = Thread.new do - payload = 'A' * 1024 * 1024 + payload = "A" * 1024 * 1024 loop do nc2.publish(subject, payload) sleep 0.01 @@ -514,138 +494,132 @@ nc2.close end - it 'should create and bind to consumer with name' do + it "should create and bind to consumer with name" do nc = NATS.connect(@s.uri) js = nc.jetstream - js.add_stream(name: "ctests", subjects: ['a', 'b', 'c.>']) - js.publish('a', 'hello world!') - js.publish('b', 'hello world!!') - js.publish('c.d', 'hello world!!!') - js.publish('c.d.e', 'hello world!!!!') + js.add_stream(name: "ctests", subjects: ["a", "b", "c.>"]) + js.publish("a", "hello world!") + js.publish("b", "hello world!!") + js.publish("c.d", "hello world!!!") + js.publish("c.d.e", "hello world!!!!") tsub = nc.subscribe("$JS.API.CONSUMER.>") # ephemeral consumer - consumer_name = 'ephemeral' + consumer_name = "ephemeral" cinfo = js.add_consumer("ctests", name: consumer_name, ack_policy: "explicit") expect(cinfo.config.name).to eql(consumer_name) msg = tsub.next_msg - expect(msg.subject).to eql('$JS.API.CONSUMER.CREATE.ctests.ephemeral') + expect(msg.subject).to eql("$JS.API.CONSUMER.CREATE.ctests.ephemeral") - sub = js.pull_subscribe("", "", stream: 'ctests', consumer: 'ephemeral') + sub = js.pull_subscribe("", "", stream: "ctests", consumer: "ephemeral") cinfo = sub.consumer_info expect(cinfo.config.name).to eql(consumer_name) msgs = sub.fetch(1) - expect(msgs.first.data).to eql('hello world!') + expect(msgs.first.data).to eql("hello world!") msgs.first.ack_sync - msg = tsub.next_msg() - expect(msg.subject).to eql('$JS.API.CONSUMER.INFO.ctests.ephemeral') + msg = tsub.next_msg + expect(msg.subject).to eql("$JS.API.CONSUMER.INFO.ctests.ephemeral") tsub.unsubscribe # Create durable pull consumer with a name. tsub = nc.subscribe("$JS.API.CONSUMER.>") - consumer_name = 'durable' + consumer_name = "durable" cinfo = js.add_consumer("ctests", - name: consumer_name, - durable_name: consumer_name, - ack_policy: "explicit", - ) + name: consumer_name, + durable_name: consumer_name, + ack_policy: "explicit") expect(cinfo.config.name).to eql(consumer_name) - msg = tsub.next_msg() - expect(msg.subject).to eql('$JS.API.CONSUMER.CREATE.ctests.durable') - sub = js.pull_subscribe("", "durable", stream: 'ctests') + msg = tsub.next_msg + expect(msg.subject).to eql("$JS.API.CONSUMER.CREATE.ctests.durable") + sub = js.pull_subscribe("", "durable", stream: "ctests") cinfo = sub.consumer_info expect(cinfo.config.name).to eql(consumer_name) msgs = sub.fetch(1) - expect(msgs.first.data).to eql('hello world!') + expect(msgs.first.data).to eql("hello world!") msgs.first.ack_sync - msg = tsub.next_msg() - expect(msg.subject).to eql('$JS.API.CONSUMER.INFO.ctests.durable') + msg = tsub.next_msg + expect(msg.subject).to eql("$JS.API.CONSUMER.INFO.ctests.durable") tsub.unsubscribe # Create durable pull consumer with a name and a filter_subject tsub = nc.subscribe("$JS.API.CONSUMER.>") - consumer_name = 'durable2' + consumer_name = "durable2" cinfo = js.add_consumer("ctests", - name: consumer_name, - durable_name: consumer_name, - filter_subject: 'b', - ack_policy: "explicit", - ) + name: consumer_name, + durable_name: consumer_name, + filter_subject: "b", + ack_policy: "explicit") expect(cinfo.config.name).to eql(consumer_name) - msg = tsub.next_msg() - expect(msg.subject).to eql('$JS.API.CONSUMER.CREATE.ctests.durable2.b') - sub = js.pull_subscribe("", "durable2", stream: 'ctests') + msg = tsub.next_msg + expect(msg.subject).to eql("$JS.API.CONSUMER.CREATE.ctests.durable2.b") + sub = js.pull_subscribe("", "durable2", stream: "ctests") msgs = sub.fetch(1) - expect(msgs.first.data).to eql('hello world!!') + expect(msgs.first.data).to eql("hello world!!") msgs.first.ack_sync tsub.unsubscribe # Create durable pull consumer with a name and a filter_subject tsub = nc.subscribe("$JS.API.CONSUMER.>") - consumer_name = 'durable3' + consumer_name = "durable3" cinfo = js.add_consumer("ctests", - name: consumer_name, - durable_name: consumer_name, - filter_subject: '>', - ack_policy: "explicit", - ) + name: consumer_name, + durable_name: consumer_name, + filter_subject: ">", + ack_policy: "explicit") expect(cinfo.config.name).to eql(consumer_name) - msg = tsub.next_msg() - expect(msg.subject).to eql('$JS.API.CONSUMER.CREATE.ctests.durable3') - sub = js.pull_subscribe("", "durable3", stream: 'ctests') + msg = tsub.next_msg + expect(msg.subject).to eql("$JS.API.CONSUMER.CREATE.ctests.durable3") + sub = js.pull_subscribe("", "durable3", stream: "ctests") msgs = sub.fetch(1) - expect(msgs.first.data).to eql('hello world!') + expect(msgs.first.data).to eql("hello world!") msgs.first.ack_sync tsub.unsubscribe # name and durable must match if both present. expect do js.add_consumer("ctests", - name: "foo", - durable_name: "bar", - ack_policy: "explicit", - ) + name: "foo", + durable_name: "bar", + ack_policy: "explicit") end.to raise_error NATS::JetStream::Error::BadRequest begin js.add_consumer("ctests", - name: "foo", - durable_name: "bar", - ack_policy: "explicit", - ) + name: "foo", + durable_name: "bar", + ack_policy: "explicit") rescue => e - expect(e.description).to eql(%Q(consumer name in subject does not match durable name in request)) + expect(e.description).to eql(%(consumer name in subject does not match durable name in request)) end # consumer name and inactive - consumer_name = 'inactive' + consumer_name = "inactive" cinfo = js.add_consumer("ctests", - name: consumer_name, - durable_name: consumer_name, - inactive_threshold: 2, - ack_policy: "explicit", - mem_storage: true - ) + name: consumer_name, + durable_name: consumer_name, + inactive_threshold: 2, + ack_policy: "explicit", + mem_storage: true) expect(cinfo.config.inactive_threshold).to eql(2000000000) expect(cinfo.config.mem_storage).to eql(true) end end - describe 'Push Subscribe' do - before(:each) do + describe "Push Subscribe" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream") @s = NatsServerControl.new("nats://127.0.0.1:4527", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - before(:each) do + before do nc = NATS.connect(@s.uri) stream_req = { name: "test", @@ -656,7 +630,7 @@ nc.close end - after(:each) do + after do nc = NATS.connect(@s.uri) stream_req = { name: "test", @@ -709,8 +683,8 @@ end msgs = future.wait_for(1) expect(msgs.count).to eql(2) - expect(msgs[0].data).to eql('1') - expect(msgs[1].data).to eql('2') + expect(msgs[0].data).to eql("1") + expect(msgs[1].data).to eql("2") info = sub.consumer_info expect(info.stream_name).to eql("hello") @@ -797,7 +771,7 @@ end 50.times do |i| - js.publish("test", "#{i}") + js.publish("test", i.to_s) end # Each should get at least a couple of messages @@ -808,13 +782,13 @@ it "should create subscribers with custom config" do js = nc.jetstream - js.add_stream(name:"custom", subjects:["custom"]) + js.add_stream(name: "custom", subjects: ["custom"]) 1.upto(10).each do |i| js.publish("custom", "n:#{i}") end - sub = js.subscribe("custom", durable: 'example', config: { deliver_policy: 'new' }) + sub = js.subscribe("custom", durable: "example", config: {deliver_policy: "new"}) js.publish("custom", "last") msg = sub.next_msg @@ -830,17 +804,17 @@ end end - describe 'Domain' do - before(:each) do + describe "Domain" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream-domain") config_opts = { - 'pid_file' => '/tmp/nats_js_domain_1.pid', - 'host' => '127.0.0.1', - 'port' => 4729, + "pid_file" => "/tmp/nats_js_domain_1.pid", + "host" => "127.0.0.1", + "port" => 4729 } @domain = "estre" - @s = NatsServerControl.init_with_config_from_string(%Q( - port = #{config_opts['port']} + @s = NatsServerControl.init_with_config_from_string(%( + port = #{config_opts["port"]} jetstream { domain = #{@domain} store_dir = "#{@tmpdir}" @@ -849,12 +823,12 @@ @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - it 'should produce, consume and ack messages in a stream' do + it "should produce, consume and ack messages in a stream" do nc = NATS.connect(@s.uri) # Create stream in the domain. @@ -865,7 +839,7 @@ subjects: [subject] } resp = nc.request("$JS.#{@domain}.API.STREAM.CREATE.#{stream_name}", - stream_req.to_json) + stream_req.to_json) expect(resp).to_not be_nil # Now create a consumer in the domain. @@ -881,7 +855,7 @@ } } resp = nc.request("$JS.#{@domain}.API.CONSUMER.DURABLE.CREATE.#{stream_name}.#{durable_name}", - consumer_req.to_json) + consumer_req.to_json) expect(resp).to_not be_nil # Create producer with custom domain. @@ -923,28 +897,28 @@ js = nc.jetstream(domain: "estre") info = js.account_info expected = a_hash_including({ - :type => "io.nats.jetstream.api.v1.account_info_response", - :memory => 0, - :storage => 66, - :streams => 1, - :consumers => 1, - :limits => a_hash_including({ - :max_memory => -1, - :max_storage => -1, - :max_streams => -1, - :max_consumers => -1, - :max_ack_pending => -1, - :memory_max_stream_bytes => -1, - :storage_max_stream_bytes => -1, - :max_bytes_required => false + type: "io.nats.jetstream.api.v1.account_info_response", + memory: 0, + storage: 66, + streams: 1, + consumers: 1, + limits: a_hash_including({ + max_memory: -1, + max_storage: -1, + max_streams: -1, + max_consumers: -1, + max_ack_pending: -1, + memory_max_stream_bytes: -1, + storage_max_stream_bytes: -1, + max_bytes_required: false }), - :domain => "estre", - :api => {:total => 5, :errors => 0} + domain: "estre", + api: {total: 5, errors: 0} }) expect(info).to match(expected) end - it 'should nack messages with a delay' do + it "should nack messages with a delay" do nc = NATS.connect(@s.uri) # Create stream in the domain. @@ -1004,7 +978,7 @@ expect(msg.nak(timeout: 2)).to be_a(NATS::Msg) end - it 'should bail when stream or consumer does not exist in domain' do + it "should bail when stream or consumer does not exist in domain" do nc = NATS.connect(@s.uri) js = nc.JetStream(domain: @domain) @@ -1020,7 +994,7 @@ # Stream that does not exist. expect do - sub = js.pull_subscribe("foo", "bar", stream: "nonexistent") + js.pull_subscribe("foo", "bar", stream: "nonexistent") end.to raise_error(NATS::JetStream::Error::StreamNotFound) # Now create the stream. @@ -1126,31 +1100,31 @@ expect do raise NATS::JetStream::Error::ServiceUnavailable end.to raise_error(an_instance_of(NATS::JetStream::Error::ServiceUnavailable) - .and having_attributes(code: 503)) + .and(having_attributes(code: 503))) expect do raise NATS::JetStream::Error::NotFound end.to raise_error(an_instance_of(NATS::JetStream::Error::NotFound) - .and having_attributes(code: 404)) + .and(having_attributes(code: 404))) expect do raise NATS::JetStream::Error::BadRequest end.to raise_error(an_instance_of(NATS::JetStream::Error::BadRequest) - .and having_attributes(code: 400)) + .and(having_attributes(code: 400))) end end - describe 'JSM' do - before(:each) do + describe "JSM" do + before do @tmpdir = Dir.mktmpdir("ruby-jsm") config_opts = { - 'pid_file' => '/tmp/nats_jsm_1.pid', - 'host' => '127.0.0.1', - 'port' => 4730, + "pid_file" => "/tmp/nats_jsm_1.pid", + "host" => "127.0.0.1", + "port" => 4730 } @domain = "estre" - @s = NatsServerControl.init_with_config_from_string(%Q( - port = #{config_opts['port']} + @s = NatsServerControl.init_with_config_from_string(%( + port = #{config_opts["port"]} jetstream { domain = #{@domain} store_dir = "#{@tmpdir}" @@ -1159,7 +1133,7 @@ @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end @@ -1172,13 +1146,13 @@ } resp = nc.jsm.add_stream(stream_config) expect(resp).to be_a NATS::JetStream::API::StreamCreateResponse - expect(resp.type).to eql('io.nats.jetstream.api.v1.stream_create_response') - expect(resp.config.name).to eql('mystream') + expect(resp.type).to eql("io.nats.jetstream.api.v1.stream_create_response") + expect(resp.config.name).to eql("mystream") expect(resp.config.num_replicas).to eql(1) - resp = nc.jsm.add_stream(name: 'stream2') + resp = nc.jsm.add_stream(name: "stream2") expect(resp).to be_a NATS::JetStream::API::StreamCreateResponse - expect(resp.config.name).to eql('stream2') + expect(resp.config.name).to eql("stream2") expect(resp.config.num_replicas).to eql(1) # Can also use the types for the request. @@ -1188,7 +1162,7 @@ config = NATS::JetStream::API::StreamConfig.new(stream_config) resp = nc.jsm.add_stream(config) expect(resp).to be_a NATS::JetStream::API::StreamCreateResponse - expect(resp.config.name).to eql('stream3') + expect(resp.config.name).to eql("stream3") expect(resp.config.num_replicas).to eql(1) expect do @@ -1204,14 +1178,13 @@ nc.jsm.add_stream(name: "foo.bar*baz") end.to raise_error(ArgumentError) - placement = { cluster: "foo", tags: ["a"]} + placement = {cluster: "foo", tags: ["a"]} resp = nc.jsm.add_stream(name: "v29", - subjects: ["v29"], - num_replicas: 1, - no_ack: true, - # allow_direct: true, - placement: placement - ) + subjects: ["v29"], + num_replicas: 1, + no_ack: true, + # allow_direct: true, + placement: placement) expect(resp).to be_a NATS::JetStream::API::StreamCreateResponse # expect(resp.config.allow_direct).to eql(true) expect(resp.config.no_ack).to eql(true) @@ -1293,7 +1266,7 @@ js.publish("foo", "Hello World!") # Now lookup the consumer using the ephemeral name. - info = nc.jsm.consumer_info(stream_name, consumer.name) + nc.jsm.consumer_info(stream_name, consumer.name) # Fetch with pull subscribe. psub = js.pull_subscribe("foo", "test-create") @@ -1314,7 +1287,7 @@ end.to raise_error NATS::Timeout # Create durable consumer - consumer_config = { + { durable_name: "test-create2", num_replicas: 3 } @@ -1328,8 +1301,8 @@ stream_name = "to-delete" consumer_name = "dur" nc.jsm.add_stream(name: stream_name) - nc.jsm.add_consumer(stream_name, { durable_name: consumer_name}) - info = nc.jsm.consumer_info(stream_name, consumer_name) + nc.jsm.add_consumer(stream_name, {durable_name: consumer_name}) + nc.jsm.consumer_info(stream_name, consumer_name) ok = nc.jsm.delete_consumer(stream_name, consumer_name) expect(ok).to eql(true) end diff --git a/spec/kv_spec.rb b/spec/kv_spec.rb index 4d629aa..3f5266a 100644 --- a/spec/kv_spec.rb +++ b/spec/kv_spec.rb @@ -1,20 +1,18 @@ -require 'spec_helper' -require 'monitor' -require 'tmpdir' +# frozen_string_literal: true -describe 'KeyValue' do - before(:each) do +describe "KeyValue" do + before do @tmpdir = Dir.mktmpdir("ruby-jetstream") @s = NatsServerControl.new("nats://127.0.0.1:4621", "/tmp/test-nats.pid", "-js -sd=#{@tmpdir}") @s.start_server(true) end - after(:each) do + after do @s.kill_server FileUtils.remove_entry(@tmpdir) end - it 'should support access to KeyValue stores' do + it "should support access to KeyValue stores" do nc = NATS.connect(@s.uri) js = nc.jetstream @@ -84,57 +82,57 @@ end.to raise_error(NATS::KeyValue::BadBucketError) end - it 'should support access to KeyValue stores from multiple instances' do + it "should support access to KeyValue stores from multiple instances" do nc = NATS.connect(@s.uri) js = nc.jetstream kv = js.create_key_value(bucket: "TEST2") - ('a'..'z').each do |l| - kv.put(l, l*10) + ("a".."z").each do |l| + kv.put(l, l * 10) end nc2 = NATS.connect(@s.uri) js2 = nc2.jetstream kv2 = js2.key_value("TEST2") a = kv2.get("a") - expect(a.value).to eql('aaaaaaaaaa') + expect(a.value).to eql("aaaaaaaaaa") nc.close nc2.close end - it 'should support get by revision' do + it "should support get by revision" do nc = NATS.connect(@s.uri) js = nc.jetstream kv = js.create_key_value(bucket: "TEST", history: 5, ttl: 3600, description: "Basic KV") si = js.stream_info("KV_TEST") config = NATS::JetStream::API::StreamConfig.new( - name: "KV_TEST", - description: "Basic KV", - subjects: ["$KV.TEST.>"], - allow_rollup_hdrs: true, - deny_delete: true, - deny_purge: false, - discard: "new", - duplicate_window: 120 * ::NATS::NANOSECONDS, - max_age: 3600 * ::NATS::NANOSECONDS, - max_bytes: -1, - max_consumers: -1, - max_msg_size: -1, - max_msgs: -1, - max_msgs_per_subject: 5, - mirror: nil, - no_ack: nil, - num_replicas: 1, - placement: nil, - retention: "limits", - sealed: false, - sources: nil, - storage: "file", - republish: nil, - allow_direct: false, - mirror_direct: false, + name: "KV_TEST", + description: "Basic KV", + subjects: ["$KV.TEST.>"], + allow_rollup_hdrs: true, + deny_delete: true, + deny_purge: false, + discard: "new", + duplicate_window: 120 * ::NATS::NANOSECONDS, + max_age: 3600 * ::NATS::NANOSECONDS, + max_bytes: -1, + max_consumers: -1, + max_msg_size: -1, + max_msgs: -1, + max_msgs_per_subject: 5, + mirror: nil, + no_ack: nil, + num_replicas: 1, + placement: nil, + retention: "limits", + sealed: false, + sources: nil, + storage: "file", + republish: nil, + allow_direct: false, + mirror_direct: false ) expect(config).to eql(si.config) @@ -144,13 +142,13 @@ end.to raise_error(NATS::KeyValue::KeyNotFoundError) # Simple put - revision = kv.put("name", 'alice') + revision = kv.put("name", "alice") expect(revision).to eql(1) # Simple get result = kv.get("name") expect(result.revision).to eq(1) - expect(result.value).to eql('alice') + expect(result.value).to eql("alice") # Delete ok = kv.delete("name") @@ -163,15 +161,15 @@ end.to raise_error(NATS::KeyValue::KeyNotFoundError) # Recreate with different name. - revision = kv.create("name", 'bob') + revision = kv.create("name", "bob") expect(revision).to eql(3) # Expect last revision to be 4. expect do - kv.delete('name', last: 4) + kv.delete("name", last: 4) end.to raise_error(NATS::JetStream::Error::BadRequest) - # Correct revision should work. + # Correct revision should work. revision = kv.delete("name", last: 3) expect(revision).to eql(4) @@ -189,26 +187,26 @@ expect(revision).to eql(6) # Create a different key. - revision = kv.create("age", '2038') + revision = kv.create("age", "2038") expect(revision).to eql(7) # Get current. entry = kv.get("age") - expect(entry.value).to eql('2038') + expect(entry.value).to eql("2038") expect(entry.revision).to eql(7) # Update the new key. - revision = kv.update("age", '2039', last: revision) + revision = kv.update("age", "2039", last: revision) expect(revision).to eql(8) # Get latest. entry = kv.get("age") - expect(entry.value).to eql('2039') + expect(entry.value).to eql("2039") expect(entry.revision).to eql(8) # Internally uses get msg API instead of get last msg. entry = kv.get("age", revision: 7) - expect(entry.value).to eql('2038') + expect(entry.value).to eql("2038") expect(entry.revision).to eql(7) # Getting past keys with the wrong expected subject is an error. @@ -219,7 +217,7 @@ kv.get("age", revision: 6) rescue => e expect(e.message).to eql( - %Q(nats: key not found: expected '$KV.TEST.age', but got '$KV.TEST.name') + %(nats: key not found: expected '$KV.TEST.age', but got '$KV.TEST.name') ) end expect do @@ -230,16 +228,16 @@ end.to raise_error NATS::KeyValue::KeyNotFoundError expect do - entry = kv.get("name", revision=3) - expect(entry.value).to eql('bob') + entry = kv.get("name", revision = 3) + expect(entry.value).to eql("bob") end # match="nats: wrong last sequence: 8") expect do - kv.create("age", '1') + kv.create("age", "1") end.to raise_error NATS::KeyValue::KeyWrongLastSequenceError begin - kv.create("age", '1') + kv.create("age", "1") rescue => e expect(e.message).to eql("nats: wrong last sequence: 8") end @@ -249,11 +247,11 @@ kv.create("age", "final") expect do - kv.create("age", '1') + kv.create("age", "1") end.to raise_error NATS::KeyValue::KeyWrongLastSequenceError begin - kv.create("age", '1') + kv.create("age", "1") rescue => e expect(e.message).to eql("nats: wrong last sequence: 10") end @@ -262,15 +260,15 @@ expect(entry.revision).to eql(10) # Purge - status = kv.status() + status = kv.status expect(status.values).to eql(9) kv.purge("age") - status = kv.status() + status = kv.status expect(status.values).to eql(6) kv.purge("name") - status = kv.status() + status = kv.status expect(status.values).to eql(2) expect do @@ -284,32 +282,32 @@ nc.close end - it 'should support direct get' do + it "should support direct get" do nc = NATS.connect(@s.uri) js = nc.jetstream kv = js.create_key_value( - bucket: "TESTDIRECT", - history: 5, - ttl: 3600, - description: "KV DIRECT", - direct: true, - ) + bucket: "TESTDIRECT", + history: 5, + ttl: 3600, + description: "KV DIRECT", + direct: true + ) si = js.stream_info("KV_TESTDIRECT") expect(si.config.allow_direct).to eql(true) - kv.create("A", '1') - kv.create("B", '2') - kv.create("C", '3') - kv.create("D", '4') - kv.create("E", '5') - kv.create("F", '6') + kv.create("A", "1") + kv.create("B", "2") + kv.create("C", "3") + kv.create("D", "4") + kv.create("E", "5") + kv.create("F", "6") - kv.put("C", '33') - kv.put("D", '44') - kv.put("C", '333') + kv.put("C", "33") + kv.put("D", "44") + kv.put("C", "333") msg = js.get_msg("KV_TESTDIRECT", seq: 1, direct: true) - expect(msg.data).to eql('1') - expect(msg.subject).to eql('$KV.TESTDIRECT.A') + expect(msg.data).to eql("1") + expect(msg.subject).to eql("$KV.TESTDIRECT.A") entry = kv.get("A") expect(entry.key).to eql("A") @@ -323,11 +321,11 @@ # last by subject msg = js.get_msg("KV_TESTDIRECT", subject: "$KV.TESTDIRECT.C", direct: true) - expect(msg.data).to eql('333') + expect(msg.data).to eql("333") # next by subject msg = js.get_msg("KV_TESTDIRECT", subject: "$KV.TESTDIRECT.C", seq: 4, next: true, direct: true) - expect(msg.data).to eql('33') + expect(msg.data).to eql("33") # Malformed request expect do @@ -348,40 +346,40 @@ nc.close end - it 'should support republish' do + it "should support republish" do nc = NATS.connect(@s.uri) js = nc.jetstream kv = js.create_key_value( - bucket: "TESTRP", - direct: true, - republish: { - src: ">", - dest: "bar.>" - } - ) + bucket: "TESTRP", + direct: true, + republish: { + src: ">", + dest: "bar.>" + } + ) sub = nc.subscribe("bar.>") - kv.put("hello.world", 'Hello World!') + kv.put("hello.world", "Hello World!") msg = sub.next_msg expect(msg.subject).to eql("bar.$KV.TESTRP.hello.world") expect(msg.data).to eql("Hello World!") sub.unsubscribe kv = js.create_key_value( - bucket: "TEST_RP_HEADERS", - direct: true, - republish: { - src: ">", - dest: "quux.>", - headers_only: true - } - ) + bucket: "TEST_RP_HEADERS", + direct: true, + republish: { + src: ">", + dest: "quux.>", + headers_only: true + } + ) sub = nc.subscribe("quux.>") - kv.put("hello.world", 'Hello World!') + kv.put("hello.world", "Hello World!") msg = sub.next_msg expect(msg.subject).to eql("quux.$KV.TEST_RP_HEADERS.hello.world") expect(msg.data).to eql("") - expect(msg.header['Nats-Msg-Size']).to eql('12') + expect(msg.header["Nats-Msg-Size"]).to eql("12") sub.unsubscribe nc.close diff --git a/spec/nuid_spec.rb b/spec/nuid_spec.rb index 64a31f6..ac021ab 100644 --- a/spec/nuid_spec.rb +++ b/spec/nuid_spec.rb @@ -1,20 +1,6 @@ -# Copyright 2016-2018 The NATS Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +# frozen_string_literal: true -require 'spec_helper' - -describe 'NUID' do +describe "NUID" do it "should have a fixed length and be unique" do nuid = NATS::NUID.new entries = [] @@ -41,17 +27,17 @@ it "should randomize the prefix after sequence is done" do nuid = NATS::NUID.new - seq_a = nuid.instance_variable_get('@seq') - inc_a = nuid.instance_variable_get('@inc') + seq_a = nuid.instance_variable_get("@seq") + inc_a = nuid.instance_variable_get("@inc") a = nuid.next - seq_b = nuid.instance_variable_get('@seq') - inc_b = nuid.instance_variable_get('@inc') + seq_b = nuid.instance_variable_get("@seq") + nuid.instance_variable_get("@inc") expect(seq_a < seq_b).to eql(true) expect(seq_b).to eql(seq_a + inc_a) b = nuid.next - nuid.instance_variable_set('@seq', NATS::NUID::MAX_SEQ+1) + nuid.instance_variable_set("@seq", NATS::NUID::MAX_SEQ + 1) c = nuid.next l = NATS::NUID::PREFIX_LENGTH expect(a[0..l]).to eql(b[0..l]) @@ -60,7 +46,7 @@ context "when using the NUID.next" do it "should be thread safe" do - ts = Hash.new { |h,k| h[k] = { }} + ts = Hash.new { |h, k| h[k] = {} } total = 100_000 10.times do |n| ts[n][:thread] = Thread.new do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a45f756..ae65bd7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,8 +11,11 @@ require "nats/io/jetstream" require "nkeys" +require "fileutils" require "tempfile" require "monitor" +require "openssl" +require "erb" Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } @@ -31,9 +34,9 @@ config.filter_run_excluding(tls_verify_hostname: true) if defined?(JRUBY_VERSION) if Process.respond_to?(:fork) - config.after(:each) do + config.after do # Mark all clients as closed to avoid reconnects in fork tests - NATS::Client::const_get(:INSTANCES).each do |client| + NATS::Client.const_get(:INSTANCES).each do |client| client.close unless client.closed? end end diff --git a/spec/support/dns.rb b/spec/support/dns.rb index a52fac1..8249d91 100644 --- a/spec/support/dns.rb +++ b/spec/support/dns.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Update Ruby DNS resolver to point custom hosts to 127.0.0.1 LOCAL_HOSTS = <<~TXT @@ -35,13 +37,11 @@ def initialize(host, serv, *rest, **kwargs) class << Socket # :stopdoc: - alias original_getaddrinfo getaddrinfo + alias_method :original_getaddrinfo, :getaddrinfo # :startdoc: def getaddrinfo(host, *args) - begin - return original_getaddrinfo(Resolv.getaddress(host).to_s, *args) - rescue Resolv::ResolvError - original_getaddrinfo(host, *args) - end + original_getaddrinfo(Resolv.getaddress(host).to_s, *args) + rescue Resolv::ResolvError + original_getaddrinfo(host, *args) end end diff --git a/spec/support/nats_server_helper.rb b/spec/support/nats_server_helper.rb index 9691ee0..a8e64ea 100644 --- a/spec/support/nats_server_helper.rb +++ b/spec/support/nats_server_helper.rb @@ -1,37 +1,38 @@ +# frozen_string_literal: true + require "socket" class NatsServerControl BIN_PATH = File.expand_path(File.join(__dir__, "../../scripts/nats-server")) attr_reader :was_running - alias :was_running? :was_running + alias_method :was_running?, :was_running class << self - def init_with_config(config_file) - config = File.open(config_file) { |f| YAML.load(f) } - if auth = config['authorization'] - uri = "nats://#{auth['user']}:#{auth['password']}@#{config['net']}:#{config['port']}" + config = File.open(config_file) { |f| YAML.safe_load(f) } + uri = if (auth = config["authorization"]) + "nats://#{auth["user"]}:#{auth["password"]}@#{config["net"]}:#{config["port"]}" else - uri = "nats://#{config['net']}:#{config['port']}" + "nats://#{config["net"]}:#{config["port"]}" end - NatsServerControl.new(uri, config['pid_file'], "-c #{config_file}") + NatsServerControl.new(uri, config["pid_file"], "-c #{config_file}") end - def init_with_config_from_string(config_string, config={}) + def init_with_config_from_string(config_string, config = {}) puts config_string if debug? - config_file = Tempfile.new(['nats-cluster-tests', '.conf']) - File.open(config_file.path, 'w') do |f| + config_file = Tempfile.new(["nats-cluster-tests", ".conf"]) + File.open(config_file.path, "w") do |f| f.puts(config_string) end - if auth = config['authorization'] - uri = "nats://#{auth['user']}:#{auth['password']}@#{config['host']}:#{config['port']}" + uri = if (auth = config["authorization"]) + "nats://#{auth["user"]}:#{auth["password"]}@#{config["host"]}:#{config["port"]}" else - uri = "nats://#{config['host']}:#{config['port']}" + "nats://#{config["host"]}:#{config["port"]}" end - NatsServerControl.new(uri, config['pid_file'], "-c #{config_file.path}", config_file) + NatsServerControl.new(uri, config["pid_file"], "-c #{config_file.path}", config_file) end def debug? @@ -41,7 +42,7 @@ def debug? attr_reader :uri - def initialize(uri='nats://127.0.0.1:4222', pid_file='/tmp/test-nats.pid', flags=nil, config_file=nil) + def initialize(uri = "nats://127.0.0.1:4222", pid_file = "/tmp/test-nats.pid", flags = nil, config_file = nil) @uri = uri.is_a?(URI) ? uri : URI.parse(uri) @pid_file = pid_file @flags = flags @@ -57,12 +58,12 @@ def server_pid end def server_mem_mb - server_status = %x[ps axo pid=,rss= | grep #{server_pid}] + server_status = `ps axo pid=,rss= | grep #{server_pid}` parts = server_status.lstrip.split(/\s+/) - rss = (parts[1].to_i)/1024 + parts[1].to_i / 1024 end - def start_server(wait_for_server=true) + def start_server(wait_for_server = true) if server_running? @uri @was_running = true return 0 @@ -91,8 +92,8 @@ def start_server(wait_for_server=true) def kill_server if FileTest.exist? @pid_file - %x[kill -TERM #{server_pid} 2> /dev/null] - %x[rm #{@pid_file} 2> /dev/null] + `kill -TERM #{server_pid} 2> /dev/null` + `rm #{@pid_file} 2> /dev/null` sleep(0.2) @pid = nil end diff --git a/spec/support/utils.rb b/spec/support/utils.rb index 54e777d..d9fea26 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -1,9 +1,9 @@ +# frozen_string_literal: true def with_timeout(timeout) start_time = Time.now yield end_time = Time.now - duration = end_time - start_time fail if end_time - start_time > timeout end @@ -18,7 +18,7 @@ def initialize @result = nil end - def wait_for(timeout=1) + def wait_for(timeout = 1) return @result if @result @mon.synchronize do @done.wait(timeout) From ffd6218fa598d9a43972cc352da743d3e350b7e3 Mon Sep 17 00:00:00 2001 From: Vladimir Dementyev Date: Wed, 8 Jan 2025 14:47:51 -0800 Subject: [PATCH 2/2] - specs: more flaky/stuck tests --- spec/client_cluster_reconnect_spec.rb | 174 +++++++++----------------- spec/client_drain_spec.rb | 23 ++-- spec/js_spec.rb | 47 +++---- 3 files changed, 99 insertions(+), 145 deletions(-) diff --git a/spec/client_cluster_reconnect_spec.rb b/spec/client_cluster_reconnect_spec.rb index 26500ce..aaf7371 100644 --- a/spec/client_cluster_reconnect_spec.rb +++ b/spec/client_cluster_reconnect_spec.rb @@ -82,8 +82,7 @@ it "should connect to another server if possible before reconnect" do @s3.kill_server - mon = Monitor.new - reconnected = mon.new_cond + reconnected = Future.new nats = NATS.connect(servers: [@s1.uri, @s2.uri], dont_randomize_servers: true) @@ -100,9 +99,7 @@ reconnects = 0 nats.on_reconnect do reconnects += 1 - mon.synchronize do - reconnected.signal - end + reconnected.set_result(:ok) end msgs = [] @@ -119,9 +116,7 @@ sleep 0.1 end - mon.synchronize do - reconnected.wait(1) - end + expect(reconnected.wait_for(1)).to eq :ok expect(nats.connected_server).to eql(@s2.uri) nats.close @@ -133,8 +128,7 @@ it "should connect to another server if possible before reconnect using multiple uris" do @s3.kill_server - mon = Monitor.new - reconnected = mon.new_cond + reconnected = Future.new nats = NATS::IO::Client.new nats.connect("nats://secret:password@127.0.0.1:4242,nats://secret:password@127.0.0.1:4243", dont_randomize_servers: true) @@ -152,9 +146,7 @@ reconnects = 0 nats.on_reconnect do reconnects += 1 - mon.synchronize do - reconnected.signal - end + reconnected.set_result(:ok) end msgs = [] @@ -171,9 +163,7 @@ sleep 0.1 end - mon.synchronize do - reconnected.wait(1) - end + expect(reconnected.wait_for(1)).to eq :ok expect(nats.connected_server.to_s).to eql(@s2.uri.to_s) nats.close @@ -185,8 +175,7 @@ it "should gracefully reconnect to another available server while publishing" do @s3.kill_server - mon = Monitor.new - reconnected = mon.new_cond + reconnected = Future.new nats = NATS::IO::Client.new nats.connect({ @@ -207,9 +196,7 @@ reconnects = 0 nats.on_reconnect do |s| reconnects += 1 - mon.synchronize do - reconnected.signal - end + reconnected.set_result(:ok) end errors = [] @@ -217,46 +204,32 @@ errors << e end - msgs = [] + msg_counter = 0 nats.subscribe("hello.*") do |msg| - msgs << msg + msg_counter += 1 + if msg_counter == 100 + @s1.kill_server + end end nats.flush expect(nats.connected_server.to_s).to eql(@s1.uri.to_s) - msg_payload = "A" * 10_000 - 1000.times do |n| - # Receive 100 messages initially and then failover - if n == 100 - nats.flush - - # Wait a bit for all messages - sleep 0.5 - expect(msgs.count).to eql(100) - @s1.kill_server - elsif n % 100 == 0 - # yield a millisecond - sleep 0.001 - end - - # Messages sent here can be lost + msg_payload = "A" * 1_000 + 100.times do |n| nats.publish("hello.#{n}", msg_payload) end # Flush everything we have sent so far nats.flush(5) - errors = [] - errors.each do |e| - errors << e - end - mon.synchronize { reconnected.wait(1) } + + expect(reconnected.wait_for(2)).to eq :ok expect(nats.connected_server).to eql(@s2.uri) nats.close expect(reconnects).to eql(1) expect(disconnects).to eql(2) expect(closes).to eql(1) - expect(errors).to be_empty + expect(errors.size).to eq(1) end end @@ -267,18 +240,20 @@ end after do - @s1.kill_server + [@s1, @s2, @s3].each do |s| + s.kill_server + end end - it "should reconnect to nodes discovered from seed server" do - # Nodes join to cluster before we try to connect - [@s2, @s3].each do |s| - s.start_server(true) + context "with nodes joined before first connect" do + before do + [@s2, @s3].each do |s| + s.start_server(true) + end end - begin - mon = Monitor.new - reconnected = mon.new_cond + it "should reconnect to nodes discovered from seed server" do + reconnected = Future.new nats = NATS::IO::Client.new disconnects = 0 @@ -294,9 +269,7 @@ reconnects = 0 nats.on_reconnect do reconnects += 1 - mon.synchronize do - reconnected.signal - end + reconnected.set_result(:ok) end errors = [] @@ -309,9 +282,8 @@ expect(nats.connected_server).to eql(@s1.uri) @s1.kill_server sleep 0.2 - mon.synchronize do - reconnected.wait(5) - end + + reconnected.wait_for(3) # Reconnected... # expect(nats.connected_server).to eql(@s2.uri) @@ -325,23 +297,11 @@ expect(nats.last_error).to eql(nil) nats.close - ensure - # Wrap up test - [@s2, @s3].each do |s| - s.kill_server - end end - end - it "should reconnect to nodes discovered from seed server with single uri" do - skip "FIXME: flaky test" - - # Nodes join to cluster before we try to connect - [@s2, @s3].each do |s| - s.start_server(true) - end + it "should reconnect to nodes discovered from seed server with single uri" do + skip "FIXME: flaky test" - begin mon = Monitor.new reconnected = mon.new_cond @@ -390,17 +350,11 @@ expect(nats.last_error).to eql(nil) nats.close - ensure - # Wrap up test - [@s2, @s3].each do |s| - s.kill_server - end end end it "should reconnect to nodes discovered in the cluster after first connect" do - mon = Monitor.new - reconnected = mon.new_cond + reconnected = Future.new nats = NATS::IO::Client.new disconnects = 0 @@ -416,9 +370,7 @@ reconnects = 0 nats.on_reconnect do reconnects += 1 - mon.synchronize do - reconnected.signal - end + reconnected.set_result(:ok) end errors = [] @@ -436,42 +388,34 @@ }) expect(nats.connected_server).to eql(@s1.uri) - begin - # Couple of servers join... - [@s2, @s3].each do |s| - s.start_server(true) - end - nats.flush + # Couple of servers join... + [@s2, @s3].each do |s| + s.start_server(true) + end + nats.flush - # Wait for a bit before disconnecting from original server - nats.flush - @s1.kill_server - mon.synchronize do - reconnected.wait(3) - end + # Wait for a bit before disconnecting from original server + nats.flush + @s1.kill_server - # We still consider the original node and we have new ones - # which can be used to failover. - expect(nats.servers.count).to eql(3) + reconnected.wait_for(3) - # Only 2 new ones should be discovered servers even after reconnect - expect(nats.discovered_servers.count).to eql(2) - expect(nats.connected_server).to eql(@s2.uri) - expect(reconnects).to eql(1) - expect(disconnects).to eql(1) - expect(closes).to eql(0) - expect(errors.count).to eql(2) - expect(errors.first).to be_a(Errno::ECONNRESET) - expect(errors.last).to be_a(Errno::ECONNREFUSED) - expect(nats.last_error).to be_a(Errno::ECONNREFUSED) + # We still consider the original node and we have new ones + # which can be used to failover. + expect(nats.servers.count).to eql(3) - nats.close - ensure - # Wrap up test - [@s2, @s3].each do |s| - s.kill_server - end - end + # Only 2 new ones should be discovered servers even after reconnect + expect(nats.discovered_servers.count).to eql(2) + expect(nats.connected_server).to eql(@s2.uri) + expect(reconnects).to eql(1) + expect(disconnects).to eql(1) + expect(closes).to eql(0) + expect(errors.count).to eql(2) + expect(errors.first).to be_a(Errno::ECONNRESET) + expect(errors.last).to be_a(Errno::ECONNREFUSED) + expect(nats.last_error).to be_a(Errno::ECONNREFUSED) + + nats.close end end end diff --git a/spec/client_drain_spec.rb b/spec/client_drain_spec.rb index 18e2f7a..127d9d8 100644 --- a/spec/client_drain_spec.rb +++ b/spec/client_drain_spec.rb @@ -27,13 +27,14 @@ wait_subs = Future.new wait_pubs = Future.new reqs_started = Queue.new + wait_reqs_start = Future.new wait_reqs = Future.new Thread.new do wait_subs.wait_for(2) 40.times do |i| ("a".."b").each do - payload = "REQ:#{_1}:#{i}" + payload = "PUB:#{_1}:#{i}" nc2.publish(_1, payload * 128) sleep 0.01 end @@ -43,9 +44,10 @@ ("a".."b").map do |sub| Thread.new do + wait_reqs_start.wait_for(5) reqs_started << sub payload = "REQ:#{sub}" - nc2.request(sub, payload) + nc2.request(sub, payload, timeout: 5) end end.each(&:join) @@ -73,20 +75,25 @@ sub_queue.push(f1) sub_queue.push(f2) - expect(f1.wait_for(1)).to eql(:ok) - expect(f2.wait_for(1)).to eql(:ok) + expect(f1.wait_for(2)).to eql(:ok) + expect(f2.wait_for(2)).to eql(:ok) wait_pubs.wait_for(2) + wait_reqs_start.done + reqs_started.pop reqs_started.pop + # sleep a bit to let requests initiate + sleep 2 + # Start draining process asynchronously. nc.drain - # Release the queue (we have 38 messages left) + # Release the queue 80.times { sub_queue.push(Future.new) } - result = future.wait_for(2) + result = future.wait_for(7) expect(result).to eql(:closed) expect(wait_reqs.wait_for(2)).to eql(:ok) end @@ -141,8 +148,8 @@ sub_queue.push(f1) sub_queue.push(f2) - expect(f1.wait_for(1)).to eql(:ok) - expect(f2.wait_for(1)).to eql(:ok) + expect(f1.wait_for(2)).to eql(:ok) + expect(f2.wait_for(2)).to eql(:ok) wait_pubs.wait_for(2) diff --git a/spec/js_spec.rb b/spec/js_spec.rb index f6dd4ac..ccbb2c5 100644 --- a/spec/js_spec.rb +++ b/spec/js_spec.rb @@ -452,6 +452,7 @@ end.to raise_error(NATS::IO::BadSubscription) end + # TODO: What do we test here? it "should account pending data" do nc = NATS.connect(@s.uri) nc2 = NATS.connect(@s.uri) @@ -464,34 +465,36 @@ js.add_stream(name: "limitstest", subjects: [subject]) - # Continuously send messages until reaching pending bytes limit. - t = Thread.new do - payload = "A" * 1024 * 1024 - loop do - nc2.publish(subject, payload) - sleep 0.01 + begin + # Continuously send messages until reaching pending bytes limit. + t = Thread.new do + payload = "A" * 1024 + loop do + nc2.publish(subject, payload) + sleep 0.01 + end end - end - sub = js.pull_subscribe(subject, "test") - 65.times do |i| - msgs = sub.fetch(1) - msgs.each do |msg| - msg.ack + sub = js.pull_subscribe(subject, "test") + 65.times do |i| + msgs = sub.fetch(1) + msgs.each do |msg| + msg.ack + end end - end - sub = js.pull_subscribe(subject, "test") - 65.times do |i| - msgs = sub.fetch(2) - msgs.each do |msg| - msg.ack + sub = js.pull_subscribe(subject, "test") + 65.times do |i| + msgs = sub.fetch(2) + msgs.each do |msg| + msg.ack + end end + ensure + nc.close + nc2.close + t.exit end - - t.exit - nc.close - nc2.close end it "should create and bind to consumer with name" do