ruby_io_splice.git  about / heads / tags
zero-copy pipe I/O for Linux and Ruby
blob d60a24d9075c9a98d80dee76e0782ceca980dc96 3772 bytes (raw)
$ git show v2.1.0: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
 
# -*- encoding: binary -*-
require 'io_splice_ext'

class IO

  module Splice

    # the version of IO::Splice, currently 2.1.0
    VERSION = '2.1.0'

    # 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

    # copies the contents of the IO object given by +src+ to +dst+
    # If len is specified, then only len bytes are 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+
    def Splice.copy_stream(src, dst, len = nil, src_offset = nil)
      close = []
      src.kind_of?(String) and close << (src = File.open(src, 'rb'))
      dst.kind_of?(String) and close << (dst = File.open(dst, 'wb'))
      src, dst = src.to_io, dst.to_io
      rv = len
      src.sysseek(src_offset) if src_offset
      select_args = selectable(src, dst)

      if src.stat.pipe? || dst.stat.pipe?
        if len
          len -= full(src, dst, len, select_args) until len == 0
        else
          rv = 0
          begin
            rv += partial(src, dst, PIPE_CAPA, select_args)
          rescue EOFError
            break
          end while true
        end
      else
        r, w = tmp = IO.pipe
        close.concat(tmp)
        if len
          while len != 0
            nr = partial(src, w, len, select_args)
            len -= full(r, dst, nr, select_args)
          end
        else
          rv = 0
          begin
            nr = partial(src, w, PIPE_CAPA, select_args)
            rv += full(r, dst, nr, select_args)
          rescue EOFError
            break
          end while true
        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+
    # bytes.  Returns the nubmer of bytes actually spliced (always +len+)
    # The +_select_args+ parameter is reserved for internal use and
    # may be removed in future versions.  Do not write code that
    # depends on +_select_args+.
    def Splice.full(src, dst, len, _select_args = selectable(src, dst))
      nr = len
      nr -= partial(src, dst, nr, _select_args) until nr == 0
      len
    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
    # The +_select_args+ parameter is reserved for internal use and
    # may be removed in future versions.  Do not write code that
    # depends on +_select_args+.
    def Splice.partial(src, dst, len, _select_args = selectable(src, dst))
      begin
        IO.splice(src, nil, dst, nil, len, F_MOVE)
      rescue Errno::EAGAIN
        IO.select(*_select_args)
        retry
      end
    end

    # returns an array suitable for splat-ing to IO.select for blocking I/O
    def Splice.selectable(src, dst)
      rv = []
      src.stat.pipe? or rv[0] = [ src ]
      dst.stat.pipe? or rv[1] = [ dst ]
      rv
    end

  end
end

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