From 1da6a1aa1e6ac8fe0a4fb02eb434d96ecf070d08 Mon Sep 17 00:00:00 2001 From: Alastair Pharo Date: Wed, 4 Sep 2019 23:13:50 +1000 Subject: [PATCH] Server/Client: add each_key support --- feature_matrix.yaml | 3 +- lib/moneta/adapters/client.rb | 31 +++++++++++++++++++ lib/moneta/lock.rb | 7 ++++- lib/moneta/server.rb | 31 ++++++++++++++++--- lib/moneta/shared.rb | 2 -- spec/helper.rb | 5 +++ .../adapters/client/adapter_client_spec.rb | 8 +++-- .../client/standard_client_tcp_spec.rb | 6 +++- .../client/standard_client_unix_spec.rb | 8 +++-- spec/moneta/proxies/shared/shared_tcp_spec.rb | 4 +-- 10 files changed, 90 insertions(+), 15 deletions(-) diff --git a/feature_matrix.yaml b/feature_matrix.yaml index da8bb57c..3021846c 100644 --- a/feature_matrix.yaml +++ b/feature_matrix.yaml @@ -219,13 +219,14 @@ backends: - adapter: Client platforms: [ MRI, JRuby ] features: [multiprocess] - unknown: [ increment, create, expires, persist ] + unknown: [ increment, create, expires, persist, each_key ] description: "Moneta client adapter" notes: increment: depends on server create: depends on server expires: depends on server persist: depends on server + each_key: depends on server - adapter: RestClient platforms: [ MRI, JRuby ] features: [ multiprocess ] diff --git a/lib/moneta/adapters/client.rb b/lib/moneta/adapters/client.rb index 8e446452..0c2513e6 100644 --- a/lib/moneta/adapters/client.rb +++ b/lib/moneta/adapters/client.rb @@ -70,6 +70,37 @@ def close nil end + # (see Proxy#each_key) + def each_key + raise NotImplementedError, 'each_key is not supported' unless supports?(:each_key) + return enum_for(:each_key) unless block_given? + + begin + write(:each_key) + yield_break = false + + loop do + write('NEXT') + + # A StopIteration error will be raised by this call if the server + # reached the end of the enumeration. This will stop the loop + # automatically. + result = read_msg + + # yield_break will be true in the ensure block (below) if anything + # happened during the yield to stop further enumeration. + yield_break = true + yield result + yield_break = false + end + ensure + write('BREAK') if yield_break + read_msg # nil return from each_key + end + + self + end + # (see Default#features) def features @features ||= diff --git a/lib/moneta/lock.rb b/lib/moneta/lock.rb index a0b38ffa..66fe552c 100644 --- a/lib/moneta/lock.rb +++ b/lib/moneta/lock.rb @@ -15,6 +15,7 @@ def initialize(adapter, options = {}) protected def wrap(name, *args, &block) + self.locks ||= Set.new if locked? yield else @@ -22,8 +23,12 @@ def wrap(name, *args, &block) end end + def locks=(locks) + Thread.current.thread_variable_set('Moneta::Lock', locks) + end + def locks - Thread.current['Moneta::Lock'] ||= Set.new + Thread.current.thread_variable_get('Moneta::Lock') end def lock!(&block) diff --git a/lib/moneta/server.rb b/lib/moneta/server.rb index 1e1f8148..980049ef 100644 --- a/lib/moneta/server.rb +++ b/lib/moneta/server.rb @@ -31,14 +31,14 @@ def mainloop def dispatch(method, args) case method - when :key?, :load, :delete, :increment, :create + when :key?, :load, :delete, :increment, :create, :features @store.send(method, *args) - when :features - # all features except each_key are supported - @store.features - [:each_key] when :store, :clear @store.send(method, *args) nil + when :each_key + yield_each(@store.each_key) + nil end rescue => e e @@ -119,6 +119,29 @@ def close(raising: nil) @io.close rescue nil raise Closed, raising if raising end + + def yield_each(enumerator) + received_break = false + loop do + case msg = read_msg + when %w{NEXT} + # This will raise a StopIteration at the end of the enumeration, + # which will exit the loop. + write(enumerator.next) + when %w{BREAK} + # This is received when the client wants to stop the enumeration. + received_break = true + break + else + # Otherwise, the client is attempting to call another method within + # an `each` block. + write_dispatch(msg) + end + end + ensure + # This tells the client to stop enumerating + write(StopIteration.new("Server initiated stop")) unless received_break + end end # @param [Hash] options diff --git a/lib/moneta/shared.rb b/lib/moneta/shared.rb index 1a676b1a..9bd680ef 100644 --- a/lib/moneta/shared.rb +++ b/lib/moneta/shared.rb @@ -11,8 +11,6 @@ module Moneta # # @api public class Shared < Wrapper - not_supports :each_key - # @param [Hash] options # @option options [Integer] :port (9000) TCP port # @option options [String] :host Server hostname diff --git a/spec/helper.rb b/spec/helper.rb index 779ab55f..51c52944 100644 --- a/spec/helper.rb +++ b/spec/helper.rb @@ -327,8 +327,13 @@ def start_server(*args) server = Moneta::Server.new(*args) Thread.new { server.run } sleep 0.1 until server.running? + server rescue Exception => ex puts "Failed to start server - #{ex.message}" + tries ||= 0 + tries += 1 + sleep Moneta::Server::TIMEOUT + tries < 3 ? retry : raise end def moneta_property_of(keys: 0, values: 0) diff --git a/spec/moneta/adapters/client/adapter_client_spec.rb b/spec/moneta/adapters/client/adapter_client_spec.rb index 97a4b94c..26ad37b9 100644 --- a/spec/moneta/adapters/client/adapter_client_spec.rb +++ b/spec/moneta/adapters/client/adapter_client_spec.rb @@ -1,11 +1,15 @@ describe 'adapter_client', isolate: true, adapter: :Client do before :all do - start_server(Moneta::Adapters::Memory.new) + @server = start_server(Moneta::Adapters::Memory.new) + end + + after :all do + @server.stop end moneta_build do Moneta::Adapters::Client.new end - moneta_specs ADAPTER_SPECS + moneta_specs ADAPTER_SPECS.with_each_key end diff --git a/spec/moneta/adapters/client/standard_client_tcp_spec.rb b/spec/moneta/adapters/client/standard_client_tcp_spec.rb index c4bb26e5..2a4208d8 100644 --- a/spec/moneta/adapters/client/standard_client_tcp_spec.rb +++ b/spec/moneta/adapters/client/standard_client_tcp_spec.rb @@ -1,6 +1,10 @@ describe "standard_client_tcp", isolate: true, adapter: :Client do before :all do - start_server(Moneta::Adapters::Memory.new) + @server = start_server(Moneta::Adapters::Memory.new) + end + + after :all do + @server.stop end moneta_store :Client diff --git a/spec/moneta/adapters/client/standard_client_unix_spec.rb b/spec/moneta/adapters/client/standard_client_unix_spec.rb index 3a2218c5..3e81cb9f 100644 --- a/spec/moneta/adapters/client/standard_client_unix_spec.rb +++ b/spec/moneta/adapters/client/standard_client_unix_spec.rb @@ -1,7 +1,11 @@ describe "standard_client_unix", isolate: true, adapter: :Client do before :all do - start_server Moneta::Adapters::Memory.new, - socket: File.join(tempdir, 'standard_client_unix') + @server = start_server Moneta::Adapters::Memory.new, + socket: File.join(tempdir, 'standard_client_unix') + end + + after :all do + @server.stop end moneta_store :Client do diff --git a/spec/moneta/proxies/shared/shared_tcp_spec.rb b/spec/moneta/proxies/shared/shared_tcp_spec.rb index d245bd07..33aaa57f 100644 --- a/spec/moneta/proxies/shared/shared_tcp_spec.rb +++ b/spec/moneta/proxies/shared/shared_tcp_spec.rb @@ -3,13 +3,13 @@ tempdir = self.tempdir Moneta.build do use(:Shared, port: 9001) do - adapter :PStore, file: File.join(tempdir, 'shared_tcp') + adapter :GDBM, file: File.join(tempdir, 'shared_tcp') end end end shared_examples :shared_tcp do - moneta_specs ADAPTER_SPECS + moneta_specs ADAPTER_SPECS.with_each_key it 'shares values' do store['shared_key'] = 'shared_value'