Implement readInto and read(n)

This commit is contained in:
Zahary Karadjov 2020-05-01 21:39:08 +03:00
commit bf930b6f1b
No known key found for this signature in database
GPG key ID: C8936F8A3073D609
12 changed files with 636 additions and 220 deletions

View file

@ -4,22 +4,13 @@ import
async_backend
type
PageKind* = enum
userPage
stringPage
mallocPage
PageSpan* = object
startAddr*, endAddr*: ptr byte
Page* = object
startOffset*: Natural
endOffset*: Natural
case kind*: PageKind
of userPage, mallocPage:
bufferStart, bufferEnd: ptr byte
of stringPage:
data*: ref string
data*: ref string
PageRef* = ref Page
@ -47,38 +38,30 @@ const
defaultPageSize* = 4096 - (pageMetadataSize + nimAllocatorMetadataSize)
maxStackUsage* = 16384
func pageBaseAddr*(page: PageRef): ptr byte =
if page.kind == stringPage:
cast[ptr byte](addr page.data[][0])
else:
page.bufferStart
proc openArrayToPair*(a: var openarray[byte]): (ptr byte, Natural) =
(addr a[0], Natural(a.len))
template pageBaseAddr*(page: PageRef): ptr byte =
cast[ptr byte](addr page.data[][0])
func pageStartAddr*(page: PageRef): ptr byte =
if page.kind == stringPage:
offset(cast[ptr byte](addr page.data[][0]), page.startOffset)
else:
offset(page.bufferStart, page.startOffset)
offset(cast[ptr byte](addr page.data[][0]), page.startOffset)
func pageEndAddr*(page: PageRef): ptr byte =
if page.kind == stringPage:
offset(cast[ptr byte](addr page.data[][0]), page.endOffset)
else:
offset(page.bufferStart, page.endOffset)
offset(cast[ptr byte](addr page.data[][0]), page.endOffset)
func pageLen*(page: PageRef): Natural =
page.endOffset - page.startOffset
template pageChars*(page: PageRef): untyped =
let baseAddr = cast[ptr UncheckedArray[char]](pageBaseAddr(page))
toOpenArray(baseAddr, page.startOffset, page.endOffset - 1)
func span*(page: PageRef, writable: static[bool] = false): PageSpan =
if page.kind == stringPage:
let baseAddr = cast[ptr byte](addr page.data[][0])
PageSpan(startAddr: offset(baseAddr, page.startOffset),
endAddr: offset(baseAddr, when writable: page.data[].len
else: page.endOffset))
else:
PageSpan(startAddr: offset(page.bufferStart, page.startOffset),
endAddr: when writable: page.bufferEnd
else: offset(page.bufferStart, page.endOffset))
let baseAddr = page.pageBaseAddr
PageSpan(startAddr: offset(baseAddr, page.startOffset),
endAddr: offset(baseAddr, when writable: page.data[].len
else: page.endOffset))
template writableSpan*(page: PageRef): PageSpan =
span(page, writable = true)
@ -94,37 +77,46 @@ template allocRef[T: not ref](x: T): ref T =
res[] = x
res
func getWritablePage*(buffers: PageBuffers): PageRef =
# TODO: The semantics of this func are quite unusual
# I should find a more appropriate name
if buffers.queue.len == 0:
result = PageRef(kind: stringPage,
data: allocRef newString(buffers.pageSize),
endOffset: buffers.pageSize)
buffers.queue.addLast result
else:
result = buffers.queue[0]
func addWritablePage*(buffers: PageBuffers, pageSize: Natural): PageRef =
result = PageRef(kind: stringPage,
data: allocRef newString(pageSize),
result = PageRef(data: allocRef newString(pageSize),
endOffset: pageSize)
buffers.queue.addLast result
func getWritablePage*(buffers: PageBuffers,
preferredSize: Natural): PageRef =
if buffers.queue.len == 1:
let recycledPage = buffers.queue.peekLast
if recycledPage.endOffset == 0 and
recycledPage.data[].len == preferredSize:
recycledPage.endOffset = recycledPage.data[].len
return recycledPage
return addWritablePage(buffers, preferredSize)
func addWritablePage*(buffers: PageBuffers): PageRef =
buffers.addWritablePage(buffers.pageSize)
template getWritableSpan*(buffers: PageBuffers): PageSpan =
getWritablePage(buffers).span(writable = true)
getWritablePage(buffers, buffers.pageSize).span(writable = true)
proc getReadableSpan*(buffers: PageBuffers): PageSpan =
if buffers.queue.len > 1:
discard buffers.queue.popFirst
buffers.queue[0].span
func ensureRunway*(buffers: PageBuffers, neededRunway: Natural): PageSpan =
doAssert buffers.queue.len == 0
buffers.pageSize = neededRunway
getWritableSpan(buffers)
template len*(buffers: PageBuffers): int =
template len*(buffers: PageBuffers): Natural =
buffers.queue.len
func totalBufferredBytes*(buffers: PageBuffers): Natural =
for i in 1 ..< buffers.queue.len:
result += buffers.queue[i].pageLen
template popFirst*(buffers: PageBuffers): PageRef =
buffers.queue.popFirst
@ -148,54 +140,36 @@ func endLastPageAt*(buffers: PageBuffers, address: ptr byte) =
var topPage = buffers.queue.peekLast
topPage.endOffset = distance(topPage.pageBaseAddr, address)
func trackPageWrite*(page: PageRef, bytesWritten: Natural) {.inline.} =
page.endOffset = page.startOffset + bytesWritten
template writeToSpan*(buffersParam: PageBuffers,
spanVarName, writeExpr: untyped) =
var
buffers = buffersParam
page = buffers.getWritablePage
spanVarName = page.writableSpan
# TODO: what if we exit with an exception here?
# Are the side-effects of `getWritablePage` above OK to keep?
let bytesWritten = writeExpr
trackPageWrite(page, bytesWritten)
if bytesWritten == 0:
buffers.eofReached = true
func nextAlignedSize*(minSize, pageSize: Natural): Natural =
# TODO: This is not perfectly accurate. Revisit later
((minSize div pageSize) + 1) * pageSize
template consumeAllPages*(buffersParam: PageBuffers,
pageAddrVar, pageLenVar, body: untyped) =
let buffers = buffersParam
iterator consumePages*(buffers: PageBuffers): PageRef =
doAssert buffers != nil
var recycledPage: PageRef
for page in buffers.queue:
let
pageAddrVar = page.pageStartAddr
pageLenVar = page.endOffset - page.startOffset
if page.kind == stringPage and page.data[].len == buffers.pageSize:
recycledPage = page
while buffers.queue.len > 0:
var page = peekFirst(buffers.queue)
# TODO: what if the body throws an exception?
# Should we do anything with the remaining pages?
body
# Should we do anything with the consumed page?
yield page
buffers.queue.clear()
if page.data[].len == buffers.pageSize:
recycledPage = page
discard buffers.queue.popFirst
if recycledPage != nil:
recycledPage.startOffset = 0
recycledPage.endOffset = 0
buffers.queue.addLast recycledPage
iterator consumePageBuffers*(buffers: PageBuffers): (ptr byte, Natural) =
for page in consumePages(buffers):
yield (page.pageStartAddr,
Natural(page.endOffset - page.startOffset))
template wasEofReached*(buffers: PageBuffers): bool =
buffers.eofReached
@ -224,3 +198,74 @@ template charsToBytes*(chars: openArray[char]): untyped =
var charsStart = unsafeAddr chars[0]
makeOpenArray(cast[ptr byte](charsStart), chars.len)
template implementWrites*(buffersParam: PageBuffers,
srcParam: pointer,
srcLenParam: Natural,
dstDesc: static string,
writeStartVar, writeLenVar,
writeBlock: untyped) =
let
buffers = buffersParam
writeStartVar = srcParam
writeLenVar = srcLenParam
template raiseError =
raise newException(IOError, "Failed to write all bytes to " & dstDesc)
if buffers != nil:
for writeStartVar, writeLenVar in consumePageBuffers(s.buffers):
let bytesWritten = writeBlock
# TODO: Can we repair the buffers here?
if bytesWritten != writeLenVar: raiseError()
buffers.totalBytesWritten += bytesWritten
if srcLen > 0:
doAssert src != nil
let bytesWritten = writeBlock
if bytesWritten != writeLenVar: raiseError()
# TODO: Fix this after removing the unbuffered streams
if buffers != nil:
buffers.totalBytesWritten += bytesWritten
type
ReadFlag* = enum
partialReadIsEof
zeroReadIsNotEof
ReadFlags* = set[ReadFlag]
template implementSingleRead*(buffersParam: PageBuffers,
dstParam: pointer,
dstLenParam: Natural,
flags: static ReadFlags,
readStartVar, readLenVar,
readBlock: untyped): Natural =
var
buffers = buffersParam
readStartVar = dstParam
readLenVar = dstLenParam
bytesRead: Natural
if readStartVar != nil:
bytesRead = readBlock
else:
let
bestPageSize = nextAlignedSize(readLenVar, buffers.pageSize)
page = getWritablePage(buffers, bestPageSize)
readStartVar = page.pageStartAddr
readLenVar = page.endOffset - page.startOffset
# TODO: what if we exit with an exception here?
# Are the side-effects of `getWritablePage` above OK to keep?
bytesRead = readBlock
page.endOffset = page.startOffset + bytesRead
if (bytesRead == 0 and zeroReadIsNotEof notin flags) or
(partialReadIsEof in flags and bytesRead < readLenVar):
buffers.eofReached = true
else:
buffers.totalBytesRead += bytesRead
bytesRead