nim-datastore/datastore/threadproxyds.nim

236 lines
5.6 KiB
Nim
Raw Normal View History

2023-08-24 15:51:25 -07:00
import std/tables
import pkg/chronos
2023-08-24 17:33:32 -07:00
import pkg/chronos/threadsync
2023-08-24 15:51:25 -07:00
import pkg/questionable
import pkg/questionable/results
import pkg/upraises
2023-08-24 18:28:30 -07:00
import pkg/taskpools
2023-08-25 14:34:11 -07:00
import pkg/stew/results
2023-08-24 15:51:25 -07:00
import ./key
import ./query
import ./datastore
2023-09-05 17:08:40 -07:00
import ./threads/threadbackend
2023-08-24 15:51:25 -07:00
2023-08-28 21:56:29 -07:00
export key, query
2023-08-24 15:51:25 -07:00
push: {.upraises: [].}
type
2023-08-29 12:50:36 -07:00
ThreadProxyDatastore* = ref object of Datastore
2023-08-24 18:28:30 -07:00
tds: ThreadDatastorePtr
2023-08-24 15:51:25 -07:00
method has*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore,
2023-08-24 15:57:15 -07:00
key: Key
): Future[?!bool] {.async.} =
2023-08-29 13:00:01 -07:00
2023-09-14 18:18:14 -07:00
var ret = newThreadResult(bool)
let sig = SharedSignal.new(0)
2023-08-29 13:00:01 -07:00
try:
2023-09-14 18:18:14 -07:00
await sig.acquireSig()
sig.has(ret, self.tds, key)
await sig.wait()
2023-09-12 20:09:55 -07:00
return ret.convert(bool)
2023-08-29 13:00:01 -07:00
finally:
2023-09-05 17:14:03 -07:00
ret.release()
2023-08-29 13:00:01 -07:00
2023-08-24 15:51:25 -07:00
method delete*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore,
2023-08-24 15:57:15 -07:00
key: Key
): Future[?!void] {.async.} =
2023-08-29 12:55:38 -07:00
2023-09-14 18:18:14 -07:00
let sig = SharedSignal.new(0)
var ret = newThreadResult(void)
2023-08-29 12:55:38 -07:00
try:
2023-09-14 18:18:14 -07:00
await sig.acquireSig()
sig.delete(ret, self.tds, key)
await sig.wait()
2023-08-29 12:55:38 -07:00
finally:
2023-09-05 17:14:03 -07:00
ret.release()
2023-08-29 12:55:38 -07:00
2023-08-29 14:47:22 -07:00
return ret.convert(void)
2023-08-24 15:51:25 -07:00
method delete*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore,
2023-08-24 15:57:15 -07:00
keys: seq[Key]
): Future[?!void] {.async.} =
2023-08-29 13:08:55 -07:00
for key in keys:
if err =? (await self.delete(key)).errorOption:
return failure err
2023-08-24 15:51:25 -07:00
return success()
method get*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore,
2023-08-24 15:57:15 -07:00
key: Key
): Future[?!seq[byte]] {.async.} =
2023-08-29 14:47:22 -07:00
## implements batch get
##
## note: this implementation is rather naive and should
## probably be switched to use a single ThreadSignal
## for the entire batch
2023-08-24 22:14:21 -07:00
2023-09-14 18:18:14 -07:00
let sig = SharedSignal.new(0)
var ret = newThreadResult(ValueBuffer)
2023-08-24 22:14:21 -07:00
2023-08-25 15:00:18 -07:00
try:
2023-09-14 18:18:14 -07:00
sig.get(ret, self.tds, key)
await sig.wait()
2023-08-25 15:00:18 -07:00
finally:
2023-09-05 17:14:03 -07:00
ret.release()
2023-08-24 22:14:21 -07:00
2023-08-29 14:40:44 -07:00
return ret.convert(seq[byte])
2023-08-24 15:51:25 -07:00
2023-09-14 15:38:02 -07:00
import ./threads/then
import std/os
2023-08-24 15:51:25 -07:00
method put*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore,
2023-08-24 15:51:25 -07:00
key: Key,
2023-08-24 15:57:15 -07:00
data: seq[byte]
2023-09-14 15:38:02 -07:00
): Future[?!void] =
echoed "put request args: ", $getThreadId()
let tds = self.tds
var putRes = newFuture[?!void]("threadbackend.put(tds, key, data)")
2023-09-14 15:43:55 -07:00
let sig = SharedSignal.new(0)
echoed "put:sig: ", sig.repr
2023-09-14 15:38:02 -07:00
acquireSig(sig).
then(proc () =
let
ret = newSharedPtr(ThreadResult[void])
bkey = KeyBuffer.new(key)
bval = DataBuffer.new(data)
2023-09-14 15:43:55 -07:00
# queue taskpool work
2023-09-14 15:38:02 -07:00
tds[].tp.spawn putTask(sig, ret, tds, bkey, bval)
2023-09-14 15:43:55 -07:00
# wait for taskpool work to finish
2023-09-14 15:38:02 -07:00
wait(sig).
then(proc () =
2023-09-14 15:51:13 -07:00
os.sleep(200) # sleep to help separate debugging output
2023-09-14 15:38:02 -07:00
let val = ret.convert(void)
putRes.complete(val)
).cancelled(proc() =
2023-09-14 15:50:15 -07:00
# TODO: could try and prevent taskpool work before it starts?
2023-09-14 15:43:55 -07:00
discard
2023-09-14 15:38:02 -07:00
).catch(proc(e: ref CatchableError) =
doAssert false, "will not be triggered"
)
).catch(proc(e: ref CatchableError) =
var res: ?!void
res.err(e)
putRes.complete(res)
)
return putRes
2023-08-24 21:55:53 -07:00
2023-08-24 15:51:25 -07:00
method put*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore,
2023-08-24 15:57:15 -07:00
batch: seq[BatchEntry]
): Future[?!void] {.async.} =
2023-08-29 14:47:22 -07:00
## implements batch put
##
## note: this implementation is rather naive and should
## probably be switched to use a single ThreadSignal
## for the entire batch
2023-08-29 13:08:55 -07:00
for entry in batch:
if err =? (await self.put(entry.key, entry.data)).errorOption:
return failure err
return success()
2023-08-24 15:51:25 -07:00
import pretty
2023-08-29 17:00:11 -07:00
2023-08-29 15:42:53 -07:00
method query*(
self: ThreadProxyDatastore,
query: Query
): Future[?!QueryIter] {.async.} =
2023-09-14 18:18:14 -07:00
let sig = SharedSignal.new(0)
await sig.acquireSig()
var ret = newThreadResult(QueryResponseBuffer)
2023-08-29 15:42:53 -07:00
2023-09-05 13:42:16 -07:00
# echo "\n\n=== Query Start === "
2023-08-29 19:43:28 -07:00
## we need to setup the query iter on the main thread
## to keep it's lifetime associated with this async Future
without it =? await self.tds[].ds.query(query), err:
ret.failure(err)
var iter = newSharedPtr(QueryIterStore)
2023-09-05 13:51:22 -07:00
## does this bypasses SharedPtr isolation? - may need `protect` here?
2023-08-29 19:43:28 -07:00
iter[].it = it
2023-08-29 15:55:42 -07:00
2023-08-29 19:43:28 -07:00
var iterWrapper = QueryIter.new()
iterWrapper.readyForNext = true
2023-08-29 15:55:42 -07:00
2023-08-29 19:43:28 -07:00
proc next(): Future[?!QueryResponse] {.async.} =
2023-09-05 13:51:22 -07:00
# print "query:next:start: "
iterWrapper.finished = iter[].it.finished
2023-08-29 19:43:28 -07:00
if not iter[].it.finished:
iterWrapper.readyForNext = false
2023-09-14 18:18:14 -07:00
sig.query(ret, self.tds, iter)
await sig.wait()
iterWrapper.readyForNext = true
2023-09-05 13:42:16 -07:00
# echo ""
# print "query:post: ", ret[].results
# print "query:post:finished: ", iter[].it.finished
# print "query:post: ", " qrb:key: ", ret[].results.get().key.toString()
# print "query:post: ", " qrb:data: ", ret[].results.get().data.toString()
result = ret.convert(QueryResponse)
2023-08-29 19:43:28 -07:00
else:
result = success (Key.none, EmptyBytes)
2023-08-29 15:55:42 -07:00
2023-08-29 19:43:28 -07:00
proc dispose(): Future[?!void] {.async.} =
2023-08-29 15:59:49 -07:00
iter[].it = nil # ensure our sharedptr doesn't try and dealloc
2023-09-05 17:14:03 -07:00
ret.release()
2023-08-29 19:43:28 -07:00
return success()
2023-08-29 15:42:53 -07:00
2023-08-29 19:43:28 -07:00
iterWrapper.next = next
iterWrapper.dispose = dispose
return success iterWrapper
2023-08-29 15:42:53 -07:00
2023-08-24 15:57:15 -07:00
method close*(
2023-08-29 12:50:36 -07:00
self: ThreadProxyDatastore
2023-08-24 15:57:15 -07:00
): Future[?!void] {.async.} =
2023-08-24 15:51:25 -07:00
# TODO: how to handle failed close?
2023-08-28 17:51:48 -07:00
result = success()
without res =? self.tds[].ds.close(), err:
result = failure(err)
2023-08-28 18:00:42 -07:00
# GC_unref(self.tds[].ds) ## TODO: is this needed?
2023-08-28 17:51:48 -07:00
if self.tds[].tp != nil:
## this can block... how to handle? maybe just leak?
self.tds[].tp.shutdown()
2023-09-13 14:29:41 -07:00
self[].tds[].tp = nil # ensure our sharedptr doesn't try and dealloc
2023-08-24 15:51:25 -07:00
2023-08-29 15:59:49 -07:00
self[].tds[].ds = nil # ensure our sharedptr doesn't try and dealloc
2023-09-13 14:40:32 -07:00
self.tds.release()
2023-08-29 14:58:33 -07:00
2023-08-29 14:43:05 -07:00
proc newThreadProxyDatastore*(
2023-08-28 18:00:42 -07:00
ds: Datastore,
2023-08-29 12:50:36 -07:00
): ?!ThreadProxyDatastore =
2023-08-29 14:43:05 -07:00
## create a new
2023-08-24 15:51:25 -07:00
2023-08-29 12:50:36 -07:00
var self = ThreadProxyDatastore()
2023-09-12 20:09:55 -07:00
var value = newSharedPtr(ThreadDatastore)
2023-08-29 14:43:05 -07:00
# GC_ref(ds) ## TODO: is this needed?
2023-09-05 13:51:22 -07:00
2023-08-25 15:00:18 -07:00
try:
2023-08-29 14:43:05 -07:00
value[].ds = ds
2023-08-28 21:48:42 -07:00
value[].tp = Taskpool.new(num_threads = 2)
except Exception as exc:
return err((ref DatastoreError)(msg: exc.msg))
2023-08-24 21:18:22 -07:00
2023-08-28 21:48:42 -07:00
self.tds = value
2023-08-24 21:55:53 -07:00
2023-08-24 15:51:25 -07:00
success self