gitlab-org--gitlab-foss/lib/gitlab/ci/trace/chunked_io.rb

225 lines
4.9 KiB
Ruby
Raw Normal View History

2018-04-04 06:19:17 -04:00
##
# This class is compatible with IO class (https://ruby-doc.org/core-2.3.1/IO.html)
# source: https://gitlab.com/snippets/1685610
module Gitlab
module Ci
class Trace
class ChunkedIO
CHUNK_SIZE = ::Ci::JobTraceChunk::CHUNK_SIZE
FailedToGetChunkError = Class.new(StandardError)
attr_reader :job
attr_reader :tell, :size
attr_reader :chunk, :chunk_range
alias_method :pos, :tell
2018-04-04 14:16:51 -04:00
def initialize(job, &block)
2018-04-04 06:19:17 -04:00
@job = job
@chunks_cache = []
@tell = 0
@size = job_chunks.last.try(&:end_offset).to_i
2018-04-04 14:16:51 -04:00
yield self if block_given?
2018-04-04 06:19:17 -04:00
end
def close
# no-op
end
def binmode
# no-op
end
def binmode?
true
end
def path
nil
end
def url
nil
end
def seek(pos, where = IO::SEEK_SET)
new_pos =
case where
when IO::SEEK_END
size + pos
when IO::SEEK_SET
pos
when IO::SEEK_CUR
tell + pos
else
-1
end
raise 'new position is outside of file' if new_pos < 0 || new_pos > size
@tell = new_pos
end
def eof?
tell == size
end
def each_line
until eof?
line = readline
break if line.nil?
yield(line)
end
end
2018-04-04 14:16:51 -04:00
def read(length = (size - tell), outbuf = "")
2018-04-04 06:19:17 -04:00
out = ""
2018-04-04 14:16:51 -04:00
end_tell = [tell + length, size].min
2018-04-04 06:19:17 -04:00
2018-04-04 14:16:51 -04:00
until end_tell <= tell
2018-04-04 06:19:17 -04:00
data = chunk_slice_from_offset
break if data.empty?
2018-04-04 14:16:51 -04:00
data = data[0, (length % CHUNK_SIZE)] if data.bytesize + tell >= end_tell
2018-04-04 06:19:17 -04:00
out << data
@tell += data.bytesize
end
2018-04-05 02:26:57 -04:00
# If outbuf is passed, we put the output into the buffer. This supports IO.copy_stream functionality
if outbuf
outbuf.slice!(0, outbuf.bytesize)
outbuf << out
end
2018-04-04 06:19:17 -04:00
out
end
def readline
out = ""
until eof?
data = chunk_slice_from_offset
new_line = data.index("\n")
if !new_line.nil?
out << data[0..new_line]
@tell += new_line + 1
break
else
out << data
@tell += data.bytesize
end
end
out
end
def write(data)
2018-04-04 14:16:51 -04:00
start_pos = tell
2018-04-04 06:19:17 -04:00
2018-04-04 14:16:51 -04:00
while tell < start_pos + data.bytesize
2018-04-04 06:19:17 -04:00
# get slice from current offset till the end where it falls into chunk
chunk_bytes = CHUNK_SIZE - chunk_offset
2018-04-04 14:16:51 -04:00
chunk_data = data.byteslice(tell - start_pos, chunk_bytes)
2018-04-04 06:19:17 -04:00
# append data to chunk, overwriting from that point
ensure_chunk.append(chunk_data, chunk_offset)
# move offsets within buffer
@tell += chunk_bytes
2018-04-04 14:16:51 -04:00
@size = [size, tell].max
2018-04-04 06:19:17 -04:00
end
end
def truncate(offset)
raise 'Outside of file' if offset > size
@tell = offset
@size = offset
invalidate_chunk_cache
# remove all next chunks
job_chunks.where('chunk_index > ?', chunk_index).destroy_all
# truncate current chunk
current_chunk.truncate(chunk_offset) if chunk_offset != 0
end
def flush
# no-op
end
def present?
true
end
def destroy!
job_chunks.destroy_all
invalidate_chunk_cache
end
private
##
# The below methods are not implemented in IO class
#
def in_range?
@chunk_range&.include?(tell)
end
def chunk_slice_from_offset
unless in_range?
current_chunk.tap do |chunk|
raise FailedToGetChunkError unless chunk
@chunk = chunk.data.force_encoding(Encoding::BINARY)
@chunk_range = chunk.range
end
end
2018-04-04 14:16:51 -04:00
@chunk[chunk_offset..CHUNK_SIZE]
2018-04-04 06:19:17 -04:00
end
def chunk_offset
tell % CHUNK_SIZE
end
def chunk_index
tell / CHUNK_SIZE
end
def chunk_start
chunk_index * CHUNK_SIZE
end
def chunk_end
[chunk_start + CHUNK_SIZE, size].min
end
def invalidate_chunk_cache
@chunks_cache = []
end
def current_chunk
@chunks_cache[chunk_index] ||= job_chunks.find_by(chunk_index: chunk_index)
end
def build_chunk
2018-04-04 14:16:51 -04:00
@chunks_cache[chunk_index] = ::Ci::JobTraceChunk.new(job: job, chunk_index: chunk_index)
2018-04-04 06:19:17 -04:00
end
def ensure_chunk
current_chunk || build_chunk
end
def job_chunks
2018-04-04 14:16:51 -04:00
::Ci::JobTraceChunk.where(job: job)
2018-04-04 06:19:17 -04:00
end
end
end
end
end