ffi-rzmq 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ require 'forwardable'
2
+ require 'io_extensions'
3
+
4
+ module ZMQ
5
+ class PollItem
6
+ extend Forwardable
7
+
8
+ def_delegators :@poll_item, :pointer, :readable?, :writable?
9
+ attr_accessor :pollable, :poll_item
10
+
11
+ def initialize(zmq_poll_item = nil)
12
+ @poll_item = zmq_poll_item || LibZMQ::PollItem.new
13
+ end
14
+
15
+ def self.from_pointer(pointer)
16
+ self.new(LibZMQ::PollItem.new(pointer))
17
+ end
18
+
19
+ def self.from_pollable(pollable)
20
+ item = self.new
21
+ item.pollable = pollable
22
+ case
23
+ when pollable.respond_to?(:socket)
24
+ item.socket = pollable.socket
25
+ when pollable.respond_to?(:posix_fileno)
26
+ item.fd = pollable.posix_fileno
27
+ when pollable.respond_to?(:io)
28
+ item.fd = pollable.io.posix_fileno
29
+ end
30
+ item
31
+ end
32
+
33
+ def closed?
34
+ case
35
+ when pollable.respond_to?(:closed?)
36
+ pollable.closed?
37
+ when pollable.respond_to?(:socket)
38
+ pollable.socket.nil?
39
+ when pollable.respond_to?(:io)
40
+ pollable.io.closed?
41
+ end
42
+ end
43
+
44
+ def socket=(arg); @poll_item[:socket] = arg; end
45
+
46
+ def socket; @poll_item[:socket]; end
47
+
48
+ def fd=(arg); @poll_item[:fd] = arg; end
49
+
50
+ def fd; @poll_item[:fd]; end
51
+
52
+ def events=(arg); @poll_item[:events] = arg; end
53
+
54
+ def events; @poll_item[:events]; end
55
+ end
56
+ end
@@ -1,100 +1,65 @@
1
+ require 'forwardable'
2
+ require 'ostruct'
1
3
 
2
4
  module ZMQ
3
5
  class PollItems
4
6
  include Enumerable
7
+ extend Forwardable
8
+
9
+ def_delegators :@pollables, :size, :empty?
5
10
 
6
11
  def initialize
7
- @element_size = LibZMQ::PollItem.size
8
- @store = nil
9
- @items = []
12
+ @pollables = {}
13
+ @item_size = LibZMQ::PollItem.size
14
+ @item_store = nil
10
15
  end
11
16
 
12
- def size; @items.size; end
13
-
14
- def empty?; @items.empty?; end
15
-
16
17
  def address
17
18
  clean
18
- @store
19
+ @item_store
19
20
  end
20
21
 
21
- def get index
22
- unless @items.empty? || index.nil?
23
- clean
24
-
25
- # pointer arithmetic in ruby! whee!
26
- pointer = @store + (@element_size * index)
27
-
28
- # cast the memory to a PollItem
29
- LibZMQ::PollItem.new pointer
30
- end
22
+ def get pollable
23
+ return unless entry = @pollables[pollable]
24
+ clean
25
+ pointer = @item_store + (@item_size * entry.index)
26
+ item = ZMQ::PollItem.from_pointer(pointer)
27
+ item.pollable = pollable
28
+ item
31
29
  end
32
30
  alias :[] :get
33
31
 
34
- def <<(obj)
32
+ def <<(poll_item)
35
33
  @dirty = true
36
- @items << obj
34
+ @pollables[poll_item.pollable] = OpenStruct.new(:index => size, :data => poll_item)
37
35
  end
38
36
  alias :push :<<
39
-
40
- def delete sock
41
- address = sock.socket.address
42
- found = false
43
-
44
- each_with_index do |item, index|
45
- if address == item[:socket].address
46
- @items.delete_at index
47
- found = true
48
- @dirty = true
49
- clean
50
- break
51
- end
52
- end
53
-
54
- # these semantics are different from the usual Array#delete; returns a
55
- # boolean instead of the actual item or nil
56
- found
57
- end
58
-
59
- def delete_at index
60
- value = nil
61
- unless @items.empty?
62
- value = @items.delete_at index
37
+
38
+ def delete pollable
39
+ if @pollables.delete(pollable)
63
40
  @dirty = true
