Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,40 @@
|
||||
# onecontext
|
||||
|
||||
A library to merge existing V contexts.
|
||||
|
||||
## Overview
|
||||
|
||||
Have you ever faced the situation where you have to merge multiple existing contexts?
|
||||
If not, then you might, eventually.
|
||||
|
||||
For example, we can face the situation where we are building an application
|
||||
using a library that gives us a global context.
|
||||
This context expires once the application is stopped.
|
||||
|
||||
Meanwhile, we are exposing a service like this:
|
||||
|
||||
```v ignore
|
||||
fn (f Foo) get(ctx context.Context, bar Bar) ?Baz {
|
||||
. . .
|
||||
}
|
||||
```
|
||||
|
||||
Here, we receive another context provided by the service.
|
||||
|
||||
Then, in the `get` implementation, we want for example to query a database and
|
||||
we must provide a context for that.
|
||||
|
||||
Ideally, we would like to provide a merged context that would expire either:
|
||||
|
||||
- When the application is stopped
|
||||
- Or when the received service context expires
|
||||
|
||||
This is exactly the purpose of this library.
|
||||
|
||||
In our case, we can now merge the two contexts in a single one like this:
|
||||
|
||||
```v ignore
|
||||
ctx, cancel := onecontext.merge(ctx1, ctx2)
|
||||
```
|
||||
|
||||
This returns a merged context that we can now propagate
|
||||
@@ -0,0 +1,164 @@
|
||||
module onecontext
|
||||
|
||||
import context
|
||||
import sync
|
||||
import time
|
||||
|
||||
// canceled is the error returned when the cancel function is called on a merged context
|
||||
pub const canceled = error('canceled context')
|
||||
|
||||
@[heap]
|
||||
struct OneContext {
|
||||
mut:
|
||||
ctx context.Context
|
||||
ctxs []context.Context
|
||||
done chan int
|
||||
err IError = none
|
||||
err_mutex &sync.Mutex = sync.new_mutex()
|
||||
cancel_fn context.CancelFn = unsafe { nil }
|
||||
cancel_ctx context.Context
|
||||
}
|
||||
|
||||
// merge allows to merge multiple contexts
|
||||
// it returns the merged context
|
||||
pub fn merge(ctx context.Context, ctxs ...context.Context) (context.Context, context.CancelFn) {
|
||||
mut background := context.background()
|
||||
cancel_ctx, cancel := context.with_cancel(mut background)
|
||||
mut octx := &OneContext{
|
||||
done: chan int{cap: 3}
|
||||
ctx: ctx
|
||||
ctxs: ctxs
|
||||
cancel_fn: cancel
|
||||
cancel_ctx: cancel_ctx
|
||||
}
|
||||
spawn octx.run()
|
||||
return context.Context(octx), context.CancelFn(cancel)
|
||||
}
|
||||
|
||||
// deadline returns the earliest deadline among all merged contexts,
|
||||
// or none if no context has a deadline set.
|
||||
pub fn (octx OneContext) deadline() ?time.Time {
|
||||
mut min := time.Time{}
|
||||
mut found := false
|
||||
|
||||
if deadline := octx.ctx.deadline() {
|
||||
min = deadline
|
||||
found = true
|
||||
}
|
||||
|
||||
for ctx in octx.ctxs {
|
||||
if deadline := ctx.deadline() {
|
||||
if !found || deadline < min {
|
||||
min = deadline
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return none
|
||||
}
|
||||
|
||||
return min
|
||||
}
|
||||
|
||||
// done returns the done channel, which is closed when the merged context is canceled.
|
||||
pub fn (octx OneContext) done() chan int {
|
||||
return octx.done
|
||||
}
|
||||
|
||||
// err returns the error from the merged context, or `none` if not yet canceled.
|
||||
pub fn (mut octx OneContext) err() IError {
|
||||
octx.err_mutex.lock()
|
||||
defer {
|
||||
octx.err_mutex.unlock()
|
||||
}
|
||||
return octx.err
|
||||
}
|
||||
|
||||
// value looks up a value by key across all merged contexts, returning
|
||||
// the first match found or none if no context holds the key.
|
||||
pub fn (octx OneContext) value(key context.Key) ?context.Any {
|
||||
if value := octx.ctx.value(key) {
|
||||
return value
|
||||
}
|
||||
|
||||
for ctx in octx.ctxs {
|
||||
if value := ctx.value(key) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// run starts listening for cancellation signals from all merged contexts.
|
||||
pub fn (mut octx OneContext) run() {
|
||||
mut wrapped_ctx := &octx.ctx
|
||||
if octx.ctxs.len == 1 {
|
||||
mut first_ctx := &octx.ctxs[0]
|
||||
octx.run_two_contexts(mut wrapped_ctx, mut first_ctx)
|
||||
return
|
||||
}
|
||||
|
||||
octx.run_multiple_contexts(mut wrapped_ctx)
|
||||
for mut ctx in octx.ctxs {
|
||||
octx.run_multiple_contexts(mut ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// str returns a string representation of the OneContext.
|
||||
pub fn (octx OneContext) str() string {
|
||||
return ''
|
||||
}
|
||||
|
||||
// cancel cancels the merged context with the given error, closing
|
||||
// the done channel and propagating cancellation to the underlying context.
|
||||
pub fn (mut octx OneContext) cancel(err IError) {
|
||||
octx.cancel_fn()
|
||||
octx.err_mutex.lock()
|
||||
octx.err = err
|
||||
octx.err_mutex.unlock()
|
||||
if !octx.done.closed {
|
||||
octx.done <- 0
|
||||
octx.done.close()
|
||||
}
|
||||
}
|
||||
|
||||
// run_two_contexts spawns a listener that cancels the merged context
|
||||
// when either of the two given contexts is done.
|
||||
pub fn (mut octx OneContext) run_two_contexts(mut ctx1 context.Context, mut ctx2 context.Context) {
|
||||
octx_cancel_done := octx.cancel_ctx.done()
|
||||
c1done := ctx1.done()
|
||||
c2done := ctx2.done()
|
||||
spawn fn (mut octx OneContext, octx_cancel_done chan int, c1done chan int, c2done chan int, mut ctx1 context.Context, mut ctx2 context.Context) {
|
||||
select {
|
||||
_ := <-octx_cancel_done {
|
||||
octx.cancel(canceled)
|
||||
}
|
||||
_ := <-c1done {
|
||||
octx.cancel(ctx1.err())
|
||||
}
|
||||
_ := <-c2done {
|
||||
octx.cancel(ctx2.err())
|
||||
}
|
||||
}
|
||||
}(mut &octx, octx_cancel_done, c1done, c2done, mut ctx1, mut ctx2)
|
||||
}
|
||||
|
||||
// run_multiple_contexts spawns a listener that cancels the merged context
|
||||
// when the given context is done.
|
||||
pub fn (mut octx OneContext) run_multiple_contexts(mut ctx context.Context) {
|
||||
octx_cancel_done := octx.cancel_ctx.done()
|
||||
cdone := ctx.done()
|
||||
spawn fn (mut octx OneContext, octx_cancel_done chan int, cdone chan int, mut ctx context.Context) {
|
||||
select {
|
||||
_ := <-octx_cancel_done {
|
||||
octx.cancel(canceled)
|
||||
}
|
||||
_ := <-cdone {
|
||||
octx.cancel(ctx.err())
|
||||
}
|
||||
}
|
||||
}(mut &octx, octx_cancel_done, cdone, mut ctx)
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
module onecontext
|
||||
|
||||
import context
|
||||
import time
|
||||
|
||||
fn eventually(ch chan int) bool {
|
||||
mut background := context.background()
|
||||
mut timeout, cancel := context.with_timeout(mut background, 30 * time.millisecond)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
|
||||
tdone := timeout.done()
|
||||
select {
|
||||
_ := <-ch {
|
||||
return true
|
||||
}
|
||||
_ := <-tdone {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
struct Value {
|
||||
val string
|
||||
}
|
||||
|
||||
fn test_merge_nominal() {
|
||||
mut background := context.background()
|
||||
foo := 'foo'
|
||||
mut value_ctx1 := context.with_value(background, 'foo', foo)
|
||||
mut ctx1, cancel := context.with_cancel(mut value_ctx1)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
|
||||
bar := 'bar'
|
||||
mut value_ctx2 := context.with_value(background, 'bar', bar)
|
||||
mut ctx2, _ := context.with_cancel(mut value_ctx2)
|
||||
|
||||
mut ctx, cancel2 := merge(ctx1, ctx2)
|
||||
|
||||
if deadline := ctx.deadline() {
|
||||
assert false
|
||||
}
|
||||
|
||||
val1 := ctx.value('foo') or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
|
||||
match val1 {
|
||||
string {
|
||||
assert foo == val1
|
||||
}
|
||||
else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
val2 := ctx.value('bar') or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
match val2 {
|
||||
string {
|
||||
assert bar == val2
|
||||
}
|
||||
else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
if _ := ctx.value('baz') {
|
||||
assert false
|
||||
}
|
||||
|
||||
assert !eventually(ctx.done())
|
||||
assert ctx.err() is none
|
||||
|
||||
cancel2()
|
||||
assert eventually(ctx.done())
|
||||
assert ctx.err().str() == 'canceled context'
|
||||
}
|
||||
|
||||
fn test_merge_deadline_context_1() {
|
||||
mut background := context.background()
|
||||
mut ctx1, cancel := context.with_timeout(mut background, time.second)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
ctx2 := context.background()
|
||||
mut ctx, _ := merge(ctx1, ctx2)
|
||||
|
||||
if deadline := ctx.deadline() {
|
||||
assert deadline.unix() != 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_merge_deadline_context_2() {
|
||||
mut background := context.background()
|
||||
ctx1 := context.background()
|
||||
mut ctx2, cancel := context.with_timeout(mut background, time.second)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
mut ctx, _ := merge(ctx1, ctx2)
|
||||
|
||||
if deadline := ctx.deadline() {
|
||||
assert deadline.unix() != 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_merge_deadline_context_n() {
|
||||
mut background := context.background()
|
||||
ctx1 := context.background()
|
||||
|
||||
mut ctxs := []context.Context{cap: 21}
|
||||
for i in 0 .. 10 {
|
||||
ctxs << context.background()
|
||||
}
|
||||
mut ctx_n, _ := context.with_timeout(mut background, 10 * time.second)
|
||||
ctxs << ctx_n
|
||||
|
||||
for i in 0 .. 10 {
|
||||
ctxs << context.background()
|
||||
}
|
||||
|
||||
mut ctx, cancel := merge(ctx1, ...ctxs)
|
||||
|
||||
assert !eventually(ctx.done())
|
||||
assert ctx.err() is none
|
||||
cancel()
|
||||
assert eventually(ctx.done())
|
||||
assert ctx.err().str() == 'canceled context'
|
||||
}
|
||||
|
||||
fn test_merge_deadline_none() {
|
||||
ctx1 := context.background()
|
||||
ctx2 := context.background()
|
||||
|
||||
mut ctx, _ := merge(ctx1, ctx2)
|
||||
|
||||
if _ := ctx.deadline() {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_merge_cancel_two() {
|
||||
ctx1 := context.background()
|
||||
ctx2 := context.background()
|
||||
|
||||
mut ctx, cancel := merge(ctx1, ctx2)
|
||||
cancel()
|
||||
|
||||
assert eventually(ctx.done())
|
||||
assert ctx.err().str() == 'canceled context'
|
||||
assert ctx.err().str() == 'canceled context'
|
||||
}
|
||||
|
||||
fn test_merge_cancel_multiple() {
|
||||
ctx1 := context.background()
|
||||
ctx2 := context.background()
|
||||
ctx3 := context.background()
|
||||
|
||||
mut ctx, cancel := merge(ctx1, ctx2, ctx3)
|
||||
cancel()
|
||||
|
||||
assert eventually(ctx.done())
|
||||
assert ctx.err().str() == 'canceled context'
|
||||
assert ctx.err().str() == 'canceled context'
|
||||
}
|
||||
Reference in new issue
Block a user