redis 4.3.1 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +190 -0
  3. data/README.md +132 -154
  4. data/lib/redis/client.rb +81 -600
  5. data/lib/redis/commands/bitmaps.rb +73 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +258 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +459 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +81 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +218 -0
  17. data/lib/redis/commands/sorted_sets.rb +922 -0
  18. data/lib/redis/commands/streams.rb +409 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +239 -0
  22. data/lib/redis/distributed.rb +236 -77
  23. data/lib/redis/errors.rb +20 -41
  24. data/lib/redis/hash_ring.rb +26 -26
  25. data/lib/redis/pipeline.rb +70 -120
  26. data/lib/redis/subscribe.rb +50 -14
  27. data/lib/redis/version.rb +1 -1
  28. data/lib/redis.rb +104 -3465
  29. metadata +27 -57
  30. data/lib/redis/cluster/command.rb +0 -81
  31. data/lib/redis/cluster/command_loader.rb +0 -34
  32. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  33. data/lib/redis/cluster/node.rb +0 -107
  34. data/lib/redis/cluster/node_key.rb +0 -31
  35. data/lib/redis/cluster/node_loader.rb +0 -37
  36. data/lib/redis/cluster/option.rb +0 -93
  37. data/lib/redis/cluster/slot.rb +0 -86
  38. data/lib/redis/cluster/slot_loader.rb +0 -49
  39. data/lib/redis/cluster.rb +0 -295
  40. data/lib/redis/connection/command_helper.rb +0 -39
  41. data/lib/redis/connection/hiredis.rb +0 -67
  42. data/lib/redis/connection/registry.rb +0 -13
  43. data/lib/redis/connection/ruby.rb +0 -427
  44. data/lib/redis/connection/synchrony.rb +0 -146
  45. data/lib/redis/connection.rb +0 -11
data/lib/redis/client.rb CHANGED
@@ -1,650 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "errors"
4
- require "socket"
5
- require "cgi"
6
-
7
3
  class Redis
8
- class Client
9
- # Defaults are also used for converting string keys to symbols.
10
- DEFAULTS = {
11
- url: -> { ENV["REDIS_URL"] },
12
- scheme: "redis",
13
- host: "127.0.0.1",
14
- port: 6379,
15
- path: nil,
16
- read_timeout: nil,
17
- write_timeout: nil,
18
- connect_timeout: nil,
19
- timeout: 5.0,
20
- username: nil,
21
- password: nil,
22
- db: 0,
23
- driver: nil,
24
- id: nil,
25
- tcp_keepalive: 0,
26
- reconnect_attempts: 1,
27
- reconnect_delay: 0,
28
- reconnect_delay_max: 0.5,
29
- inherit_socket: false,
30
- logger: nil,
31
- sentinels: nil,
32
- role: nil
33
- }.freeze
34
-
35
- attr_reader :options
36
-
37
- def scheme
38
- @options[:scheme]
39
- end
40
-
41
- def host
42
- @options[:host]
43
- end
44
-
45
- def port
46
- @options[:port]
47
- end
48
-
49
- def path
50
- @options[:path]
51
- end
52
-
53
- def read_timeout
54
- @options[:read_timeout]
55
- end
56
-
57
- def connect_timeout
58
- @options[:connect_timeout]
59
- end
60
-
61
- def timeout
62
- @options[:read_timeout]
63
- end
64
-
65
- def username
66
- @options[:username]
4
+ class Client < ::RedisClient
5
+ ERROR_MAPPING = {
6
+ RedisClient::ConnectionError => Redis::ConnectionError,
7
+ RedisClient::CommandError => Redis::CommandError,
8
+ RedisClient::ReadTimeoutError => Redis::TimeoutError,
9
+ RedisClient::CannotConnectError => Redis::CannotConnectError,
10
+ RedisClient::AuthenticationError => Redis::CannotConnectError,
11
+ RedisClient::FailoverError => Redis::CannotConnectError,
12
+ RedisClient::PermissionError => Redis::PermissionError,
13
+ RedisClient::WrongTypeError => Redis::WrongTypeError,
14
+ RedisClient::ReadOnlyError => Redis::ReadOnlyError,
15
+ RedisClient::ProtocolError => Redis::ProtocolError,
16
+ RedisClient::OutOfMemoryError => Redis::OutOfMemoryError,
17
+ }
18
+ if defined?(RedisClient::NoScriptError)
19
+ ERROR_MAPPING[RedisClient::NoScriptError] = Redis::NoScriptError
67
20
  end
