nats-pure 0.7.2 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -0
  3. data/README.md +251 -0
  4. data/lib/nats/client.rb +16 -0
  5. data/lib/nats/io/client.rb +1559 -1277
  6. data/lib/nats/io/errors.rb +74 -0
  7. data/lib/nats/io/jetstream/api.rb +309 -0
  8. data/lib/nats/io/jetstream/errors.rb +104 -0
  9. data/lib/nats/io/jetstream/js/config.rb +26 -0
  10. data/lib/nats/io/jetstream/js/header.rb +31 -0
  11. data/lib/nats/io/jetstream/js/status.rb +27 -0
  12. data/lib/nats/io/jetstream/js/sub.rb +30 -0
  13. data/lib/nats/io/jetstream/js.rb +93 -0
  14. data/lib/nats/io/jetstream/manager.rb +303 -0
  15. data/lib/nats/io/jetstream/msg/ack.rb +57 -0
  16. data/lib/nats/io/jetstream/msg/ack_methods.rb +111 -0
  17. data/lib/nats/io/jetstream/msg/metadata.rb +37 -0
  18. data/lib/nats/io/jetstream/msg.rb +26 -0
  19. data/lib/nats/io/jetstream/pull_subscription.rb +260 -0
  20. data/lib/nats/io/jetstream/push_subscription.rb +42 -0
  21. data/lib/nats/io/jetstream.rb +344 -0
  22. data/lib/nats/io/kv/api.rb +39 -0
  23. data/lib/nats/io/kv/bucket_status.rb +38 -0
  24. data/lib/nats/io/kv/errors.rb +60 -0
  25. data/lib/nats/io/kv/manager.rb +89 -0
  26. data/lib/nats/io/kv.rb +178 -0
  27. data/lib/nats/io/msg.rb +58 -0
  28. data/lib/nats/io/parser.rb +7 -7
  29. data/lib/nats/io/rails.rb +29 -0
  30. data/lib/nats/io/subscription.rb +157 -0
  31. data/lib/nats/io/version.rb +8 -4
  32. data/lib/nats/io/websocket.rb +75 -0
  33. data/lib/nats/nuid.rb +3 -1
  34. data/lib/nats.rb +39 -0
  35. data/sig/nats/io/client.rbs +304 -0
  36. data/sig/nats/io/errors.rbs +54 -0
  37. data/sig/nats/io/jetstream/api.rbs +35 -0
  38. data/sig/nats/io/jetstream/errors.rbs +54 -0
  39. data/sig/nats/io/jetstream/js/config.rbs +11 -0
  40. data/sig/nats/io/jetstream/js/header.rbs +17 -0
  41. data/sig/nats/io/jetstream/js/status.rbs +13 -0
  42. data/sig/nats/io/jetstream/js/sub.rbs +14 -0
  43. data/sig/nats/io/jetstream/js.rbs +27 -0
  44. data/sig/nats/io/jetstream/manager.rbs +33 -0
  45. data/sig/nats/io/jetstream/msg/ack.rbs +35 -0
  46. data/sig/nats/io/jetstream/msg/ack_methods.rbs +25 -0
  47. data/sig/nats/io/jetstream/msg/metadata.rbs +15 -0
  48. data/sig/nats/io/jetstream/msg.rbs +6 -0
  49. data/sig/nats/io/jetstream/pull_subscription.rbs +14 -0
  50. data/sig/nats/io/jetstream/push_subscription.rbs +7 -0
  51. data/sig/nats/io/jetstream.rbs +15 -0
  52. data/sig/nats/io/kv/api.rbs +8 -0
  53. data/sig/nats/io/kv/bucket_status.rbs +17 -0
  54. data/sig/nats/io/kv/errors.rbs +30 -0
  55. data/sig/nats/io/kv/manager.rbs +11 -0
  56. data/sig/nats/io/kv.rbs +39 -0
  57. data/sig/nats/io/msg.rbs +14 -0
  58. data/sig/nats/io/parser.rbs +32 -0
  59. data/sig/nats/io/subscription.rbs +33 -0
  60. data/sig/nats/io/version.rbs +9 -0
  61. data/sig/nats/nuid.rbs +32 -0
  62. metadata +74 -4
