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,282 @@
1
+
2
+ module XS
3
+
4
+ # The factory constructor optionally takes a string as an argument. It will
5
+ # copy this string to native memory in preparation for transmission.
6
+ # So, don't pass a string unless you intend to send it. Internally it
7
+ # calls #copy_in_string.
8
+ #
9
+ # Call #close to release buffers when you are done with the data.
10
+ #
11
+ # (This class is not really zero-copy. Ruby makes this near impossible
12
+ # since Ruby objects can be relocated in memory by the GC at any
13
+ # time. There is no way to peg them to native memory or have them
14
+ # use non-movable native memory as backing store.)
15
+ #
16
+ # Message represents ruby equivalent of the +xs_msg_t+ C struct.
17
+ # Access the underlying memory buffer and the buffer size using the
18
+ # #data and #size methods respectively.
19
+ #
20
+ # It is recommended that this class be composed inside another class for
21
+ # access to the underlying buffer. The outer wrapper class can provide
22
+ # nice accessors for the information in the data buffer; a clever
23
+ # implementation can probably lazily encode/decode the data buffer
24
+ # on demand. Lots of protocols send more information than is strictly
25
+ # necessary, so only decode (copy from the 0mq buffer to Ruby) that
26
+ # which is necessary.
27
+ #
28
+ # When you are done using a *received* message object, call #close to
29
+ # release the associated buffers.
30
+ #
31
+ # received_message = Message.create
32
+ # if received_message
33
+ # rc = socket.recvmsg(received_message)
34
+ # if XS::Util.resultcode_ok?(rc)
35
+ # puts "Message contained: #{received_message.copy_out_string}"
36
+ # else
37
+ # STDERR.puts "Error when receiving message: #{XS::Util.error_string}"
38
+ # end
39
+ #
40
+ #
41
+ # Define a custom layout for the data sent between Crossroads peers.
42
+ #
43
+ # class MyMessage
44
+ # class Layout < FFI::Struct
45
+ # layout :value1, :uint8,
46
+ # :value2, :uint64,
47
+ # :value3, :uint32,
48
+ # :value4, [:char, 30]
49
+ # end
50
+ #
51
+ # def initialize msg_struct = nil
52
+ # if msg_struct
53
+ # @msg_t = msg_struct
54
+ # @data = Layout.new(@msg_t.data)
55
+ # else
56
+ # @pointer = FFI::MemoryPointer.new :byte, Layout.size, true
57
+ # @data = Layout.new @pointer
58
+ # end
59
+ # end
60
+ #
61
+ # def size() @size = @msg_t.size; end
62
+ #
63
+ # def value1
64
+ # @data[:value1]
65
+ # end
66
+ #
67
+ # def value4
68
+ # @data[:value4].to_ptr.read_string
69
+ # end
70
+ #
71
+ # def value1=(val)
72
+ # @data[:value1] = val
73
+ # end
74
+ #
75
+ # def create_sendable_message
76
+ # msg = Message.new
77
+ # msg.copy_in_bytes @pointer, Layout.size
78
+ # end
79
+ #
80
+ #
81
+ # message = Message.new
82
+ # successful_read = socket.recv message
83
+ # message = MyMessage.new message if successful_read
84
+ # puts "value1 is #{message.value1}"
85
+ #
86
+ class Message
87
+ include XS::Util
88
+
89
+ # Recommended way to create a standard message. A Message object is
90
+ # returned upon success, nil when allocation fails.
91
+ #
92
+ def self.create message = nil
93
+ new(message) rescue nil
94
+ end
95
+
96
+ def initialize message = nil
97
+ # allocate our own pointer so that we can tell it to *not* zero out
98
+ # the memory; it's pointless work since the library is going to
99
+ # overwrite it anyway.
100
+ @pointer = FFI::MemoryPointer.new Message.msg_size, 1, false
101
+
102
+ if message
103
+ copy_in_string message
104
+ else
105
+ # initialize an empty message structure to receive a message
106
+ result_code = LibXS.xs_msg_init @pointer
107
+ raise unless Util.resultcode_ok?(result_code)
108
+ end
109
+ end
110
+
111
+ # Makes a copy of the ruby +string+ into a native memory buffer so
112
+ # that libxs can send it. The underlying library will handle
113
+ # deallocation of the native memory buffer.
114
+ #
115
+ # Can only be initialized via #copy_in_string or #copy_in_bytes once.
116
+ #
117
+ def copy_in_string string
118
+ string_size = string.respond_to?(:bytesize) ? string.bytesize : string.size
119
+ copy_in_bytes string, string_size if string
120
+ end
121
+
122
+ # Makes a copy of +len+ bytes from the ruby string +bytes+. Library
123
+ # handles deallocation of the native memory buffer.
124
+ #
125
+ # Can only be initialized via #copy_in_string or #copy_in_bytes once.
126
+ #
127
+ def copy_in_bytes bytes, len
128
+ data_buffer = LibC.malloc len
129
+ # writes the exact number of bytes, no null byte to terminate string
130
+ data_buffer.write_string bytes, len
131
+
132
+ # use libC to call free on the data buffer; earlier versions used an
133
+ # FFI::Function here that called back into Ruby, but Rubinius won't
134
+ # support that and there are issues with the other runtimes too
135
+ LibXS.xs_msg_init_data @pointer, data_buffer, len, LibC::Free, nil
136
+ end
137
+
138
+ # Provides the memory address of the +xs_msg_t+ struct. Used mostly for
139
+ # passing to other methods accessing the underlying library that
140
+ # require a real data address.
141
+ #
142
+ def address
143
+ @pointer
144
+ end
145
+ alias :pointer :address
146
+
147
+ def copy source
148
+ LibXS.xs_msg_copy @pointer, source
149
+ end
150
+
151
+ def move source
152
+ LibXS.xs_msg_move @pointer, source
153
+ end
154
+
155
+ # Provides the size of the data buffer for this +xs_msg_t+ C struct.
156
+ #
157
+ def size
158
+ LibXS.xs_msg_size @pointer
159
+ end
160
+
161
+ # Returns a pointer to the data buffer.
162
+ # This pointer should *never* be freed. It will automatically be freed
163
+ # when the +message+ object goes out of scope and gets garbage
164
+ # collected.
165
+ #
166
+ def data
167
+ LibXS.xs_msg_data @pointer
168
+ end
169
+
170
+ # Returns the data buffer as a string.
171
+ #
172
+ # Note: If this is binary data, it won't print very prettily.
173
+ #
174
+ def copy_out_string
175
+ data.read_string(size)
176
+ end
177
+
178
+ # Manually release the message struct and its associated data
179
+ # buffer.
180
+ #
181
+ # Only releases the buffer a single time. Subsequent calls are
182
+ # no ops.
183
+ #
184
+ def close
185
+ rc = 0
186
+
187
+ if @pointer
188
+ rc = LibXS.xs_msg_close @pointer
189
+ @pointer = nil
190
+ end
191
+
192
+ rc
193
+ end
194
+
195
+ # cache the msg size so we don't have to recalculate it when creating
196
+ # each new instance
197
+ @msg_size = LibXS::Msg.size
198
+
199
+ def self.msg_size() @msg_size; end
200
+
201
+ end # class Message
202
+
203
+
204
+
205
+ # A subclass of #Message that includes finalizers for deallocating
206
+ # native memory when this object is garbage collected. Note that on
207
+ # certain Ruby runtimes the use of finalizers can add 10s of
208
+ # microseconds of overhead for each message. The convenience comes
209
+ # at a price.
210
+ #
211
+ # The constructor optionally takes a string as an argument. It will
212
+ # copy this string to native memory in preparation for transmission.
213
+ # So, don't pass a string unless you intend to send it. Internally it
214
+ # calls #copy_in_string.
215
+ #
216
+ # Call #close to release buffers when you have *not* passed this on
217
+ # to Socket#send. That method calls #close on your behalf.
218
+ #
219
+ # When you are done using a *received* message object, just let it go out of
220
+ # scope to release the memory. During the next garbage collection run
221
+ # it will call the equivalent of #LibXS.xs_msg_close to release
222
+ # all buffers. Obviously, this automatic collection of message objects
223
+ # comes at the price of a larger memory footprint (for the
224
+ # finalizer proc object) and lower performance. If you wanted blistering
225
+ # performance, Ruby isn't there just yet.
226
+ #
227
+ # As noted above, for sent objects the underlying library will call close
228
+ # for you.
229
+ #
230
+ class ManagedMessage < Message
231
+ # Makes a copy of +len+ bytes from the ruby string +bytes+. Library
232
+ # handles deallocation of the native memory buffer.
233
+ #
234
+ def copy_in_bytes bytes, len
235
+ rc = super(bytes, len)
236
+
237
+ # make sure we have a way to deallocate this memory if the object goes
238
+ # out of scope
239
+ define_finalizer
240
+ rc
241
+ end
242
+
243
+ # Manually release the message struct and its associated data
244
+ # buffer.
245
+ #
246
+ def close
247
+ rc = super()
248
+ remove_finalizer
249
+ rc
250
+ end
251
+
252
+
253
+ private
254
+
255
+ def define_finalizer
256
+ ObjectSpace.define_finalizer(self, self.class.close(@pointer))
257
+ end
258
+
259
+ def remove_finalizer
260
+ ObjectSpace.undefine_finalizer self
261
+ end
262
+
263
+ # Message finalizer
264
+ # Note that there is no error checking for the call to #xs_msg_close.
265
+ # This is intentional. Since this code runs as a finalizer, there is no
266
+ # way to catch a raised exception anywhere near where the error actually
267
+ # occurred in the code, so we just ignore deallocation failures here.
268
+ def self.close ptr
269
+ Proc.new do
270
+ # release the data buffer
271
+ LibXS.xs_msg_close ptr
272
+ end
273
+ end
274
+
275
+ # cache the msg size so we don't have to recalculate it when creating
276
+ # each new instance
277
+ # need to do this again because ivars are not inheritable
278
+ @msg_size = LibXS::Msg.size
279
+
280
+ end # class ManagedMessage
281
+
282
+ end # module XS
@@ -0,0 +1,282 @@
1
+
2
+ module XS
3
+
4
+ # The factory constructor optionally takes a string as an argument. It will
5
+ # copy this string to native memory in preparation for transmission.
6
+ # So, don't pass a string unless you intend to send it. Internally it
7
+ # calls #copy_in_string.
8
+ #
9
+ # Call #close to release buffers when you are done with the data.
10
+ #
11
+ # (This class is not really zero-copy. Ruby makes this near impossible
12
+ # since Ruby objects can be relocated in memory by the GC at any
13
+ # time. There is no way to peg them to native memory or have them
14
+ # use non-movable native memory as backing store.)
15
+ #
16
+ # Message represents ruby equivalent of the +xs_msg_t+ C struct.
17
+ # Access the underlying memory buffer and the buffer size using the
18
+ # #data and #size methods respectively.
19
+ #
20
+ # It is recommended that this class be composed inside another class for
21
+ # access to the underlying buffer. The outer wrapper class can provide
22
+ # nice accessors for the information in the data buffer; a clever
23
+ # implementation can probably lazily encode/decode the data buffer
24
+ # on demand. Lots of protocols send more information than is strictly
25
+ # necessary, so only decode (copy from the 0mq buffer to Ruby) that
26
+ # which is necessary.
27
+ #
28
+ # When you are done using a *received* message object, call #close to
29
+ # release the associated buffers.
30
+ #
31
+ # received_message = Message.create
32
+ # if received_message
33
+ # rc = socket.recvmsg(received_message)
34
+ # if XS::Util.resultcode_ok?(rc)
35
+ # puts "Message contained: #{received_message.copy_out_string}"
36
+ # else
37
+ # STDERR.puts "Error when receiving message: #{XS::Util.error_string}"
38
+ # end
39
+ #
40
+ #
41
+ # Define a custom layout for the data sent between Crossroads peers.
42
+ #
43
+ # class MyMessage
44
+ # class Layout < FFI::Struct
45
+ # layout :value1, :uint8,
46
+ # :value2, :uint64,
47
+ # :value3, :uint32,
48
+ # :value4, [:char, 30]
49
+ # end
50
+ #
51
+ # def initialize msg_struct = nil
52
+ # if msg_struct
53
+ # @msg_t = msg_struct
54
+ # @data = Layout.new(@msg_t.data)
55
+ # else
56
+ # @pointer = FFI::MemoryPointer.new :byte, Layout.size, true
57
+ # @data = Layout.new @pointer
58
+ # end
59
+ # end
60
+ #
61
+ # def size() @size = @msg_t.size; end
62
+ #
63
+ # def value1
64
+ # @data[:value1]
65
+ # end
66
+ #
67
+ # def value4
68
+ # @data[:value4].to_ptr.read_string
69
+ # end
70
+ #
71
+ # def value1=(val)
72
+ # @data[:value1] = val
73
+ # end
74
+ #
75
+ # def create_sendable_message
76
+ # msg = Message.new
77
+ # msg.copy_in_bytes @pointer, Layout.size
78
+ # end
79
+ #
80
+ #
81
+ # message = Message.new
82
+ # successful_read = socket.recv message
83
+ # message = MyMessage.new message if successful_read
84
+ # puts "value1 is #{message.value1}"
85
+ #
86
+ class Message
87
+ include XS::Util
88
+
89
+ # Recommended way to create a standard message. A Message object is
90
+ # returned upon success, nil when allocation fails.
91
+ #
92
+ def self.create message = nil
93
+ new(message) rescue nil
94
+ end
95
+
96
+ def initialize message = nil
97
+ # allocate our own pointer so that we can tell it to *not* zero out
98
+ # the memory; it's pointless work since the library is going to
99
+ # overwrite it anyway.
100
+ @pointer = FFI::MemoryPointer.new Message.msg_size, 1, false
101
+
102
+ if message
103
+ copy_in_string message
104
+ else
105
+ # initialize an empty message structure to receive a message
106
+ result_code = LibXS.xs_msg_init @pointer
107
+ raise unless Util.resultcode_ok?(result_code)
108
+ end
109
+ end
110
+
111
+ # Makes a copy of the ruby +string+ into a native memory buffer so
112
+ # that libzmq can send it. The underlying library will handle
113
+ # deallocation of the native memory buffer.
114
+ #
115
+ # Can only be initialized via #copy_in_string or #copy_in_bytes once.
116
+ #
117
+ def copy_in_string string
118
+ string_size = string.respond_to?(:bytesize) ? string.bytesize : string.size
119
+ copy_in_bytes string, string_size if string
120
+ end
121
+
122
+ # Makes a copy of +len+ bytes from the ruby string +bytes+. Library
123
+ # handles deallocation of the native memory buffer.
124
+ #
125
+ # Can only be initialized via #copy_in_string or #copy_in_bytes once.
126
+ #
127
+ def copy_in_bytes bytes, len
128
+ data_buffer = LibC.malloc len
129
+ # writes the exact number of bytes, no null byte to terminate string
130
+ data_buffer.write_string bytes, len
131
+
132
+ # use libC to call free on the data buffer; earlier versions used an
133
+ # FFI::Function here that called back into Ruby, but Rubinius won't
134
+ # support that and there are issues with the other runtimes too
135
+ LibXS.zmq_msg_init_data @pointer, data_buffer, len, LibC::Free, nil
136
+ end
137
+
138
+ # Provides the memory address of the +zmq_msg_t+ struct. Used mostly for
139
+ # passing to other methods accessing the underlying library that
140
+ # require a real data address.
141
+ #
142
+ def address
143
+ @pointer
144
+ end
145
+ alias :pointer :address
146
+
147
+ def copy source
148
+ LibXS.xs_msg_copy @pointer, source
149
+ end
150
+
151
+ def move source
152
+ LibXS.xs_msg_move @pointer, source
153
+ end
154
+
155
+ # Provides the size of the data buffer for this +zmq_msg_t+ C struct.
156
+ #
157
+ def size
158
+ LibZMQ.xs_msg_size @pointer
159
+ end
160
+
161
+ # Returns a pointer to the data buffer.
162
+ # This pointer should *never* be freed. It will automatically be freed
163
+ # when the +message+ object goes out of scope and gets garbage
164
+ # collected.
165
+ #
166
+ def data
167
+ LibXS.xs_msg_data @pointer
168
+ end
169
+
170
+ # Returns the data buffer as a string.
171
+ #
172
+ # Note: If this is binary data, it won't print very prettily.
173
+ #
174
+ def copy_out_string
175
+ data.read_string(size)
176
+ end
177
+
178
+ # Manually release the message struct and its associated data
179
+ # buffer.
180
+ #
181
+ # Only releases the buffer a single time. Subsequent calls are
182
+ # no ops.
183
+ #
184
+ def close
185
+ rc = 0
186
+
187
+ if @pointer
188
+ rc = LibXS.xs_msg_close @pointer
189
+ @pointer = nil
190
+ end
191
+
192
+ rc
193
+ end
194
+
195
+ # cache the msg size so we don't have to recalculate it when creating
196
+ # each new instance
197
+ @msg_size = LibXS::Msg.size
198
+
199
+ def self.msg_size() @msg_size; end
200
+
201
+ end # class Message
202
+
203
+
204
+
205
+ # A subclass of #Message that includes finalizers for deallocating
206
+ # native memory when this object is garbage collected. Note that on
207
+ # certain Ruby runtimes the use of finalizers can add 10s of
208
+ # microseconds of overhead for each message. The convenience comes
209
+ # at a price.
210
+ #
211
+ # The constructor optionally takes a string as an argument. It will
212
+ # copy this string to native memory in preparation for transmission.
213
+ # So, don't pass a string unless you intend to send it. Internally it
214
+ # calls #copy_in_string.
215
+ #
216
+ # Call #close to release buffers when you have *not* passed this on
217
+ # to Socket#send. That method calls #close on your behalf.
218
+ #
219
+ # When you are done using a *received* message object, just let it go out of
220
+ # scope to release the memory. During the next garbage collection run
221
+ # it will call the equivalent of #LibZMQ.zmq_msg_close to release
222
+ # all buffers. Obviously, this automatic collection of message objects
223
+ # comes at the price of a larger memory footprint (for the
224
+ # finalizer proc object) and lower performance. If you wanted blistering
225
+ # performance, Ruby isn't there just yet.
226
+ #
227
+ # As noted above, for sent objects the underlying library will call close
228
+ # for you.
229
+ #
230
+ class ManagedMessage < Message
231
+ # Makes a copy of +len+ bytes from the ruby string +bytes+. Library
232
+ # handles deallocation of the native memory buffer.
233
+ #
234
+ def copy_in_bytes bytes, len
235
+ rc = super(bytes, len)
236
+
237
+ # make sure we have a way to deallocate this memory if the object goes
238
+ # out of scope
239
+ define_finalizer
240
+ rc
241
+ end
242
+
243
+ # Manually release the message struct and its associated data
244
+ # buffer.
245
+ #
246
+ def close
247
+ rc = super()
248
+ remove_finalizer
249
+ rc
250
+ end
251
+
252
+
253
+ private
254
+
255
+ def define_finalizer
256
+ ObjectSpace.define_finalizer(self, self.class.close(@pointer))
257
+ end
258
+
259
+ def remove_finalizer
260
+ ObjectSpace.undefine_finalizer self
261
+ end
262
+
263
+ # Message finalizer
264
+ # Note that there is no error checking for the call to #zmq_msg_close.
265
+ # This is intentional. Since this code runs as a finalizer, there is no
266
+ # way to catch a raised exception anywhere near where the error actually
267
+ # occurred in the code, so we just ignore deallocation failures here.
268
+ def self.close ptr
269
+ Proc.new do
270
+ # release the data buffer
271
+ LibXS.zmq_msg_close ptr
272
+ end
273
+ end
274
+
275
+ # cache the msg size so we don't have to recalculate it when creating
276
+ # each new instance
277
+ # need to do this again because ivars are not inheritable
278
+ @msg_size = LibXS::Msg.size
279
+
280
+ end # class ManagedMessage
281
+
282
+ end # module XS