ffi-rxs 1.0.0

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