ffi-rzmq 0.9.6 → 0.9.7

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,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