PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/redis/connection/ruby.rb

https://github.com/radiohead/redis-rb
Ruby | 258 lines | 193 code | 57 blank | 8 comment | 22 complexity | 445740b5b3a92d22477a2f5256884f14 MD5 | raw file
  1. require "redis/connection/registry"
  2. require "redis/connection/command_helper"
  3. require "redis/errors"
  4. require "socket"
  5. require "rbconfig"
  6. class Redis
  7. module Connection
  8. module SocketMixin
  9. CRLF = "\r\n".freeze
  10. def initialize(*args)
  11. super(*args)
  12. @timeout = nil
  13. @buffer = ""
  14. end
  15. def timeout=(timeout)
  16. if timeout && timeout > 0
  17. @timeout = timeout
  18. else
  19. @timeout = nil
  20. end
  21. end
  22. def read(nbytes)
  23. result = @buffer.slice!(0, nbytes)
  24. while result.bytesize < nbytes
  25. result << _read_from_socket(nbytes - result.bytesize)
  26. end
  27. result
  28. end
  29. def gets
  30. crlf = nil
  31. while (crlf = @buffer.index(CRLF)) == nil
  32. @buffer << _read_from_socket(1024)
  33. end
  34. @buffer.slice!(0, crlf + CRLF.bytesize)
  35. end
  36. def _read_from_socket(nbytes)
  37. begin
  38. read_nonblock(nbytes)
  39. rescue Errno::EWOULDBLOCK, Errno::EAGAIN
  40. if IO.select([self], nil, nil, @timeout)
  41. retry
  42. else
  43. raise Redis::TimeoutError
  44. end
  45. end
  46. rescue EOFError
  47. raise Errno::ECONNRESET
  48. end
  49. end
  50. if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
  51. require "timeout"
  52. class TCPSocket < ::TCPSocket
  53. include SocketMixin
  54. def self.connect(host, port, timeout)
  55. Timeout.timeout(timeout) do
  56. sock = new(host, port)
  57. sock
  58. end
  59. rescue Timeout::Error
  60. raise TimeoutError
  61. end
  62. end
  63. if RbConfig::CONFIG["host_os"] !~ /mswin|windows/i
  64. class UNIXSocket < ::UNIXSocket
  65. # This class doesn't include the mixin, because JRuby raises
  66. # Errno::EAGAIN on #read_nonblock even when IO.select says it is
  67. # readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
  68. # Therefore, fall back on the default Unix socket implementation,
  69. # without timeouts.
  70. def self.connect(path, timeout)
  71. Timeout.timeout(timeout) do
  72. sock = new(path)
  73. sock
  74. end
  75. rescue Timeout::Error
  76. raise TimeoutError
  77. end
  78. end
  79. end
  80. else
  81. class TCPSocket < ::Socket
  82. include SocketMixin
  83. def self.connect(host, port, timeout)
  84. # Limit lookup to IPv4, as Redis doesn't yet do IPv6...
  85. addr = ::Socket.getaddrinfo(host, nil, Socket::AF_INET)
  86. sock = new(::Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
  87. sockaddr = ::Socket.pack_sockaddr_in(port, addr[0][3])
  88. begin
  89. sock.connect_nonblock(sockaddr)
  90. rescue Errno::EINPROGRESS
  91. if IO.select(nil, [sock], nil, timeout) == nil
  92. raise TimeoutError
  93. end
  94. begin
  95. sock.connect_nonblock(sockaddr)
  96. rescue Errno::EISCONN
  97. end
  98. end
  99. sock
  100. end
  101. end
  102. class UNIXSocket < ::Socket
  103. # This class doesn't include the mixin to keep its behavior in sync
  104. # with the JRuby implementation.
  105. def self.connect(path, timeout)
  106. sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
  107. sockaddr = ::Socket.pack_sockaddr_un(path)
  108. begin
  109. sock.connect_nonblock(sockaddr)
  110. rescue Errno::EINPROGRESS
  111. if IO.select(nil, [sock], nil, timeout) == nil
  112. raise TimeoutError
  113. end
  114. begin
  115. sock.connect_nonblock(sockaddr)
  116. rescue Errno::EISCONN
  117. end
  118. end
  119. sock
  120. end
  121. end
  122. end
  123. class Ruby
  124. include Redis::Connection::CommandHelper
  125. MINUS = "-".freeze
  126. PLUS = "+".freeze
  127. COLON = ":".freeze
  128. DOLLAR = "$".freeze
  129. ASTERISK = "*".freeze
  130. def self.connect(config)
  131. if config[:scheme] == "unix"
  132. sock = UNIXSocket.connect(config[:path], config[:timeout])
  133. else
  134. sock = TCPSocket.connect(config[:host], config[:port], config[:timeout])
  135. end
  136. instance = new(sock)
  137. instance.timeout = config[:timeout]
  138. instance
  139. end
  140. def initialize(sock)
  141. @sock = sock
  142. end
  143. def connected?
  144. !! @sock
  145. end
  146. def disconnect
  147. @sock.close
  148. rescue
  149. ensure
  150. @sock = nil
  151. end
  152. def timeout=(timeout)
  153. if @sock.respond_to?(:timeout=)
  154. @sock.timeout = timeout
  155. end
  156. end
  157. def write(command)
  158. @sock.write(build_command(command))
  159. end
  160. def read
  161. line = @sock.gets
  162. reply_type = line.slice!(0, 1)
  163. format_reply(reply_type, line)
  164. rescue Errno::EAGAIN
  165. raise TimeoutError
  166. end
  167. def format_reply(reply_type, line)
  168. case reply_type
  169. when MINUS then format_error_reply(line)
  170. when PLUS then format_status_reply(line)
  171. when COLON then format_integer_reply(line)
  172. when DOLLAR then format_bulk_reply(line)
  173. when ASTERISK then format_multi_bulk_reply(line)
  174. else raise ProtocolError.new(reply_type)
  175. end
  176. end
  177. def format_error_reply(line)
  178. CommandError.new(line.strip)
  179. end
  180. def format_status_reply(line)
  181. line.strip
  182. end
  183. def format_integer_reply(line)
  184. line.to_i
  185. end
  186. def format_bulk_reply(line)
  187. bulklen = line.to_i
  188. return if bulklen == -1
  189. reply = encode(@sock.read(bulklen))
  190. @sock.read(2) # Discard CRLF.
  191. reply
  192. end
  193. def format_multi_bulk_reply(line)
  194. n = line.to_i
  195. return if n == -1
  196. Array.new(n) { read }
  197. end
  198. end
  199. end
  200. end
  201. Redis::Connection.drivers << Redis::Connection::Ruby