64
41
  clean
42
+ true
43
+ else
44
+ false
65
45
  end
66
-
67
- value
68
46
  end
69
47
 
70
48
  def each &blk
71
49
  clean
72
- index = 0
73
- until index >= @items.size do
74
- struct = get index
75
- yield struct
76
- index += 1
50
+ @pollables.each_key do |pollable|
51
+ yield get(pollable)
77
52
  end
78
53
  end
79
54
 
80
- def each_with_index &blk
81
- clean
82
- index = 0
83
- until index >= @items.size do
84
- struct = get index
85
- yield struct, index
86
- index += 1
87
- end
88
- end
89
-
90
55
  def inspect
91
56
  clean
92
57
  str = ""
93
58
  each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " }
94
59
  str.chop.chop
95
60
  end
96
-
97
- def to_s(); inspect; end
61
+
62
+ def to_s; inspect; end
98
63
 
99
64
  private
100
65
 
@@ -103,18 +68,18 @@ module ZMQ
103
68
  # it is garbage collected that native memory should be automatically freed.
104
69
  def clean
105
70
  if @dirty
106
- @store = FFI::MemoryPointer.new @element_size, @items.size, true
71
+ @item_store = FFI::MemoryPointer.new @item_size, size, true
107
72
 
108
- # copy over
109
73
  offset = 0
110
- @items.each do |item|
111
- LibC.memcpy(@store + offset, item.pointer, @element_size)
112
- offset += @element_size
74
+ @pollables.each_with_index do |(pollable, entry), index|
75
+ entry.index = index
76
+ LibC.memcpy(@item_store + offset, entry.data.pointer, @item_size)
77
+ offset += @item_size
113
78
  end
114
79
 
115
80
  @dirty = false
116
81
  end
117
82
  end
118
83
 
119
- end # class PollItems
120
- end # module ZMQ
84
+ end
85
+ end
@@ -263,15 +263,7 @@ module ZMQ
263
263
  # cause.
264
264
  #
265
265
  def send_strings parts, flags = 0
266
- return -1 if !parts || parts.empty?
267
- flags = NonBlocking if dontwait?(flags)
268
-
269
- parts[0..-2].each do |part|
270
- rc = send_string part, (flags | ZMQ::SNDMORE)
271
- return rc unless Util.resultcode_ok?(rc)
272
- end
273
-
274
- send_string parts[-1], flags
266
+ send_multiple(parts, flags, :send_string)
275
267
  end
276
268
 
277
269
  # Send a sequence of messages as a multipart message out of the +parts+
@@ -289,15 +281,7 @@ module ZMQ
289
281
  # cause.
290
282
  #
291
283
  def sendmsgs parts, flags = 0
292
- return -1 if !parts || parts.empty?
293
- flags = NonBlocking if dontwait?(flags)
294
-
295
- parts[0..-2].each do |part|
296
- rc = sendmsg part, (flags | ZMQ::SNDMORE)
297
- return rc unless Util.resultcode_ok?(rc)
298
- end
299
-
300
- sendmsg parts[-1], flags
284
+ send_multiple(parts, flags, :sendmsg)
301
285
  end
302
286
 
303
287
  # Sends a message. This will automatically close the +message+ for both successful
@@ -444,6 +428,23 @@ module ZMQ
444
428
 
445
429
 
446
430
  private
431
+
432
+ def send_multiple(parts, flags, method_name)
433
+ if !parts || parts.empty?
434
+ -1
435
+ else
436
+ flags = NonBlocking if dontwait?(flags)
437
+ rc = nil
438
+
439
+ parts[0..-2].each do |part|
440
+ rc = send(method_name, part, (flags | ZMQ::SNDMORE))
441
+ break unless Util.resultcode_ok?(rc)
442
+ end
443
+
444
+ Util.resultcode_ok?(rc) ? send(method_name, parts[-1], flags) : rc
445
+ end
446
+
447
+ end
447
448
 
448
449
  def __getsockopt__ name, array
449
450
  # a small optimization so we only have to determine the option