68
21
 
69
- def password
70
- @options[:password]
71
- end
72
-
73
- def db
74
- @options[:db]
75
- end
76
-
77
- def db=(db)
78
- @options[:db] = db.to_i
79
- end
80
-
81
- def driver
82
- @options[:driver]
83
- end
84
-
85
- def inherit_socket?
86
- @options[:inherit_socket]
87
- end
88
-
89
- attr_accessor :logger
90
- attr_reader :connection
91
- attr_reader :command_map
92
-
93
- def initialize(options = {})
94
- @options = _parse_options(options)
95
- @reconnect = true
96
- @logger = @options[:logger]
97
- @connection = nil
98
- @command_map = {}
99
-
100
- @pending_reads = 0
101
-
102
- @connector =
103
- if !@options[:sentinels].nil?
104
- Connector::Sentinel.new(@options)
105
- elsif options.include?(:connector) && options[:connector].respond_to?(:new)
106
- options.delete(:connector).new(@options)
107
- else
108
- Connector.new(@options)
109
- end
110
- end
111
-
112
- def connect
113
- @pid = Process.pid
114
-
115
- # Don't try to reconnect when the connection is fresh
116
- with_reconnect(false) do
117
- establish_connection
118
- if password
119
- if username
120
- begin
121
- call [:auth, username, password]
122
- rescue CommandError # Likely on Redis < 6
123
- call [:auth, password]
124
- end
125
- else
126
- call [:auth, password]
127
- end
128
- end
129
- call [:select, db] if db != 0
130
- call [:client, :setname, @options[:id]] if @options[:id]
131
- @connector.check(self)
22
+ class << self
23
+ def config(**kwargs)
24
+ super(protocol: 2, **kwargs)
132
25
  end
133
26
 
134
- self
135
- end
136
-
137
- def id
138
- @options[:id] || "redis://#{location}/#{db}"
139
- end
140
-
141
- def location
142
- path || "#{host}:#{port}"
143
- end
144
-
145
- def call(command)
146
- reply = process([command]) { read }
147
- raise reply if reply.is_a?(CommandError)
148
-
149
- if block_given? && reply != 'QUEUED'
150
- yield reply
151
- else
152
- reply
153
- end
154
- end
155
-
156
- def call_loop(command, timeout = 0)
157
- error = nil
158
-
159
- result = with_socket_timeout(timeout) do
160
- process([command]) do
161
- loop do
162
- reply = read
163
- if reply.is_a?(CommandError)
164
- error = reply
165
- break
166
- else
167
- yield reply
168
- end
169
- end
170
- end
27
+ def sentinel(**kwargs)
28
+ super(protocol: 2, **kwargs, client_implementation: ::RedisClient)
171
29
  end
172
30
 
173
- # Raise error when previous block broke out of the loop.
174
- raise error if error
175
-
176
- # Result is set to the value that the provided block used to break.
177
- result
178
- end
179
-
180
- def call_pipeline(pipeline)
181
- return [] if pipeline.futures.empty?
182
-
183
- with_reconnect pipeline.with_reconnect? do
184
- begin
185
- pipeline.finish(call_pipelined(pipeline)).tap do
186
- self.db = pipeline.db if pipeline.db
187
- end
188
- rescue ConnectionError => e
189
- return nil if pipeline.shutdown?
190
-
191
- # Assume the pipeline was sent in one piece, but execution of
192
- # SHUTDOWN caused none of the replies for commands that were executed
193
- # prior to it from coming back around.
194
- raise e
195
- end
31
+ def translate_error!(error, mapping: ERROR_MAPPING)
32
+ redis_error = translate_error_class(error.class, mapping: mapping)
33
+ raise redis_error, error.message, error.backtrace
196
34
  end
