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,659 @@
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.xs_sendmsg(socket, address, flags)
619
+ end
620
+
621
+ def __recvmsg__(socket, address, flags)
622
+ LibXS.xs_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.xs_close socket }
656
+ end
657
+ end
658
+
659
+ end # module XS