Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+40
View File
@@ -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'
}