newque 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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