@@ -0,0 +1,38 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ module NATS
16
+ class KeyValue
17
+ class BucketStatus
18
+ attr_reader :bucket
19
+
20
+ def initialize(info, bucket)
21
+ @nfo = info
22
+ @bucket = bucket
23
+ end
24
+
25
+ def values
26
+ @nfo.state.messages
27
+ end
28
+
29
+ def history
30
+ @nfo.config.max_msgs_per_subject
31
+ end
32
+
33
+ def ttl
34
+ @nfo.config.max_age / ::NATS::NANOSECONDS
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ require_relative '../errors'
16
+
17
+ module NATS
18
+ class KeyValue
19
+ class Error < NATS::Error; end
20
+
21
+ # When a key is not found.
22
+ class KeyNotFoundError < Error
23
+ attr_reader :entry, :op
24
+ def initialize(params={})
25
+ @entry = params[:entry]
26
+ @op = params[:op]
27
+ @message = params[:message]
28
+ end
29
+
30
+ def to_s
31
+ msg = "nats: key not found"
32
+ msg = "#{msg}: #{@message}" if @message
33
+ msg
34
+ end
35
+ end
36
+
37
+ # When a key is not found because it was deleted.
38
+ class KeyDeletedError < KeyNotFoundError
39
+ def to_s
40
+ "nats: key was deleted"
41
+ end
42
+ end
43
+
44
+ # When there was no bucket present.
45
+ class BucketNotFoundError < Error; end
46
+
47
+ # When it is an invalid bucket.
48
+ class BadBucketError < Error; end
49
+
50
+ # When the result is an unexpected sequence.
51
+ class KeyWrongLastSequenceError < Error
52
+ def initialize(msg)
53
+ @msg = msg
54
+ end
55
+ def to_s
56
+ "nats: #{@msg}"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,89 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ module NATS
16
+ class KeyValue
17
+ module Manager
18
+ def key_value(bucket)
19
+ stream = "KV_#{bucket}"
20
+ begin
21
+ si = stream_info(stream)
22
+ rescue NATS::JetStream::Error::NotFound
23
+ raise BucketNotFoundError.new("nats: bucket not found")
24
+ end
25
+ if si.config.max_msgs_per_subject < 1
26
+ raise BadBucketError.new("nats: bad bucket")
27
+ end
28
+
29
+ KeyValue.new(
30
+ name: bucket,
31
+ stream: stream,
32
+ pre: "$KV.#{bucket}.",
33
+ js: self,
34
+ direct: si.config.allow_direct
35
+ )
36
+ end
37
+
38
+ def create_key_value(config)
39
+ config = if not config.is_a?(KeyValue::API::KeyValueConfig)
40
+ KeyValue::API::KeyValueConfig.new(config)
41
+ else
42
+ config
43
+ end
44
+ config.history ||= 1
45
+ config.replicas ||= 1
46
+ duplicate_window = 2 * 60 # 2 minutes
47
+ if config.ttl
48
+ if config.ttl < duplicate_window
49
+ duplicate_window = config.ttl
50
+ end
51
+ config.ttl = config.ttl * ::NATS::NANOSECONDS
52
+ end
53
+
54
+ stream = JetStream::API::StreamConfig.new(
55
+ name: "KV_#{config.bucket}",
56
+ description: config.description,
57
+ subjects: ["$KV.#{config.bucket}.>"],
58
+ allow_direct: config.direct,
59
+ allow_rollup_hdrs: true,
60
+ deny_delete: true,
61
+ discard: "new",
62
+ duplicate_window: duplicate_window * ::NATS::NANOSECONDS,
63
+ max_age: config.ttl,
64
+ max_bytes: config.max_bytes,
65
+ max_consumers: -1,
66
+ max_msg_size: config.max_value_size,
67
+ max_msgs: -1,
68
+ max_msgs_per_subject: config.history,
69
+ num_replicas: config.replicas,
70
+ storage: config.storage,
71
+ republish: config.republish,
72
+ )
73
+
74
+ si = add_stream(stream)
75
+ KeyValue.new(
76
+ name: config.bucket,
77
+ stream: stream.name,
78
+ pre: "$KV.#{config.bucket}.",
79
+ js: self,
80
+ direct: si.config.allow_direct
81
+ )
82
+ end
83
+
84
+ def delete_key_value(bucket)
85
+ delete_stream("KV_#{bucket}")
86
+ end
87
+ end
88
+ end
89
+ end
data/lib/nats/io/kv.rb ADDED
@@ -0,0 +1,178 @@
1
+ # Copyright 2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ require_relative 'kv/api'
16
+ require_relative 'kv/bucket_status'
17
+ require_relative 'kv/errors'
18
+ require_relative 'kv/manager'
19
+
20
+ module NATS
21
+ class KeyValue
22
+ KV_OP = "KV-Operation"
23
+ KV_DEL = "DEL"
24
+ KV_PURGE = "PURGE"
25
+ MSG_ROLLUP_SUBJECT = "sub"
26
+ MSG_ROLLUP_ALL = "all"
27
+ ROLLUP = "Nats-Rollup"
28
+
29
+ def initialize(opts={})
30
+ @name = opts[:name]
31
+ @stream = opts[:stream]
32
+ @pre = opts[:pre]
33
+ @js = opts[:js]
34
+ @direct = opts[:direct]
35
+ end
36
+
37
+ # get returns the latest value for the key.
38
+ def get(key, params={})
39
+ entry = nil
40
+ begin
41
+ entry = _get(key, params)
42
+ rescue KeyDeletedError
43
+ raise KeyNotFoundError
44
+ end
45
+
46
+ entry
47
+ end
48
+
49
+ def _get(key, params={})
50
+ msg = nil
51
+ subject = "#{@pre}#{key}"
52
+
53
+ if params[:revision]
54
+ msg = @js.get_msg(@stream,
55
+ seq: params[:revision],
56
+ direct: @direct)
57
+ else
58
+ msg = @js.get_msg(@stream,
59
+ subject: subject,
60
+ seq: params[:revision],
61
+ direct: @direct)
62
+ end
63
+
64
+ entry = Entry.new(bucket: @name, key: key, value: msg.data, revision: msg.seq)
65
+
66
+ if subject != msg.subject
67
+ raise KeyNotFoundError.new(
68
+ entry: entry,
69
+ message: "expected '#{subject}', but got '#{msg.subject}'"
70
+ )
71
+ end
72
+
73
+ if not msg.headers.nil?
74
+ op = msg.headers[KV_OP]
75
+ if op == KV_DEL or op == KV_PURGE
76
+ raise KeyDeletedError.new(entry: entry, op: op)
77
+ end
78
+ end
79
+
80
+ entry
81
+ rescue NATS::JetStream::Error::NotFound
82
+ raise KeyNotFoundError
83
+ end
84
+ private :_get
85
+
86
+ # put will place the new value for the key into the store
87
+ # and return the revision number.
88
+ def put(key, value)
89
+ ack = @js.publish("#{@pre}#{key}", value)
90
+ ack.seq
91
+ end
92
+
93
+ # create will add the key/value pair iff it does not exist.
94
+ def create(key, value)
95
+ pa = nil
96
+ begin
97
+ pa = update(key, value, last: 0)
98
+ rescue KeyWrongLastSequenceError => err
99
+ # In case of attempting to recreate an already deleted key,
100
+ # the client would get a KeyWrongLastSequenceError. When this happens,
101
+ # it is needed to fetch latest revision number and attempt to update.
102
+ begin
103
+ # NOTE: This reimplements the following behavior from Go client.
104
+ #
105
+ # Since we have tombstones for DEL ops for watchers, this could be from that
106
+ # so we need to double check.
107
+ #
108
+ _get(key)
109
+
110
+ # No exception so not a deleted key, so reraise the original KeyWrongLastSequenceError.
111
+ # If it was deleted then the error exception will contain metadata
112
+ # to recreate using the last revision.
113
+ raise err
114
+ rescue KeyDeletedError => err
115
+ pa = update(key, value, last: err.entry.revision)
116
+ end
117
+ end
118
+
119
+ pa
120
+ end
121
+
122
+ EXPECTED_LAST_SUBJECT_SEQUENCE = "Nats-Expected-Last-Subject-Sequence"
123
+
124
+ # update will update the value iff the latest revision matches.
125
+ def update(key, value, params={})
126
+ hdrs = {}
127
+ last = (params[:last] ||= 0)
128
+ hdrs[EXPECTED_LAST_SUBJECT_SEQUENCE] = last.to_s
129
+ ack = nil
130
+ begin
131
+ ack = @js.publish("#{@pre}#{key}", value, header: hdrs)
132
+ rescue NATS::JetStream::Error::APIError => err
133
+ if err.err_code == 10071
134
+ raise KeyWrongLastSequenceError.new(err.description)
135
+ else
136
+ raise err
137
+ end
138
+ end
139
+
140
+ ack.seq
141
+ end
142
+
143
+ # delete will place a delete marker and remove all previous revisions.
144
+ def delete(key, params={})
145
+ hdrs = {}
146
+ hdrs[KV_OP] = KV_DEL
147
+ last = (params[:last] ||= 0)
148
+ if last > 0
149
+ hdrs[EXPECTED_LAST_SUBJECT_SEQUENCE] = last.to_s
150
+ end
151
+ ack = @js.publish("#{@pre}#{key}", header: hdrs)
152
+
153
+ ack.seq
154
+ end
155
+
156
+ # purge will remove the key and all revisions.
157
+ def purge(key)
158
+ hdrs = {}
159
+ hdrs[KV_OP] = KV_PURGE
160
+ hdrs[ROLLUP] = MSG_ROLLUP_SUBJECT
161
+ @js.publish("#{@pre}#{key}", header: hdrs)
162
+ end
163
+
164
+ # status retrieves the status and configuration of a bucket.
165
+ def status
166
+ info = @js.stream_info(@stream)
167
+ BucketStatus.new(info, @name)
168
+ end
169
+
170
+ Entry = Struct.new(:bucket, :key, :value, :revision, :delta, :created, :operation, keyword_init: true) do
171
+ def initialize(opts={})
172
+ rem = opts.keys - members
173
+ opts.delete_if { |k| rem.include?(k) }
174
+ super(opts)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright 2016-2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+ require_relative 'jetstream'
15
+
16
+ module NATS
17
+ class Msg
18
+ attr_accessor :subject, :reply, :data, :header
19
+
20
+ # Enhance it with ack related methods from JetStream to ack msgs.
21
+ include JetStream::Msg::AckMethods
22
+
23
+ def initialize(opts={})
24
+ @subject = opts[:subject]
25
+ @reply = opts[:reply]
26
+ @data = opts[:data]
27
+ @header = opts[:header]
28
+ @nc = opts[:nc]
29
+ @sub = opts[:sub]
30
+ @ackd = false
31
+ @meta = nil
32
+ end
33
+
34
+ def respond(data='')
35
+ return unless @nc
36
+ if self.header
37
+ dmsg = self.dup
38
+ dmsg.subject = self.reply
39
+ dmsg.data = data
40
+ @nc.publish_msg(dmsg)
41
+ else
42
+ @nc.publish(self.reply, data)
43
+ end
44
+ end
45
+
46
+ def respond_msg(msg)
47
+ return unless @nc
48
+ @nc.publish_msg(msg)
49
+ end
50
+
51
+ def inspect
52
+ hdr = ", header=#{@header}" if @header
53
+ dot = '...' if @data.length > 10
54
+ dat = "#{data.slice(0, 10)}#{dot}"
55
+ "#<NATS::Msg(subject: \"#{@subject}\", reply: \"#{@reply}\", data: #{dat.inspect}#{hdr})>"
56
+ end
57
+ end
58
+ end
@@ -71,22 +71,22 @@ module NATS
71
71
  @buf = $'