@@ -473,39 +474,19 @@ module ZMQ
473
474
  def sockopt_buffers option_type
474
475
  if 1 == option_type
475
476
  # int64_t or uint64_t
476
- unless @longlong_cache
477
- length = FFI::MemoryPointer.new :size_t
478
- length.write_int 8
479
- @longlong_cache = [FFI::MemoryPointer.new(:int64), length]
480
- end
481
-
482
- @longlong_cache
477
+ @longlong_cache ||= alloc_pointer(:int64, 8)
483
478
 
484
479
  elsif 0 == option_type
485
480
  # int, 0mq assumes int is 4-bytes
486
- unless @int_cache
487
- length = FFI::MemoryPointer.new :size_t
488
- length.write_int 4
489
- @int_cache = [FFI::MemoryPointer.new(:int32), length]
490
- end
491
-
492
- @int_cache
481
+ @int_cache ||= alloc_pointer(:int32, 4)
493
482
 
494
483
  elsif 2 == option_type
495
- length = FFI::MemoryPointer.new :size_t
496
- # could be a string of up to 255 bytes
497
- length.write_int 255
498
- [FFI::MemoryPointer.new(255), length]
484
+ # could be a string of up to 255 bytes, so allocate for worst case
485
+ alloc_pointer(255, 255)
499
486
 
500
487
  else
501
488
  # uh oh, someone passed in an unknown option; use a slop buffer
502
- unless @int_cache
503
- length = FFI::MemoryPointer.new :size_t
504
- length.write_int 4
505
- @int_cache = [FFI::MemoryPointer.new(:int32), length]
506
- end
507
-
508
- @int_cache
489
+ @int_cache ||= alloc_pointer(:int32, 4)
509
490
  end
510
491
  end
511
492
 
@@ -529,6 +510,12 @@ module ZMQ
529
510
  (NonBlocking & flags) == NonBlocking
530
511
  end
531
512
  alias :noblock? :dontwait?
513
+
514
+ def alloc_pointer(kind, length)
515
+ pointer = FFI::MemoryPointer.new :size_t
516
+ pointer.write_int(length)
517
+ [FFI::MemoryPointer.new(kind), pointer]
518
+ end
532
519
  end # module CommonSocketBehavior
533
520
 
534
521
 
@@ -1,3 +1,3 @@
1
1
  module ZMQ
2
- VERSION = "0.9.6"
2
+ VERSION = "0.9.7"
3
3
  end
@@ -0,0 +1,19 @@
1
+ class IO
2
+ if defined? JRUBY_VERSION
3
+ require 'jruby'
4
+ def posix_fileno
5
+ case self
6
+ when STDIN, $stdin
7
+ 0
8
+ when STDOUT, $stdout
9
+ 1
10
+ when STDERR, $stderr
11
+ 2
12
+ else
13
+ JRuby.reference(self).getOpenFile.getMainStream.getDescriptor.getChannel.getFDVal
14
+ end
15
+ end
16
+ else
17
+ alias :posix_fileno :fileno
18
+ end
19
+ end
data/spec/poll_spec.rb CHANGED
@@ -1,10 +1,7 @@
1
- $: << "." # added for ruby 1.9.2 compatibilty; it doesn't include the current directory on the load path anymore
2
-
3
- require File.join(File.dirname(__FILE__), %w[spec_helper])
1
+ require 'spec_helper'
4
2
 
5
3
  module ZMQ
6
4
 
7
-
8
5
  describe Poller do
9
6
 
10
7
  context "when initializing" do
@@ -12,67 +9,123 @@ module ZMQ
12
9
 
13
10
  it "should allocate a PollItems instance" do
14
11
  PollItems.should_receive(:new)
15
-
16
12
  Poller.new
17
13
  end
18
14
 
19
- end # context initializing
20
-
21
-
15
+ end
16
+
22
17
  context "#register" do
23
-
18
+
19
+ let(:pollable) { mock('pollable') }
24
20
  let(:poller) { Poller.new }