197
- end
198
-
199
- def call_pipelined(pipeline)
200
- return [] if pipeline.futures.empty?
201
-
202
- # The method #ensure_connected (called from #process) reconnects once on
203
- # I/O errors. To make an effort in making sure that commands are not
204
- # executed more than once, only allow reconnection before the first reply
205
- # has been read. When an error occurs after the first reply has been
206
- # read, retrying would re-execute the entire pipeline, thus re-issuing
207
- # already successfully executed commands. To circumvent this, don't retry
208
- # after the first reply has been read successfully.
209
-
210
- commands = pipeline.commands
211
35
 
212
- result = Array.new(commands.size)
213
- reconnect = @reconnect
36
+ private
214
37
 
215
- begin
216
- exception = nil
217
-
218
- process(commands) do
219
- pipeline.timeouts.each_with_index do |timeout, i|
220
- reply = if timeout
221
- with_socket_timeout(timeout) { read }
222
- else
223
- read
224
- end
225
- result[i] = reply
226
- @reconnect = false
227
- exception = reply if exception.nil? && reply.is_a?(CommandError)
228
- end
38
+ def translate_error_class(error_class, mapping: ERROR_MAPPING)
39
+ mapping.fetch(error_class)
40
+ rescue IndexError
41
+ if (client_error = error_class.ancestors.find { |a| mapping[a] })
42
+ mapping[error_class] = mapping[client_error]
43
+ else
44
+ raise
229
45
  end
230
-
231
- raise exception if exception
232
- ensure
233
- @reconnect = reconnect
234
46
  end
235
-
236
- result
237
47
  end
238
48
 
239
- def call_with_timeout(command, timeout, &blk)
240
- with_socket_timeout(timeout) do
241
- call(command, &blk)
242
- end
243
- rescue ConnectionError
244
- retry
49
+ def id
50
+ config.id
245
51
  end
246
52
 
247
- def call_without_timeout(command, &blk)
248
- call_with_timeout(command, 0, &blk)
53
+ def server_url
54
+ config.server_url
249
55
  end
250
56
 
251
- def process(commands)
252
- logging(commands) do
253
- ensure_connected do
254
- commands.each do |command|
255
- if command_map[command.first]
256
- command = command.dup
257
- command[0] = command_map[command.first]
258
- end
259
-
260
- write(command)
261
- end
262
-
263
- yield if block_given?
264
- end
265
- end
266
- end
267
-
268
- def connected?
269
- !!(connection && connection.connected?)
57
+ def timeout
58
+ config.read_timeout
270
59
  end
271
60
 
272
- def disconnect
273
- connection.disconnect if connected?
61
+ def db
62
+ config.db
274
63
  end
275
- alias close disconnect
276
64
 
277
- def reconnect
278
- disconnect
279
- connect
65
+ def host
66
+ config.host unless config.path
280
67
  end
281
68
 
282
- def io
283
- yield
284
- rescue TimeoutError => e1
285
- # Add a message to the exception without destroying the original stack
286
- e2 = TimeoutError.new("Connection timed out")
287
- e2.set_backtrace(e1.backtrace)
288
- raise e2
289
- rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
290
- raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
69
+ def port
70
+ config.port unless config.path
291
71
  end
292
72
 
293
- def read
294
- io do
295
- value = connection.read
296
- @pending_reads -= 1
297
- value
298
- end
73
+ def path
74
+ config.path
299
75
  end
