ffi-rxs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,212 @@
1
+
2
+ module XS
3
+
4
+ class Poller
5
+ include XS::Util
6
+
7
+ attr_reader :readables, :writables
8
+
9
+ def initialize
10
+ @items = XS::PollItems.new
11
+ @raw_to_socket = {}
12
+ @sockets = []
13
+ @readables = []
14
+ @writables = []
15
+ end
16
+
17
+ # Checks each registered socket for selectability based on the poll items'
18
+ # registered +events+. Will block for up to +timeout+ milliseconds
19
+ # A millisecond is 1/1000 of a second, so to block for 1 second
20
+ # pass the value "1000" to #poll.
21
+ #
22
+ # Pass "-1" or +:blocking+ for +timeout+ for this call to block
23
+ # indefinitely.
24
+ #
25
+ # This method will return *immediately* when there are no registered
26
+ # sockets. In that case, the +timeout+ parameter is not honored. To
27
+ # prevent a CPU busy-loop, the caller of this method should detect
28
+ # this possible condition (via #size) and throttle the call
29
+ # frequency.
30
+ #
31
+ # Returns 0 when there are no registered sockets that are readable
32
+ # or writable.
33
+ #
34
+ # Return 1 (or greater) to indicate the number of readable or writable
35
+ # sockets. These sockets should be processed using the #readables and
36
+ # #writables accessors.
37
+ #
38
+ # Returns -1 when there is an error. Use XS::Util.errno to get the related
39
+ # error number.
40
+ #
41
+ def poll timeout = :blocking
42
+ unless @items.empty?
43
+ timeout = adjust timeout
44
+ items_triggered = LibXS.xs_poll @items.address, @items.size, timeout
45
+
46
+ if Util.resultcode_ok?(items_triggered)
47
+ update_selectables
48
+ end
49
+
50
+ items_triggered
51
+ else
52
+ 0
53
+ end
54
+ end
55
+
56
+ # The non-blocking version of #poll. See the #poll description for
57
+ # potential exceptions.
58
+ #
59
+ # May return -1 when an error is encounted. Check XS::Util.errno
60
+ # to determine the underlying cause.
61
+ #
62
+ def poll_nonblock
63
+ poll 0
64
+ end
65
+
66
+ # Register the +sock+ for +events+. This method is idempotent meaning
67
+ # it can be called multiple times with the same data and the socket
68
+ # will only get registered at most once. Calling multiple times with
69
+ # different values for +events+ will OR the event information together.
70
+ #
71
+ def register sock, events = XS::POLLIN | XS::POLLOUT, fd = 0
72
+ return false if (sock.nil? && fd.zero?) || events.zero?
73
+
74
+ item = @items.get(@sockets.index(sock))
75
+
76
+ unless item
77
+ @sockets << sock
78
+ item = LibXS::PollItem.new
79
+
80
+ if sock.kind_of?(XS::Socket) || sock.kind_of?(Socket)
81
+ item[:socket] = sock.socket
82
+ item[:fd] = 0
83
+ else
84
+ item[:socket] = FFI::MemoryPointer.new(0)
85
+ item[:fd] = fd
86
+ end
87
+
88
+ @raw_to_socket[item.socket.address] = sock
89
+ @items << item
90
+ end
91
+
92
+ item[:events] |= events
93
+ end
94
+
95
+ # Deregister the +sock+ for +events+. When there are no events left,
96
+ # this also deletes the socket from the poll items.
97
+ #
98
+ def deregister sock, events, fd = 0
99
+ return unless sock || !fd.zero?
100
+
101
+ item = @items.get(@sockets.index(sock))
102
+
103
+ if item && (item[:events] & events) > 0
104
+ # change the value in place
105
+ item[:events] ^= events
106
+
107
+ delete sock if item[:events].zero?
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ # A helper method to register a +sock+ as readable events only.
115
+ #
116
+ def register_readable sock
117
+ register sock, XS::POLLIN, 0
118
+ end
119
+
120
+ # A helper method to register a +sock+ for writable events only.
121
+ #
122
+ def register_writable sock
123
+ register sock, XS::POLLOUT, 0
124
+ end
125
+
126
+ # A helper method to deregister a +sock+ for readable events.
127
+ #
128
+ def deregister_readable sock
129
+ deregister sock, XS::POLLIN, 0
130
+ end
131
+
132
+ # A helper method to deregister a +sock+ for writable events.
133
+ #
134
+ def deregister_writable sock
135
+ deregister sock, XS::POLLOUT, 0
136
+ end
137
+
138
+ # Deletes the +sock+ for all subscribed events. Called internally
139
+ # when a socket has been deregistered and has no more events
140
+ # registered anywhere.
141
+ #
142
+ # Can also be called directly to remove the socket from the polling
143
+ # array.
144
+ #
145
+ def delete sock
146
+ unless (size = @sockets.size).zero?
147
+ @sockets.delete_if { |socket| socket.socket.address == sock.socket.address }
148
+ socket_deleted = size != @sockets.size
149
+
150
+ item_deleted = @items.delete sock
151
+
152
+ raw_deleted = @raw_to_socket.delete(sock.socket.address)
153
+
154
+ socket_deleted && item_deleted && raw_deleted
155
+
156
+ else
157
+ false
158
+ end
159
+ end
160
+
161
+ def size(); @items.size; end
162
+
163
+ def inspect
164
+ @items.inspect
165
+ end
166
+
167
+ def to_s(); inspect; end
168
+
169
+
170
+ private
171
+
172
+ def items_hash hash
173
+ @items.each do |poll_item|
174
+ hash[@raw_to_socket[poll_item.socket.address]] = poll_item
175
+ end
176
+ end
177
+
178
+ def update_selectables
179
+ @readables.clear
180
+ @writables.clear
181
+
182
+ @items.each do |poll_item|
183
+ #FIXME: spec for sockets *and* file descriptors
184
+ if poll_item.readable?
185
+ @readables << (poll_item.socket.address.zero? ? poll_item.fd : @raw_to_socket[poll_item.socket.address])
186
+ end
187
+
188
+ if poll_item.writable?
189
+ @writables << (poll_item.socket.address.zero? ? poll_item.fd : @raw_to_socket[poll_item.socket.address])
190
+ end
191
+ end
192
+ end
193
+
194
+ # Convert the timeout value to something usable by
195
+ # the library.
196
+ #
197
+ # -1 or :blocking should be converted to -1.
198
+ #
199
+ # Users will pass in values measured as
200
+ # milliseconds, so we need to convert that value to
201
+ # microseconds for the library.
202
+
203
+ def adjust timeout
204
+ if :blocking == timeout || -1 == timeout
205
+ -1
206
+ else
207
+ timeout.to_i
208
+ end
209
+ end
210
+ end
211
+
212
+ end # module XS
@@ -0,0 +1,212 @@
1
+
2
+ module XS
3
+
4
+ class Poller
5
+ include XS::Util
6
+
7
+ attr_reader :readables, :writables
8
+
9
+ def initialize
10
+ @items = XS::PollItems.new
11
+ @raw_to_socket = {}
12
+ @sockets = []
13
+ @readables = []
14
+ @writables = []
15
+ end
16
+
17
+ # Checks each registered socket for selectability based on the poll items'
18
+ # registered +events+. Will block for up to +timeout+ milliseconds
19
+ # A millisecond is 1/1000 of a second, so to block for 1 second
20
+ # pass the value "1000" to #poll.
21
+ #
22
+ # Pass "-1" or +:blocking+ for +timeout+ for this call to block
23
+ # indefinitely.
24
+ #
25
+ # This method will return *immediately* when there are no registered
26
+ # sockets. In that case, the +timeout+ parameter is not honored. To
27
+ # prevent a CPU busy-loop, the caller of this method should detect
28
+ # this possible condition (via #size) and throttle the call
29
+ # frequency.
30
+ #
31
+ # Returns 0 when there are no registered sockets that are readable
32
+ # or writable.
33
+ #
34
+ # Return 1 (or greater) to indicate the number of readable or writable
35
+ # sockets. These sockets should be processed using the #readables and
36
+ # #writables accessors.
37
+ #
38
+ # Returns -1 when there is an error. Use ZMQ::Util.errno to get the related
39
+ # error number.
40
+ #
41
+ def poll timeout = :blocking
42
+ unless @items.empty?
43
+ timeout = adjust timeout
44
+ items_triggered = LibXS.xs_poll @items.address, @items.size, timeout
45
+
46
+ if Util.resultcode_ok?(items_triggered)
47
+ update_selectables
48
+ end
49
+
50
+ items_triggered
51
+ else
52
+ 0
53
+ end
54
+ end
55
+
56
+ # The non-blocking version of #poll. See the #poll description for
57
+ # potential exceptions.
58
+ #
59
+ # May return -1 when an error is encounted. Check ZMQ::Util.errno
60
+ # to determine the underlying cause.
61
+ #
62
+ def poll_nonblock
63
+ poll 0
64
+ end
65
+
66
+ # Register the +sock+ for +events+. This method is idempotent meaning
67
+ # it can be called multiple times with the same data and the socket
68
+ # will only get registered at most once. Calling multiple times with
69
+ # different values for +events+ will OR the event information together.
70
+ #
71
+ def register sock, events = XS::POLLIN | XS::POLLOUT, fd = 0
72
+ return false if (sock.nil? && fd.zero?) || events.zero?
73
+
74
+ item = @items.get(@sockets.index(sock))
75
+
76
+ unless item
77
+ @sockets << sock
78
+ item = LibXS::PollItem.new
79
+
80
+ if sock.kind_of?(XS::Socket) || sock.kind_of?(Socket)
81
+ item[:socket] = sock.socket
82
+ item[:fd] = 0
83
+ else
84
+ item[:socket] = FFI::MemoryPointer.new(0)
85
+ item[:fd] = fd
86
+ end
87
+
88
+ @raw_to_socket[item.socket.address] = sock
89
+ @items << item
90
+ end
91
+
92
+ item[:events] |= events
93
+ end
94
+
95
+ # Deregister the +sock+ for +events+. When there are no events left,
96
+ # this also deletes the socket from the poll items.
97
+ #
98
+ def deregister sock, events, fd = 0
99
+ return unless sock || !fd.zero?
100
+
101
+ item = @items.get(@sockets.index(sock))
102
+
103
+ if item && (item[:events] & events) > 0
104
+ # change the value in place
105
+ item[:events] ^= events
106
+
107
+ delete sock if item[:events].zero?
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ # A helper method to register a +sock+ as readable events only.
115
+ #
116
+ def register_readable sock
117
+ register sock, XS::POLLIN, 0
118
+ end
119
+
120
+ # A helper method to register a +sock+ for writable events only.
121
+ #
122
+ def register_writable sock
123
+ register sock, XS::POLLOUT, 0
124
+ end
125
+
126
+ # A helper method to deregister a +sock+ for readable events.
127
+ #
128
+ def deregister_readable sock
129
+ deregister sock, XS::POLLIN, 0
130
+ end
131
+
132
+ # A helper method to deregister a +sock+ for writable events.
133
+ #
134
+ def deregister_writable sock
135
+ deregister sock, XS::POLLOUT, 0
136
+ end
137
+
138
+ # Deletes the +sock+ for all subscribed events. Called internally
139
+ # when a socket has been deregistered and has no more events
140
+ # registered anywhere.
141
+ #
142
+ # Can also be called directly to remove the socket from the polling
143
+ # array.
144
+ #
145
+ def delete sock
146
+ unless (size = @sockets.size).zero?
147
+ @sockets.delete_if { |socket| socket.socket.address == sock.socket.address }
148
+ socket_deleted = size != @sockets.size
149
+
150
+ item_deleted = @items.delete sock
151
+
152
+ raw_deleted = @raw_to_socket.delete(sock.socket.address)
153
+
154
+ socket_deleted && item_deleted && raw_deleted
155
+
156
+ else
157
+ false
158
+ end
159
+ end
160
+
161
+ def size(); @items.size; end
162
+
163
+ def inspect
164
+ @items.inspect
165
+ end
166
+
167
+ def to_s(); inspect; end
168
+
169
+
170
+ private
171
+
172
+ def items_hash hash
173
+ @items.each do |poll_item|
174
+ hash[@raw_to_socket[poll_item.socket.address]] = poll_item
175
+ end
176
+ end
177
+
178
+ def update_selectables
179
+ @readables.clear
180
+ @writables.clear
181
+
182
+ @items.each do |poll_item|
183
+ #FIXME: spec for sockets *and* file descriptors
184
+ if poll_item.readable?
185
+ @readables << (poll_item.socket.address.zero? ? poll_item.fd : @raw_to_socket[poll_item.socket.address])
186
+ end
187
+
188
+ if poll_item.writable?
189
+ @writables << (poll_item.socket.address.zero? ? poll_item.fd : @raw_to_socket[poll_item.socket.address])
190
+ end
191
+ end
192
+ end
193
+
194
+ # Convert the timeout value to something usable by
195
+ # the library.
196
+ #
197
+ # -1 or :blocking should be converted to -1.
198
+ #
199
+ # Users will pass in values measured as
200
+ # milliseconds, so we need to convert that value to
201
+ # microseconds for the library.
202
+
203
+ def adjust timeout
204
+ if :blocking == timeout || -1 == timeout
205
+ -1
206
+ else
207
+ timeout.to_i
208
+ end
209
+ end
210
+ end
211
+
212
+ end # module XS
@@ -0,0 +1,120 @@
1
+
2
+ module XS
3
+ class PollItems
4
+ include Enumerable
5
+
6
+ def initialize
7
+ @element_size = LibXS::PollItem.size
8
+ @store = nil
9
+ @items = []
10
+ end
11
+
12
+ def size; @items.size; end
13
+
14
+ def empty?; @items.empty?; end
15
+
16
+ def address
17
+ clean
18
+ @store
19
+ end
20
+
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
+ LibXS::PollItem.new pointer
30
+ end
31
+ end
32
+ alias :[] :get
33
+
34
+ def <<(obj)
35
+ @dirty = true
36
+ @items << obj
37
+ end
38
+ 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
63
+ @dirty = true
64
+ clean
65
+ end
66
+
67
+ value
68
+ end
69
+
70
+ def each &blk
71
+ clean
72
+ index = 0
73
+ until index >= @items.size do
74
+ struct = get index
75
+ yield struct
76
+ index += 1
77
+ end
78
+ end
79
+
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
+ def inspect
91
+ clean
92
+ str = ""
93
+ each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " }
94
+ str.chop.chop
95
+ end
96
+
97
+ def to_s(); inspect; end
98
+
99
+ private
100
+
101
+ # Allocate a contiguous chunk of memory and copy over the PollItem structs
102
+ # to this block. Note that the old +@store+ value goes out of scope so when
103
+ # it is garbage collected that native memory should be automatically freed.
104
+ def clean
105
+ if @dirty
106
+ @store = FFI::MemoryPointer.new @element_size, @items.size, true
107
+
108
+ # copy over
109
+ offset = 0
110
+ @items.each do |item|
111
+ LibC.memcpy(@store + offset, item.pointer, @element_size)
112
+ offset += @element_size
113
+ end
114
+
115
+ @dirty = false
116
+ end
117
+ end
118
+
119
+ end # class PollItems
120
+ end # module XS
@@ -0,0 +1,120 @@
1
+
2
+ module ZMQ
3
+ class PollItems
4
+ include Enumerable
5
+
6
+ def initialize
7
+ @element_size = LibZMQ::PollItem.size
8
+ @store = nil
9
+ @items = []
10
+ end
11
+
12
+ def size; @items.size; end
13
+
14
+ def empty?; @items.empty?; end
15
+
16
+ def address
17
+ clean
18
+ @store
19
+ end
20
+
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
31
+ end
32
+ alias :[] :get
33
+
34
+ def <<(obj)
35
+ @dirty = true
36
+ @items << obj
37
+ end
38
+ 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
63
+ @dirty = true
64
+ clean
65
+ end
66
+
67
+ value
68
+ end
69
+
70
+ def each &blk
71
+ clean
72
+ index = 0
73
+ until index >= @items.size do
74
+ struct = get index
75
+ yield struct
76
+ index += 1
77
+ end
78
+ end
79
+
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
+ def inspect
91
+ clean
92
+ str = ""
93
+ each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " }
94
+ str.chop.chop
95
+ end
96
+
97
+ def to_s(); inspect; end
98
+
99
+ private
100
+
101
+ # Allocate a contiguous chunk of memory and copy over the PollItem structs
102
+ # to this block. Note that the old +@store+ value goes out of scope so when
103
+ # it is garbage collected that native memory should be automatically freed.
104
+ def clean
105
+ if @dirty
106
+ @store = FFI::MemoryPointer.new @element_size, @items.size, true
107
+
108
+ # copy over
109
+ offset = 0
110
+ @items.each do |item|
111
+ LibC.memcpy(@store + offset, item.pointer, @element_size)
112
+ offset += @element_size
113
+ end
114
+
115
+ @dirty = false
116
+ end
117
+ end
118
+
119
+ end # class PollItems
120
+ end # module ZMQ