25
- let(:socket) { mock('socket') }
26
-
27
- it "should return false when given a nil socket and no file descriptor" do
28
- poller.register(nil, ZMQ::POLLIN, 0).should be_false
21
+ let(:socket) { FFI::MemoryPointer.new(4) }
22
+ let(:io) { stub(:posix_fileno => fd) }
23
+ let(:fd) { 1 }
24
+
25
+ it "returns false when given a nil pollable" do
26
+ poller.register(nil, ZMQ::POLLIN).should be_false
29
27
  end
30
-
31
- it "should return false when given 0 for +events+ (e.g. no registration)" do
32
- poller.register(socket, 0).should be_false
28
+
29
+ it "returns false when given 0 for +events+ (e.g. no registration)" do
30
+ poller.register(pollable, 0).should be_false
33
31
  end
34
-
35
- it "should return the registered event value when given a nil socket and a valid non-zero file descriptor" do
36
- poller.register(nil, ZMQ::POLLIN, 1).should == ZMQ::POLLIN
32
+
33
+ it "returns the default registered event value when given a valid pollable" do
34
+ poller.register(pollable).should == (ZMQ::POLLIN | ZMQ::POLLOUT)
37
35
  end
38
-
39
- it "should return the default registered event value when given a valid socket" do
40
- poller.register(socket).should == (ZMQ::POLLIN | ZMQ::POLLOUT)
36
+
37
+ it "returns the registered event value when given a pollable responding to socket (ZMQ::Socket)" do
38
+ pollable.should_receive(:socket).and_return(socket)
39
+ poller.register(pollable, ZMQ::POLLIN).should == ZMQ::POLLIN
40
+ end
41
+
42
+ it "returns the registered event value when given a pollable responding to file descriptor (IO, BasicSocket)" do
43
+ pollable.should_receive(:posix_fileno).and_return(fd)
44
+ poller.register(pollable, ZMQ::POLLIN).should == ZMQ::POLLIN
41
45
  end
42
-
43
- it "should access the raw 0mq socket" do
44
- raw_socket = FFI::MemoryPointer.new(4)
45
- socket.should_receive(:kind_of?).with(ZMQ::Socket).and_return(true)
46
- socket.should_receive(:socket).and_return(raw_socket)
47
46
 
48
- poller.register(socket)
47
+ it "returns the registered event value when given a pollable responding to io (SSLSocket)" do
48
+ pollable.should_receive(:io).and_return(io)
49
+ poller.register(pollable, ZMQ::POLLIN).should == ZMQ::POLLIN
49
50
  end
51
+
50
52
  end
51
-
52
-
53
- context "#delete" do
54
- before(:all) do
55
- @context = Context.new
53
+
54
+ context "#deregister" do
55
+
56
+ let(:pollable) { mock('pollable') }
57
+ let(:poller) { Poller.new }
58
+ let(:socket) { FFI::MemoryPointer.new(4) }
59
+ let(:io) { stub(:posix_fileno => fd) }
60
+ let(:fd) { 1 }
61
+
62
+ it "returns true when deregistered pollable from event" do
63
+ pollable.should_receive(:socket).at_least(:once).and_return(socket)
64
+ poller.register(pollable)
65
+ poller.deregister(pollable, ZMQ::POLLIN).should be_true
56
66
  end
57
-
67
+
68
+ it "returns false when pollable not registered" do
69
+ poller.deregister(pollable, ZMQ::POLLIN).should be_false
70
+ end
71
+
72
+ it "returns false when pollable not registered for deregistered event" do
73
+ pollable.should_receive(:socket).at_least(:once).and_return(socket)
74
+ poller.register(pollable, ZMQ::POLLOUT)
75
+ poller.deregister(pollable, ZMQ::POLLIN).should be_false
76
+ end
77
+
78
+ it "deletes pollable when no events left" do
79
+ poller.register(pollable, ZMQ::POLLIN)
80
+ poller.deregister(pollable, ZMQ::POLLIN).should be_true
81
+ poller.size.should == 0
82
+ end
83
+
84
+ it "deletes closed pollable responding to socket (ZMQ::Socket)" do
85
+ pollable.should_receive(:socket).and_return(socket)
86
+ poller.register(pollable)
87
+ pollable.should_receive(:socket).and_return(nil)
88
+ poller.deregister(pollable, ZMQ::POLLIN).should be_true
89
+ poller.size.should == 0
90
+ end
91
+
92
+ it "deletes closed pollable responding to fileno (IO, BasicSocket)" do
93
+ pollable.should_receive(:posix_fileno).and_return(fd)
94
+ poller.register(pollable)
95
+ pollable.should_receive(:closed?).and_return(true)
96
+ poller.deregister(pollable, ZMQ::POLLIN).should be_true
97
+ poller.size.should == 0
98
+ end
99
+
100
+ it "deletes closed pollable responding to io (SSLSocket)" do
101
+ pollable.should_receive(:io).at_least(:once).and_return(io)
102
+ poller.register(pollable)
103
+ io.should_receive(:closed?).and_return(true)
104
+ poller.deregister(pollable, ZMQ::POLLIN).should be_true
105
+ poller.size.should == 0
106
+ end
107
+
108
+ end
109
+
110
+ context "#delete" do
111
+
112
+ before(:all) { @context = Context.new }
113
+ after(:all) { @context.terminate }
114
+
58
115
  before(:each) do