300
76
 
301
- def write(command)
302
- io do
303
- @pending_reads += 1
304
- connection.write(command)
305
- end
77
+ def username
78
+ config.username
306
79
  end
307
80
 
308
- def with_socket_timeout(timeout)
309
- connect unless connected?
310
- original = @options[:read_timeout]
311
-
312
- begin
313
- connection.timeout = timeout
314
- @options[:read_timeout] = timeout # for reconnection
315
- yield
316
- ensure
317
- connection.timeout = self.timeout if connected?
318
- @options[:read_timeout] = original
319
- end
81
+ def password
82
+ config.password
320
83
  end
321
84
 
322
- def without_socket_timeout(&blk)
323
- with_socket_timeout(0, &blk)
324
- end
85
+ undef_method :call
86
+ undef_method :call_once
87
+ undef_method :call_once_v
88
+ undef_method :blocking_call
325
89
 
326
- def with_reconnect(val = true)
327
- original, @reconnect = @reconnect, val
328
- yield
329
- ensure
330
- @reconnect = original
90
+ def ensure_connected(retryable: true, &block)
91
+ super(retryable: retryable, &block)
92
+ rescue ::RedisClient::Error => error
93
+ Client.translate_error!(error)
331
94
  end
332
95
 
333
- def without_reconnect(&blk)
334
- with_reconnect(false, &blk)
96
+ def call_v(command, &block)
97
+ super(command, &block)
98
+ rescue ::RedisClient::Error => error
99
+ Client.translate_error!(error)
335
100
  end
336
101
 
337
- protected
338
-
339
- def logging(commands)
340
- return yield unless @logger&.debug?
341
-
342
- begin
343
- commands.each do |name, *args|
344
- logged_args = args.map do |a|
345
- if a.respond_to?(:inspect) then a.inspect
346
- elsif a.respond_to?(:to_s) then a.to_s
347
- else
348
- # handle poorly-behaved descendants of BasicObject
349
- klass = a.instance_exec { (class << self; self end).superclass }
350
- "\#<#{klass}:#{a.__id__}>"
351
- end
352
- end
353
- @logger.debug("[Redis] command=#{name.to_s.upcase} args=#{logged_args.join(' ')}")
354
- end
355
-
356
- t1 = Time.now
357
- yield
358
- ensure
359
- @logger.debug("[Redis] call_time=%0.2f ms" % ((Time.now - t1) * 1000)) if t1
102
+ def blocking_call_v(timeout, command, &block)
103
+ if timeout && timeout > 0
104
+ # Can't use the command timeout argument as the connection timeout
105
+ # otherwise it would be very racy. So we add the regular read_timeout on top
106
+ # to account for the network delay.
107
+ timeout += config.read_timeout
360
108
  end
361
- end
362
-
363
- def establish_connection
364
- server = @connector.resolve.dup
365
-
366
- @options[:host] = server[:host]
367
- @options[:port] = Integer(server[:port]) if server.include?(:port)
368
109
 
369
- @connection = @options[:driver].connect(@options)
370
- @pending_reads = 0
371
- rescue TimeoutError,
372
- SocketError,
373
- Errno::EADDRNOTAVAIL,
374
- Errno::ECONNREFUSED,
375
- Errno::EHOSTDOWN,
376
- Errno::EHOSTUNREACH,
377
- Errno::ENETUNREACH,
378
- Errno::ENOENT,
379
- Errno::ETIMEDOUT,
380
- Errno::EINVAL => error
381
-
382
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
110
+ super(timeout, command, &block)
111
+ rescue ::RedisClient::Error => error
112
+ Client.translate_error!(error)
383
113
  end
384
114
 