72
72
  when ERR
73
73
  @buf = $'
74
- @nc.process_err($1)
74
+ @nc.send(:process_err, $1)
75
75
  when PING
76
76
  @buf = $'
77
- @nc.process_ping
77
+ @nc.send(:process_ping)
78
78
  when PONG
79
79
  @buf = $'
80
- @nc.process_pong
80
+ @nc.send(:process_pong)
81
81
  when INFO
82
82
  @buf = $'
83
83
  # First INFO message is processed synchronously on connect,
84
84
  # and onwards we would be receiving asynchronously INFO commands
85
85
  # signaling possible changes in the topology of the NATS cluster.
86
- @nc.process_info($1)
86
+ @nc.send(:process_info, $1)
87
87
  when UNKNOWN
88
88
  @buf = $'
89
- @nc.process_err("Unknown protocol: #{$1}")
89
+ @nc.send(:process_err, "Unknown protocol: #{$1}")
90
90
  else
91
91
  # If we are here we do not have a complete line yet that we understand.
92
92
  return
@@ -98,10 +98,10 @@ module NATS
98
98
  if @header_needed
99
99
  hbuf = @buf.slice(0, @header_needed)
100
100
  payload = @buf.slice(@header_needed, (@needed-@header_needed))
101
- @nc.process_msg(@sub, @sid, @reply, payload, hbuf)
101
+ @nc.send(:process_msg, @sub, @sid, @reply, payload, hbuf)
102
102
  @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