59
116
  @socket = @context.socket(XREQ)
60
117
  @socket.setsockopt(LINGER, 0)
61
118
  @poller = Poller.new
62
119
  end
63
-
120
+
64
121
  after(:each) do
65
122
  @socket.close
66
123
  end
67
-
68
- after(:all) do
69
- @context.terminate
70
- end
71
-
124
+
72
125
  it "should return false for an unregistered socket (i.e. not found)" do
73
126
  @poller.delete(@socket).should be_false
74
127
  end
75
-
128
+
76
129
  it "returns true for a sucessfully deleted socket when only 1 is registered" do
77
130
  socket1 = @context.socket(REP)
78
131
  socket1.setsockopt(LINGER, 0)
@@ -81,7 +134,7 @@ module ZMQ
81
134
  @poller.delete(socket1).should be_true
82
135
  socket1.close
83
136
  end
84
-
137
+
85
138
  it "returns true for a sucessfully deleted socket when more than 1 is registered" do
86
139
  socket1 = @context.socket(REP)
87
140
  socket2 = @context.socket(REP)
@@ -94,72 +147,130 @@ module ZMQ
94
147
  socket1.close
95
148
  socket2.close
96
149
  end
97
-
150
+
151
+ it "returns true for a successfully deleted socket when the socket has been previously closed" do
152
+ socket1 = @context.socket(REP)
153
+ socket1.setsockopt(LINGER, 0)
154
+
155
+ @poller.register socket1
156
+ socket1.close
157
+ @poller.delete(socket1).should be_true
158
+ end
159
+
98
160
  end
99
-
100
-
161
+
101
162
  context "poll" do
102
163
  include APIHelper
103
-
104
- before(:all) do
105
- end
106
-
107
- before(:each) do
108
- # Must recreate context for each test otherwise some poll tests fail.
109
- # This is likely due to a race condition in event handling when reusing
110
- # the same inproc inside the same context over and over. Making a new
111
- # context solves it.
112
- @context = Context.new
113
- endpoint = "inproc://poll_test"
114
- @socket = @context.socket(DEALER)
115
- @socket2 = @context.socket(ROUTER)
116
- @socket.setsockopt(LINGER, 0)
117
- @socket2.setsockopt(LINGER, 0)
118
- @socket.bind(endpoint)
119
- connect_to_inproc(@socket2, endpoint)
120
164
 
165
+ before(:all) { @context = Context.new }
166
+ after(:all) { @context.terminate }
167
+
168
+ before(:each) do
169
+ endpoint = "inproc://poll_test_#{SecureRandom.hex}"
170
+ @sockets = [@context.socket(DEALER), @context.socket(ROUTER)]
171
+ @sockets.each { |s| s.setsockopt(LINGER, 0) }
172
+ @sockets.first.bind(endpoint)
173
+ connect_to_inproc(@sockets.last, endpoint)
121
174
  @poller = Poller.new
122
175
  end
123
-
124
- after(:each) do
125
- @socket.close
126
- @socket2.close
127
- @context.terminate
128
- end
129
-
176
+
177
+ after(:each) { @sockets.each(&:close) }
178
+
130
179
  it "returns 0 when there are no sockets to poll" do
