rubcask 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/.standard.yml +3 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +14 -0
- data/benchmark/benchmark_io.rb +49 -0
- data/benchmark/benchmark_server.rb +10 -0
- data/benchmark/benchmark_server_pipeline.rb +24 -0
- data/benchmark/benchmark_worker.rb +46 -0
- data/benchmark/op_times.rb +32 -0
- data/benchmark/profile.rb +15 -0
- data/benchmark/server_benchmark_helper.rb +138 -0
- data/example/server_runner.rb +15 -0
- data/lib/rubcask/bytes.rb +11 -0
- data/lib/rubcask/concurrency/fake_atomic_fixnum.rb +34 -0
- data/lib/rubcask/concurrency/fake_lock.rb +41 -0
- data/lib/rubcask/concurrency/fake_monitor_mixin.rb +21 -0
- data/lib/rubcask/config.rb +55 -0
- data/lib/rubcask/data_entry.rb +9 -0
- data/lib/rubcask/data_file.rb +91 -0
- data/lib/rubcask/directory.rb +437 -0
- data/lib/rubcask/expirable_entry.rb +9 -0
- data/lib/rubcask/hint_entry.rb +9 -0
- data/lib/rubcask/hint_file.rb +56 -0
- data/lib/rubcask/hinted_file.rb +148 -0
- data/lib/rubcask/keydir_entry.rb +9 -0
- data/lib/rubcask/merge_directory.rb +75 -0
- data/lib/rubcask/protocol.rb +74 -0
- data/lib/rubcask/server/abstract_server.rb +113 -0
- data/lib/rubcask/server/async.rb +78 -0
- data/lib/rubcask/server/client.rb +131 -0
- data/lib/rubcask/server/config.rb +31 -0
- data/lib/rubcask/server/pipeline.rb +49 -0
- data/lib/rubcask/server/runner/config.rb +43 -0
- data/lib/rubcask/server/runner.rb +107 -0
- data/lib/rubcask/server/threaded.rb +171 -0
- data/lib/rubcask/task/clean_directory.rb +19 -0
- data/lib/rubcask/tombstone.rb +40 -0
- data/lib/rubcask/version.rb +5 -0
- data/lib/rubcask/worker/direct_worker.rb +23 -0
- data/lib/rubcask/worker/factory.rb +42 -0
- data/lib/rubcask/worker/ractor_worker.rb +40 -0
- data/lib/rubcask/worker/thread_worker.rb +40 -0
- data/lib/rubcask.rb +19 -0
- metadata +102 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
module Server
|
5
|
+
class Runner
|
6
|
+
# @!attribute merge_interval
|
7
|
+
# How frequent in seconds should merge operation by run.
|
8
|
+
#
|
9
|
+
# Default: 3600
|
10
|
+
# @return [Integer, null]
|
11
|
+
# @!attribute server_type
|
12
|
+
# Which type of server should be run.
|
13
|
+
#
|
14
|
+
# Use threaded if you are not on MRI. If you are on mri and can install `async-io` use :async.
|
15
|
+
#
|
16
|
+
# Default: :threaded
|
17
|
+
# @return [:threaded, :async]
|
18
|
+
# @!attribute directory_path
|
19
|
+
# Path of the directory in which the data is stored.
|
20
|
+
#
|
21
|
+
# Default: no default value, user has to set it manually.
|
22
|
+
# @return [String]
|
23
|
+
# Server runner config
|
24
|
+
Config = Struct.new(:merge_interval, :server_type, :directory_path) do
|
25
|
+
# Overide fields with the block
|
26
|
+
# @yieldparam [self] config
|
27
|
+
def initialize
|
28
|
+
self.server_type = :threaded
|
29
|
+
self.merge_interval = 3_600
|
30
|
+
self.directory_path = nil
|
31
|
+
|
32
|
+
yield(self) if block_given?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Calls new and freezes the config
|
36
|
+
# @see .initialize
|
37
|
+
def self.configure(&block)
|
38
|
+
new(&block).freeze
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
4
|
+
require_relative "./config"
|
5
|
+
require_relative "./runner/config"
|
6
|
+
require_relative "../config"
|
7
|
+
|
8
|
+
module Rubcask
|
9
|
+
module Server
|
10
|
+
# ServerRunner runs a server alongside merge worker
|
11
|
+
# It supports graceful shutdown with Ctrl-c
|
12
|
+
class Runner
|
13
|
+
def initialize(
|
14
|
+
server_config: Rubcask::Server::Config.new,
|
15
|
+
dir_config: Rubcask::Config::DEFAULT_SERVER_CONFIG,
|
16
|
+
runner_config: Rubcask::Server::Runner::Config.new
|
17
|
+
)
|
18
|
+
@dir = Rubcask::Directory.new(
|
19
|
+
runner_config.directory_path,
|
20
|
+
config: dir_config
|
21
|
+
)
|
22
|
+
@server = new_server(runner_config.server_type, server_config)
|
23
|
+
@merge_worker = if runner_config.merge_interval && runner_config.merge_interval > 0
|
24
|
+
Concurrent::TimerTask.new(
|
25
|
+
execution_interval: runner_config.merge_interval
|
26
|
+
) do
|
27
|
+
merge_dir
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Starts the runner.
|
33
|
+
# @note It blocks the current thread
|
34
|
+
def start
|
35
|
+
install_trap!
|
36
|
+
@merge_worker.execute
|
37
|
+
@server.start
|
38
|
+
end
|
39
|
+
|
40
|
+
# Stops the server
|
41
|
+
def close
|
42
|
+
close_server
|
43
|
+
mutex_close
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def close_server
|
49
|
+
puts "Shutting down server!"
|
50
|
+
begin
|
51
|
+
@server.shutdown
|
52
|
+
rescue
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def mutex_close
|
57
|
+
if @merge_worker
|
58
|
+
puts "Stoping merge worker"
|
59
|
+
@merge_worker.shutdown
|
60
|
+
if @merge_worker.wait_for_termination(60)
|
61
|
+
puts "Closed merge worker"
|
62
|
+
else
|
63
|
+
puts "Failed to close worker"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
puts "Closing Dir!"
|
68
|
+
begin
|
69
|
+
@dir.close
|
70
|
+
rescue
|
71
|
+
end
|
72
|
+
puts "Closed dir"
|
73
|
+
end
|
74
|
+
|
75
|
+
def install_trap!
|
76
|
+
Signal.trap("INT") do
|
77
|
+
puts ""
|
78
|
+
# Close server in the same thread
|
79
|
+
close_server
|
80
|
+
|
81
|
+
# Other things might needs mutex so a new thread is needed
|
82
|
+
Thread.new do
|
83
|
+
mutex_close
|
84
|
+
end.join
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def new_server(type, config)
|
89
|
+
if type == :threaded
|
90
|
+
require_relative "threaded"
|
91
|
+
Rubcask::Server::Threaded.new(@dir, config: config)
|
92
|
+
elsif type == :async
|
93
|
+
require_relative "async"
|
94
|
+
Rubcask::Server::Async.new(@dir, config: config)
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Unknown server typ #{type}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def merge_dir
|
101
|
+
@server.dir.merge
|
102
|
+
rescue => e
|
103
|
+
puts e
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "socket"
|
5
|
+
require "io/wait"
|
6
|
+
require "stringio"
|
7
|
+
|
8
|
+
require_relative "../bytes"
|
9
|
+
require_relative "../protocol"
|
10
|
+
require_relative "config"
|
11
|
+
require_relative "abstract_server"
|
12
|
+
|
13
|
+
module Rubcask
|
14
|
+
module Server
|
15
|
+
# Thread-based server supporting Rubcask protocol
|
16
|
+
# If you are running on CRuby you should consider using Server::Async as it is generally more performant
|
17
|
+
class Threaded < AbstractServer
|
18
|
+
include Protocol
|
19
|
+
|
20
|
+
def initialize(dir, config: Server::Config.new)
|
21
|
+
@dir = dir
|
22
|
+
@config = config
|
23
|
+
@hostname = config.hostname
|
24
|
+
@port = config.port
|
25
|
+
@logger = Logger.new($stdout)
|
26
|
+
@logger.level = Logger::INFO
|
27
|
+
@threads = ThreadGroup.new
|
28
|
+
@connected = false
|
29
|
+
@status = :stopped
|
30
|
+
@listeners = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates sockets
|
34
|
+
# @return [self]
|
35
|
+
def connect
|
36
|
+
return if @connected
|
37
|
+
@connected = true
|
38
|
+
@listeners = Socket.tcp_server_sockets(@hostname, @port)
|
39
|
+
if @config.keepalive
|
40
|
+
@listeners.each do |s|
|
41
|
+
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@listeners.each do |s|
|
45
|
+
address = s.connect_address
|
46
|
+
logger.info "Listening on #{address.ip_address}:#{address.ip_port}"
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Starts the server
|
52
|
+
# @note It blocks the current thread
|
53
|
+
def start
|
54
|
+
connect
|
55
|
+
|
56
|
+
setup_shutdown_pipe
|
57
|
+
|
58
|
+
@status = :running
|
59
|
+
|
60
|
+
Thread.handle_interrupt(Exception => :never) do
|
61
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
62
|
+
accept_loop
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
cleanup_shutdown_pipe
|
66
|
+
@status = :shutdown
|
67
|
+
cleanup_listeners
|
68
|
+
@threads.list.each(&:kill)
|
69
|
+
@status = :stopped
|
70
|
+
@connected = false
|
71
|
+
logger.info "Closed server"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Shuts down the server
|
76
|
+
# @note You probably want to use it in a signal trap
|
77
|
+
def shutdown
|
78
|
+
if @status == :running
|
79
|
+
@status = :shutdown
|
80
|
+
end
|
81
|
+
@shutdown_pipe[1].write_nonblock("\0")
|
82
|
+
@shutdown_pipe[1].close
|
83
|
+
end
|
84
|
+
|
85
|
+
# Prepares an IO pipe that is used in shutdown process
|
86
|
+
# Call if you need to shutdown the server from a different thread
|
87
|
+
# @return [self]
|
88
|
+
def setup_shutdown_pipe
|
89
|
+
@shutdown_pipe ||= IO.pipe
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
attr_reader :logger
|
96
|
+
|
97
|
+
def cleanup_listeners
|
98
|
+
@listeners.each do |listener|
|
99
|
+
listener.shutdown
|
100
|
+
rescue Errno::ENOTCONN
|
101
|
+
listener.close
|
102
|
+
else
|
103
|
+
listener.close
|
104
|
+
end
|
105
|
+
@listeners.clear
|
106
|
+
end
|
107
|
+
|
108
|
+
def cleanup_shutdown_pipe
|
109
|
+
pipe = @shutdown_pipe
|
110
|
+
pipe&.each(&:close)
|
111
|
+
@shutdown_pipe = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def accept_loop
|
115
|
+
shutdown_read = @shutdown_pipe[0]
|
116
|
+
while @status == :running
|
117
|
+
begin
|
118
|
+
fds = IO.select([shutdown_read, *@listeners])[0]
|
119
|
+
if fds.include?(shutdown_read)
|
120
|
+
consume_pipe(shutdown_read)
|
121
|
+
break
|
122
|
+
end
|
123
|
+
fds.each do |listener|
|
124
|
+
client = accept_client(listener)
|
125
|
+
next unless client
|
126
|
+
@threads.add(
|
127
|
+
Thread.start(client) { |conn| client_block(conn) }
|
128
|
+
)
|
129
|
+
end
|
130
|
+
rescue Errno::EBADF, Errno::ENOTSOCK, IOError
|
131
|
+
# Possible if socket was manually shut down
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def accept_client(listener)
|
137
|
+
sock = listener.accept_nonblock(exception: false)
|
138
|
+
return nil if sock == :wait_readable
|
139
|
+
sock[0]
|
140
|
+
rescue
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
def consume_pipe(pipe)
|
145
|
+
buf = +""
|
146
|
+
while String === pipe.read_nonblock([pipe.nread, 8].max, buf, exception: false)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def client_block(conn)
|
151
|
+
conn.binmode
|
152
|
+
with_interrupt_handle(conn) do |io|
|
153
|
+
client_loop(io)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def with_interrupt_handle(conn)
|
158
|
+
Thread.handle_interrupt(Exception => :never) do
|
159
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
160
|
+
yield conn
|
161
|
+
end
|
162
|
+
ensure
|
163
|
+
begin
|
164
|
+
conn.close
|
165
|
+
rescue # It sometimes failes on jruby
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
module Task
|
5
|
+
# Removes all files marked as executable in the directory
|
6
|
+
class CleanDirectory
|
7
|
+
def initialize(directory)
|
8
|
+
@directory = directory
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
Dir.glob(["*.data", "*.hint"].map! { |ext| File.join(@directory, ext) }).each do |file|
|
13
|
+
next unless File.executable?(file)
|
14
|
+
File.delete(file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
# Tombstone represents deleted value
|
5
|
+
|
6
|
+
# The prev_file_id is
|
7
|
+
# stored to support merge of subset of directory files, that is currently not implemented
|
8
|
+
module Tombstone
|
9
|
+
extend self
|
10
|
+
|
11
|
+
PREFIX = "TOMBSTONE".b
|
12
|
+
PREFIX_SIZE = PREFIX.bytesize
|
13
|
+
FILE_ID_FORMAT = "N"
|
14
|
+
FULL_BYTE_SIZE = PREFIX_SIZE + 4
|
15
|
+
|
16
|
+
# @param [String] value value to check
|
17
|
+
# @return true if value is a tombstone
|
18
|
+
# @return false otherwise
|
19
|
+
def is_tombstone?(value)
|
20
|
+
value.bytesize <= FULL_BYTE_SIZE && value.start_with?(PREFIX)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates a new tombstone value
|
24
|
+
# @param [Integer] current_file_id Id of the active file
|
25
|
+
# @param [Integer] prev_file_id Id of the file where the record is currently located
|
26
|
+
# @return [String]
|
27
|
+
def new_tombstone(current_file_id, prev_file_id)
|
28
|
+
return PREFIX if prev_file_id == current_file_id
|
29
|
+
PREFIX.b << [prev_file_id].pack(FILE_ID_FORMAT)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets file id from tombstone value
|
33
|
+
# @param [String] value Tombstone value
|
34
|
+
# @return [Integer, nil]
|
35
|
+
def tombstone_file_id(value)
|
36
|
+
return nil if value.bytesize < FULL_BYTE_SIZE
|
37
|
+
value.byteslice(PREFIX_SIZE, 4).unpack1(FILE_ID_FORMAT)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Rubcask
|
6
|
+
module Worker
|
7
|
+
# Worker implementation that executes the job in the current thread
|
8
|
+
class DirectWorker
|
9
|
+
def initialize
|
10
|
+
@logger = Logger.new($stdout)
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(task)
|
14
|
+
task.call
|
15
|
+
rescue => e
|
16
|
+
@logger.warn("Error while executing task #{e}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "direct_worker"
|
4
|
+
require_relative "ractor_worker"
|
5
|
+
require_relative "thread_worker"
|
6
|
+
|
7
|
+
module Rubcask
|
8
|
+
module Worker
|
9
|
+
module Factory
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Returns a new worker of provided type
|
13
|
+
# @param [:direct, :thread, :reactor] type Type of worker to create
|
14
|
+
# @return [Worker]
|
15
|
+
# @raise [ArgumentError] if unknown type
|
16
|
+
def new_worker(type)
|
17
|
+
case type
|
18
|
+
when :direct
|
19
|
+
DirectWorker.new
|
20
|
+
when :thread
|
21
|
+
ThreadWorker.new
|
22
|
+
when :ractor
|
23
|
+
RactorWorker.new
|
24
|
+
else
|
25
|
+
raise ArgumentError, "#{type} is not a known worker type"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Class for documentation purposes
|
30
|
+
class Worker
|
31
|
+
# @param [#call] arg job to execute
|
32
|
+
# @return [void]
|
33
|
+
def push(arg)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [void]
|
37
|
+
def close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module Rubcask
|
7
|
+
module Worker
|
8
|
+
# Worker implementation that delegates work to a dedicated ractor
|
9
|
+
class RactorWorker
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegator :@ractor, :send, :push
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@ractor = new_ractor
|
16
|
+
@logger = Logger.new($stdout)
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
push(nil)
|
21
|
+
@ractor.take
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def new_ractor
|
27
|
+
Ractor.new(@logger) do |logger|
|
28
|
+
while (value = Ractor.receive)
|
29
|
+
begin
|
30
|
+
value.call
|
31
|
+
rescue => e
|
32
|
+
logger.warn("Error while executing task " + e)
|
33
|
+
end
|
34
|
+
Ractor.yield(nil)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Rubcask
|
6
|
+
module Worker
|
7
|
+
# Worker implementation that delegates work to a dedicated thread
|
8
|
+
class ThreadWorker
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegator :@queue, :push
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@queue = Queue.new
|
15
|
+
@logger = Logger.new($stdout)
|
16
|
+
@thread = new_thread
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
@queue.close
|
21
|
+
@thread.join
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def new_thread
|
28
|
+
Thread.new(@queue) do |queue|
|
29
|
+
while (el = queue.pop)
|
30
|
+
begin
|
31
|
+
el.call
|
32
|
+
rescue => e
|
33
|
+
@logger.warn(e)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/rubcask.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rubcask/version"
|
4
|
+
require_relative "rubcask/directory"
|
5
|
+
require_relative "rubcask/bytes"
|
6
|
+
|
7
|
+
module Rubcask
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
class LoadError < Error; end
|
11
|
+
|
12
|
+
class ChecksumError < LoadError; end
|
13
|
+
|
14
|
+
class MergeAlreadyInProgressError < Error; end
|
15
|
+
|
16
|
+
class ConfigValidationError < Error; end
|
17
|
+
|
18
|
+
NO_EXPIRE_TIMESTAMP = 0
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubcask
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marcin Henryk Bartkowiak
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-28 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: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
description: Bitcask-like Key/Value storege library with a TCP server included
|
28
|
+
email:
|
29
|
+
- mhbartkowiak@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".standard.yml"
|
35
|
+
- Gemfile
|
36
|
+
- Gemfile.lock
|
37
|
+
- LICENSE.txt
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- benchmark/benchmark_io.rb
|
41
|
+
- benchmark/benchmark_server.rb
|
42
|
+
- benchmark/benchmark_server_pipeline.rb
|
43
|
+
- benchmark/benchmark_worker.rb
|
44
|
+
- benchmark/op_times.rb
|
45
|
+
- benchmark/profile.rb
|
46
|
+
- benchmark/server_benchmark_helper.rb
|
47
|
+
- example/server_runner.rb
|
48
|
+
- lib/rubcask.rb
|
49
|
+
- lib/rubcask/bytes.rb
|
50
|
+
- lib/rubcask/concurrency/fake_atomic_fixnum.rb
|
51
|
+
- lib/rubcask/concurrency/fake_lock.rb
|
52
|
+
- lib/rubcask/concurrency/fake_monitor_mixin.rb
|
53
|
+
- lib/rubcask/config.rb
|
54
|
+
- lib/rubcask/data_entry.rb
|
55
|
+
- lib/rubcask/data_file.rb
|
56
|
+
- lib/rubcask/directory.rb
|
57
|
+
- lib/rubcask/expirable_entry.rb
|
58
|
+
- lib/rubcask/hint_entry.rb
|
59
|
+
- lib/rubcask/hint_file.rb
|
60
|
+
- lib/rubcask/hinted_file.rb
|
61
|
+
- lib/rubcask/keydir_entry.rb
|
62
|
+
- lib/rubcask/merge_directory.rb
|
63
|
+
- lib/rubcask/protocol.rb
|
64
|
+
- lib/rubcask/server/abstract_server.rb
|
65
|
+
- lib/rubcask/server/async.rb
|
66
|
+
- lib/rubcask/server/client.rb
|
67
|
+
- lib/rubcask/server/config.rb
|
68
|
+
- lib/rubcask/server/pipeline.rb
|
69
|
+
- lib/rubcask/server/runner.rb
|
70
|
+
- lib/rubcask/server/runner/config.rb
|
71
|
+
- lib/rubcask/server/threaded.rb
|
72
|
+
- lib/rubcask/task/clean_directory.rb
|
73
|
+
- lib/rubcask/tombstone.rb
|
74
|
+
- lib/rubcask/version.rb
|
75
|
+
- lib/rubcask/worker/direct_worker.rb
|
76
|
+
- lib/rubcask/worker/factory.rb
|
77
|
+
- lib/rubcask/worker/ractor_worker.rb
|
78
|
+
- lib/rubcask/worker/thread_worker.rb
|
79
|
+
homepage: https://github.com/mhib/rubcask
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 2.7.0
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.4.1
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Key/Value storage library
|
102
|
+
test_files: []
|