newque 0.0.1

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.
@@ -0,0 +1,121 @@
1
+ require 'ffi-rzmq'
2
+ require 'securerandom'
3
+
4
+ module Newque
5
+
6
+ class Newque_zmq
7
+ attr_reader :sock
8
+
9
+ def initialize host, port, options, timeout
10
+ @ctx = ZMQ::Context.new
11
+ @options = Util.compute_options Zmq_tools::BASE_OPTIONS, options
12
+ @timeout = timeout / 1000.0
13
+
14
+ @sock = @ctx.socket ZMQ::DEALER
15
+ Zmq_tools.set_zmq_sock_options @sock, @options
16
+
17
+ @poller = ZMQ::Poller.new
18
+ @poller.register_readable @sock
19
+
20
+ @router = {}
21
+ @sock.connect "tcp://#{host}:#{port}"
22
+ start_loop
23
+ @thread
24
+ end
25
+
26
+ def write channel, atomic, msgs, ids=nil
27
+ input = Input.new(channel: channel, write_input: Write_Input.new(atomic: atomic, ids: ids))
28
+ send_request input, msgs do |buffers|
29
+ output = parse_response buffers[0], :write_output
30
+ Write_response.new output.saved
31
+ end
32
+ end
33
+
34
+ def read channel, mode, limit=nil
35
+ input = Input.new(channel: channel, read_input: Read_Input.new(mode: mode, limit: limit))
36
+ send_request input do |buf, *messages|
37
+ output = parse_response buf, :read_output
38
+ Read_response.new output.length, output.last_id, output.last_timens, messages
39
+ end
40
+ end
41
+
42
+ def read_stream channel, mode, limit=nil
43
+ raise NewqueError.new "Read_stream is only available in :http mode"
44
+ end
45
+
46
+ def count channel
47
+ input = Input.new(channel: channel, count_input: Count_Input.new)
48
+ send_request input do |buffers|
49
+ output = parse_response buffers[0], :count_output
50
+ Count_response.new output.count
51
+ end
52
+ end
53
+
54
+ def delete channel
55
+ input = Input.new(channel: channel, delete_input: Delete_Input.new)
56
+ send_request input do |buffers|
57
+ output = parse_response buffers[0], :delete_output
58
+ Delete_response.new
59
+ end
60
+ end
61
+
62
+ def health channel, global=false
63
+ input = Input.new(channel: channel, health_input: Health_Input.new(global: global))
64
+ send_request input do |buffers|
65
+ output = parse_response buffers[0], :health_output
66
+ Health_response.new
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def send_request input, msgs=[], async:false, &block
73
+ id = SecureRandom.uuid
74
+ meta = input.encode.to_s
75
+ thread = register id, &block
76
+ @sock.send_strings (msgs.size > 0 ? [id, meta] + msgs : [id, meta]), ZMQ::DONTWAIT
77
+ Future.new thread, @timeout
78
+ end
79
+
80
+ def start_loop
81
+ @thread = Thread.new do
82
+ while @poller.poll(:blocking) > 0
83
+ buffers = []
84
+ @sock.recv_strings buffers, ZMQ::DONTWAIT
85
+ id, *frames = buffers
86
+
87
+ thread = @router[id]
88
+ while thread.status == 'run'
89
+ # If the scheduler didn't run the other thread and execute its Thread.stop
90
+ # then we have to wait before we can continue. Sleep 0 yields to the scheduler.
91
+ sleep 0
92
+ end
93
+
94
+ thread.thread_variable_set :result, frames
95
+ thread.run if thread.status == 'sleep' # 'if' not necessary, it's a sanity check
96
+ end
97
+ end
98
+ @thread.abort_on_exception = true
99
+ end
100
+
101
+ def register id
102
+ thread = Thread.new do
103
+ Thread.stop
104
+ @router.delete id
105
+ yield Thread.current.thread_variable_get(:result)
106
+ end
107
+ Thread.pass # Give a hint to schedule the new thread now
108
+ @router[id] = thread
109
+ end
110
+
111
+ def parse_response buffer, type
112
+ output = Output.decode buffer
113
+ if output.errors
114
+ raise Util.newque_error output.errors
115
+ end
116
+ output.send type
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,96 @@
1
+ require 'beefcake'
2
+
3
+ module Newque
4
+
5
+ # ------------------------------------
6
+ # REQUEST OBJECTS
7
+
8
+ class Write_Input
9
+ include Beefcake::Message
10
+ optional :atomic, :bool, 1
11
+ repeated :ids, :bytes, 2
12
+ end
13
+
14
+ class Read_Input
15
+ include Beefcake::Message
16
+ required :mode, :bytes, 1
17
+ optional :limit, :int64, 2
18
+ end
19
+
20
+ class Count_Input
21
+ include Beefcake::Message
22
+ end
23
+
24
+ class Delete_Input
25
+ include Beefcake::Message
26
+ end
27
+
28
+ class Health_Input
29
+ include Beefcake::Message
30
+ required :global, :bool, 1
31
+ end
32
+
33
+ class Input
34
+ include Beefcake::Message
35
+ required :channel, :bytes, 1
36
+ optional :write_input, Write_Input, 11
37
+ optional :read_input, Read_Input, 12
38
+ optional :count_input, Count_Input, 13
39
+ optional :delete_input, Delete_Input, 14
40
+ optional :health_input, Health_Input, 15
41
+ end
42
+
43
+ # ------------------------------------
44
+ # RESPONSE OBJECTS
45
+
46
+ class Error_Output
47
+ include Beefcake::Message
48
+ end
49
+
50
+ class Write_Output
51
+ include Beefcake::Message
52
+ optional :saved, :int32, 1
53
+ end
54
+
55
+ class Read_Output
56
+ include Beefcake::Message
57
+ required :length, :int32, 1
58
+ optional :last_id, :bytes, 2
59
+ optional :last_timens, :int64, 3
60
+ end
61
+
62
+ class Count_Output
63
+ include Beefcake::Message
64
+ optional :count, :int64, 1
65
+ end
66
+
67
+ class Delete_Output
68
+ include Beefcake::Message
69
+ end
70
+
71
+ class Health_Output
72
+ include Beefcake::Message
73
+ end
74
+
75
+ class Output
76
+ include Beefcake::Message
77
+ repeated :errors, :bytes, 1
78
+ optional :error_output, Error_Output, 11
79
+ optional :write_output, Write_Output, 12
80
+ optional :read_output, Read_Output, 13
81
+ optional :count_output, Count_Output, 14
82
+ optional :delete_output, Delete_Output, 15
83
+ optional :health_output, Health_Output, 16
84
+ end
85
+
86
+ # ------------------------------------
87
+ # WRAPPERS
88
+
89
+ class Many
90
+ include Beefcake::Message
91
+ repeated :buffers, :bytes, 1
92
+ end
93
+
94
+ # ------------------------------------
95
+
96
+ end
@@ -0,0 +1,52 @@
1
+ require 'ffi-rzmq'
2
+
3
+ module Newque
4
+
5
+ class Zmq_tools
6
+
7
+ BASE_OPTIONS = {
8
+ ZMQ_MAXMSGSIZE: -1,
9
+ ZMQ_LINGER: 60000,
10
+ ZMQ_RECONNECT_IVL: 100,
11
+ ZMQ_RECONNECT_IVL_MAX: 60000,
12
+ ZMQ_BACKLOG: 100,
13
+ ZMQ_SNDHWM: 5000,
14
+ ZMQ_RCVHWM: 5000
15
+ }
16
+ # BASE_OPTIONS, with the values being the ZMQ constants for those options
17
+ ZMQ_OPT_MAPPING = Hash[
18
+ BASE_OPTIONS.map do |name, value|
19
+ [name, ZMQ.const_get(name.to_s.slice(4..-1))]
20
+ end
21
+ ]
22
+
23
+ def self.set_zmq_sock_options sock, options
24
+ options.each do |name, value|
25
+ sock.setsockopt ZMQ_OPT_MAPPING[name], value
26
+ end
27
+ end
28
+
29
+ def self.parse_input buffers
30
+ buf, *messages = buffers
31
+ input = Input.decode buf
32
+
33
+ action = if !input.write_input.nil?
34
+ Write_request.new input.write_input.atomic, input.write_input.ids
35
+ elsif !input.read_input.nil?
36
+ Read_request.new input.read_input.mode, input.read_input.limit
37
+ elsif !input.count_input.nil?
38
+ Count_request.new
39
+ elsif !input.delete_input.nil?
40
+ Delete_request.new
41
+ elsif !input.health_input.nil?
42
+ Health_request.new input.health_input.global
43
+ else
44
+ raise NewqueError.new "Cannot find a valid message type"
45
+ end
46
+
47
+ Input_request.new input.channel, action, messages
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'newque/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'newque'
8
+ gem.version = Newque::VERSION
9
+ gem.authors = ['Simon Grondin']
10
+ gem.email = ['simon.grondin@outlook.com']
11
+ gem.description = 'Ruby Client library for Newque'
12
+ gem.summary = 'Ruby Client library for Newque'
13
+ gem.homepage = 'http://github.com/newque/newque-ruby'
14
+ gem.license = 'MPL-2.0'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = []
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.required_ruby_version = '>= 2.0.0'
22
+
23
+ gem.add_dependency 'beefcake', '~> 1.2.0'
24
+ gem.add_dependency 'ffi-rzmq', '~> 2.0.5'
25
+ gem.add_dependency 'faraday', '~> 0.12.2'
26
+
27
+ gem.add_development_dependency 'rake'
28
+ gem.add_development_dependency 'rspec'
29
+ gem.add_development_dependency 'pry'
30
+
31
+ gem.extra_rdoc_files = [
32
+ 'LICENSE',
33
+ 'README.md'
34
+ ]
35
+ end
@@ -0,0 +1,124 @@
1
+ require 'newque'
2
+ require 'pry'
3
+ require './spec/helpers'
4
+
5
+ module Newque
6
+ describe 'Client' do
7
+
8
+ run_shared_tests = -> (name, client, channel) {
9
+ def validate_read read, num
10
+ expect(read.class).to eq Read_response
11
+ expect(read.length).to eq num
12
+ expect(read.last_id).to_not be_empty
13
+ expect(read.last_timens).to be_a_kind_of Numeric
14
+ expect(read.messages.size).to eq num
15
+ end
16
+
17
+ before(:each) do
18
+ client.delete(channel).get
19
+ end
20
+
21
+ it "#{name} should write" do
22
+ write = client.write(channel, false, ['msg1', 'msg2', Helpers.bin_str]).get
23
+ expect(write.class).to eq Write_response
24
+ expect(write.saved).to eq 3
25
+ end
26
+
27
+ it "#{name} should read nothing" do
28
+ read = client.read(channel, "one").get
29
+ expect(read.class).to eq Read_response
30
+ expect(read.length).to eq 0
31
+ end
32
+
33
+ it "#{name} should read messages" do
34
+ client.write(channel, false, Helpers.make_msgs(5)).get
35
+ read = client.read(channel, "one").get
36
+ validate_read read, 1
37
+
38
+ read = client.read(channel, "many 3").get
39
+ validate_read read, 3
40
+
41
+ read = client.read(channel, "after_id #{read.last_id}").get
42
+ validate_read read, 2
43
+ end
44
+
45
+ it "#{name} should read binary data correctly" do
46
+ client.write(channel, false, [Helpers.bin_str]).get
47
+ read = client.read(channel, "one").get
48
+ expect(read.messages.first.length).to eq Helpers.bin_str.length
49
+ expect(read.messages.first).to eq Helpers.bin_str
50
+ end
51
+
52
+ it "#{name} should count" do
53
+ count = client.count(channel).get
54
+ expect(count.class).to eq Count_response
55
+ expect(count.count).to eq 0
56
+
57
+ client.write(channel, false, Helpers.make_msgs(5)).get
58
+ count = client.count(channel).get
59
+ expect(count.class).to eq Count_response
60
+ expect(count.count).to eq 5
61
+ end
62
+
63
+ it "#{name} should check health" do
64
+ health = client.health(channel).get
65
+ expect(health.class).to eq Health_response
66
+ end
67
+
68
+ it "#{name} should check health globally" do
69
+ health = client.health(channel, true).get
70
+ expect(health.class).to eq Health_response
71
+ end
72
+
73
+ it "#{name} should support concurrent calls" do
74
+ write1 = client.write(channel, false, Helpers.make_msgs(5))
75
+ write2 = client.write(channel, false, Helpers.make_msgs(3))
76
+
77
+ expect(write2.get.saved).to eq 3
78
+ expect(write1.get.saved).to eq 5
79
+ end
80
+
81
+ it "#{name} should pass errors" do
82
+ write = client.write('invalid_channel', false, Helpers.make_msgs(5))
83
+ expect { write.get }.to raise_error NewqueError
84
+ end
85
+ }
86
+
87
+ host = '127.0.0.1'
88
+ timeout = 3000
89
+ zmq_client = Client.new(:zmq, host, 8005, timeout:timeout)
90
+ http_json_client = Client.new(:http, host, 8000, timeout:timeout)
91
+ http_plaintext_client = Client.new(:http, host, 8000, protocol_options:{http_format: :plaintext}, timeout:timeout)
92
+
93
+ run_shared_tests.('ZMQ', zmq_client, 'example')
94
+ run_shared_tests.('HTTP JSON', http_json_client, 'example')
95
+ run_shared_tests.('HTTP Plaintext', http_plaintext_client, 'example_plaintext')
96
+
97
+ describe 'HTTP-specific' do
98
+ run_http_tests = -> (name, client, channel) {
99
+ it "#{name} should support Read Stream" do
100
+ client.delete(channel).get
101
+ num_batches = 25
102
+ batch_size = 100
103
+ num_batches.times do |i|
104
+ write = client.write(channel, false, Helpers.make_msgs(batch_size, from:(i*batch_size))).get
105
+ expect(write.saved).to eq batch_size
106
+ end
107
+
108
+ counter = 0
109
+ # TODO: Fix Limit
110
+ enum = client.read_stream channel, "many #{num_batches * batch_size}" #, limit:100
111
+ enum.each.with_index do |x, i|
112
+ expect("msg#{i}").to eq x
113
+ counter = counter + 1
114
+ end
115
+ expect(counter).to eq(num_batches * batch_size)
116
+ end
117
+ }
118
+
119
+ run_http_tests.('HTTP JSON', http_json_client, 'example')
120
+ run_http_tests.('HTTP Plaintext', http_plaintext_client, 'example_plaintext')
121
+ end
122
+ end
123
+
124
+ end
@@ -0,0 +1,130 @@
1
+ require 'newque'
2
+ require 'pry'
3
+ require './spec/helpers'
4
+
5
+ module Newque
6
+ describe 'Fifo_client' do
7
+ let(:channel) { 'example_fifo' }
8
+
9
+ before(:each) do
10
+ host = '127.0.0.1'
11
+ timeout = 3000
12
+ @producer1 = Client.new(:zmq, host, 8005, timeout:timeout)
13
+ @consumer1 = Fifo_client.new host, 8007
14
+ @consumer2 = Fifo_client.new host, 8007
15
+ end
16
+
17
+ after(:each) do
18
+ @consumer1.disconnect
19
+ @consumer2.disconnect
20
+ end
21
+
22
+ it 'should write' do
23
+ messages = ['msg1', 'msg2']
24
+ ready = @consumer1.connect do |input|
25
+ expect(input.channel).to eq channel
26
+ expect(input.action.class).to eq Write_request
27
+ expect(input.action.ids.size).to eq messages.size
28
+ expect(input.messages).to eq messages
29
+ Write_response.new 9
30
+ end
31
+
32
+ ready.get
33
+ write = @producer1.write(channel, false, messages).get
34
+ expect(write.saved).to eq 9
35
+ end
36
+
37
+ it 'should read' do
38
+ should_receive = ['msg1', 'msg2']
39
+ ready = @consumer1.connect do |input|
40
+ expect(input.channel).to eq channel
41
+ expect(input.action.class).to eq Read_request
42
+ expect(input.action.mode).to eq 'Many 2'
43
+ expect(input.action.limit).to eq 2
44
+ Read_response.new 2, 'some_id', 12345, ['msg123', 'msg456']
45
+ end
46
+
47
+ ready.get
48
+ read = @producer1.read(channel, 'many 2').get
49
+ expect(read.length).to eq 2
50
+ expect(read.last_id).to_not be_empty
51
+ expect(read.last_timens).to be_a_kind_of Numeric
52
+ expect(read.messages.size).to eq 2
53
+ end
54
+
55
+ it 'should count' do
56
+ ready = @consumer1.connect do |input|
57
+ expect(input.channel).to eq channel
58
+ expect(input.action.class).to eq Count_request
59
+ Count_response.new 8
60
+ end
61
+
62
+ ready.get
63
+ count = @producer1.count(channel).get
64
+ expect(count.count).to eq 8
65
+ end
66
+
67
+ it 'should delete' do
68
+ ready = @consumer1.connect do |input|
69
+ expect(input.channel).to eq channel
70
+ expect(input.action.class).to eq Delete_request
71
+ Delete_response.new
72
+ end
73
+
74
+ ready.get
75
+ @producer1.delete(channel).get
76
+ end
77
+
78
+ it 'should check health' do
79
+ ready = @consumer1.connect do |input|
80
+ expect(input.channel).to eq channel
81
+ expect(input.action.class).to eq Health_request
82
+ expect(input.action.global).to eq false
83
+ Health_response.new
84
+ end
85
+
86
+ ready.get
87
+ health = @producer1.health(channel, true).get
88
+ end
89
+
90
+ it 'should not be connected twice' do
91
+ expect {
92
+ ready = @consumer1.connect { puts; }
93
+ ready.get
94
+ }.to_not raise_error
95
+
96
+ expect {
97
+ # We're already connected
98
+ @consumer1.connect { puts; }
99
+ }.to raise_error NewqueError
100
+ end
101
+
102
+ it 'should not accept invalid response types' do
103
+ ready = @consumer1.connect do |input|
104
+ expect(input.channel).to eq channel
105
+ expect(input.action.class).to eq Count_request
106
+ 'SOME STRING'
107
+ end
108
+
109
+ ready.get
110
+ expect {
111
+ @producer1.count(channel).get
112
+ }.to raise_error NewqueError
113
+ end
114
+
115
+ it 'should not accept incorrect responses' do
116
+ ready = @consumer1.connect do |input|
117
+ expect(input.channel).to eq channel
118
+ expect(input.action.class).to eq Count_request
119
+ Delete_response.new
120
+ end
121
+
122
+ ready.get
123
+ expect {
124
+ @producer1.count(channel).get
125
+ }.to raise_error NewqueError
126
+ end
127
+
128
+
129
+ end
130
+ end