Class: Rubcask::Directory

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/rubcask/directory.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dir, config: Config.new) ⇒ Directory

Returns a new instance of Directory.

Parameters:

  • dir (String)

    Path to the directory where data is stored

  • config (Config) (defaults to: Config.new)

    Config of the directory



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/rubcask/directory.rb', line 61

def initialize(dir, config: Config.new)
  @dir = dir
  @config = check_config(config)

  max_id = 0
  files = dir_data_files
  @files = files.each_with_object({}) do |file, hash|
    next if File.executable?(file)
    if file.equal?(files.last) && File.size(file) < config.max_file_size
      hinted_file = open_write_file(file)
      @active = hinted_file
    else
      hinted_file = open_read_only_file(file)
    end

    id = hinted_file.id

    hash[id] = hinted_file
    max_id = id # dir_data_files returns an already sorted collection
  end
  @max_id = (config.threadsafe ? Concurrent::AtomicFixnum : Concurrency::FakeAtomicFixnum).new(max_id)
  @lock = config.threadsafe ? Concurrent::ReentrantReadWriteLock.new : Concurrency::FakeLock.new
  @worker = Worker::Factory.new_worker(@config.worker)

  @logger = Logger.new($stdin)
  @logger.level = Logger::INFO

  @merge_mutex = Thread::Mutex.new

  load_keydir!
  create_new_file! unless @active
end

Class Method Details

.with_directory(dir, config: Config.new) {|directory| ... } ⇒ void

This method returns an undefined value.

yields directory to the block and closes it after the block is terminated

Yield Parameters:

See Also:



38
39
40
41
42
43
44
45
# File 'lib/rubcask/directory.rb', line 38

def self.with_directory(dir, config: Config.new)
  directory = new(dir, config: config)
  begin
    yield directory
  ensure
    directory.close
  end
end

Instance Method Details

#[](key) ⇒ String?

Note:

key is always treated as byte array, encoding is ignored

Gets value associated with the key

Parameters:

  • key (String)

Returns:

  • (String)

    value associatiod with the key

  • (nil)

    If no value associated with the key



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rubcask/directory.rb', line 123

def [](key)
  key = normalize_key(key)
  entry = nil
  data_file = nil
  @lock.with_read_lock do
    entry = @keydir[key]
    return nil unless entry

    if entry.expired?
      return nil
    end

    data_file = @files[entry.file_id]

    # We are using pread so there's no need to synchronize the read
    value = data_file.pread(entry.value_pos, entry.value_size).value
    return nil if Tombstone.is_tombstone?(value)
    return value
  end
end

#[]=(key, value) ⇒ String

Note:

key is always treated as byte array, encoding is ignored

Set value associated with given key.

Parameters:

  • key (String)
  • value (String)

Returns:

  • (String)

    the value provided by the user



99
100
101
102
# File 'lib/rubcask/directory.rb', line 99

def []=(key, value)
  put(key, value, NO_EXPIRE_TIMESTAMP)
  value # rubocop:disable Lint/Void
end

#clear_filesObject

Removes files that are not needed after the merge



246
247
248
# File 'lib/rubcask/directory.rb', line 246

def clear_files
  worker.push(Rubcask::Task::CleanDirectory.new(@dir))
end

#closeObject

Closes all the files and the worker



181
182
183
184
185
186
187
188
189
# File 'lib/rubcask/directory.rb', line 181

def close
  @lock.with_write_lock do
    @files.each_value(&:close)
    if active.write_pos == 0
      File.delete(active.path)
    end
  end
  worker.close
end

#delete(key) ⇒ Object

Note:

key is always treated as byte array, encoding is ignored

Remove entry associated with the key.

Parameters:

  • key (String)

Returns:

  • false if the existing value does not exist

  • true if the delete was succesfull



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/rubcask/directory.rb', line 149

