Skip to content

Commit f7926ec

Browse files
vlib/context: add onecontext as submodule (#12549)
1 parent 2144471 commit f7926ec

File tree

6 files changed

+369
-3
lines changed

6 files changed

+369
-3
lines changed

cmd/tools/vtest-self.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const github_job = os.getenv('GITHUB_JOB')
88

99
const (
1010
skip_test_files = [
11+
'vlib/context/onecontext/onecontext_test.v',
1112
'vlib/context/deadline_test.v' /* sometimes blocks */,
1213
'vlib/mysql/mysql_orm_test.v' /* mysql not installed */,
1314
'vlib/pg/pg_orm_test.v' /* pg not installed */,

vlib/context/cancel.v

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ mut:
5151
pub fn with_cancel(mut parent Context) (Context, CancelFn) {
5252
mut c := new_cancel_context(parent)
5353
propagate_cancel(mut parent, mut c)
54-
return Context(c), fn [mut c] () {
54+
cancel_fn := fn [mut c] () {
5555
c.cancel(true, canceled)
5656
}
57+
return Context(c), CancelFn(cancel_fn)
5758
}
5859

5960
// new_cancel_context returns an initialized CancelContext.

vlib/context/deadline.v

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ pub fn with_deadline(mut parent Context, d time.Time) (Context, CancelFn) {
4444
dur := d - time.now()
4545
if dur.nanoseconds() <= 0 {
4646
ctx.cancel(true, deadline_exceeded) // deadline has already passed
47-
return Context(ctx), fn [mut ctx] () {
47+
cancel_fn := fn [mut ctx] () {
4848
ctx.cancel(true, canceled)
4949
}
50+
return Context(ctx), CancelFn(cancel_fn)
5051
}
5152

5253
if ctx.err() is none {
@@ -55,9 +56,11 @@ pub fn with_deadline(mut parent Context, d time.Time) (Context, CancelFn) {
5556
ctx.cancel(true, deadline_exceeded)
5657
}(mut ctx, dur)
5758
}
58-
return Context(ctx), fn [mut ctx] () {
59+
60+
cancel_fn := fn [mut ctx] () {
5961
ctx.cancel(true, canceled)
6062
}
63+
return Context(ctx), CancelFn(cancel_fn)
6164
}
6265

6366
// with_timeout returns with_deadline(parent, time.now().add(timeout)).

vlib/context/onecontext/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# onecontext
2+
3+
A library to merge existing V contexts.
4+
5+
## Overview
6+
7+
Have you ever faced the situation where you have to merge multiple existing contexts?
8+
If not, then you might, eventually.
9+
10+
For example, we can face the situation where we are building an application
11+
using a library that gives us a global context.
12+
This context expires once the application is stopped.
13+
14+
Meanwhile, we are exposing a service like this:
15+
16+
```v ignore
17+
fn (f Foo) get(ctx context.Context, bar Bar) ?Baz {
18+
. . .
19+
}
20+
```
21+
22+
Here, we receive another context provided by the service.
23+
24+
Then, in the `get` implementation, we want for example to query a database and
25+
we must provide a context for that.
26+
27+
Ideally, we would like to provide a merged context that would expire either:
28+
29+
- When the application is stopped
30+
- Or when the received service context expires
31+
32+
This is exactly the purpose of this library.
33+
34+
In our case, we can now merge the two contexts in a single one like this:
35+
36+
```v ignore
37+
ctx, cancel := onecontext.merge(ctx1, ctx2)
38+
```
39+
40+
This returns a merged context that we can now propagate

vlib/context/onecontext/onecontext.v

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
module onecontext
2+
3+
import context
4+
import sync
5+
import time
6+
7+
// canceled is the error returned when the cancel function is called on a merged context
8+
pub const canceled = error('canceled context')
9+
10+
struct OneContext {
11+
mut:
12+
ctx context.Context
13+
ctxs []context.Context
14+
done chan int
15+
err IError = none
16+
err_mutex sync.Mutex
17+
cancel_fn context.CancelFn
18+
cancel_ctx context.Context
19+
}
20+
21+
// merge allows to merge multiple contexts
22+
// it returns the merged context
23+
pub fn merge(ctx context.Context, ctxs ...context.Context) (context.Context, context.CancelFn) {
24+
mut background := context.background()
25+
cancel_ctx, cancel := context.with_cancel(mut &background)
26+
mut octx := &OneContext{
27+
done: chan int{cap: 3}
28+
ctx: ctx
29+
ctxs: ctxs
30+
cancel_fn: cancel
31+
cancel_ctx: cancel_ctx
32+
}
33+
go octx.run()
34+
return context.Context(octx), context.CancelFn(cancel)
35+
}
36+
37+
pub fn (octx OneContext) deadline() ?time.Time {
38+
mut min := time.Time{}
39+
40+
if deadline := octx.ctx.deadline() {
41+
min = deadline
42+
}
43+
44+
for ctx in octx.ctxs {
45+
if deadline := ctx.deadline() {
46+
if min.unix_time() == 0 || deadline < min {
47+
min = deadline
48+
}
49+
}
50+
}
51+
52+
if min.unix_time() == 0 {
53+
return none
54+
}
55+
56+
return min
57+
}
58+
59+
pub fn (octx OneContext) done() chan int {
60+
return octx.done
61+
}
62+
63+
pub fn (mut octx OneContext) err() IError {
64+
octx.err_mutex.@lock()
65+
defer {
66+
octx.err_mutex.unlock()
67+
}
68+
return octx.err
69+
}
70+
71+
pub fn (octx OneContext) value(key context.Key) ?context.Any {
72+
if value := octx.ctx.value(key) {
73+
return value
74+
}
75+
76+
for ctx in octx.ctxs {
77+
if value := ctx.value(key) {
78+
return value
79+
}
80+
}
81+
82+
return none
83+
}
84+
85+
pub fn (mut octx OneContext) run() {
86+
mut wrapped_ctx := &octx.ctx
87+
if octx.ctxs.len == 1 {
88+
mut first_ctx := &octx.ctxs[0]
89+
octx.run_two_contexts(mut wrapped_ctx, mut first_ctx)
90+
return
91+
}
92+
93+
octx.run_multiple_contexts(mut wrapped_ctx)
94+
for mut ctx in octx.ctxs {
95+
octx.run_multiple_contexts(mut &ctx)
96+
}
97+
}
98+
99+
pub fn (octx OneContext) str() string {
100+
return ''
101+
}
102+
103+
pub fn (mut octx OneContext) cancel(err IError) {
104+
octx.cancel_fn()
105+
octx.err_mutex.@lock()
106+
octx.err = err
107+
octx.err_mutex.unlock()
108+
if !octx.done.closed {
109+
octx.done <- 0
110+
octx.done.close()
111+
}
112+
}
113+
114+
pub fn (mut octx OneContext) run_two_contexts(mut ctx1 context.Context, mut ctx2 context.Context) {
115+
go fn (mut octx OneContext, mut ctx1 context.Context, mut ctx2 context.Context) {
116+
octx_cancel_done := octx.cancel_ctx.done()
117+
c1done := ctx1.done()
118+
c2done := ctx2.done()
119+
select {
120+
_ := <-octx_cancel_done {
121+
octx.cancel(onecontext.canceled)
122+
}
123+
_ := <-c1done {
124+
octx.cancel(ctx1.err())
125+
}
126+
_ := <-c2done {
127+
octx.cancel(ctx1.err())
128+
}
129+
}
130+
}(mut &octx, mut &ctx1, mut &ctx2)
131+
}
132+
133+
pub fn (mut octx OneContext) run_multiple_contexts(mut ctx context.Context) {
134+
go fn (mut octx OneContext, mut ctx context.Context) {
135+
octx_cancel_done := octx.cancel_ctx.done()
136+
cdone := ctx.done()
137+
select {
138+
_ := <-octx_cancel_done {
139+
octx.cancel(onecontext.canceled)
140+
}
141+
_ := <-cdone {
142+
octx.cancel(ctx.err())
143+
}
144+
}
145+
}(mut &octx, mut &ctx)
146+
}

0 commit comments

Comments
 (0)