ffi-rxs 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,659 +0,0 @@
1
-
2
- module XS
3
-
4
- module CommonSocketBehavior
5
- include XS::Util
6
-
7
- attr_reader :socket, :name
8
-
9
- # Allocates a socket of type +type+ for sending and receiving data.
10
- #
11
- # +type+ can be one of XS::REQ, XS::REP, XS::PUB,
12
- # XS::SUB, XS::PAIR, XS::PULL, XS::PUSH, XS::XREQ, XS::REP,
13
- # XS::DEALER or XS::ROUTER.
14
- #
15
- # By default, this class uses XS::Message for manual
16
- # memory management. For automatic garbage collection of received messages,
17
- # it is possible to override the :receiver_class to use XS::ManagedMessage.
18
- #
19
- # sock = Socket.create(Context.create, XS::REQ, :receiver_class => XS::ManagedMessage)
20
- #
21
- # Advanced users may want to replace the receiver class with their
22
- # own custom class. The custom class must conform to the same public API
23
- # as XS::Message.
24
- #
25
- # Creation of a new Socket object can return nil when socket creation
26
- # fails.
27
- #
28
- # if (socket = Socket.new(context.pointer, XS::REQ))
29
- # ...
30
- # else
31
- # STDERR.puts "Socket creation failed"
32
- # end
33
- #
34
- def self.create context_ptr, type, opts = {:receiver_class => XS::Message}
35
- new(context_ptr, type, opts) rescue nil
36
- end
37
-
38
- # To avoid rescuing exceptions, use the factory method #create for
39
- # all socket creation.
40
- #
41
- # Allocates a socket of type +type+ for sending and receiving data.
42
- #
43
- # +type+ can be one of XS::REQ, XS::REP, XS::PUB,
44
- # XS::SUB, XS::PAIR, XS::PULL, XS::PUSH, XS::XREQ, XS::REP,
45
- # XS::DEALER or XS::ROUTER.
46
- #
47
- # By default, this class uses XS::Message for manual
48
- # memory management. For automatic garbage collection of received messages,
49
- # it is possible to override the :receiver_class to use XS::ManagedMessage.
50
- #
51
- # sock = Socket.new(Context.new, XS::REQ, :receiver_class => XS::ManagedMessage)
52
- #
53
- # Advanced users may want to replace the receiver class with their
54
- # own custom class. The custom class must conform to the same public API
55
- # as XS::Message.
56
- #
57
- # Creation of a new Socket object can raise an exception. This occurs when the
58
- # +context_ptr+ is null or when the allocation of the Crossroads socket within the
59
- # context fails.
60
- #
61
- # begin
62
- # socket = Socket.new(context.pointer, XS::REQ)
63
- # rescue ContextError => e
64
- # # error handling
65
- # end
66
- #
67
- def initialize context_ptr, type, opts = {:receiver_class => XS::Message}
68
- # users may override the classes used for receiving; class must conform to the
69
- # same public API as XS::Message
70
- @receiver_klass = opts[:receiver_class]
71
-
72
- context_ptr = context_ptr.pointer if context_ptr.kind_of?(XS::Context)
73
-
74
- unless context_ptr.null?
75
- @socket = LibXS.xs_socket context_ptr, type
76
- if @socket && !@socket.null?
77
- @name = SocketTypeNameMap[type]
78
- else
79
- raise ContextError.new 'xs_socket', 0, ETERM, "Socket pointer was null"
80
- end
81
- else
82
- raise ContextError.new 'xs_socket', 0, ETERM, "Context pointer was null"
83
- end
84
-
85
- @longlong_cache = @int_cache = nil
86
- @more_parts_array = []
87
- @option_lookup = []
88
- populate_option_lookup
89
-
90
- define_finalizer
91
- end
92
-
93
- # Set the queue options on this socket.
94
- #
95
- # Valid +name+ values that take a numeric +value+ are:
96
- # XS::HWM
97
- # XS::SWAP (version 2 only)
98
- # XS::AFFINITY
99
- # XS::RATE
100
- # XS::RECOVERY_IVL
101
- # XS::MCAST_LOOP (version 2 only)
102
- # XS::LINGER
103
- # XS::RECONNECT_IVL
104
- # XS::BACKLOG
105
- # XS::RECOVER_IVL_MSEC (version 2 only)
106
- # XS::RECONNECT_IVL_MAX (version 3 only)
107
- # XS::MAXMSGSIZE (version 3 only)
108
- # XS::SNDHWM (version 3 only)
109
- # XS::RCVHWM (version 3 only)
110
- # XS::MULTICAST_HOPS (version 3 only)
111
- # XS::RCVTIMEO (version 3 only)
112
- # XS::SNDTIMEO (version 3 only)
113
- #
114
- # Valid +name+ values that take a string +value+ are:
115
- # XS::IDENTITY (version 2/3 only)
116
- # XS::SUBSCRIBE
117
- # XS::UNSUBSCRIBE
118
- #
119
- # Returns 0 when the operation completed successfully.
120
- # Returns -1 when this operation failed.
121
- #
122
- # With a -1 return code, the user must check XS.errno to determine the
123
- # cause.
124
- #
125
- # rc = socket.setsockopt(XS::LINGER, 1_000)
126
- # XS::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed")
127
- #
128
- def setsockopt name, value, length = nil
129
- if 1 == @option_lookup[name]
130
- length = 8
131
- pointer = LibC.malloc length
132
- pointer.write_long_long value
133
-
134
- elsif 0 == @option_lookup[name]
135
- length = 4
136
- pointer = LibC.malloc length
137
- pointer.write_int value
138
-
139
- elsif 2 == @option_lookup[name]
140
- length ||= value.size
141
-
142
- # note: not checking errno for failed memory allocations :(
143
- pointer = LibC.malloc length
144
- pointer.write_string value
145
- end
146
-
147
- rc = LibXS.xs_setsockopt @socket, name, pointer, length
148
- LibC.free(pointer) unless pointer.nil? || pointer.null?
149
- rc
150
- end
151
-
152
- # Convenience method for checking on additional message parts.
153
- #
154
- # Equivalent to calling Socket#getsockopt with XS::RCVMORE.
155
- #
156
- # Warning: if the call to #getsockopt fails, this method will return
157
- # false and swallow the error.
158
- #
159
- # message_parts = []
160
- # message = Message.new
161
- # rc = socket.recvmsg(message)
162
- # if XS::Util.resultcode_ok?(rc)
163
- # message_parts << message
164
- # while more_parts?
165
- # message = Message.new
166
- # rc = socket.recvmsg(message)
167
- # message_parts.push(message) if resulcode_ok?(rc)
168
- # end
169
- # end
170
- #
171
- def more_parts?
172
- rc = getsockopt XS::RCVMORE, @more_parts_array
173
-
174
- Util.resultcode_ok?(rc) ? @more_parts_array.at(0) : false
175
- end
176
-
177
- # Binds the socket to an +address+.
178
- #
179
- # socket.bind("tcp://127.0.0.1:5555")
180
- #
181
- def bind address
182
- LibXS.xs_bind @socket, address
183
- end
184
-
185
- # Connects the socket to an +address+.
186
- #
187
- # rc = socket.connect("tcp://127.0.0.1:5555")
188
- #
189
- def connect address
190
- rc = LibXS.xs_connect @socket, address
191
- end
192
-
193
- # Closes the socket. Any unprocessed messages in queue are sent or dropped
194
- # depending upon the value of the socket option XS::LINGER.
195
- #
196
- # Returns 0 upon success *or* when the socket has already been closed.
197
- # Returns -1 when the operation fails. Check XS.errno for the error code.
198
- #
199
- # rc = socket.close
200
- # puts("Given socket was invalid!") unless 0 == rc
201
- #
202
- def close
203
- if @socket
204
- remove_finalizer
205
- rc = LibXS.xs_close @socket
206
- @socket = nil
207
- release_cache
208
- rc
209
- else
210
- 0
211
- end
212
- end
213
-
214
- # Queues the message for transmission. Message is assumed to conform to the
215
- # same public API as #Message.
216
- #
217
- # +flags+ may take two values:
218
- # * 0 (default) - blocking operation
219
- # * XS::NonBlocking - non-blocking operation
220
- # * XS::SNDMORE - this message is part of a multi-part message
221
- #
222
- # Returns 0 when the message was successfully enqueued.
223
- # Returns -1 under two conditions.
224
- # 1. The message could not be enqueued
225
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
226
- #
227
- # With a -1 return code, the user must check XS.errno to determine the
228
- # cause.
229
- #
230
- def sendmsg message, flags = 0
231
- __sendmsg__(@socket, message.address, flags)
232
- end
233
-
234
- # Helper method to make a new #Message instance out of the +string+ passed
235
- # in for transmission.
236
- #
237
- # +flags+ may be XS::NonBlocking and XS::SNDMORE.
238
- #
239
- # Returns 0 when the message was successfully enqueued.
240
- # Returns -1 under two conditions.
241
- # 1. The message could not be enqueued
242
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
243
- #
244
- # With a -1 return code, the user must check XS.errno to determine the
245
- # cause.
246
- #
247
- def send_string string, flags = 0
248
- message = Message.new string
249
- send_and_close message, flags
250
- end
251
-
252
- # Send a sequence of strings as a multipart message out of the +parts+
253
- # passed in for transmission. Every element of +parts+ should be
254
- # a String.
255
- #
256
- # +flags+ may be XS::NonBlocking.
257
- #
258
- # Returns 0 when the messages were successfully enqueued.
259
- # Returns -1 under two conditions.
260
- # 1. A message could not be enqueued
261
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
262
- #
263
- # With a -1 return code, the user must check XS.errno to determine the
264
- # cause.
265
- #
266
- def send_strings parts, flags = 0
267
- return -1 if !parts || parts.empty?
268
- flags = NonBlocking if dontwait?(flags)
269
-
270
- parts[0..-2].each do |part|
271
- rc = send_string part, (flags | XS::SNDMORE)
272
- return rc unless Util.resultcode_ok?(rc)
273
- end
274
-
275
- send_string parts[-1], flags
276
- end
277
-
278
- # Send a sequence of messages as a multipart message out of the +parts+
279
- # passed in for transmission. Every element of +parts+ should be
280
- # a Message (or subclass).
281
- #
282
- # +flags+ may be XS::NonBlocking.
283
- #
284
- # Returns 0 when the messages were successfully enqueued.
285
- # Returns -1 under two conditions.
286
- # 1. A message could not be enqueued
287
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
288
- #
289
- # With a -1 return code, the user must check XS.errno to determine the
290
- # cause.
291
- #
292
- def sendmsgs parts, flags = 0
293
- return -1 if !parts || parts.empty?
294
- flags = NonBlocking if dontwait?(flags)
295
-
296
- parts[0..-2].each do |part|
297
- rc = sendmsg part, (flags | XS::SNDMORE)
298
- return rc unless Util.resultcode_ok?(rc)
299
- end
300
-
301
- sendmsg parts[-1], flags
302
- end
303
-
304
- # Sends a message. This will automatically close the +message+ for both successful
305
- # and failed sends.
306
- #
307
- # Returns 0 when the message was successfully enqueued.
308
- # Returns -1 under two conditions.
309
- # 1. The message could not be enqueued
310
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
311
- #
312
- # With a -1 return code, the user must check XS.errno to determine the
313
- # cause.
314
- #
315
- def send_and_close message, flags = 0
316
- rc = sendmsg message, flags
317
- message.close
318
- rc
319
- end
320
-
321
- # Dequeues a message from the underlying queue. By default, this is a blocking operation.
322
- #
323
- # +flags+ may take two values:
324
- # 0 (default) - blocking operation
325
- # XS::NonBlocking - non-blocking operation
326
- #
327
- # Returns 0 when the message was successfully dequeued.
328
- # Returns -1 under two conditions.
329
- # 1. The message could not be dequeued
330
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
331
- #
332
- # With a -1 return code, the user must check XS.errno to determine the
333
- # cause.
334
- #
335
- # The application code is responsible for handling the +message+ object lifecycle
336
- # when #recv returns an error code.
337
- #
338
- def recvmsg message, flags = 0
339
- #LibXS.xs_recvmsg @socket, message.address, flags
340
- __recvmsg__(@socket, message.address, flags)
341
- end
342
-
343
- # Helper method to make a new #Message instance and convert its payload
344
- # to a string.
345
- #
346
- # +flags+ may be XS::NonBlocking.
347
- #
348
- # Returns 0 when the message was successfully dequeued.
349
- # Returns -1 under two conditions.
350
- # 1. The message could not be dequeued
351
- # 2. When +flags+ is set with XS::NonBlocking and the socket returned EAGAIN.
352
- #
353
- # With a -1 return code, the user must check XS.errno to determine the
354
- # cause.
355
- #
356
- # The application code is responsible for handling the +message+ object lifecycle
357
- # when #recv returns an error code.
358
- #
359
- def recv_string string, flags = 0
360
- message = @receiver_klass.new
361
- rc = recvmsg message, flags
362
- string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
363
- message.close
364
- rc
365
- end
366
-
367
- # Receive a multipart message as a list of strings.
368
- #
369
- # +flag+ may be XS::NonBlocking. Any other flag will be
370
- # removed.
371
- #
372
- def recv_strings list, flag = 0
373
- array = []
374
- rc = recvmsgs array, flag
375
-
376
- if Util.resultcode_ok?(rc)
377
- array.each do |message|
378
- list << message.copy_out_string
379
- message.close
380
- end
381
- end
382
-
383
- rc
384
- end
385
-
386
- # Receive a multipart message as an array of objects
387
- # (by default these are instances of Message).
388
- #
389
- # +flag+ may be XS::NonBlocking. Any other flag will be
390
- # removed.
391
- #
392
- def recvmsgs list, flag = 0
393
- flag = NonBlocking if dontwait?(flag)
394
-
395
- message = @receiver_klass.new
396
- rc = recvmsg message, flag
397
-
398
- if Util.resultcode_ok?(rc)
399
- list << message
400
-
401
- # check rc *first*; necessary because the call to #more_parts? can reset
402
- # the xs_errno to a weird value, so the xs_errno that was set on the
403
- # call to #recv gets lost
404
- while Util.resultcode_ok?(rc) && more_parts?
405
- message = @receiver_klass.new
406
- rc = recvmsg message, flag
407
-
408
- if Util.resultcode_ok?(rc)
409
- list << message
410
- else
411
- message.close
412
- list.each { |msg| msg.close }
413
- list.clear
414
- end
415
- end
416
- else
417
- message.close
418
- end
419
-
420
- rc
421
- end
422
-
423
- # Should only be used for XREQ, XREP, DEALER and ROUTER type sockets. Takes
424
- # a +list+ for receiving the message body parts and a +routing_envelope+
425
- # for receiving the message parts comprising the 0mq routing information.
426
- #
427
- def recv_multipart list, routing_envelope, flag = 0
428
- parts = []
429
- rc = recvmsgs parts, flag
430
-
431
- if Util.resultcode_ok?(rc)
432
- routing = true
433
- parts.each do |part|
434
- if routing
435
- routing_envelope << part
436
- routing = part.size > 0
437
- else
438
- list << part
439
- end
440
- end
441
- end
442
-
443
- rc
444
- end
445
-
446
-
447
- private
448
-
449
- def __getsockopt__ name, array
450
- # a small optimization so we only have to determine the option
451
- # type a single time; gives approx 5% speedup to do it this way.
452
- option_type = @option_lookup[name]
453
-
454
- value, length = sockopt_buffers option_type
455
-
456
- rc = LibXS.xs_getsockopt @socket, name, value, length
457
-
458
- if Util.resultcode_ok?(rc)
459
- array[0] = if 1 == option_type
460
- value.read_long_long
461
- elsif 0 == option_type
462
- value.read_int
463
- elsif 2 == option_type
464
- value.read_string(length.read_int)
465
- end
466
- end
467
-
468
- rc
469
- end
470
-
471
- # Calls to XS.getsockopt require us to pass in some pointers. We can cache and save those buffers
472
- # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
473
- # Cannot save the buffer for the IDENTITY.
474
- def sockopt_buffers option_type
475
- if 1 == option_type
476
- # int64_t or uint64_t
477
- unless @longlong_cache
478
- length = FFI::MemoryPointer.new :size_t
479
- length.write_int 8
480
- @longlong_cache = [FFI::MemoryPointer.new(:int64), length]
481
- end
482
-
483
- @longlong_cache
484
-
485
- elsif 0 == option_type
486
- # int, Crossroads assumes int is 4-bytes
487
- unless @int_cache
488
- length = FFI::MemoryPointer.new :size_t
489
- length.write_int 4
490
- @int_cache = [FFI::MemoryPointer.new(:int32), length]
491
- end
492
-
493
- @int_cache
494
-
495
- elsif 2 == option_type
496
- length = FFI::MemoryPointer.new :size_t
497
- # could be a string of up to 255 bytes
498
- length.write_int 255
499
- [FFI::MemoryPointer.new(255), length]
500
-
501
- else
502
- # uh oh, someone passed in an unknown option; use a slop buffer
503
- unless @int_cache
504
- length = FFI::MemoryPointer.new :size_t
505
- length.write_int 4
506
- @int_cache = [FFI::MemoryPointer.new(:int32), length]
507
- end
508
-
509
- @int_cache
510
- end
511
- end
512
-
513
- def populate_option_lookup
514
- # integer options
515
- [EVENTS, LINGER, RECONNECT_IVL, FD, TYPE, BACKLOG].each { |option| @option_lookup[option] = 0 }
516
-
517
- # long long options
518
- [RCVMORE, AFFINITY].each { |option| @option_lookup[option] = 1 }
519
-
520
- # string options
521
- [SUBSCRIBE, UNSUBSCRIBE].each { |option| @option_lookup[option] = 2 }
522
- end
523
-
524
- def release_cache
525
- @longlong_cache = nil
526
- @int_cache = nil
527
- end
528
-
529
- def dontwait?(flags)
530
- (NonBlocking & flags) == NonBlocking
531
- end
532
- alias :noblock? :dontwait?
533
- end # module CommonSocketBehavior
534
-
535
-
536
- module IdentitySupport
537
-
538
- # Convenience method for getting the value of the socket IDENTITY.
539
- #
540
- def identity
541
- array = []
542
- getsockopt IDENTITY, array
543
- array.at(0)
544
- end
545
-
546
- # Convenience method for setting the value of the socket IDENTITY.
547
- #
548
- def identity=(value)
549
- setsockopt IDENTITY, value.to_s
550
- end
551
-
552
-
553
- private
554
-
555
- def populate_option_lookup
556
- super()
557
-
558
- # string options
559
- [IDENTITY].each { |option| @option_lookup[option] = 2 }
560
- end
561
-
562
- end # module IdentitySupport
563
-
564
- class Socket
565
- include CommonSocketBehavior
566
- include IdentitySupport
567
-
568
- # Get the options set on this socket.
569
- #
570
- # +name+ determines the socket option to request
571
- # +array+ should be an empty array; a result of the proper type
572
- # (numeric, string, boolean) will be inserted into
573
- # the first position.
574
- #
575
- # Valid +option_name+ values:
576
- # XS::RCVMORE - true or false
577
- # XS::HWM - integer
578
- # XS::SWAP - integer
579
- # XS::AFFINITY - bitmap in an integer
580
- # XS::IDENTITY - string
581
- # XS::RATE - integer
582
- # XS::RECOVERY_IVL - integer
583
- # XS::SNDBUF - integer
584
- # XS::RCVBUF - integer
585
- # XS::FD - fd in an integer
586
- # XS::EVENTS - bitmap integer
587
- # XS::LINGER - integer measured in milliseconds
588
- # XS::RECONNECT_IVL - integer measured in milliseconds
589
- # XS::BACKLOG - integer
590
- # XS::RECOVER_IVL_MSEC - integer measured in milliseconds
591
- #
592
- # Returns 0 when the operation completed successfully.
593
- # Returns -1 when this operation failed.
594
- #
595
- # With a -1 return code, the user must check XS.errno to determine the
596
- # cause.
597
- #
598
- # # retrieve high water mark
599
- # array = []
600
- # rc = socket.getsockopt(XS::HWM, array)
601
- # hwm = array.first if XS::Util.resultcode_ok?(rc)
602
- #
603
- def getsockopt name, array
604
- rc = __getsockopt__ name, array
605
-
606
- if Util.resultcode_ok?(rc) && (RCVMORE == name)
607
- # convert to boolean
608
- array[0] = 1 == array[0]
609
- end
610
-
611
- rc
612
- end
613
-
614
-
615
- private
616
-
617
- def __sendmsg__(socket, address, flags)
618
- LibXS.zmq_sendmsg(socket, address, flags)
619
- end
620
-
621
- def __recvmsg__(socket, address, flags)
622
- LibXS.zmq_recvmsg(socket, address, flags)
623
- end
624
-
625
- def int_option? name
626
- super(name) ||
627
- RECONNECT_IVL_MAX == name ||
628
- RCVHWM == name ||
629
- SNDHWM == name ||
630
- RATE == name ||
631
- RECOVERY_IVL == name ||
632
- SNDBUF == name ||
633
- RCVBUF == name
634
- end
635
-
636
- def populate_option_lookup
637
- super()
638
-
639
- # integer options
640
- [RECONNECT_IVL_MAX, RCVHWM, SNDHWM, RATE, RECOVERY_IVL, SNDBUF, RCVBUF].each { |option| @option_lookup[option] = 0 }
641
- end
642
-
643
- # these finalizer-related methods cannot live in the CommonSocketBehavior
644
- # module; they *must* be in the class definition directly
645
-
646
- def define_finalizer
647
- ObjectSpace.define_finalizer(self, self.class.close(@socket))
648
- end
649
-
650
- def remove_finalizer
651
- ObjectSpace.undefine_finalizer self
652
- end
653
-
654
- def self.close socket
655
- Proc.new { LibXS.zmq_close socket }
656
- end
657
- end
658
-
659
- end # module XS