mini-safe-lib 0.0.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.
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ # Base error for all redis-rb errors.
5
+ class BaseError < StandardError
6
+ end
7
+
8
+ # Raised by the connection when a protocol error occurs.
9
+ class ProtocolError < BaseError
10
+ def initialize(reply_type)
11
+ super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
12
+ Got '#{reply_type}' as initial reply byte.
13
+ If you're in a forking environment, such as Unicorn, you need to
14
+ connect to Redis after forking.
15
+ EOS
16
+ end
17
+ end
18
+
19
+ # Raised by the client when command execution returns an error reply.
20
+ class CommandError < BaseError
21
+ end
22
+
23
+ class PermissionError < CommandError
24
+ end
25
+
26
+ class WrongTypeError < CommandError
27
+ end
28
+
29
+ class OutOfMemoryError < CommandError
30
+ end
31
+
32
+ if defined?(RedisClient::NoScriptError)
33
+ class NoScriptError < CommandError
34
+ end
35
+ end
36
+
37
+ # Base error for connection related errors.
38
+ class BaseConnectionError < BaseError
39
+ end
40
+
41
+ # Raised when connection to a Redis server cannot be made.
42
+ class CannotConnectError < BaseConnectionError
43
+ end
44
+
45
+ # Raised when connection to a Redis server is lost.
46
+ class ConnectionError < BaseConnectionError
47
+ end
48
+
49
+ # Raised when performing I/O times out.
50
+ class TimeoutError < BaseConnectionError
51
+ end
52
+
53
+ # Raised when the connection was inherited by a child process.
54
+ class InheritedError < BaseConnectionError
55
+ end
56
+
57
+ # Generally raised during Redis failover scenarios
58
+ class ReadOnlyError < BaseConnectionError
59
+ end
60
+
61
+ # Raised when client options are invalid.
62
+ class InvalidClientOptionError < BaseError
63
+ end
64
+
65
+ class SubscriptionError < BaseError
66
+ end
67
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+ require 'digest/md5'
5
+
6
+ class Redis
7
+ class HashRing
8
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
9
+
10
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
11
+
12
+ # nodes is a list of objects that have a proper to_s representation.
13
+ # replicas indicates how many virtual points should be used pr. node,
14
+ # replicas are required to improve the distribution.
15
+ def initialize(nodes = [], replicas = POINTS_PER_SERVER)
16
+ @replicas = replicas
17
+ @ring = {}
18
+ @nodes = []
19
+ @sorted_keys = []
20
+ nodes.each do |node|
21
+ add_node(node)
22
+ end
23
+ end
24
+
25
+ # Adds a `node` to the hash ring (including a number of replicas).
26
+ def add_node(node)
27
+ @nodes << node
28
+ @replicas.times do |i|
29
+ key = server_hash_for("#{node.id}:#{i}")
30
+ @ring[key] = node
31
+ @sorted_keys << key
32
+ end
33
+ @sorted_keys.sort!
34
+ end
35
+
36
+ def remove_node(node)
37
+ @nodes.reject! { |n| n.id == node.id }
38
+ @replicas.times do |i|
39
+ key = server_hash_for("#{node.id}:#{i}")
40
+ @ring.delete(key)
41
+ @sorted_keys.reject! { |k| k == key }
42
+ end
43
+ end
44
+
45
+ # get the node in the hash ring for this key
46
+ def get_node(key)
47
+ hash = hash_for(key)
48
+ idx = binary_search(@sorted_keys, hash)
49
+ @ring[@sorted_keys[idx]]
50
+ end
51
+
52
+ def iter_nodes(key)
53
+ return [nil, nil] if @ring.empty?
54
+
55
+ crc = hash_for(key)
56
+ pos = binary_search(@sorted_keys, crc)
57
+ @ring.size.times do |n|
58
+ yield @ring[@sorted_keys[(pos + n) % @ring.size]]
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def hash_for(key)
65
+ Zlib.crc32(key)
66
+ end
67
+
68
+ def server_hash_for(key)
69
+ Digest::MD5.digest(key).unpack1("L>")
70
+ end
71
+
72
+ # Find the closest index in HashRing with value <= the given value
73
+ def binary_search(ary, value)
74
+ upper = ary.size
75
+ lower = 0
76
+
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
81
+ else
82
+ lower = mid + 1
83
+ end
84
+ end
85
+
86
+ upper - 1
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ class Redis
6
+ class PipelinedConnection
7
+ attr_accessor :db
8
+
9
+ def initialize(pipeline, futures = [], exception: true)
10
+ @pipeline = pipeline
11
+ @futures = futures
12
+ @exception = exception
13
+ end
14
+
15
+ include Commands
16
+
17
+ def pipelined
18
+ yield self
19
+ end
20
+
21
+ def multi
22
+ transaction = MultiConnection.new(@pipeline, @futures)
23
+ send_command([:multi])
24
+ size = @futures.size
25
+ yield transaction
26
+ multi_future = MultiFuture.new(@futures[size..-1])
27
+ @pipeline.call_v([:exec]) do |result|
28
+ multi_future._set(result)
29
+ end
30
+ @futures << multi_future
31
+ multi_future
32
+ end
33
+
34
+ private
35
+
36
+ def synchronize
37
+ yield self
38
+ end
39
+
40
+ def send_command(command, &block)
41
+ future = Future.new(command, block, @exception)
42
+ @pipeline.call_v(command) do |result|
43
+ future._set(result)
44
+ end
45
+ @futures << future
46
+ future
47
+ end
48
+
49
+ def send_blocking_command(command, timeout, &block)
50
+ future = Future.new(command, block, @exception)
51
+ @pipeline.blocking_call_v(timeout, command) do |result|
52
+ future._set(result)
53
+ end
54
+ @futures << future
55
+ future
56
+ end
57
+ end
58
+
59
+ class MultiConnection < PipelinedConnection
60
+ def multi
61
+ raise Redis::BaseError, "Can't nest multi transaction"
62
+ end
63
+
64
+ private
65
+
66
+ # Blocking commands inside transaction behave like non-blocking.
67
+ # It shouldn't be done though.
68
+ # https://redis.io/commands/blpop/#blpop-inside-a-multi--exec-transaction
69
+ def send_blocking_command(command, _timeout, &block)
70
+ send_command(command, &block)
71
+ end
72
+ end
73
+
74
+ class FutureNotReady < RuntimeError
75
+ def initialize
76
+ super("Value will be available once the pipeline executes.")
77
+ end
78
+ end
79
+
80
+ class Future < BasicObject
81
+ FutureNotReady = ::Redis::FutureNotReady.new
82
+
83
+ def initialize(command, coerce, exception)
84
+ @command = command
85
+ @object = FutureNotReady
86
+ @coerce = coerce
87
+ @exception = exception
88
+ end
89
+
90
+ def inspect
91
+ "<Redis::Future #{@command.inspect}>"
92
+ end
93
+
94
+ def _set(object)
95
+ @object = @coerce ? @coerce.call(object) : object
96
+ value
97
+ end
98
+
99
+ def value
100
+ ::Kernel.raise(@object) if @exception && @object.is_a?(::StandardError)
101
+ @object
102
+ end
103
+
104
+ def is_a?(other)
105
+ self.class.ancestors.include?(other)
106
+ end
107
+
108
+ def class
109
+ Future
110
+ end
111
+ end
112
+
113
+ class MultiFuture < Future
114
+ def initialize(futures)
115
+ @futures = futures
116
+ @command = [:exec]
117
+ @object = FutureNotReady
118
+ end
119
+
120
+ def _set(replies)
121
+ @object = if replies
122
+ @futures.map.with_index do |future, index|
123
+ future._set(replies[index])
124
+ future.value
125
+ end
126
+ else
127
+ replies
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ class SubscribedClient
5
+ def initialize(client)
6
+ @client = client
7
+ @write_monitor = Monitor.new
8
+ end
9
+
10
+ def call_v(command)
11
+ @write_monitor.synchronize do
12
+ @client.call_v(command)
13
+ end
14
+ end
15
+
16
+ def subscribe(*channels, &block)
17
+ subscription("subscribe", "unsubscribe", channels, block)
18
+ end
19
+
20
+ def subscribe_with_timeout(timeout, *channels, &block)
21
+ subscription("subscribe", "unsubscribe", channels, block, timeout)
22
+ end
23
+
24
+ def psubscribe(*channels, &block)
25
+ subscription("psubscribe", "punsubscribe", channels, block)
26
+ end
27
+
28
+ def psubscribe_with_timeout(timeout, *channels, &block)
29
+ subscription("psubscribe", "punsubscribe", channels, block, timeout)
30
+ end
31
+
32
+ def ssubscribe(*channels, &block)
33
+ subscription("ssubscribe", "sunsubscribe", channels, block)
34
+ end
35
+
36
+ def ssubscribe_with_timeout(timeout, *channels, &block)
37
+ subscription("ssubscribe", "sunsubscribe", channels, block, timeout)
38
+ end
39
+
40
+ def unsubscribe(*channels)
41
+ call_v([:unsubscribe, *channels])
42
+ end
43
+
44
+ def punsubscribe(*channels)
45
+ call_v([:punsubscribe, *channels])
46
+ end
47
+
48
+ def sunsubscribe(*channels)
49
+ call_v([:sunsubscribe, *channels])
50
+ end
51
+
52
+ def close
53
+ @client.close
54
+ end
55
+
56
+ protected
57
+
58
+ def subscription(start, stop, channels, block, timeout = 0)
59
+ sub = Subscription.new(&block)
60
+
61
+ case start
62
+ when "ssubscribe" then channels.each { |c| call_v([start, c]) } # avoid cross-slot keys
63
+ else call_v([start, *channels])
64
+ end
65
+
66
+ while event = @client.next_event(timeout)
67
+ if event.is_a?(::RedisClient::CommandError)
68
+ raise Client::ERROR_MAPPING.fetch(event.class), event.message
69
+ end
70
+
71
+ type, *rest = event
72
+ if callback = sub.callbacks[type]
73
+ callback.call(*rest)
74
+ end
75
+ break if type == stop && rest.last == 0
76
+ end
77
+ # No need to unsubscribe here. The real client closes the connection
78
+ # whenever an exception is raised (see #ensure_connected).
79
+ end
80
+ end
81
+
82
+ class Subscription
83
+ attr :callbacks
84
+
85
+ def initialize
86
+ @callbacks = {}
87
+ yield(self)
88
+ end
89
+
90
+ def subscribe(&block)
91
+ @callbacks["subscribe"] = block
92
+ end
93
+
94
+ def unsubscribe(&block)
95
+ @callbacks["unsubscribe"] = block
96
+ end
97
+
98
+ def message(&block)
99
+ @callbacks["message"] = block
100
+ end
101
+
102
+ def psubscribe(&block)
103
+ @callbacks["psubscribe"] = block
104
+ end
105
+
106
+ def punsubscribe(&block)
107
+ @callbacks["punsubscribe"] = block
108
+ end
109
+
110
+ def pmessage(&block)
111
+ @callbacks["pmessage"] = block
112
+ end
113
+
114
+ def ssubscribe(&block)
115
+ @callbacks["ssubscribe"] = block
116
+ end
117
+
118
+ def sunsubscribe(&block)
119
+ @callbacks["sunsubscribe"] = block
120
+ end
121
+
122
+ def smessage(&block)
123
+ @callbacks["smessage"] = block
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ VERSION = '5.4.1'
5
+ end