131
- rc = @poller.poll(100)
132
- rc.should be_zero
180
+ @poller.poll(100).should be_zero
133
181
  end
134
-
182
+
135
183
  it "returns 0 when there is a single socket to poll and no events" do
136
- @poller.register(@socket, 0)
137
- rc = @poller.poll(100)
138
- rc.should be_zero
184
+ @poller.register(@sockets.first, 0)
185
+ @poller.poll(100).should be_zero
139
186
  end
140
-
187
+
141
188
  it "returns 1 when there is a read event on a socket" do
142
- @poller.register_readable(@socket2)
143
-
144
- @socket.send_string('test')
145
- rc = @poller.poll(1000)
146
- rc.should == 1
189
+ first, last = @sockets
190
+ @poller.register_readable(last)
191
+
192
+ first.send_string('test')
193
+ @poller.poll(1000).should == 1
147
194
  end
148
-
195
+
149
196
  it "returns 1 when there is a read event on one socket and the second socket has been removed from polling" do
150
- @poller.register_readable(@socket2)
151
- @poller.register_writable(@socket)
152
-
153
- @socket.send_string('test')
154
- @poller.deregister_writable(@socket)
197
+ first, last = @sockets
198
+ @poller.register_readable(last)
199
+ @poller.register_writable(first)
155
200
 
156
- rc = @poller.poll(1000)
157
- rc.should == 1
201
+ first.send_string('test')
202
+ @poller.deregister_writable(first)
203
+ @poller.poll(1000).should == 1
158
204
  end
159
- end # poll
160
205
 
206
+ it "works with BasiSocket" do
207
+ server = TCPServer.new("127.0.0.1", 0)
208
+ f, port, host, addr = server.addr
209
+ client = TCPSocket.new("127.0.0.1", port)
210
+ s = server.accept
161
211
 
162
- end # describe Poll
212
+ @poller.register(s, ZMQ::POLLIN)
213
+ @poller.register(client, ZMQ::POLLOUT)
214
+
215
+ client.send("message", 0)
216
+
217
+ @poller.poll.should == 2
218
+ @poller.readables.should == [s]
219
+ @poller.writables.should == [client]
220
+
221
+ msg = s.read_nonblock(7)
222
+ msg.should == "message"
223
+ end
224
+
225
+ it "works with IO objects" do
226
+ r, w = IO.pipe
227
+ @poller.register(r, ZMQ::POLLIN)
228
+ @poller.register(w, ZMQ::POLLOUT)
229
+
230
+ w.write("message")
231
+
232
+ @poller.poll.should == 2
233
+ @poller.readables.should == [r]
234
+ @poller.writables.should == [w]
235
+
236
+ msg = r.read(7)
237
+ msg.should == "message"
238
+ end
239
+
240
+ it "works with SSLSocket" do
241
+ crt, key = %w[crt key].map { |ext| File.read(File.join(File.dirname(__FILE__), "support", "test." << ext)) }
242
+
243
+ ctx = OpenSSL::SSL::SSLContext.new
244
+ ctx.key = OpenSSL::PKey::RSA.new(key)
245
+ ctx.cert = OpenSSL::X509::Certificate.new(crt)
246
+
247
+ server = TCPServer.new("127.0.0.1", 0)
248
+ f, port, host, addr = server.addr
249
+ client = TCPSocket.new("127.0.0.1", port)
250
+ s = server.accept
251
+
252
+ client = OpenSSL::SSL::SSLSocket.new(client)
253
+
254
+ server = OpenSSL::SSL::SSLSocket.new(s, ctx)
255
+
256
+ t = Thread.new { client.connect }
257
+ s = server.accept
258
+ t.join
259
+
260
+ @poller.register_readable(s)
261
+ @poller.register_writable(client)
262
+
263
+ client.write("message")
264
+
265
+ @poller.poll.should == 2
266
+ @poller.readables.should == [s]
267
+ @poller.writables.should == [client]
268
+
269
+ msg = s.read(7)
270
+ msg.should == "message"
271
+ end
272
+ end
163
273
 
274
+ end
164
275
 
165
- end # module ZMQ
276
+ end