mudis 0.8.0 → 0.9.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 +4 -4
- data/README.md +958 -932
- data/lib/mudis/expiry.rb +53 -0
- data/lib/mudis/lru.rb +66 -0
- data/lib/mudis/metrics.rb +42 -0
- data/lib/mudis/namespace.rb +47 -0
- data/lib/mudis/persistence.rb +113 -0
- data/lib/mudis/version.rb +3 -3
- data/lib/mudis.rb +370 -631
- data/lib/mudis_client.rb +107 -65
- data/lib/mudis_config.rb +35 -35
- data/lib/mudis_ipc_config.rb +13 -0
- data/lib/mudis_proxy.rb +39 -37
- data/lib/mudis_server.rb +100 -81
- data/sig/mudis.rbs +62 -56
- data/sig/mudis_client.rbs +24 -22
- data/sig/mudis_config.rbs +10 -10
- data/sig/mudis_expiry.rbs +13 -0
- data/sig/mudis_ipc_config.rbs +10 -0
- data/sig/mudis_lru.rbs +21 -0
- data/sig/mudis_metrics.rbs +11 -0
- data/sig/mudis_namespace.rbs +13 -0
- data/sig/mudis_persistence.rbs +19 -0
- data/sig/mudis_server.rbs +16 -6
- data/spec/api_compatibility_spec.rb +155 -0
- data/spec/eviction_spec.rb +29 -29
- data/spec/guardrails_spec.rb +138 -138
- data/spec/memory_guard_spec.rb +33 -33
- data/spec/metrics_spec.rb +34 -34
- data/spec/modules/expiry_spec.rb +170 -0
- data/spec/modules/lru_spec.rb +149 -0
- data/spec/modules/metrics_spec.rb +105 -0
- data/spec/modules/namespace_spec.rb +157 -0
- data/spec/modules/persistence_spec.rb +125 -0
- data/spec/mudis_client_spec.rb +147 -137
- data/spec/mudis_server_spec.rb +100 -90
- data/spec/mudis_spec.rb +183 -183
- data/spec/namespace_spec.rb +69 -69
- data/spec/persistence_spec.rb +38 -37
- data/spec/reset_spec.rb +31 -31
- metadata +51 -3
data/lib/mudis_client.rb
CHANGED
|
@@ -1,65 +1,107 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "socket"
|
|
4
|
-
require "json"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "mudis_ipc_config"
|
|
6
|
+
|
|
7
|
+
# Thread-safe client for communicating with the MudisServer
|
|
8
|
+
# Automatically uses UNIX sockets on Linux/macOS and TCP on Windows
|
|
9
|
+
class MudisClient
|
|
10
|
+
include MudisIPCConfig
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Open a connection to the server (TCP or UNIX)
|
|
17
|
+
def open_connection
|
|
18
|
+
if MudisIPCConfig.use_tcp?
|
|
19
|
+
TCPSocket.new(TCP_HOST, TCP_PORT)
|
|
20
|
+
else
|
|
21
|
+
UNIXSocket.open(SOCKET_PATH)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Send a request to the MudisServer and return the response
|
|
26
|
+
# @param payload [Hash] The request payload
|
|
27
|
+
# @return [Object] The response value from the server
|
|
28
|
+
def request(payload) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
29
|
+
@mutex.synchronize do
|
|
30
|
+
sock = open_connection
|
|
31
|
+
sock.puts(JSON.dump(payload))
|
|
32
|
+
response = sock.gets
|
|
33
|
+
sock.close
|
|
34
|
+
|
|
35
|
+
return nil unless response
|
|
36
|
+
|
|
37
|
+
res = JSON.parse(response, symbolize_names: true)
|
|
38
|
+
raise res[:error] unless res[:ok]
|
|
39
|
+
|
|
40
|
+
res[:value]
|
|
41
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED
|
|
42
|
+
warn "[MudisClient] Cannot connect to MudisServer. Is it running?"
|
|
43
|
+
nil
|
|
44
|
+
rescue JSON::ParserError
|
|
45
|
+
warn "[MudisClient] Invalid JSON response from server"
|
|
46
|
+
nil
|
|
47
|
+
rescue IOError, SystemCallError => e
|
|
48
|
+
warn "[MudisClient] Connection error: #{e.message}"
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# --- Forwarded Mudis methods ---
|
|
54
|
+
|
|
55
|
+
# Read a value from the Mudis server
|
|
56
|
+
def read(key, namespace: nil)
|
|
57
|
+
command("read", key:, namespace:)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Write a value to the Mudis server
|
|
61
|
+
def write(key, value, expires_in: nil, namespace: nil)
|
|
62
|
+
command("write", key:, value:, ttl: expires_in, namespace:)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Delete a value from the Mudis server
|
|
66
|
+
def delete(key, namespace: nil)
|
|
67
|
+
command("delete", key:, namespace:)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Check if a key exists in the Mudis server
|
|
71
|
+
def exists?(key, namespace: nil)
|
|
72
|
+
command("exists", key:, namespace:)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Fetch a value, computing and storing it if not present
|
|
76
|
+
def fetch(key, expires_in: nil, namespace: nil)
|
|
77
|
+
val = read(key, namespace:)
|
|
78
|
+
return val if val
|
|
79
|
+
|
|
80
|
+
new_val = yield
|
|
81
|
+
write(key, new_val, expires_in:, namespace:)
|
|
82
|
+
new_val
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Retrieve metrics from the Mudis server
|
|
86
|
+
def metrics
|
|
87
|
+
command("metrics")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Reset metrics on the Mudis server
|
|
91
|
+
def reset_metrics!
|
|
92
|
+
command("reset_metrics")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Reset the Mudis server cache state
|
|
96
|
+
def reset!
|
|
97
|
+
command("reset")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# Helper to send a command with options
|
|
103
|
+
def command(cmd, **opts)
|
|
104
|
+
request({ cmd:, **opts })
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
data/lib/mudis_config.rb
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# MudisConfig holds all configuration values for Mudis,
|
|
4
|
-
# and provides defaults that can be overridden via Mudis.configure.
|
|
5
|
-
class MudisConfig
|
|
6
|
-
attr_accessor :serializer,
|
|
7
|
-
:compress,
|
|
8
|
-
:max_value_bytes,
|
|
9
|
-
:hard_memory_limit,
|
|
10
|
-
:max_bytes,
|
|
11
|
-
:buckets,
|
|
12
|
-
:max_ttl,
|
|
13
|
-
:default_ttl,
|
|
14
|
-
# Persistence settings
|
|
15
|
-
:persistence_enabled,
|
|
16
|
-
:persistence_path,
|
|
17
|
-
:persistence_format,
|
|
18
|
-
:persistence_safe_write
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@serializer = JSON # Default serialization strategy
|
|
22
|
-
@compress = false # Whether to compress values with Zlib
|
|
23
|
-
@max_value_bytes = nil # Max size per value (optional)
|
|
24
|
-
@hard_memory_limit = false # Enforce max_bytes as hard cap
|
|
25
|
-
@max_bytes = 1_073_741_824 # 1 GB default max cache size
|
|
26
|
-
@buckets = nil # use nil to signal fallback to ENV or default
|
|
27
|
-
@max_ttl = nil # Max TTL for cache entries (optional)
|
|
28
|
-
@default_ttl = nil # Default TTL for cache entries (optional)
|
|
29
|
-
# Persistence settings
|
|
30
|
-
@persistence_enabled = false # Whether persistence is enabled
|
|
31
|
-
@persistence_path =
|
|
32
|
-
@persistence_format = :json # Default persistence file format
|
|
33
|
-
@persistence_safe_write = true # Whether to use safe write for persistence
|
|
34
|
-
end
|
|
35
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# MudisConfig holds all configuration values for Mudis,
|
|
4
|
+
# and provides defaults that can be overridden via Mudis.configure.
|
|
5
|
+
class MudisConfig
|
|
6
|
+
attr_accessor :serializer,
|
|
7
|
+
:compress,
|
|
8
|
+
:max_value_bytes,
|
|
9
|
+
:hard_memory_limit,
|
|
10
|
+
:max_bytes,
|
|
11
|
+
:buckets,
|
|
12
|
+
:max_ttl,
|
|
13
|
+
:default_ttl,
|
|
14
|
+
# Persistence settings
|
|
15
|
+
:persistence_enabled,
|
|
16
|
+
:persistence_path,
|
|
17
|
+
:persistence_format,
|
|
18
|
+
:persistence_safe_write
|
|
19
|
+
|
|
20
|
+
def initialize # rubocop:disable Metrics/MethodLength
|
|
21
|
+
@serializer = JSON # Default serialization strategy
|
|
22
|
+
@compress = false # Whether to compress values with Zlib
|
|
23
|
+
@max_value_bytes = nil # Max size per value (optional)
|
|
24
|
+
@hard_memory_limit = false # Enforce max_bytes as hard cap
|
|
25
|
+
@max_bytes = 1_073_741_824 # 1 GB default max cache size
|
|
26
|
+
@buckets = nil # use nil to signal fallback to ENV or default
|
|
27
|
+
@max_ttl = nil # Max TTL for cache entries (optional)
|
|
28
|
+
@default_ttl = nil # Default TTL for cache entries (optional)
|
|
29
|
+
# Persistence settings
|
|
30
|
+
@persistence_enabled = false # Whether persistence is enabled
|
|
31
|
+
@persistence_path = "mudis_data" # Default path for persistence files
|
|
32
|
+
@persistence_format = :json # Default persistence file format
|
|
33
|
+
@persistence_safe_write = true # Whether to use safe write for persistence
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared configuration for IPC mode (server and client)
|
|
4
|
+
module MudisIPCConfig
|
|
5
|
+
SOCKET_PATH = "/tmp/mudis.sock"
|
|
6
|
+
TCP_HOST = "127.0.0.1"
|
|
7
|
+
TCP_PORT = 9876
|
|
8
|
+
|
|
9
|
+
# Check if TCP mode should be used (Windows or forced via ENV)
|
|
10
|
+
def self.use_tcp?
|
|
11
|
+
ENV["MUDIS_FORCE_TCP"] == "true" || Gem.win_platform?
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/mudis_proxy.rb
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Optional Mudis proxy layer for IPC mode.
|
|
4
|
-
#
|
|
5
|
-
# To enable:
|
|
6
|
-
# require "mudis_proxy"
|
|
7
|
-
#
|
|
8
|
-
# The proxy will forward calls to `$mudis` (an instance of MudisClient)
|
|
9
|
-
# if it is defined, otherwise fallback to standard in-process behaviour.
|
|
10
|
-
|
|
11
|
-
require_relative "mudis"
|
|
12
|
-
require_relative "mudis_client"
|
|
13
|
-
|
|
14
|
-
# Note that this file must be required after MudisServer
|
|
15
|
-
# has been loaded, otherwise the proxy will not be activated.
|
|
16
|
-
|
|
17
|
-
unless defined?(MudisClient)
|
|
18
|
-
warn "[MudisProxy] MudisClient not loaded: proxy not activated"
|
|
19
|
-
return
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
unless defined?($mudis) && $mudis # rubocop:disable Style/GlobalVars
|
|
23
|
-
warn "[MudisProxy] $mudis not set: proxy not activated"
|
|
24
|
-
return
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def
|
|
31
|
-
def
|
|
32
|
-
def
|
|
33
|
-
def
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Optional Mudis proxy layer for IPC mode.
|
|
4
|
+
#
|
|
5
|
+
# To enable:
|
|
6
|
+
# require "mudis_proxy"
|
|
7
|
+
#
|
|
8
|
+
# The proxy will forward calls to `$mudis` (an instance of MudisClient)
|
|
9
|
+
# if it is defined, otherwise fallback to standard in-process behaviour.
|
|
10
|
+
|
|
11
|
+
require_relative "mudis"
|
|
12
|
+
require_relative "mudis_client"
|
|
13
|
+
|
|
14
|
+
# Note that this file must be required after MudisServer
|
|
15
|
+
# has been loaded, otherwise the proxy will not be activated.
|
|
16
|
+
|
|
17
|
+
unless defined?(MudisClient)
|
|
18
|
+
warn "[MudisProxy] MudisClient not loaded: proxy not activated"
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
unless defined?($mudis) && $mudis # rubocop:disable Style/GlobalVars
|
|
23
|
+
warn "[MudisProxy] $mudis not set: proxy not activated"
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# --- Proxy method forwarding ---
|
|
28
|
+
|
|
29
|
+
class << Mudis
|
|
30
|
+
def read(*a, **k) = $mudis.read(*a, **k) # rubocop:disable Naming/MethodParameterName,Style/GlobalVars
|
|
31
|
+
def write(*a, **k) = $mudis.write(*a, **k) # rubocop:disable Naming/MethodParameterName,Style/GlobalVars
|
|
32
|
+
def delete(*a, **k) = $mudis.delete(*a, **k) # rubocop:disable Naming/MethodParameterName,Style/GlobalVars
|
|
33
|
+
def fetch(*a, **k, &b) = $mudis.fetch(*a, **k, &b) # rubocop:disable Naming/MethodParameterName,Style/GlobalVars
|
|
34
|
+
def metrics = $mudis.metrics # rubocop:disable Style/GlobalVars
|
|
35
|
+
def reset_metrics! = $mudis.reset_metrics! # rubocop:disable Style/GlobalVars
|
|
36
|
+
def reset! = $mudis.reset! # rubocop:disable Style/GlobalVars
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
warn "[MudisProxy] Proxy activated: forwarding calls to $mudis"
|
data/lib/mudis_server.rb
CHANGED
|
@@ -1,81 +1,100 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "socket"
|
|
4
|
-
require "json"
|
|
5
|
-
require_relative "mudis"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def self.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
end
|
|
81
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "mudis"
|
|
6
|
+
require_relative "mudis_ipc_config"
|
|
7
|
+
|
|
8
|
+
# Socket server for handling Mudis operations via IPC mode
|
|
9
|
+
# Automatically uses UNIX sockets on Linux/macOS and TCP on Windows
|
|
10
|
+
class MudisServer
|
|
11
|
+
include MudisIPCConfig
|
|
12
|
+
|
|
13
|
+
# Define command handlers mapping
|
|
14
|
+
# Each command maps to a lambda that takes a request hash and performs the corresponding Mudis operation.
|
|
15
|
+
COMMANDS = {
|
|
16
|
+
"read" => ->(r) { Mudis.read(r[:key], namespace: r[:namespace]) },
|
|
17
|
+
"write" => ->(r) { Mudis.write(r[:key], r[:value], expires_in: r[:ttl], namespace: r[:namespace]) },
|
|
18
|
+
"delete" => ->(r) { Mudis.delete(r[:key], namespace: r[:namespace]) },
|
|
19
|
+
"exists" => ->(r) { Mudis.exists?(r[:key], namespace: r[:namespace]) },
|
|
20
|
+
"fetch" => ->(r) { Mudis.fetch(r[:key], expires_in: r[:ttl], namespace: r[:namespace]) { r[:fallback] } },
|
|
21
|
+
"metrics" => ->(_) { Mudis.metrics },
|
|
22
|
+
"reset_metrics" => ->(_) { Mudis.reset_metrics! },
|
|
23
|
+
"reset" => ->(_) { Mudis.reset! }
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# Start the MudisServer
|
|
27
|
+
# Automatically selects TCP on Windows, UNIX sockets elsewhere
|
|
28
|
+
# This will run in a separate thread and handle incoming client connections.
|
|
29
|
+
def self.start!
|
|
30
|
+
if MudisIPCConfig.use_tcp?
|
|
31
|
+
start_tcp_server!
|
|
32
|
+
else
|
|
33
|
+
start_unix_server!
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Start TCP server (for Windows or development)
|
|
38
|
+
def self.start_tcp_server!
|
|
39
|
+
warn "[MudisServer] Using TCP mode - recommended for development only"
|
|
40
|
+
server = TCPServer.new(TCP_HOST, TCP_PORT)
|
|
41
|
+
puts "[MudisServer] Listening on TCP #{TCP_HOST}:#{TCP_PORT}"
|
|
42
|
+
accept_connections(server)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Start UNIX socket server (production mode for Linux/macOS)
|
|
46
|
+
def self.start_unix_server! # rubocop:disable Metrics/MethodLength
|
|
47
|
+
File.unlink(SOCKET_PATH) if File.exist?(SOCKET_PATH)
|
|
48
|
+
server = UNIXServer.new(SOCKET_PATH)
|
|
49
|
+
server.listen(128)
|
|
50
|
+
puts "[MudisServer] Listening on UNIX socket #{SOCKET_PATH}"
|
|
51
|
+
accept_connections(server)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Accept connections in a loop (works for both TCP and UNIX)
|
|
55
|
+
def self.accept_connections(server)
|
|
56
|
+
Thread.new do
|
|
57
|
+
loop do
|
|
58
|
+
client = server.accept
|
|
59
|
+
Thread.new(client) do |sock|
|
|
60
|
+
handle_client(sock)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Handle a single client connection
|
|
67
|
+
# Reads the request, processes it, and sends back the response
|
|
68
|
+
# @param socket [Socket] The client socket (TCP or UNIX)
|
|
69
|
+
# @return [void]
|
|
70
|
+
def self.handle_client(socket)
|
|
71
|
+
request = JSON.parse(socket.gets, symbolize_names: true)
|
|
72
|
+
return unless request
|
|
73
|
+
|
|
74
|
+
response = process_request(request)
|
|
75
|
+
write_response(socket, ok: true, value: response)
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
write_response(socket, ok: false, error: e.message)
|
|
78
|
+
ensure
|
|
79
|
+
socket.close
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Process a request hash and return the result
|
|
83
|
+
# Raises an error if the command is unknown
|
|
84
|
+
# @param req [Hash] The request hash containing :cmd and other parameters
|
|
85
|
+
# @return [Object] The result of the command execution
|
|
86
|
+
def self.process_request(req)
|
|
87
|
+
handler = COMMANDS[req[:cmd]]
|
|
88
|
+
raise "Unknown command: #{req[:cmd]}" unless handler
|
|
89
|
+
|
|
90
|
+
handler.call(req)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Write a response to the client socket
|
|
94
|
+
# @param socket [Socket] The client socket
|
|
95
|
+
# @param payload [Hash] The response payload
|
|
96
|
+
# @return [void]
|
|
97
|
+
def self.write_response(socket, payload)
|
|
98
|
+
socket.puts(JSON.dump(payload))
|
|
99
|
+
end
|
|
100
|
+
end
|
data/sig/mudis.rbs
CHANGED
|
@@ -1,56 +1,62 @@
|
|
|
1
|
-
class Mudis
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
attr_accessor
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def self.
|
|
28
|
-
def self.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def self.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def self.
|
|
47
|
-
def self.
|
|
48
|
-
def self.
|
|
49
|
-
def self.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def self.
|
|
55
|
-
def self.
|
|
56
|
-
|
|
1
|
+
class Mudis
|
|
2
|
+
extend LRU
|
|
3
|
+
extend Persistence
|
|
4
|
+
extend Metrics
|
|
5
|
+
extend Namespace
|
|
6
|
+
extend Expiry
|
|
7
|
+
|
|
8
|
+
# Configuration
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor serializer : Object
|
|
11
|
+
attr_accessor compress : bool
|
|
12
|
+
attr_accessor hard_memory_limit : bool
|
|
13
|
+
attr_reader max_bytes : Integer
|
|
14
|
+
attr_reader max_value_bytes : Integer?
|
|
15
|
+
attr_accessor max_ttl: Integer?
|
|
16
|
+
attr_accessor default_ttl: Integer?
|
|
17
|
+
|
|
18
|
+
def configure: () { (config: MudisConfig) -> void } -> void
|
|
19
|
+
def config: () -> MudisConfig
|
|
20
|
+
def apply_config!: () -> void
|
|
21
|
+
def validate_config!: () -> void
|
|
22
|
+
|
|
23
|
+
def buckets: () -> Integer
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Lifecycle
|
|
27
|
+
def self.start_expiry_thread: (?interval: Integer) -> void
|
|
28
|
+
def self.stop_expiry_thread: () -> void
|
|
29
|
+
|
|
30
|
+
# Core operations
|
|
31
|
+
def self.write: (String, untyped, ?expires_in: Integer, ?namespace: String) -> void
|
|
32
|
+
def self.read: (String, ?namespace: String) -> untyped?
|
|
33
|
+
def self.update: (String, ?namespace: String) { (untyped) -> untyped } -> void
|
|
34
|
+
def self.delete: (String, ?namespace: String) -> void
|
|
35
|
+
def self.exists?: (String, ?namespace: String) -> bool
|
|
36
|
+
|
|
37
|
+
# DSL & Helpers
|
|
38
|
+
def self.fetch: (
|
|
39
|
+
String,
|
|
40
|
+
?expires_in: Integer,
|
|
41
|
+
?force: bool,
|
|
42
|
+
?namespace: String
|
|
43
|
+
) { () -> untyped } -> untyped
|
|
44
|
+
|
|
45
|
+
def self.clear: (String, ?namespace: String) -> void
|
|
46
|
+
def self.replace: (String, untyped, ?expires_in: Integer, ?namespace: String) -> void
|
|
47
|
+
def self.inspect: (String, ?namespace: String) -> Hash[Symbol, untyped]?
|
|
48
|
+
def self.keys: (?namespace: String) -> Array[String]
|
|
49
|
+
def self.clear_namespace: (?namespace: String) -> void
|
|
50
|
+
|
|
51
|
+
# Introspection & management
|
|
52
|
+
def self.metrics: () -> Hash[Symbol, untyped]
|
|
53
|
+
def self.cleanup_expired!: () -> void
|
|
54
|
+
def self.all_keys: () -> Array[String]
|
|
55
|
+
def self.current_memory_bytes: () -> Integer
|
|
56
|
+
def self.max_memory_bytes: () -> Integer
|
|
57
|
+
def self.least_touched: (?Integer) -> Array[[String, Integer]]
|
|
58
|
+
|
|
59
|
+
# State reset
|
|
60
|
+
def self.reset!: () -> void
|
|
61
|
+
def self.reset_metrics!: () -> void
|
|
62
|
+
end
|