Class: Yahns::Wbuf

Inherits:
Object
  • Object
show all
Includes:
WbufCommon
Defined in:
lib/yahns/wbuf.rb

Overview

This class is triggered whenever we need write buffering for clients reading responses slowly. Small responses which fit into kernel socket buffers do not trigger this. yahns will always attempt to write to kernel socket buffers first to avoid unnecessary copies in userspace.

Thus, most data is into copied to the kernel only once, the kernel will perform zero-copy I/O from the page cache to the socket. The only time data may be copied twice is the initial write()/send() which triggers EAGAIN.

We only buffer to the filesystem (note: likely not a disk, since this is short-lived). We let the sysadmin/kernel decide whether or not the data needs to hit disk or not.

This avoids large allocations from malloc, potentially limiting fragmentation and keeping (common) smaller allocations fast. General purpose malloc implementations in the 64-bit era tend to avoid releasing memory back to the kernel, so large heap allocations are best avoided as the kernel has little chance to reclaim memory used for a temporary buffer.

The biggest downside of this approach is it requires an FD, but yahns configurations are configured for many FDs anyways, so it’s unlikely to be a scalability issue.

Instance Method Summary collapse

Methods included from WbufCommon

#wbuf_close_common, #wbuf_flush

Constructor Details

#initialize(body, persist, tmpdir) ⇒ Wbuf

Returns a new instance of Wbuf.



33
34
35
36
37
38
39
40
# File 'lib/yahns/wbuf.rb', line 33

def initialize(body, persist, tmpdir)
  @tmpio = nil
  @tmpdir = tmpdir
  @sf_offset = @sf_count = 0
  @wbuf_persist = persist # whether or not we keep the connection alive
  @body = body
  @bypass = false
end

Instance Method Details

#wbuf_close(client) ⇒ Object

called by last wbuf_flush



75
76
77
78
# File 'lib/yahns/wbuf.rb', line 75

def wbuf_close(client)
  @tmpio = @tmpio.close if @tmpio
  wbuf_close_common(client)
end

#wbuf_write(client, buf) ⇒ Object



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
# File 'lib/yahns/wbuf.rb', line 42

def wbuf_write(client, buf)
  # try to bypass the VFS layer if we're all caught up
  case rv = client.kgio_trywrite(buf)
  when String
    buf = rv # retry in loop
  when nil
    return # yay! hopefully we don't have to buffer again
  when :wait_writable, :wait_readable
    @bypass = false # ugh, continue to buffering to file
  end while @bypass

  @tmpio ||= Yahns::TmpIO.new(@tmpdir)
  @sf_count += @tmpio.write(buf)
  case rv = client.trysendfile(@tmpio, @sf_offset, @sf_count)
  when Integer
    @sf_count -= rv
    @sf_offset += rv
  when :wait_writable, :wait_readable
    return rv
  else
    raise "BUG: #{rv.nil ? "EOF" : rv.inspect} on tmpio " \
          "sf_offset=#@sf_offset sf_count=#@sf_count"
  end while @sf_count > 0

  # we're all caught up, try to prevent dirty data from getting flushed
  # to disk if we can help it.
  @tmpio = @tmpio.close
  @sf_offset = 0
  @bypass = true
  nil
end