redis_dynamic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/redis_pool.rb +88 -0
- data/lib/redis_pool/connection_queue.rb +115 -0
- data/lib/redis_pool/reaper.rb +33 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 046e2753ce2319d2b8c1ab4972d677eeaa92b7c2d2e8696a7224edf222d49b1c
|
4
|
+
data.tar.gz: fcccc0a2969bb430df31ddcad70e4e715cd8bdd59fbc2fd1158c4838fa6ebea8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a4f85d887156bbd10ce067e5561cd213093f591b19ba1ecda9ddeb6853a8be6a124d0e597b9a10e631541866b353ab3fea4749c388b1f678224b0a1639fea92
|
7
|
+
data.tar.gz: 8e25c5daf8c85d0594ca7cf1b26d179f9e0cdb4c92bc9789f6d431cff97a583c9f1d52ee369f7695e35f21d49256c6e13f9cc7a0f9c13e234c14437cd42cf448
|
data/lib/redis_pool.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require_relative './redis_pool/connection_queue'
|
3
|
+
require_relative './redis_pool/reaper'
|
4
|
+
|
5
|
+
class RedisPool
|
6
|
+
DEFAULT_REDIS_CONFIG = { host: 'localhost', port: 6379 }.freeze
|
7
|
+
|
8
|
+
attr_reader :max_size, :connection_timeout, :idle_timeout,
|
9
|
+
:reaping_frequency, :available
|
10
|
+
|
11
|
+
def initialize(max_size: 5, connection_timeout: 5, idle_timeout: 100, reaping_frequency: 300, redis_config: {})
|
12
|
+
@redis_config = DEFAULT_REDIS_CONFIG.merge(redis_config)
|
13
|
+
|
14
|
+
@max_size = max_size
|
15
|
+
@connection_timeout = connection_timeout
|
16
|
+
@idle_timeout = idle_timeout
|
17
|
+
@reaping_frequency = reaping_frequency
|
18
|
+
|
19
|
+
@available = ConnectionQueue.new(@max_size, &redis_creation_block)
|
20
|
+
@reaper = Reaper.new(self, @reaping_frequency, @idle_timeout)
|
21
|
+
|
22
|
+
@key = :"pool-#{@available.object_id}"
|
23
|
+
@key_count = :"pool-#{@available.object_id}-count"
|
24
|
+
|
25
|
+
@reaper.reap
|
26
|
+
end
|
27
|
+
|
28
|
+
def with(timeout = nil)
|
29
|
+
Thread.handle_interrupt(Exception => :never) do
|
30
|
+
conn = checkout(timeout)
|
31
|
+
begin
|
32
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
33
|
+
yield conn.first
|
34
|
+
end
|
35
|
+
ensure
|
36
|
+
checkin
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
alias with_conn with
|
41
|
+
alias with_connection with
|
42
|
+
|
43
|
+
def checkout(timeout = nil)
|
44
|
+
if current_thread[@key]
|
45
|
+
current_thread[@key_count] += 1
|
46
|
+
current_thread[@key]
|
47
|
+
else
|
48
|
+
current_thread[@key_count] = 1
|
49
|
+
current_thread[@key] = @available.poll(timeout || @connection_timeout)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def checkin
|
54
|
+
raise 'no connections are checked out' unless current_thread[@key]
|
55
|
+
|
56
|
+
if current_thread[@key_count] == 1
|
57
|
+
@available.add current_thread[@key]
|
58
|
+
current_thread[@key] = nil
|
59
|
+
current_thread[@key_count] = nil
|
60
|
+
else
|
61
|
+
current_thread[@key_count] -= 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def stats
|
66
|
+
conn_stats = @available.queue.map do |conn|
|
67
|
+
conn.last
|
68
|
+
end
|
69
|
+
pool_stats = {
|
70
|
+
available_to_create: @available.available_to_create,
|
71
|
+
total_available: @available.total_available,
|
72
|
+
connections_stats: conn_stats
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def current_thread
|
79
|
+
Thread.current
|
80
|
+
end
|
81
|
+
|
82
|
+
def redis_creation_block
|
83
|
+
-> { Redis.new(@redis_config) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
require 'redis_pool/connection_queue'
|
88
|
+
require 'redis_pool/reaper'
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'concurrent'
|
3
|
+
|
4
|
+
##
|
5
|
+
# A thread-safe implementation of a connection queue.
|
6
|
+
# Supports adding, removing, and polling a connection
|
7
|
+
# synchronously, and doesn't create more than `max_size`
|
8
|
+
# elements.
|
9
|
+
# All connections are created lazily (only when needed).
|
10
|
+
#
|
11
|
+
class ConnectionQueue
|
12
|
+
attr_reader :max_size, :queue
|
13
|
+
|
14
|
+
def initialize(max_size = 0, &block)
|
15
|
+
@create_block = block
|
16
|
+
@created = 0
|
17
|
+
@queue = []
|
18
|
+
@max_size = max_size
|
19
|
+
@lock = Monitor.new
|
20
|
+
@lock_cond = @lock.new_cond
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Adds (or returns) a connection to the available queue, synchronously.
|
25
|
+
#
|
26
|
+
def add(element)
|
27
|
+
synchronize do
|
28
|
+
@queue.push element
|
29
|
+
@lock_cond.signal
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias << add
|
33
|
+
alias push add
|
34
|
+
|
35
|
+
##
|
36
|
+
# Fetches any available connection from the queue. If a connection
|
37
|
+
# is not available, waits for +timeout+ until a connection is
|
38
|
+
# available or raises a TimeoutError.
|
39
|
+
#
|
40
|
+
def poll(timeout = 5)
|
41
|
+
t0 = Concurrent.monotonic_time
|
42
|
+
elapsed = 0
|
43
|
+
synchronize do
|
44
|
+
loop do
|
45
|
+
return get_connection if connection_available?
|
46
|
+
|
47
|
+
connection = create_connection
|
48
|
+
return connection if connection
|
49
|
+
|
50
|
+
elapsed = Concurrent.monotonic_time - t0
|
51
|
+
raise TimeoutError, 'could not obtain connection' if elapsed >= timeout
|
52
|
+
|
53
|
+
@lock_cond.wait(timeout - elapsed)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
alias pop poll
|
58
|
+
|
59
|
+
##
|
60
|
+
# Removes an idle connection from the queue
|
61
|
+
# synchronously.
|
62
|
+
#
|
63
|
+
def delete(element)
|
64
|
+
synchronize do
|
65
|
+
@queue.delete element
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Returns the total available connections to be used. This
|
71
|
+
# takes into account the number of connections that can be
|
72
|
+
# created as well. So it is all connections that can be used
|
73
|
+
# AND created.
|
74
|
+
#
|
75
|
+
def total_available
|
76
|
+
@max_size - @created + @queue.length
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Returns the number of available connections to create.
|
81
|
+
#
|
82
|
+
def available_to_create
|
83
|
+
@max_size - @created
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def synchronize(&block)
|
89
|
+
@lock.synchronize(&block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def connection_available?
|
93
|
+
!@queue.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_connection
|
97
|
+
conn = @queue.pop
|
98
|
+
conn.last[:last_used_at] = Time.now.utc
|
99
|
+
conn
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_connection
|
103
|
+
return unless @created < @max_size
|
104
|
+
|
105
|
+
conn = @create_block.call
|
106
|
+
# TODO: add more stats.
|
107
|
+
stats = {
|
108
|
+
id: @created,
|
109
|
+
alive_since: Time.now.utc,
|
110
|
+
last_used_at: Time.now.utc
|
111
|
+
}
|
112
|
+
@created += 1
|
113
|
+
[conn, stats]
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
##
|
2
|
+
# A reaper class that initializes a thread running in the
|
3
|
+
# background, that kills all connections in `pool` that
|
4
|
+
# has been idle for more than `idle_timeout`.
|
5
|
+
#
|
6
|
+
class Reaper
|
7
|
+
attr_reader :frequency, :idle_timeout
|
8
|
+
|
9
|
+
def initialize(pool, frequency, idle_timeout)
|
10
|
+
@frequency = frequency
|
11
|
+
@idle_timeout = idle_timeout
|
12
|
+
@pool = pool
|
13
|
+
@lock = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def reap
|
17
|
+
Thread.new do
|
18
|
+
loop do
|
19
|
+
@pool.available.queue.each do |conn|
|
20
|
+
idle_since = conn.last[:last_used_at] - Time.now.utc
|
21
|
+
|
22
|
+
next unless idle_since >= @idle_timeout
|
23
|
+
|
24
|
+
@lock.synchronize do
|
25
|
+
@pool.available.delete conn
|
26
|
+
conn.first.disconnect!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
sleep @frequency
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis_dynamic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mohammed Amarnah
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redis
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A simple dynamic-sized redis connection pool.
|
70
|
+
email: m.amarnah@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- lib/redis_pool.rb
|
76
|
+
- lib/redis_pool/connection_queue.rb
|
77
|
+
- lib/redis_pool/reaper.rb
|
78
|
+
homepage: https://github.com/mohammedamarnah/redis-pool
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 2.2.0
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.1.2
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Redis Dynamic Pool
|
101
|
+
test_files: []
|