103
103
  else
104
- @nc.process_msg(@sub, @sid, @reply, @buf.slice(0, @needed), nil)
104
+ @nc.send(:process_msg, @sub, @sid, @reply, @buf.slice(0, @needed), nil)
105
105
  @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
106
106
  end
107
107
 
@@ -0,0 +1,29 @@
1
+ require "rails"
2
+
3
+ module NATS
4
+ class Rails < ::Rails::Engine
5
+ # This class is used to free resources managed by Rails (e.g. database connections)
6
+ # that were implicitly acquired in subscription callbacks
7
+ # Implementation is based on https://github.com/sidekiq/sidekiq/blob/5e1a77a6d03193dd977fbfe8961ab78df91bb392/lib/sidekiq/rails.rb
8
+ class Reloader
9
+ def initialize(app = ::Rails.application)
10
+ @app = app
11
+ end
12
+
13
+ def call
14
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "gem.nats"} : {}
15
+ @app.reloader.wrap(**params) do
16
+ yield
17
+ end
18
+ end
19
+
20
+ def inspect
21
+ "#<NATS::Rails::Reloader @app=#{@app.class.name}>"
22
+ end
23
+ end
24
+
25
+ config.after_initialize do
26
+ NATS::Client.default_reloader = NATS::Rails::Reloader.new
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,157 @@
1
+ # Copyright 2016-2021 The NATS Authors
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+
15
+ module NATS
16
+
17
+ # A Subscription represents interest in a given subject.
18
+ #
19
+ # @example Create NATS subscription with callback.
20
+ # require 'nats/client'
21
+ #
22
+ # nc = NATS.connect("demo.nats.io")
23
+ # sub = nc.subscribe("foo") do |msg|
24
+ # puts "Received [#{msg.subject}]: #{}"
25
+ # end
26
+ #
27
+ class Subscription
28
+ include MonitorMixin
29
+
30
+ attr_accessor :subject, :queue, :future, :callback, :response, :received, :max, :pending, :sid
31
+ attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond, :concurrency_semaphore
32
+ attr_accessor :pending_msgs_limit, :pending_bytes_limit
33
+ attr_accessor :nc
34
+ attr_accessor :jsi
35
+ attr_accessor :closed
36
+
37
+ def initialize(**opts)
38
+ super() # required to initialize monitor
39
+ @subject = ''
40
+ @queue = nil
41
+ @future = nil
42
+ @callback = nil
43
+ @response = nil
44
+ @received = 0
45
+ @max = nil
46
+ @pending = nil
47
+ @sid = nil
48
+ @nc = nil
49
+ @closed = nil
50
+
51
+ # State from async subscriber messages delivery
52
+ @pending_queue = nil
53
+ @pending_size = 0
54
+ @pending_msgs_limit = nil
55
+ @pending_bytes_limit = nil
56
+
57
+ # Sync subscriber
58
+ @wait_for_msgs_cond = nil
59
+
60
+ # To limit number of concurrent messages being processed (1 to only allow sequential processing)
61
+ @processing_concurrency = opts.fetch(:processing_concurrency, NATS::IO::DEFAULT_SINGLE_SUB_CONCURRENCY)
62
+ end
63
+
64
+ # Concurrency of message processing for a single subscription.
65
+ # 1 means sequential processing
66
+ # 2+ allow processed concurrently and possibly out of order.
67
+ def processing_concurrency=(value)
68
+ raise ArgumentError, "nats: subscription processing concurrency must be positive integer" unless value.positive?
69
+ return if @processing_concurrency == value
70
+
71
+ @processing_concurrency = value
72
+ @concurrency_semaphore = Concurrent::Semaphore.new(value)
73
+ end
74
+
75
+ def concurrency_semaphore
76
+ @concurrency_semaphore ||= Concurrent::Semaphore.new(@processing_concurrency)
77
+ end
78
+
79
+ # Auto unsubscribes the server by sending UNSUB command and throws away
80
+ # subscription in case already present and has received enough messages.
81
+ def unsubscribe(opt_max=nil)
82
+ @nc.send(:unsubscribe, self, opt_max)
83
+ end
84
+
85
+ # next_msg blocks and waiting for the next message to be received.
86
+ def next_msg(opts={})
87
+ timeout = opts[:timeout] ||= 0.5
88
+ synchronize do
89
+ return @pending_queue.pop if not @pending_queue.empty?
90
+
91
+ # Wait for a bit until getting a signal.
92
+ MonotonicTime::with_nats_timeout(timeout) do
93
+ wait_for_msgs_cond.wait(timeout)
94
+ end
95
+
96
+ if not @pending_queue.empty?
97
+ return @pending_queue.pop
98
+ else
99
+ raise NATS::Timeout
100
+ end
101
+ end
102
+ end
103
+
104
+ def inspect
105
+ "#<NATS::Subscription(subject: \"#{@subject}\", queue: \"#{@queue}\", sid: #{@sid})>"
106
+ end
107
+
108
+ def dispatch(msg)
109
+ pending_queue << msg
110
+ synchronize { self.pending_size += msg.data.size }
111
+
112
+ # For async subscribers, send message for processing to the thread pool.
113
+ enqueue_processing(@nc.subscription_executor) if callback
114
+
115
+ # For sync subscribers, signal that there is a new message.
116
+ wait_for_msgs_cond&.signal
117
+ end
118
+
119
+ def process(msg)
120
+ return unless callback
121
+
122
+ # Decrease pending size since consumed already
123
+ synchronize { self.pending_size -= msg.data.size }
124
+
125
+ nc.reloader.call do
126
+ # Note: Keep some of the alternative arity versions to slightly
127
+ # improve backwards compatibility. Eventually fine to deprecate
128
+ # since recommended version would be arity of 1 to get a NATS::Msg.
129
+ case callback.arity
130
+ when 0 then callback.call
131
+ when 1 then callback.call(msg)
132
+ when 2 then callback.call(msg.data, msg.reply)
133
+ when 3 then callback.call(msg.data, msg.reply, msg.subject)
134
+ else callback.call(msg.data, msg.reply, msg.subject, msg.header)
135
+ end
136
+ rescue => e
137
+ synchronize { nc.send(:err_cb_call, nc, e, self) }
138
+ end
139
+ end
140
+
141
+ # Send a message for its processing to a separate thread
142
+ def enqueue_processing(executor)
143
+ concurrency_semaphore.try_acquire || return # Previous message is being executed, let it finish and enqueue next one.
144
+ executor.post do
145
+ msg = pending_queue.pop(true)
146
+ process(msg)
147
+ rescue ThreadError # queue is empty
148
+ concurrency_semaphore.release
149
+ ensure
150
+ concurrency_semaphore.release
151
+ [concurrency_semaphore.available_permits, pending_queue.size].min.times do
152
+ enqueue_processing(executor)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -1,4 +1,4 @@
1
- # Copyright 2016-2021 The NATS Authors
1
+ # Copyright 2016-2022 The NATS Authors
2
2
  # Licensed under the Apache License, Version 2.0 (the "License");
3
3
  # you may not use this file except in compliance with the License.
4
4
  # You may obtain a copy of the License at
@@ -14,9 +14,13 @@
14
14
 
15
15
  module NATS
16
16
  module IO
17
- # NOTE: These are all announced to the server on CONNECT
18
- VERSION = "0.7.2"
19
- LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
17
+ # VERSION is the version of the client announced on CONNECT to the server.
18
+ VERSION = "2.4.0".freeze
19
+
20
+ # LANG is the lang runtime of the client announced on CONNECT to the server.
21
+ LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
22
+
23
+ # PROTOCOL is the supported version of the protocol in the client.
20
24
  PROTOCOL = 1
21
25
  end
22
26
  end