Class: Yahns::Wbuf
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
-
#initialize(body, persist, tmpdir) ⇒ Wbuf
constructor
A new instance of Wbuf.
-
#wbuf_close(client) ⇒ Object
called by last wbuf_flush.
- #wbuf_write(client, buf) ⇒ Object
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 |