def delete(key)
  key = normalize_key(key)
  @lock.with_write_lock do
    prev_val = @keydir[key]
    if prev_val.nil?
      return false
    end
    if prev_val.expired?
      @keydir.delete(key)
      return false
    end
    do_delete(key, prev_val.file_id)
    true
  end
end

#each {|key, value| ... } ⇒ Enumerator<Array(String, String)>

Note:

This method blocks writes for the entire iteration

Note:

Keys might be in any order

Returns if no block given.

Yield Parameters:

  • key (String)
  • value (String)

Returns:

  • (Enumerator<Array(String, String)>)

    if no block given



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/rubcask/directory.rb', line 196

def each
  return to_enum(__method__) unless block_given?

  @lock.with_read_lock do
    @keydir.each do |key, entry|
      file = @files[entry.file_id]
      value = file[entry.value_pos, entry.value_size].value
      next if Tombstone.is_tombstone?(value)
      yield [key, value]
    end
  end
end

#each_key {|key| ... } ⇒ Enumerator<String>

Note:

It might include deleted keys

Note:

Keys might be in any order

Note:

This method blocks writes for the entire iteration

Returns if no block given.

Yield Parameters:

  • key (String)

Returns:

  • (Enumerator<String>)

    if no block given



214
215
216
217
218
219
220
# File 'lib/rubcask/directory.rb', line 214

def each_key(&block)
  return to_enum(__method__) unless block

  @lock.with_read_lock do
    @keydir.each_key(&block)
  end
end

#generate_missing_hint_files!Object

Generate hint files for data files that do not have hint files



223
224
225
226
227
228
229
230
231
232
# File 'lib/rubcask/directory.rb', line 223

def generate_missing_hint_files!
  @lock.with_read_lock do
    @files.each_value do |data_file|
      next if data_file.has_hint_file? && !data_file.dirty?
      data_file.synchronize do
        data_file.save_hint_file
      end
    end
  end
end

#key_countInteger

Note:

It might count some deleted keys

Returns number of keys in the store

Returns:

  • (Integer)


253
254
255
256
257
# File 'lib/rubcask/directory.rb', line 253

def key_count
  @lock.with_read_lock do
    @keydir.size
  end
end

#keysArray<String>

Note:

It might include deleted keys

Note:

Keys might be in any order

Returns array of keys in store

Returns:

  • (Array<String>)


263
264
265
266
267
# File 'lib/rubcask/directory.rb', line 263

def keys
  @lock.with_read_lock do
    @keydir.keys
  end
end

#mergeObject

Starts the merge operation.

Raises:

  • (MergeAlreadyInProgress)

    if another merge operation is in progress



167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/rubcask/directory.rb', line 167

def merge
  unless @merge_mutex.try_lock
    raise MergeAlreadyInProgressError, "Merge is already in progress"
  end
  begin
    non_synced_merge
  rescue => ex
    logger.error("Error while merging #{ex}")
  ensure
    @merge_mutex.unlock
  end
end

#regenerate_hint_files!Object

Generate hint files for all the data files



235
236
237
238
239
240
241
242
243
# File 'lib/rubcask/directory.rb', line 235

def regenerate_hint_files!
  @lock.with_read_lock do
    @files.each_value do |data_file|
      data_file.synchronize do
        data_file.save_hint_file
      end
    end
  end
end

#set_with_ttl(key, value, ttl) ⇒ String

Note:

key is always treated as byte array, encoding is ignored

Set value associated with given key with given ttl

Parameters:

  • key (String)
  • value (String)
  • ttl (Integer)

    Time to live

Returns:

  • (String)

    the value provided by the user

  • (String)

    the value provided by the user

Raises:

  • (ArgumentError)

    if ttl is negative



112
113
114
115
116
# File 'lib/rubcask/directory.rb', line 112

def set_with_ttl(key, value, ttl)
  raise ArgumentError, "Negative ttl" if ttl.negative?
  put(key, value, Time.now.to_i + ttl)
  value # rubocop:disable Lint/Void
end