ruby_io_splice.git  about / heads / tags
zero-copy pipe I/O for Linux and Ruby
blob 9188c300adba700ae229050dc2b9239ba681eb5e 4326 bytes (raw)
$ git show HEAD:lib/io/splice.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 
# -*- encoding: binary -*-
require 'io_splice_ext'
require 'io/wait'

module IO::Splice
  @warned = false

  def self.__deprecated # :nodoc:
    return if @warned
    warn("IO::Splice.{copy_stream,full} are deprecated " \
         "and to be removed in io_splice 5.x")
    @warned = true
  end

  # The maximum default capacity of the pipe in bytes.
  # Under stock Linux, this is 65536 bytes as of 2.6.11, and 4096 before
  # We detect this at runtime as it is easy to recompile the kernel
  # and set a new value.
  # Starting with Linux 2.6.35, pipe capacity will be tunable
  # and this will only represent the default capacity of a
  # newly-created pipe.
  PIPE_CAPA = begin
    rd, wr = IO.pipe
    buf = ' ' * PIPE_BUF
    n = 0
    begin
      n += wr.write_nonblock(buf)
    rescue Errno::EAGAIN
      break
    end while true
    wr.close
    rd.close
    n
  end

  def self.need_open?(obj) # :nodoc:
    return false if obj.respond_to?(:to_io)
    obj.respond_to?(:to_path) || obj.kind_of?(String)
  end

  # copies the contents of the IO object given by +src+ to +dst+
  # If +len+ is specified, then only +len+ bytes are copied and
  # +EOFError+ is raised if fewer than +len+ bytes could be copied.
  # Otherwise the copy will be until EOF is reached on the +src+.
  # +src+ and +dst+ must be IO objects or respond to +to_io+
  #
  # Unlike IO.copy_stream, this does not take into account
  # userspace I/O buffers nor IO-like objects with no underlying
  # file descriptor (e.g. StringIO).
  #
  # This is unsafe for socket-to-socket copies unless there is an
  # active (blocking) reader on the other end.
  #
  # This method is deprecated and will be removed in a future, as it is
  # potentially unsafe for socket-to-socket operations and difficult-to-use.
  # IO.copy_stream on Linux 2.6.33 and later allows using sendfile for
  # file-to-file copies, so this offers no advantage.
  def self.copy_stream(src, dst, len = nil, src_offset = nil)
    __deprecated
    close = []
    need_open?(src) and close << (src = File.open(src))
    need_open?(dst) and close << (dst = File.open(dst, "w"))
    rv = len
    src, dst = src.to_io, dst.to_io

    if src.stat.pipe? || dst.stat.pipe?
      return full(src, dst, len, src_offset) if len
      rv = 0
      while n = partial(src, dst, MAX_AT_ONCE, src_offset)
        rv += n
        src_offset += n if src_offset
      end
    else
      r, w = tmp = IO.pipe
      close.concat(tmp)
      rv = 0
      if len
        while len != 0 && n = partial(src, w, len, src_offset)
          src_offset += n if src_offset
          rv += n
          len -= full(r, dst, n, nil)
        end
      else
        while n = partial(src, w, MAX_AT_ONCE, src_offset)
          src_offset += n if src_offset
          rv += full(r, dst, n, nil)
        end
      end
    end

    rv
  ensure
    close.each { |io| io.close }
  end

  # splice the full amount specified from +src+ to +dst+
  # Either +dst+ or +src+ must be a pipe.  +dst+ and +src+
  # may BOTH be pipes in Linux 2.6.31 or later.
  # This will block and wait for IO completion of +len+
  # Raises +EOFError+ if end of file is reached.
  # bytes.  Returns the number of bytes actually spliced (always +len+)
  # unless +src+ does not have +len+ bytes to read.
  #
  # Do not use this method to splice a socket +src+ into a pipe +dst+
  # unless there is another process or native thread doing a blocking
  # read on the other end of the +dst+ pipe.
  #
  # This method is safe for splicing a pipe +src+ into any type of +dst+ IO.
  def self.full(src, dst, len, src_offset)
    __deprecated
    IO.splice(src, src_offset, dst, nil, len, F_MOVE | WAITALL)
  end

  # splice up to +len+ bytes from +src+ to +dst+.
  # Either +dst+ or +src+ must be a pipe.  +dst+ and +src+
  # may BOTH be pipes in Linux 2.6.31 or later.
  # Returns the number of bytes actually spliced.
  # Like IO#readpartial, this never returns Errno::EAGAIN
  def self.partial(src, dst, len, src_offset)
    case rv = IO.trysplice(src, src_offset, dst, nil, len, F_MOVE)
    when :EAGAIN
      src.to_io.wait
      IO.select(nil, [dst])
    when Integer
      return rv
    else
      return nil
    end while true
  end
end
if (! defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") &&
   RUBY_VERSION.to_f <= 1.8
  require "io/splice/mri_18"
end

git clone https://yhbt.net/ruby_io_splice.git