mudis 0.9.1 → 0.9.4
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 +4 -4
- data/README.md +41 -5
- data/lib/mudis/bound.rb +128 -0
- data/lib/mudis/metrics.rb +21 -2
- data/lib/mudis/version.rb +1 -1
- data/lib/mudis.rb +99 -23
- data/lib/mudis_client.rb +69 -31
- data/lib/mudis_config.rb +2 -0
- data/lib/mudis_ipc_config.rb +10 -0
- data/lib/mudis_server.rb +14 -9
- data/sig/mudis.rbs +11 -2
- data/sig/mudis_bound.rbs +25 -0
- data/sig/mudis_client.rbs +13 -3
- data/sig/mudis_config.rbs +1 -0
- data/sig/mudis_ipc_config.rbs +8 -0
- data/sig/mudis_metrics.rbs +1 -1
- data/spec/api_compatibility_spec.rb +3 -0
- data/spec/bound_spec.rb +89 -0
- data/spec/guardrails_spec.rb +14 -0
- data/spec/metrics_spec.rb +21 -0
- data/spec/modules/metrics_spec.rb +4 -1
- data/spec/modules/persistence_spec.rb +24 -26
- data/spec/mudis_client_spec.rb +83 -9
- data/spec/mudis_server_spec.rb +51 -17
- data/spec/mudis_spec.rb +37 -0
- metadata +7 -2
data/spec/mudis_client_spec.rb
CHANGED
|
@@ -99,32 +99,93 @@ RSpec.describe MudisClient do # rubocop:disable Metrics/BlockLength
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
describe "#
|
|
103
|
-
it "sends
|
|
104
|
-
payload = { cmd: "
|
|
105
|
-
response = { ok: true, value:
|
|
102
|
+
describe "#inspect" do
|
|
103
|
+
it "sends an inspect command and returns metadata" do
|
|
104
|
+
payload = { cmd: "inspect", key: "test_key", namespace: nil }
|
|
105
|
+
response = { ok: true, value: { key: "test_key", size_bytes: 10 } }.to_json
|
|
106
106
|
|
|
107
107
|
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
108
108
|
expect(mock_socket).to receive(:gets).and_return(response)
|
|
109
109
|
|
|
110
|
-
expect(client.
|
|
110
|
+
expect(client.inspect("test_key")).to eq({ key: "test_key", size_bytes: 10 })
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
-
describe "#
|
|
115
|
-
it "sends a
|
|
116
|
-
payload = { cmd: "
|
|
114
|
+
describe "#keys" do
|
|
115
|
+
it "sends a keys command and returns keys" do
|
|
116
|
+
payload = { cmd: "keys", namespace: "ns" }
|
|
117
|
+
response = { ok: true, value: ["a", "b"] }.to_json
|
|
118
|
+
|
|
119
|
+
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
120
|
+
expect(mock_socket).to receive(:gets).and_return(response)
|
|
121
|
+
|
|
122
|
+
expect(client.keys(namespace: "ns")).to eq(["a", "b"])
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "#clear_namespace" do
|
|
127
|
+
it "sends a clear_namespace command" do
|
|
128
|
+
payload = { cmd: "clear_namespace", namespace: "ns" }
|
|
117
129
|
response = { ok: true, value: nil }.to_json
|
|
118
130
|
|
|
119
131
|
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
120
132
|
expect(mock_socket).to receive(:gets).and_return(response)
|
|
121
133
|
|
|
122
|
-
expect(client.
|
|
134
|
+
expect(client.clear_namespace(namespace: "ns")).to be_nil
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "#least_touched" do
|
|
139
|
+
it "sends a least_touched command" do
|
|
140
|
+
payload = { cmd: "least_touched", limit: 5 }
|
|
141
|
+
response = { ok: true, value: [["a", 0], ["b", 1]] }.to_json
|
|
142
|
+
|
|
143
|
+
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
144
|
+
expect(mock_socket).to receive(:gets).and_return(response)
|
|
145
|
+
|
|
146
|
+
expect(client.least_touched(5)).to eq([["a", 0], ["b", 1]])
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe "#all_keys" do
|
|
151
|
+
it "sends an all_keys command" do
|
|
152
|
+
payload = { cmd: "all_keys" }
|
|
153
|
+
response = { ok: true, value: ["k1"] }.to_json
|
|
154
|
+
|
|
155
|
+
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
156
|
+
expect(mock_socket).to receive(:gets).and_return(response)
|
|
157
|
+
|
|
158
|
+
expect(client.all_keys).to eq(["k1"])
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe "#current_memory_bytes" do
|
|
163
|
+
it "sends a current_memory_bytes command" do
|
|
164
|
+
payload = { cmd: "current_memory_bytes" }
|
|
165
|
+
response = { ok: true, value: 123 }.to_json
|
|
166
|
+
|
|
167
|
+
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
168
|
+
expect(mock_socket).to receive(:gets).and_return(response)
|
|
169
|
+
|
|
170
|
+
expect(client.current_memory_bytes).to eq(123)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe "#max_memory_bytes" do
|
|
175
|
+
it "sends a max_memory_bytes command" do
|
|
176
|
+
payload = { cmd: "max_memory_bytes" }
|
|
177
|
+
response = { ok: true, value: 456 }.to_json
|
|
178
|
+
|
|
179
|
+
expect(mock_socket).to receive(:puts).with(payload.to_json)
|
|
180
|
+
expect(mock_socket).to receive(:gets).and_return(response)
|
|
181
|
+
|
|
182
|
+
expect(client.max_memory_bytes).to eq(456)
|
|
123
183
|
end
|
|
124
184
|
end
|
|
125
185
|
|
|
126
186
|
describe "error handling" do
|
|
127
187
|
it "warns when the socket is missing" do
|
|
188
|
+
allow(MudisIPCConfig).to receive(:retries).and_return(1)
|
|
128
189
|
if MudisIPCConfig.use_tcp?
|
|
129
190
|
allow(TCPSocket).to receive(:new).and_raise(Errno::ECONNREFUSED)
|
|
130
191
|
else
|
|
@@ -143,5 +204,18 @@ RSpec.describe MudisClient do # rubocop:disable Metrics/BlockLength
|
|
|
143
204
|
|
|
144
205
|
expect { client.read("test_key") }.to raise_error("Something went wrong")
|
|
145
206
|
end
|
|
207
|
+
|
|
208
|
+
it "retries on timeout and then warns" do
|
|
209
|
+
allow(MudisIPCConfig).to receive(:retries).and_return(1)
|
|
210
|
+
allow(MudisIPCConfig).to receive(:timeout).and_return(0.01)
|
|
211
|
+
|
|
212
|
+
if MudisIPCConfig.use_tcp?
|
|
213
|
+
allow(TCPSocket).to receive(:new).and_raise(Timeout::Error)
|
|
214
|
+
else
|
|
215
|
+
allow(UNIXSocket).to receive(:open).and_raise(Timeout::Error)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
expect { client.read("test_key") }.to output(/Cannot connect/).to_stderr
|
|
219
|
+
end
|
|
146
220
|
end
|
|
147
221
|
end
|
data/spec/mudis_server_spec.rb
CHANGED
|
@@ -13,8 +13,6 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
|
|
|
13
13
|
next
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
let(:socket_path) { MudisIPCConfig::SOCKET_PATH }
|
|
17
|
-
|
|
18
16
|
before(:all) do
|
|
19
17
|
# Start the server once for all tests
|
|
20
18
|
Thread.new { MudisServer.start! }
|
|
@@ -27,12 +25,18 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
|
|
|
27
25
|
allow(Mudis).to receive(:delete)
|
|
28
26
|
allow(Mudis).to receive(:exists?).and_return(true)
|
|
29
27
|
allow(Mudis).to receive(:fetch).and_return("mock_fetched_value")
|
|
28
|
+
allow(Mudis).to receive(:inspect).and_return({ key: "test_key", size_bytes: 10 })
|
|
29
|
+
allow(Mudis).to receive(:keys).and_return(["a", "b"])
|
|
30
|
+
allow(Mudis).to receive(:clear_namespace)
|
|
31
|
+
allow(Mudis).to receive(:least_touched).and_return([["a", 0]])
|
|
32
|
+
allow(Mudis).to receive(:all_keys).and_return(["k1"])
|
|
33
|
+
allow(Mudis).to receive(:current_memory_bytes).and_return(123)
|
|
34
|
+
allow(Mudis).to receive(:max_memory_bytes).and_return(456)
|
|
30
35
|
allow(Mudis).to receive(:metrics).and_return({ reads: 1, writes: 1 })
|
|
31
|
-
allow(Mudis).to receive(:reset_metrics!)
|
|
32
|
-
allow(Mudis).to receive(:reset!)
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
after do
|
|
38
|
+
after(:all) do
|
|
39
|
+
socket_path = MudisIPCConfig::SOCKET_PATH
|
|
36
40
|
File.unlink(socket_path) if File.exist?(socket_path) && !MudisIPCConfig.use_tcp?
|
|
37
41
|
end
|
|
38
42
|
|
|
@@ -43,7 +47,7 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
|
|
|
43
47
|
JSON.parse(sock.gets, symbolize_names: true)
|
|
44
48
|
end
|
|
45
49
|
else
|
|
46
|
-
UNIXSocket.open(
|
|
50
|
+
UNIXSocket.open(MudisIPCConfig::SOCKET_PATH) do |sock|
|
|
47
51
|
sock.puts(JSON.dump(request))
|
|
48
52
|
JSON.parse(sock.gets, symbolize_names: true)
|
|
49
53
|
end
|
|
@@ -81,22 +85,52 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
|
|
|
81
85
|
expect(Mudis).to have_received(:fetch).with("test_key", expires_in: 60, namespace: "test_ns")
|
|
82
86
|
end
|
|
83
87
|
|
|
84
|
-
it "handles the '
|
|
85
|
-
response = send_request({ cmd: "
|
|
86
|
-
expect(response).to eq({ ok: true, value: {
|
|
87
|
-
expect(Mudis).to have_received(:
|
|
88
|
+
it "handles the 'inspect' command" do
|
|
89
|
+
response = send_request({ cmd: "inspect", key: "test_key", namespace: "test_ns" })
|
|
90
|
+
expect(response).to eq({ ok: true, value: { key: "test_key", size_bytes: 10 } })
|
|
91
|
+
expect(Mudis).to have_received(:inspect).with("test_key", namespace: "test_ns")
|
|
88
92
|
end
|
|
89
93
|
|
|
90
|
-
it "handles the '
|
|
91
|
-
response = send_request({ cmd: "
|
|
92
|
-
expect(response).to eq({ ok: true, value:
|
|
93
|
-
expect(Mudis).to have_received(:
|
|
94
|
+
it "handles the 'keys' command" do
|
|
95
|
+
response = send_request({ cmd: "keys", namespace: "test_ns" })
|
|
96
|
+
expect(response).to eq({ ok: true, value: ["a", "b"] })
|
|
97
|
+
expect(Mudis).to have_received(:keys).with(namespace: "test_ns")
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
it "handles the '
|
|
97
|
-
response = send_request({ cmd: "
|
|
100
|
+
it "handles the 'clear_namespace' command" do
|
|
101
|
+
response = send_request({ cmd: "clear_namespace", namespace: "test_ns" })
|
|
98
102
|
expect(response).to eq({ ok: true, value: nil })
|
|
99
|
-
expect(Mudis).to have_received(:
|
|
103
|
+
expect(Mudis).to have_received(:clear_namespace).with(namespace: "test_ns")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "handles the 'least_touched' command" do
|
|
107
|
+
response = send_request({ cmd: "least_touched", limit: 5 })
|
|
108
|
+
expect(response).to eq({ ok: true, value: [["a", 0]] })
|
|
109
|
+
expect(Mudis).to have_received(:least_touched).with(5)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "handles the 'all_keys' command" do
|
|
113
|
+
response = send_request({ cmd: "all_keys" })
|
|
114
|
+
expect(response).to eq({ ok: true, value: ["k1"] })
|
|
115
|
+
expect(Mudis).to have_received(:all_keys)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "handles the 'current_memory_bytes' command" do
|
|
119
|
+
response = send_request({ cmd: "current_memory_bytes" })
|
|
120
|
+
expect(response).to eq({ ok: true, value: 123 })
|
|
121
|
+
expect(Mudis).to have_received(:current_memory_bytes)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "handles the 'max_memory_bytes' command" do
|
|
125
|
+
response = send_request({ cmd: "max_memory_bytes" })
|
|
126
|
+
expect(response).to eq({ ok: true, value: 456 })
|
|
127
|
+
expect(Mudis).to have_received(:max_memory_bytes)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "handles the 'metrics' command" do
|
|
131
|
+
response = send_request({ cmd: "metrics" })
|
|
132
|
+
expect(response).to eq({ ok: true, value: { reads: 1, writes: 1 } })
|
|
133
|
+
expect(Mudis).to have_received(:metrics)
|
|
100
134
|
end
|
|
101
135
|
|
|
102
136
|
it "handles unknown commands" do
|
data/spec/mudis_spec.rb
CHANGED
|
@@ -55,6 +55,19 @@ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
|
|
|
55
55
|
Mudis.update("counter") { |v| v + 1 }
|
|
56
56
|
expect(Mudis.read("counter")).to eq(6)
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
it "refreshes TTL based on original duration" do
|
|
60
|
+
Mudis.write("ttl_key", "v", expires_in: 2)
|
|
61
|
+
meta_before = Mudis.inspect("ttl_key")
|
|
62
|
+
original_ttl = meta_before[:expires_at] - meta_before[:created_at]
|
|
63
|
+
|
|
64
|
+
sleep 1
|
|
65
|
+
Mudis.update("ttl_key") { |v| v }
|
|
66
|
+
meta_after = Mudis.inspect("ttl_key")
|
|
67
|
+
|
|
68
|
+
expect(meta_after[:created_at]).to be > meta_before[:created_at]
|
|
69
|
+
expect(meta_after[:expires_at]).to be_within(0.5).of(Time.now + original_ttl)
|
|
70
|
+
end
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
describe ".fetch" do
|
|
@@ -76,6 +89,30 @@ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
|
|
|
76
89
|
result = Mudis.fetch("k", force: true) { 200 } # fix
|
|
77
90
|
expect(result).to eq(200)
|
|
78
91
|
end
|
|
92
|
+
|
|
93
|
+
it "executes the block once with singleflight: true" do
|
|
94
|
+
Mudis.delete("sf")
|
|
95
|
+
count = 0
|
|
96
|
+
count_mutex = Mutex.new
|
|
97
|
+
results = []
|
|
98
|
+
results_mutex = Mutex.new
|
|
99
|
+
|
|
100
|
+
threads = 5.times.map do
|
|
101
|
+
Thread.new do
|
|
102
|
+
value = Mudis.fetch("sf", singleflight: true) do
|
|
103
|
+
count_mutex.synchronize { count += 1 }
|
|
104
|
+
sleep 0.05
|
|
105
|
+
"v"
|
|
106
|
+
end
|
|
107
|
+
results_mutex.synchronize { results << value }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
threads.each(&:join)
|
|
112
|
+
expect(count).to eq(1)
|
|
113
|
+
expect(results).to all(eq("v"))
|
|
114
|
+
expect(Mudis.read("sf")).to eq("v")
|
|
115
|
+
end
|
|
79
116
|
end
|
|
80
117
|
|
|
81
118
|
describe ".clear" do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mudis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kiebor81
|
|
@@ -60,6 +60,7 @@ executables: []
|
|
|
60
60
|
extensions: []
|
|
61
61
|
extra_rdoc_files:
|
|
62
62
|
- sig/mudis.rbs
|
|
63
|
+
- sig/mudis_bound.rbs
|
|
63
64
|
- sig/mudis_client.rbs
|
|
64
65
|
- sig/mudis_config.rbs
|
|
65
66
|
- sig/mudis_expiry.rbs
|
|
@@ -72,6 +73,7 @@ extra_rdoc_files:
|
|
|
72
73
|
files:
|
|
73
74
|
- README.md
|
|
74
75
|
- lib/mudis.rb
|
|
76
|
+
- lib/mudis/bound.rb
|
|
75
77
|
- lib/mudis/expiry.rb
|
|
76
78
|
- lib/mudis/lru.rb
|
|
77
79
|
- lib/mudis/metrics.rb
|
|
@@ -84,6 +86,7 @@ files:
|
|
|
84
86
|
- lib/mudis_proxy.rb
|
|
85
87
|
- lib/mudis_server.rb
|
|
86
88
|
- sig/mudis.rbs
|
|
89
|
+
- sig/mudis_bound.rbs
|
|
87
90
|
- sig/mudis_client.rbs
|
|
88
91
|
- sig/mudis_config.rbs
|
|
89
92
|
- sig/mudis_expiry.rbs
|
|
@@ -94,6 +97,7 @@ files:
|
|
|
94
97
|
- sig/mudis_persistence.rbs
|
|
95
98
|
- sig/mudis_server.rbs
|
|
96
99
|
- spec/api_compatibility_spec.rb
|
|
100
|
+
- spec/bound_spec.rb
|
|
97
101
|
- spec/eviction_spec.rb
|
|
98
102
|
- spec/guardrails_spec.rb
|
|
99
103
|
- spec/memory_guard_spec.rb
|
|
@@ -121,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
121
125
|
requirements:
|
|
122
126
|
- - ">="
|
|
123
127
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '3.
|
|
128
|
+
version: '3.3'
|
|
125
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
130
|
requirements:
|
|
127
131
|
- - ">="
|
|
@@ -135,6 +139,7 @@ summary: A fast in-memory, thread-safe and high performance Ruby LRU cache with
|
|
|
135
139
|
and auto-expiry.
|
|
136
140
|
test_files:
|
|
137
141
|
- spec/api_compatibility_spec.rb
|
|
142
|
+
- spec/bound_spec.rb
|
|
138
143
|
- spec/eviction_spec.rb
|
|
139
144
|
- spec/guardrails_spec.rb
|
|
140
145
|
- spec/memory_guard_spec.rb
|