385
- def ensure_connected
386
- disconnect if @pending_reads > 0
387
-
388
- attempts = 0
389
-
390
- begin
391
- attempts += 1
392
-
393
- if connected?
394
- unless inherit_socket? || Process.pid == @pid
395
- raise InheritedError,
396
- "Tried to use a connection from a child process without reconnecting. " \
397
- "You need to reconnect to Redis after forking " \
398
- "or set :inherit_socket to true."
399
- end
400
- else
401
- connect
402
- end
403
-
404
- yield
405
- rescue BaseConnectionError
406
- disconnect
407
-
408
- if attempts <= @options[:reconnect_attempts] && @reconnect
409
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
410
- @options[:reconnect_delay_max]].min
411
-
412
- Kernel.sleep(sleep_t)
413
- retry
414
- else
415
- raise
416
- end
417
- rescue Exception
418
- disconnect
419
- raise
420
- end
421
- end
422
-
423
- def _parse_options(options)
424
- return options if options[:_parsed]
425
-
426
- defaults = DEFAULTS.dup
427
- options = options.dup
428
-
429
- defaults.keys.each do |key|
430
- # Fill in defaults if needed
431
- defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
432
-
433
- # Symbolize only keys that are needed
434
- options[key] = options[key.to_s] if options.key?(key.to_s)
435
- end
436
-
437
- url = options[:url]
438
- url = defaults[:url] if url.nil?
439
-
440
- # Override defaults from URL if given
441
- if url
442
- require "uri"
443
-
444
- uri = URI(url)
445
-
446
- if uri.scheme == "unix"
447
- defaults[:path] = uri.path
448
- elsif uri.scheme == "redis" || uri.scheme == "rediss"
449
- defaults[:scheme] = uri.scheme
450
- defaults[:host] = uri.host if uri.host
451
- defaults[:port] = uri.port if uri.port
452
- defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
453
- defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
454
- defaults[:db] = uri.path[1..-1].to_i if uri.path
455
- defaults[:role] = :master
456
- else
457
- raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
458
- end
459
-
460
- defaults[:ssl] = true if uri.scheme == "rediss"
461
- end
462
-
463
- # Use default when option is not specified or nil
464
- defaults.keys.each do |key|
465
- options[key] = defaults[key] if options[key].nil?
466
- end
467
-
468
- if options[:path]
469
- # Unix socket
470
- options[:scheme] = "unix"
471
- options.delete(:host)
472
- options.delete(:port)
473
- else
474
- # TCP socket
475
- options[:host] = options[:host].to_s
476
- options[:port] = options[:port].to_i
477
- end
478
-
479
- if options.key?(:timeout)
480
- options[:connect_timeout] ||= options[:timeout]
481
- options[:read_timeout] ||= options[:timeout]
482
- options[:write_timeout] ||= options[:timeout]
483
- end
484
-
485
- options[:connect_timeout] = Float(options[:connect_timeout])
486
- options[:read_timeout] = Float(options[:read_timeout])
487
- options[:write_timeout] = Float(options[:write_timeout])
488
-
489
- options[:reconnect_attempts] = options[:reconnect_attempts].to_i
490
- options[:reconnect_delay] = options[:reconnect_delay].to_f
491
- options[:reconnect_delay_max] = options[:reconnect_delay_max].to_f
492
-
493
- options[:db] = options[:db].to_i
494
- options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
495
-
496
- case options[:tcp_keepalive]
497
- when Hash
498
- %i[time intvl probes].each do |key|
499
- unless options[:tcp_keepalive][key].is_a?(Integer)
500
- raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
501
- end
502
- end
503
-
504
- when Integer
505
- if options[:tcp_keepalive] >= 60
506
- options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
507
-
508
- elsif options[:tcp_keepalive] >= 30
509
- options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
510
-
511
- elsif options[:tcp_keepalive] >= 5
512
- options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
513
- end
514
- end
515
-
516
- options[:_parsed] = true
517
-
518
- options
115
+ def pipelined(exception: true)
116
+ super
117
+ rescue ::RedisClient::Error => error
118
+ Client.translate_error!(error)
519
119
  end
