PageRenderTime 63ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/ruby/ffi-rxs/socket.rb

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