520
120
 
521
- def _parse_driver(driver)
522
- driver = driver.to_s if driver.is_a?(Symbol)
523
-
524
- if driver.is_a?(String)
525
- begin
526
- require_relative "connection/#{driver}"
527
- rescue LoadError, NameError
528
- begin
529
- require "redis/connection/#{driver}"
530
- rescue LoadError, NameError => error
531
- raise "Cannot load driver #{driver.inspect}: #{error.message}"
532
- end
533
- end
534
-
535
- driver = Connection.const_get(driver.capitalize)
536
- end
537
-
538
- driver
121
+ def multi(watch: nil)
122
+ super
123
+ rescue ::RedisClient::Error => error
124
+ Client.translate_error!(error)
539
125
  end
540
126
 
541
- class Connector
542
- def initialize(options)
543
- @options = options.dup
544
- end
545
-
546
- def resolve
547
- @options
548
- end
549
-
550
- def check(client); end
551
-
552
- class Sentinel < Connector
553
- def initialize(options)
554
- super(options)
555
-
556
- @options[:db] = DEFAULTS.fetch(:db)
557
-
558
- @sentinels = @options.delete(:sentinels).dup
559
- @role = (@options[:role] || "master").to_s
560
- @master = @options[:host]
561
- end
562
-
563
- def check(client)
564
- # Check the instance is really of the role we are looking for.
565
- # We can't assume the command is supported since it was introduced
566
- # recently and this client should work with old stuff.
567
- begin
568
- role = client.call([:role])[0]
569
- rescue Redis::CommandError
570
- # Assume the test is passed if we can't get a reply from ROLE...
571
- role = @role
572
- end
573
-
574
- if role != @role
575
- client.disconnect
576
- raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
577
- end
578
- end
579
-
580
- def resolve
581
- result = case @role
582
- when "master"
583
- resolve_master
584
- when "slave"
585
- resolve_slave
586
- else
587
- raise ArgumentError, "Unknown instance role #{@role}"
588
- end
589
-
590
- result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
591
- end
592
-
593
- def sentinel_detect
594
- @sentinels.each do |sentinel|
595
- client = Client.new(@options.merge({
596
- host: sentinel[:host] || sentinel["host"],
597
- port: sentinel[:port] || sentinel["port"],
598
- username: sentinel[:username] || sentinel["username"],
599
- password: sentinel[:password] || sentinel["password"],
600
- reconnect_attempts: 0
601
- }))
602
-
603
- begin
604
- if result = yield(client)
605
- # This sentinel responded. Make sure we ask it first next time.
606
- @sentinels.delete(sentinel)
607
- @sentinels.unshift(sentinel)
608
-
609
- return result
610
- end
611
- rescue BaseConnectionError
612
- ensure
613
- client.disconnect
614
- end
615
- end
616
-
617
- raise CannotConnectError, "No sentinels available."
618
- end
619
-
620
- def resolve_master
621
- sentinel_detect do |client|
622
- if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
623
- { host: reply[0], port: reply[1] }
624
- end
625
- end
626
- end
627
-
628
- def resolve_slave
629
- sentinel_detect do |client|
630
- if reply = client.call(["sentinel", "slaves", @master])
631
- slaves = reply.map { |s| s.each_slice(2).to_h }
632
- slaves.each { |s| s['flags'] = s.fetch('flags').split(',') }
633
- slaves.reject! { |s| s.fetch('flags').include?('s_down') }
634
-
635
- if slaves.empty?
636
- raise CannotConnectError, 'No slaves available.'
637
- else
638
- slave = slaves.sample
639
- {
640
- host: slave.fetch('ip'),
641
- port: slave.fetch('port')
642
- }
643
- end
644
- end
645
- end
646
- end
647
- end
127
+ def inherit_socket!
128
+ @inherit_socket = true
648
129
  end
649
130
  end
650
131
  end