Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,269 @@
|
||||
# Context
|
||||
|
||||
This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
and other request-scoped values across API boundaries and between processes.
|
||||
|
||||
Incoming requests to a server should create a Context, and outgoing calls to servers
|
||||
should accept a Context. The chain of function calls between them must propagate the
|
||||
Context, optionally replacing it with a derived Context created using with_cancel,
|
||||
with_deadline, with_timeout, or with_value. When a Context is canceled, all Contexts
|
||||
derived from it are also canceled.
|
||||
|
||||
The with_cancel, with_deadline, and with_timeout functions take a Context (the parent)
|
||||
and return a derived Context (the child). Calling the cancel function
|
||||
cancels the child and its children, removes the parent's reference to the child,
|
||||
and stops any associated timers.
|
||||
|
||||
Programs that use Contexts should follow these rules to keep interfaces consistent
|
||||
across different modules.
|
||||
|
||||
Do not store Contexts inside a struct type; instead, pass a Context explicitly
|
||||
to each function that needs it. The Context should be the first parameter,
|
||||
typically named ctx, just to make it more consistent.
|
||||
|
||||
## Cancellation with Causes
|
||||
|
||||
When a context is canceled, you can optionally attach a **cause** — an error
|
||||
describing why the cancellation happened. Use `with_cancel_cause` instead of
|
||||
`with_cancel` to attach a cause:
|
||||
|
||||
```v
|
||||
import context
|
||||
|
||||
fn main() {
|
||||
mut bg := context.background()
|
||||
mut ctx, cancel := context.with_cancel_cause(mut bg)
|
||||
defer { cancel(none) }
|
||||
|
||||
// Cancel with a specific error cause
|
||||
cancel(error('request timed out'))
|
||||
}
|
||||
```
|
||||
|
||||
Retrieve the cause from any context using `cause(ctx)`:
|
||||
|
||||
```v
|
||||
import context
|
||||
|
||||
fn main() {
|
||||
mut bg := context.background()
|
||||
mut ctx, cancel := context.with_cancel_cause(mut bg)
|
||||
cancel(none)
|
||||
c := context.cause(ctx)
|
||||
if c !is none {
|
||||
println('canceled: ${c}')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The cause is propagated through the context tree. If a parent context is
|
||||
canceled with a cause, all derived contexts inherit the same cause.
|
||||
If multiple ancestors are canceled, the first cause encountered is returned.
|
||||
|
||||
### Convenience variants
|
||||
|
||||
- `with_timeout_cause(ctx, dur, err)` — timeout with a custom cause
|
||||
- `with_deadline_cause(ctx, deadline, err)` — deadline with a custom cause
|
||||
|
||||
## Examples
|
||||
|
||||
In this section you can see some usage examples for this module
|
||||
|
||||
### Context With Cancellation
|
||||
|
||||
```v
|
||||
import time
|
||||
import context
|
||||
|
||||
// This example demonstrates the use of a cancelable context to prevent a
|
||||
// routine leak. By the end of the example function, the routine started
|
||||
// by gen will return without leaking.
|
||||
fn main() {
|
||||
// gen generates integers in a separate routine and
|
||||
// sends them to the returned channel.
|
||||
// The callers of gen need to cancel the context once
|
||||
// they are done consuming generated integers not to leak
|
||||
// the internal routine started by gen.
|
||||
gen := fn (mut ctx context.Context) chan int {
|
||||
dst := chan int{}
|
||||
spawn fn (mut ctx context.Context, dst chan int) {
|
||||
mut v := 0
|
||||
ch := ctx.done()
|
||||
for {
|
||||
select {
|
||||
_ := <-ch {
|
||||
// returning not to leak the routine
|
||||
eprintln('> go thread returns because ctx was canceled/done')
|
||||
return
|
||||
}
|
||||
dst <- v {
|
||||
v++
|
||||
}
|
||||
}
|
||||
}
|
||||
}(mut ctx, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
mut background := context.background()
|
||||
mut ctx, cancel := context.with_cancel(mut background)
|
||||
defer {
|
||||
cancel()
|
||||
time.sleep(2 * time.millisecond) // give a small time window, in which the go thread routine has a chance to return
|
||||
}
|
||||
|
||||
mut mut_ctx := ctx
|
||||
mut ctx2 := &mut_ctx
|
||||
ch := gen(mut ctx2)
|
||||
for i in 0 .. 5 {
|
||||
v := <-ch
|
||||
println('> received value: ${v}')
|
||||
assert i == v
|
||||
}
|
||||
println('> main is finished here')
|
||||
}
|
||||
```
|
||||
|
||||
### Context With Deadline
|
||||
|
||||
```v
|
||||
import time
|
||||
import context
|
||||
|
||||
const short_duration = 2 * time.millisecond // a reasonable duration to block in an example
|
||||
|
||||
// This example passes a context with an arbitrary deadline to tell a blocking
|
||||
// function that it should abandon its work as soon as it gets to it.
|
||||
fn main() {
|
||||
dur := time.now().add(short_duration)
|
||||
mut background := context.background()
|
||||
mut ctx, cancel := context.with_deadline(mut background, dur)
|
||||
|
||||
defer {
|
||||
// Even though ctx will be expired, it is good practice to call its
|
||||
// cancellation function in any case. Failure to do so may keep the
|
||||
// context and its parent alive longer than necessary.
|
||||
cancel()
|
||||
time.sleep(short_duration) // give a small time window, in which the go thread routine has a chance to return
|
||||
eprintln('> defer block finishes')
|
||||
}
|
||||
|
||||
ctx_ch := ctx.done()
|
||||
select {
|
||||
_ := <-ctx_ch {
|
||||
println('>> read from ctx_ch succeeded')
|
||||
}
|
||||
1 * time.second {
|
||||
panic('This should not happen')
|
||||
}
|
||||
}
|
||||
eprintln('> main finishes')
|
||||
}
|
||||
```
|
||||
|
||||
### Context With Timeout
|
||||
|
||||
```v
|
||||
import time
|
||||
import context
|
||||
|
||||
const short_duration = 2 * time.millisecond // a reasonable duration to block in an example
|
||||
|
||||
// This example passes a context with a timeout to tell a blocking function that
|
||||
// it should abandon its work after the timeout elapses.
|
||||
fn main() {
|
||||
// Pass a context with a timeout to tell a blocking function that it
|
||||
// should abandon its work after the timeout elapses.
|
||||
mut background := context.background()
|
||||
mut ctx, cancel := context.with_timeout(mut background, short_duration)
|
||||
defer {
|
||||
cancel()
|
||||
eprintln('> defer finishes')
|
||||
}
|
||||
|
||||
ctx_ch := ctx.done()
|
||||
select {
|
||||
_ := <-ctx_ch {
|
||||
eprintln('> reading from ctx_ch succeeded')
|
||||
}
|
||||
1 * time.second {
|
||||
panic('This should not happen')
|
||||
}
|
||||
}
|
||||
eprintln('> main finishes')
|
||||
}
|
||||
```
|
||||
|
||||
### Context With Value
|
||||
|
||||
```v
|
||||
import context
|
||||
|
||||
const not_found_value = &Value{
|
||||
val: 'key not found'
|
||||
}
|
||||
|
||||
struct Value {
|
||||
val string
|
||||
}
|
||||
|
||||
// This example demonstrates how a value can be passed to the context
|
||||
// and also how to retrieve it if it exists.
|
||||
fn main() {
|
||||
f := fn (ctx context.Context, key context.Key) &Value {
|
||||
if value := ctx.value(key) {
|
||||
match value {
|
||||
Value {
|
||||
return value
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return not_found_value
|
||||
}
|
||||
|
||||
key := 'language'
|
||||
value := &Value{
|
||||
val: 'VAL'
|
||||
}
|
||||
ctx := context.with_value(context.background(), key, value)
|
||||
|
||||
assert value == dump(f(ctx, key))
|
||||
assert not_found_value == dump(f(ctx, 'color'))
|
||||
}
|
||||
```
|
||||
|
||||
### Context With Cancellation Cause
|
||||
|
||||
```v
|
||||
import context
|
||||
|
||||
struct AppError {
|
||||
msg string
|
||||
}
|
||||
|
||||
fn (e AppError) msg() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
fn (e AppError) code() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut bg := context.background()
|
||||
mut ctx, cancel := context.with_cancel_cause(mut bg)
|
||||
defer { cancel(none) }
|
||||
|
||||
// Simulate an error condition and cancel with a cause
|
||||
cancel(AppError{
|
||||
msg: 'operation failed'
|
||||
})
|
||||
|
||||
// Retrieve the cause
|
||||
c := context.cause(ctx)
|
||||
if c !is none {
|
||||
eprintln('context canceled: ${c}')
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,192 @@
|
||||
// This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
// and other request-scoped values across API boundaries and between processes.
|
||||
// Based on: https://github.com/golang/go/tree/master/src/context
|
||||
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
|
||||
module context
|
||||
|
||||
import rand
|
||||
import sync
|
||||
import time
|
||||
|
||||
pub type CancelFn = fn ()
|
||||
|
||||
pub interface Canceler {
|
||||
id string
|
||||
mut:
|
||||
cancel(remove_from_parent bool, err IError)
|
||||
done() chan int
|
||||
}
|
||||
|
||||
// A CancelContext can be canceled. When canceled, it also cancels any children
|
||||
// that implement Canceler.
|
||||
pub struct CancelContext {
|
||||
id string
|
||||
mut:
|
||||
context Context
|
||||
mutex &sync.Mutex = sync.new_mutex()
|
||||
done chan int
|
||||
children map[string]Canceler
|
||||
err IError = none
|
||||
}
|
||||
|
||||
// with_cancel returns a copy of parent with a new done channel. The returned
|
||||
// context's done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
pub fn with_cancel(mut parent Context) (Context, CancelFn) {
|
||||
mut c := new_cancel_context(parent)
|
||||
propagate_cancel(mut parent, mut c)
|
||||
cancel_fn := fn [mut c] () {
|
||||
c.cancel(true, canceled)
|
||||
}
|
||||
return Context(c), CancelFn(cancel_fn)
|
||||
}
|
||||
|
||||
// new_cancel_context returns an initialized CancelContext.
|
||||
fn new_cancel_context(parent Context) &CancelContext {
|
||||
return &CancelContext{
|
||||
id: rand.uuid_v4()
|
||||
context: parent
|
||||
mutex: sync.new_mutex()
|
||||
done: chan int{cap: 2}
|
||||
err: none
|
||||
}
|
||||
}
|
||||
|
||||
// deadline always returns none for a CancelContext, since it has no deadline.
|
||||
pub fn (ctx &CancelContext) deadline() ?time.Time {
|
||||
return none
|
||||
}
|
||||
|
||||
// done returns the done channel, which is closed when this context is canceled.
|
||||
pub fn (mut ctx CancelContext) done() chan int {
|
||||
ctx.mutex.lock()
|
||||
done := ctx.done
|
||||
ctx.mutex.unlock()
|
||||
return done
|
||||
}
|
||||
|
||||
// err returns `canceled` after the context has been canceled, or `none` if not yet canceled.
|
||||
pub fn (mut ctx CancelContext) err() IError {
|
||||
ctx.mutex.lock()
|
||||
err := ctx.err
|
||||
ctx.mutex.unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// value returns the CancelContext itself if the key matches the cancel context key,
|
||||
// otherwise delegates to the parent context.
|
||||
pub fn (ctx &CancelContext) value(key Key) ?Any {
|
||||
if key == cancel_context_key {
|
||||
return ctx
|
||||
}
|
||||
return ctx.context.value(key)
|
||||
}
|
||||
|
||||
// str returns a string representation of the CancelContext,
|
||||
// showing the parent context name suffixed with '.with_cancel'.
|
||||
pub fn (ctx &CancelContext) str() string {
|
||||
return context_name(ctx.context) + '.with_cancel'
|
||||
}
|
||||
|
||||
fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {
|
||||
if err is none {
|
||||
panic('context: internal error: missing cancel error')
|
||||
}
|
||||
|
||||
ctx.mutex.lock()
|
||||
if ctx.err !is none {
|
||||
ctx.mutex.unlock()
|
||||
// already canceled
|
||||
return
|
||||
}
|
||||
|
||||
ctx.err = err
|
||||
|
||||
if !ctx.done.closed {
|
||||
ctx.done <- 0
|
||||
ctx.done.close()
|
||||
}
|
||||
|
||||
for _, child in ctx.children {
|
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
mut c := child
|
||||
c.cancel(false, err)
|
||||
}
|
||||
|
||||
ctx.children = map[string]Canceler{}
|
||||
ctx.mutex.unlock()
|
||||
|
||||
if remove_from_parent {
|
||||
mut cc := &ctx.context
|
||||
remove_child(mut cc, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_cancel(mut parent Context, mut child Canceler) {
|
||||
done := parent.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
// parent is already canceled
|
||||
child.cancel(false, parent.err())
|
||||
return
|
||||
}
|
||||
else {}
|
||||
}
|
||||
mut p := parent_cancel_context(mut parent) or {
|
||||
// Pre-extract the parent done channel and pass it by value into the
|
||||
// spawn. The goroutine may outlive the caller's stack frame, so
|
||||
// dereferencing `parent` inside the spawn (e.g. via parent.done())
|
||||
// reads freed stack memory once the caller returns and reuses that
|
||||
// region — observed as a consistent segfault in vlib/context/cause_test.v
|
||||
// on Linux x86_64. Mirrors the fix in onecontext.run_two_contexts.
|
||||
spawn fn (pdone chan int, mut parent Context, mut child Canceler) {
|
||||
select {
|
||||
_ := <-pdone {
|
||||
child.cancel(false, parent.err())
|
||||
}
|
||||
}
|
||||
}(done, mut parent, mut child)
|
||||
return
|
||||
}
|
||||
|
||||
if p.err is none {
|
||||
p.children[child.id] = child
|
||||
} else {
|
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err)
|
||||
}
|
||||
}
|
||||
|
||||
// parent_cancel_context returns the underlying CancelContext for parent.
|
||||
// It does this by looking up parent.value(&cancel_context_key) to find
|
||||
// the innermost enclosing CancelContext and then checking whether
|
||||
// parent.done() matches that CancelContext. (If not, the CancelContext
|
||||
// has been wrapped in a custom implementation providing a
|
||||
// different done channel, in which case we should not bypass it.)
|
||||
fn parent_cancel_context(mut parent Context) ?&CancelContext {
|
||||
done := parent.done()
|
||||
if done.closed {
|
||||
return none
|
||||
}
|
||||
mut p := parent.value(cancel_context_key)?
|
||||
match mut p {
|
||||
CancelContext {
|
||||
pdone := p.done()
|
||||
if done == pdone {
|
||||
return p
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// remove_child removes a context from its parent.
|
||||
fn remove_child(mut parent Context, child Canceler) {
|
||||
mut p := parent_cancel_context(mut parent) or { return }
|
||||
p.children.delete(child.id)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// vtest retry: 3
|
||||
// vtest build: amd64 || arm64
|
||||
import context
|
||||
|
||||
// This example demonstrates the use of a cancelable context to prevent a
|
||||
// routine leak. By the end of the example function, the routine started
|
||||
// by gen will return without leaking.
|
||||
fn test_with_cancel() {
|
||||
// gen generates integers in a separate routine and
|
||||
// sends them to the returned channel.
|
||||
// The callers of gen need to cancel the context once
|
||||
// they are done consuming generated integers not to leak
|
||||
// the internal routine started by gen.
|
||||
gen := fn (mut ctx context.Context) chan int {
|
||||
dst := chan int{}
|
||||
spawn fn (mut ctx context.Context, dst chan int) {
|
||||
mut v := 0
|
||||
ch := ctx.done()
|
||||
for {
|
||||
select {
|
||||
_ := <-ch {
|
||||
// returning not to leak the routine
|
||||
return
|
||||
}
|
||||
dst <- v {
|
||||
v++
|
||||
}
|
||||
}
|
||||
}
|
||||
}(mut ctx, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
mut background := context.background()
|
||||
mut ctx, cancel := context.with_cancel(mut background)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
|
||||
mut mut_ctx := ctx
|
||||
mut ctx2 := &mut_ctx
|
||||
ch := gen(mut ctx2)
|
||||
for i in 0 .. 5 {
|
||||
v := <-ch
|
||||
assert i == v
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
// and other request-scoped values across API boundaries and between processes.
|
||||
// Based on: https://github.com/golang/go/tree/master/src/context
|
||||
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
|
||||
module context
|
||||
|
||||
import rand
|
||||
import time
|
||||
|
||||
// cause_context_key is the value-context key used to store the cause in a CauseContext.
|
||||
const cause_context_key = Key('context.CauseContext')
|
||||
|
||||
// CancelCauseFunc is a cancel function that accepts an optional cause error.
|
||||
// Calling it with a non-none cause records that cause; calling it with none
|
||||
// is equivalent to calling a plain CancelFn.
|
||||
pub type CancelCauseFunc = fn (cause IError)
|
||||
|
||||
// A CauseContext wraps a CancelContext and records a cause error set via
|
||||
// CancelCauseFunc. It implements the Context interface by delegating to the
|
||||
// embedded CancelContext.
|
||||
@[heap]
|
||||
struct CauseContext {
|
||||
id string
|
||||
deadline time.Time
|
||||
mut:
|
||||
cancel_ctx CancelContext
|
||||
cause IError = none
|
||||
}
|
||||
|
||||
// with_cancel_cause behaves like with_cancel but returns a CancelCauseFunc
|
||||
// instead of a CancelFn. Calling the returned function with a non-none error
|
||||
// records that error as the cause, retrievable via cause(ctx). Calling it with
|
||||
// none is identical to a plain cancel.
|
||||
//
|
||||
// Example:
|
||||
// mut bg := context.background()
|
||||
// mut ctx, cancel := context.with_cancel_cause(mut bg)
|
||||
// defer { cancel(none) }
|
||||
// cancel(my_err)
|
||||
// assert context.cause(ctx).str() == my_err.str()
|
||||
pub fn with_cancel_cause(mut parent Context) (Context, CancelCauseFunc) {
|
||||
id := rand.uuid_v4()
|
||||
inner := new_cancel_context(parent)
|
||||
mut ctx := &CauseContext{
|
||||
id: id
|
||||
cancel_ctx: inner
|
||||
}
|
||||
propagate_cancel(mut parent, mut ctx)
|
||||
cancel_fn := fn [mut ctx] (cause IError) {
|
||||
if cause !is none {
|
||||
ctx.cancel_ctx.mutex.lock()
|
||||
if ctx.cause is none {
|
||||
ctx.cause = cause
|
||||
}
|
||||
ctx.cancel_ctx.mutex.unlock()
|
||||
}
|
||||
ctx.cancel_ctx.cancel(true, canceled)
|
||||
}
|
||||
return Context(ctx), CancelCauseFunc(cancel_fn)
|
||||
}
|
||||
|
||||
// with_deadline_cause behaves like with_deadline but accepts a cause error that
|
||||
// is recorded when the deadline fires (instead of the generic deadline_exceeded).
|
||||
// If cancel is called before the deadline, that cancellation takes effect instead.
|
||||
pub fn with_deadline_cause(mut parent Context, d time.Time, deadline_cause IError) (Context, CancelFn) {
|
||||
if cur := parent.deadline() {
|
||||
if cur < d {
|
||||
return with_cancel(mut parent)
|
||||
}
|
||||
}
|
||||
inner := new_cancel_context(parent)
|
||||
id := rand.uuid_v4()
|
||||
mut ctx := &CauseContext{
|
||||
id: id
|
||||
cancel_ctx: inner
|
||||
deadline: d
|
||||
}
|
||||
propagate_cancel(mut parent, mut ctx)
|
||||
dur := d - time.now()
|
||||
if dur.nanoseconds() <= 0 {
|
||||
// deadline already passed
|
||||
ctx.cause = deadline_cause
|
||||
ctx.cancel_ctx.cancel(false, deadline_exceeded)
|
||||
cancel_fn := fn [mut ctx] () {
|
||||
ctx.cancel_ctx.cancel(true, canceled)
|
||||
}
|
||||
return Context(ctx), CancelFn(cancel_fn)
|
||||
}
|
||||
spawn fn (mut ctx CauseContext, dur time.Duration, deadline_cause IError) {
|
||||
time.sleep(dur)
|
||||
ctx.cancel_ctx.mutex.lock()
|
||||
// Only record deadline cause if the context wasn't already canceled,
|
||||
// so that the first cancellation's cause (manual or from parent) wins.
|
||||
if ctx.cancel_ctx.err is none {
|
||||
ctx.cause = deadline_cause
|
||||
}
|
||||
ctx.cancel_ctx.mutex.unlock()
|
||||
ctx.cancel_ctx.cancel(true, deadline_exceeded)
|
||||
}(mut ctx, dur, deadline_cause)
|
||||
cancel_fn := fn [mut ctx] () {
|
||||
ctx.cancel_ctx.cancel(true, canceled)
|
||||
}
|
||||
return Context(ctx), CancelFn(cancel_fn)
|
||||
}
|
||||
|
||||
// with_timeout_cause is a convenience wrapper around with_deadline_cause using
|
||||
// a relative duration instead of an absolute time.
|
||||
pub fn with_timeout_cause(mut parent Context, timeout time.Duration, deadline_cause IError) (Context, CancelFn) {
|
||||
return with_deadline_cause(mut parent, time.now().add(timeout), deadline_cause)
|
||||
}
|
||||
|
||||
// cause returns the cause of the context's cancellation.
|
||||
// If ctx was canceled via CancelCauseFunc with a non-none cause, that cause is
|
||||
// returned. Otherwise, cause returns ctx.err() (i.e. canceled or
|
||||
// deadline_exceeded).
|
||||
pub fn cause(ctx Context) IError {
|
||||
mut mctx := ctx
|
||||
if val := mctx.value(cause_context_key) {
|
||||
match val {
|
||||
CauseContext {
|
||||
val.cancel_ctx.mutex.lock()
|
||||
c := val.cause
|
||||
val.cancel_ctx.mutex.unlock()
|
||||
if c !is none {
|
||||
return c
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return mctx.err()
|
||||
}
|
||||
|
||||
// --- CauseContext implements Context and Canceler ---
|
||||
|
||||
// deadline returns the deadline for the context, or none if no deadline is set.
|
||||
pub fn (ctx &CauseContext) deadline() ?time.Time {
|
||||
if ctx.deadline != time.Time{} {
|
||||
return ctx.deadline
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// done returns a channel that is closed when the context is canceled.
|
||||
pub fn (mut ctx CauseContext) done() chan int {
|
||||
return ctx.cancel_ctx.done()
|
||||
}
|
||||
|
||||
// err returns the error that canceled the context, or none if not canceled.
|
||||
pub fn (mut ctx CauseContext) err() IError {
|
||||
return ctx.cancel_ctx.err()
|
||||
}
|
||||
|
||||
// value returns the cause context itself when the key matches cause_context_key,
|
||||
// otherwise delegates to the embedded cancel context.
|
||||
pub fn (ctx &CauseContext) value(key Key) ?Any {
|
||||
if key == cause_context_key {
|
||||
return ctx
|
||||
}
|
||||
return ctx.cancel_ctx.value(key)
|
||||
}
|
||||
|
||||
// str returns a string representation of the context (e.g. 'EmptyContext.with_cancel_cause').
|
||||
pub fn (ctx &CauseContext) str() string {
|
||||
return context_name(ctx.cancel_ctx.context) + '.with_cancel_cause'
|
||||
}
|
||||
|
||||
// cancel cancels the context. If remove_from_parent is true, it also removes
|
||||
// this context from its parent's children list. The err is recorded as the cause.
|
||||
pub fn (mut ctx CauseContext) cancel(remove_from_parent bool, err IError) {
|
||||
// If being canceled with the generic 'context canceled' error and we
|
||||
// have a parent, propagate the parent's cause so children can retrieve it.
|
||||
// We walk the value chain directly instead of calling cause(): cause()
|
||||
// would fall through to err() on a parent CancelContext, which would
|
||||
// deadlock here because this cancel is typically invoked from within a
|
||||
// parent's cancel() while that parent's mutex is held. The cause field
|
||||
// on a CauseContext is always assigned before its cancel propagates, so
|
||||
// reading it without locking is safe along this downward path.
|
||||
if err.str() == 'context canceled' {
|
||||
match ctx.cancel_ctx.context {
|
||||
EmptyContext {}
|
||||
else {
|
||||
mut parent := ctx.cancel_ctx.context
|
||||
if val := parent.value(cause_context_key) {
|
||||
match val {
|
||||
CauseContext {
|
||||
if val.cause !is none {
|
||||
ctx.cause = val.cause
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.cancel_ctx.cancel(false, err)
|
||||
if remove_from_parent {
|
||||
mut cc := &ctx.cancel_ctx.context
|
||||
remove_child(mut cc, ctx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// vtest retry: 3
|
||||
module context
|
||||
|
||||
import time
|
||||
|
||||
fn test_with_cancel_cause_no_cause() {
|
||||
mut bg := background()
|
||||
mut ctx, cancel := with_cancel_cause(mut bg)
|
||||
defer {
|
||||
cancel(none)
|
||||
}
|
||||
|
||||
assert ctx.err() is none
|
||||
assert cause(ctx) is none
|
||||
|
||||
cancel(none)
|
||||
assert ctx.err().str() == 'context canceled'
|
||||
// cause falls back to err() when no cause was set
|
||||
assert cause(ctx).str() == 'context canceled'
|
||||
}
|
||||
|
||||
fn test_with_cancel_cause_with_cause() {
|
||||
my_err := error('my custom cause')
|
||||
|
||||
mut bg := background()
|
||||
mut ctx, cancel := with_cancel_cause(mut bg)
|
||||
defer {
|
||||
cancel(none)
|
||||
}
|
||||
|
||||
assert ctx.err() is none
|
||||
|
||||
cancel(my_err)
|
||||
assert ctx.err().str() == 'context canceled'
|
||||
assert cause(ctx).str() == 'my custom cause'
|
||||
}
|
||||
|
||||
fn test_with_cancel_cause_first_cause_wins() {
|
||||
first := error('first cause')
|
||||
second := error('second cause')
|
||||
|
||||
mut bg := background()
|
||||
mut ctx, cancel := with_cancel_cause(mut bg)
|
||||
|
||||
cancel(first)
|
||||
cancel(second) // should be ignored
|
||||
cancel(none) // should be ignored
|
||||
|
||||
assert cause(ctx).str() == 'first cause'
|
||||
}
|
||||
|
||||
fn test_with_cancel_cause_parent_cancel_propagates() {
|
||||
mut bg := background()
|
||||
mut parent, parent_cancel := with_cancel(mut bg)
|
||||
mut ctx, _ := with_cancel_cause(mut parent)
|
||||
|
||||
assert ctx.err() is none
|
||||
|
||||
parent_cancel()
|
||||
// give goroutine a moment to propagate
|
||||
time.sleep(5 * time.millisecond)
|
||||
|
||||
assert ctx.err().str() == 'context canceled'
|
||||
}
|
||||
|
||||
fn test_with_timeout_cause_fires_cause() {
|
||||
my_cause := error('timed out for testing')
|
||||
|
||||
mut bg := background()
|
||||
mut ctx, cancel := with_timeout_cause(mut bg, 30 * time.millisecond, my_cause)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
|
||||
// not done yet
|
||||
assert ctx.err() is none
|
||||
|
||||
// poll for the deadline goroutine to record the cancellation; busy CI
|
||||
// runners can take well over 60ms to schedule it.
|
||||
deadline := time.now().add(2 * time.second)
|
||||
for ctx.err() is none && time.now() < deadline {
|
||||
time.sleep(5 * time.millisecond)
|
||||
}
|
||||
|
||||
assert ctx.err().str() == 'context deadline exceeded'
|
||||
assert cause(ctx).str() == 'timed out for testing'
|
||||
}
|
||||
|
||||
fn test_with_timeout_cause_cancel_before_deadline() {
|
||||
my_cause := error('deadline cause')
|
||||
|
||||
mut bg := background()
|
||||
mut ctx, cancel := with_timeout_cause(mut bg, 500 * time.millisecond, my_cause)
|
||||
|
||||
// cancel before deadline fires
|
||||
cancel()
|
||||
time.sleep(5 * time.millisecond)
|
||||
|
||||
assert ctx.err().str() == 'context canceled'
|
||||
// no cause was set via CancelCauseFunc; cause falls back to err()
|
||||
assert cause(ctx).str() == 'context canceled'
|
||||
}
|
||||
|
||||
fn test_cause_on_plain_cancel_context() {
|
||||
// cause() on a non-cause context should just return ctx.err()
|
||||
mut bg := background()
|
||||
mut ctx, cancel := with_cancel(mut bg)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
|
||||
assert cause(ctx) is none
|
||||
|
||||
cancel()
|
||||
assert cause(ctx).str() == 'context canceled'
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
// and other request-scoped values across API boundaries and between processes.
|
||||
// Based on: https://github.com/golang/go/tree/master/src/context
|
||||
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
|
||||
module context
|
||||
|
||||
import time
|
||||
|
||||
const cancel_context_key = Key('context.CancelContext')
|
||||
|
||||
// canceled is the error returned by Context.err when the context is canceled.
|
||||
const canceled = error('context canceled')
|
||||
|
||||
// deadline_exceeded is the error returned by Context.err when the context's
|
||||
// deadline passes.
|
||||
const deadline_exceeded = error('context deadline exceeded')
|
||||
|
||||
// Key represents the type for the ValueContext key
|
||||
pub type Key = bool | f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64 | u8 | voidptr
|
||||
|
||||
// Any represents a generic type for the ValueContext
|
||||
pub interface Any {}
|
||||
|
||||
// `Context` is an interface that defined the minimum required functionality
|
||||
// for a Context.
|
||||
//
|
||||
// `deadline()` returns the time when work done on behalf of this context
|
||||
// should be canceled. deadline returns none when no deadline is
|
||||
// set. Successive calls to deadline return the same results.
|
||||
//
|
||||
// `value(key)` returns an Optional that wraps the value associated with this context for key.
|
||||
// It returns none if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.with_value and
|
||||
// Context.value. A key can be any type that supports equality;
|
||||
// modules should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// `done()` returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. done may return a closed channel if this context can
|
||||
// never be canceled. Successive calls to done return the same value.
|
||||
// The close of the done channel may happen asynchronously,
|
||||
// after the cancel function returns.
|
||||
//
|
||||
// with_cancel arranges for done to be closed when cancel is called;
|
||||
// with_deadline arranges for done to be closed when the deadline
|
||||
// expires; with_timeout arranges for done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// `err()` returns an IError based on some conditions
|
||||
// If done is not yet closed, err returns none.
|
||||
// If done is closed, err returns a non-none error explaining why:
|
||||
// canceled if the context was canceled
|
||||
// or deadline_exceeded if the context's deadline passed.
|
||||
// After err returns a non-none error, successive calls to err return the same error.
|
||||
pub interface Context {
|
||||
deadline() ?time.Time
|
||||
value(key Key) ?Any
|
||||
mut:
|
||||
done() chan int
|
||||
err() IError
|
||||
}
|
||||
|
||||
// str returns the `str` method of the corresponding Context struct
|
||||
pub fn (ctx &Context) str() string {
|
||||
// since `Context` is an interface we have to manually match every possible
|
||||
// type that implements `Context` if we want to use a `Context` as a field in a struct
|
||||
// since the `Context` interface has to implement its own `str` method.
|
||||
match ctx {
|
||||
BackgroundContext {
|
||||
return ctx.str()
|
||||
}
|
||||
EmptyContext {
|
||||
return ctx.str()
|
||||
}
|
||||
TodoContext {
|
||||
return ctx.str()
|
||||
}
|
||||
CancelContext {
|
||||
return ctx.str()
|
||||
}
|
||||
CauseContext {
|
||||
return ctx.str()
|
||||
}
|
||||
TimerContext {
|
||||
return ctx.str()
|
||||
}
|
||||
ValueContext {
|
||||
return ctx.str()
|
||||
}
|
||||
else {
|
||||
return context_name(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn context_name(ctx Context) string {
|
||||
return typeof(ctx)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
// and other request-scoped values across API boundaries and between processes.
|
||||
// Based on: https://github.com/golang/go/tree/master/src/context
|
||||
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
|
||||
module context
|
||||
|
||||
import rand
|
||||
import time
|
||||
|
||||
// A TimerContext carries a timer and a deadline. It embeds a CancelContext to
|
||||
// implement done and err. It implements cancel by stopping its timer then
|
||||
// delegating to CancelContext.cancel
|
||||
pub struct TimerContext {
|
||||
id string
|
||||
mut:
|
||||
cancel_ctx CancelContext
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
// with_deadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// with_deadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
pub fn with_deadline(mut parent Context, d time.Time) (Context, CancelFn) {
|
||||
id := rand.uuid_v4()
|
||||
if cur := parent.deadline() {
|
||||
if cur < d {
|
||||
// The current deadline is already sooner than the new one.
|
||||
return with_cancel(mut parent)
|
||||
}
|
||||
}
|
||||
cancel_ctx := new_cancel_context(parent)
|
||||
mut ctx := &TimerContext{
|
||||
cancel_ctx: cancel_ctx
|
||||
deadline: d
|
||||
id: id
|
||||
}
|
||||
propagate_cancel(mut parent, mut ctx)
|
||||
dur := d - time.now()
|
||||
if dur.nanoseconds() <= 0 {
|
||||
ctx.cancel(true, deadline_exceeded) // deadline has already passed
|
||||
cancel_fn := fn [mut ctx] () {
|
||||
ctx.cancel(true, canceled)
|
||||
}
|
||||
return Context(ctx), CancelFn(cancel_fn)
|
||||
}
|
||||
|
||||
if ctx.err() is none {
|
||||
spawn fn (mut ctx TimerContext, dur time.Duration) {
|
||||
time.sleep(dur)
|
||||
ctx.cancel(true, deadline_exceeded)
|
||||
}(mut ctx, dur)
|
||||
}
|
||||
|
||||
cancel_fn := fn [mut ctx] () {
|
||||
ctx.cancel(true, canceled)
|
||||
}
|
||||
return Context(ctx), CancelFn(cancel_fn)
|
||||
}
|
||||
|
||||
// with_timeout returns with_deadline(parent, time.now().add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete
|
||||
pub fn with_timeout(mut parent Context, timeout time.Duration) (Context, CancelFn) {
|
||||
return with_deadline(mut parent, time.now().add(timeout))
|
||||
}
|
||||
|
||||
// deadline returns the deadline for this context.
|
||||
pub fn (ctx &TimerContext) deadline() ?time.Time {
|
||||
return ctx.deadline
|
||||
}
|
||||
|
||||
// done returns the done channel from the embedded CancelContext.
|
||||
pub fn (mut ctx TimerContext) done() chan int {
|
||||
return ctx.cancel_ctx.done()
|
||||
}
|
||||
|
||||
// err returns the error from the embedded CancelContext.
|
||||
pub fn (mut ctx TimerContext) err() IError {
|
||||
return ctx.cancel_ctx.err()
|
||||
}
|
||||
|
||||
// value returns the value associated with the given key by delegating
|
||||
// to the embedded CancelContext.
|
||||
pub fn (ctx &TimerContext) value(key Key) ?Any {
|
||||
return ctx.cancel_ctx.value(key)
|
||||
}
|
||||
|
||||
// cancel cancels the timer context by delegating to the embedded CancelContext
|
||||
// and optionally removing itself from its parent's children.
|
||||
pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err IError) {
|
||||
ctx.cancel_ctx.cancel(false, err)
|
||||
if remove_from_parent {
|
||||
// Remove this TimerContext from its parent CancelContext's children.
|
||||
mut cc := &ctx.cancel_ctx.context
|
||||
remove_child(mut cc, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// str returns a string representation of the TimerContext, showing the
|
||||
// parent context name, the deadline time, and the duration remaining.
|
||||
pub fn (ctx &TimerContext) str() string {
|
||||
return context_name(ctx.cancel_ctx.context) + '.with_deadline(' + ctx.deadline.str() + ' [' +
|
||||
(time.now() - ctx.deadline).str() + '])'
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// vtest build: false // TODO: it sometimes blocks, investigate why
|
||||
import context
|
||||
import time
|
||||
|
||||
// a reasonable duration to block in an example
|
||||
const short_duration = 1 * time.millisecond
|
||||
|
||||
// This example passes a context with an arbitrary deadline to tell a blocking
|
||||
// function that it should abandon its work as soon as it gets to it.
|
||||
fn test_with_deadline() {
|
||||
dur := time.now().add(short_duration)
|
||||
mut background := context.background()
|
||||
mut ctx, cancel := context.with_deadline(mut background, dur)
|
||||
|
||||
defer {
|
||||
// Even though ctx will be expired, it is good practice to call its
|
||||
// cancellation function in any case. Failure to do so may keep the
|
||||
// context and its parent alive longer than necessary.
|
||||
cancel()
|
||||
}
|
||||
|
||||
ctx_ch := ctx.done()
|
||||
select {
|
||||
_ := <-ctx_ch {}
|
||||
1 * time.second {
|
||||
panic('This should not happen')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This example passes a context with a timeout to tell a blocking function that
|
||||
// it should abandon its work after the timeout elapses.
|
||||
fn test_with_timeout() {
|
||||
// Pass a context with a timeout to tell a blocking function that it
|
||||
// should abandon its work after the timeout elapses.
|
||||
mut background := context.background()
|
||||
mut ctx, cancel := context.with_timeout(mut background, short_duration)
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
|
||||
ctx_ch := ctx.done()
|
||||
select {
|
||||
_ := <-ctx_ch {}
|
||||
1 * time.second {
|
||||
panic('This should not happen')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
// and other request-scoped values across API boundaries and between processes.
|
||||
// Based on: https://github.com/golang/go/tree/master/src/context
|
||||
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
|
||||
module context
|
||||
|
||||
import time
|
||||
|
||||
// An EmptyContext is never canceled, has no values.
|
||||
// The done_ch field is an open channel that is never closed, returned by done()
|
||||
// on every call. This mirrors Go's context.Background().Done() == nil behavior:
|
||||
// selecting on this channel blocks forever, meaning the context is never done.
|
||||
pub struct EmptyContext {
|
||||
mut:
|
||||
done_ch chan int
|
||||
}
|
||||
|
||||
// deadline returns none, since an EmptyContext has no deadline.
|
||||
pub fn (ctx &EmptyContext) deadline() ?time.Time {
|
||||
return none
|
||||
}
|
||||
|
||||
// done returns an open channel that is never closed, since an EmptyContext can
|
||||
// never be canceled. Selecting on the returned channel blocks forever.
|
||||
// The same channel instance is returned on every call for a given EmptyContext.
|
||||
pub fn (mut ctx EmptyContext) done() chan int {
|
||||
return ctx.done_ch
|
||||
}
|
||||
|
||||
// err returns none, since an EmptyContext is never canceled.
|
||||
pub fn (ctx &EmptyContext) err() IError {
|
||||
return none
|
||||
}
|
||||
|
||||
// value returns none, since an EmptyContext carries no values.
|
||||
pub fn (ctx &EmptyContext) value(key Key) ?Any {
|
||||
return none
|
||||
}
|
||||
|
||||
// str returns a string describing the Context.
|
||||
pub fn (ctx &EmptyContext) str() string {
|
||||
return 'unknown empty Context'
|
||||
}
|
||||
|
||||
// A BackgroundContext is never canceled, has no values.
|
||||
struct BackgroundContext {
|
||||
EmptyContext
|
||||
}
|
||||
|
||||
// str returns a string describing the Context.
|
||||
pub fn (ctx &BackgroundContext) str() string {
|
||||
return 'context.Background'
|
||||
}
|
||||
|
||||
// background returns an empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
pub fn background() Context {
|
||||
return &BackgroundContext{}
|
||||
}
|
||||
|
||||
// A TodoContext is never canceled, has no values, and has no deadline. It is
|
||||
// never used for real work.
|
||||
struct TodoContext {
|
||||
EmptyContext
|
||||
}
|
||||
|
||||
// str returns a string describing the Context.
|
||||
pub fn (ctx &TodoContext) str() string {
|
||||
return 'context.TODO'
|
||||
}
|
||||
|
||||
// TODO: returns an empty Context. Code should use todo when
|
||||
// it's unclear which Context to use or it is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter).
|
||||
pub fn todo() Context {
|
||||
return &TodoContext{}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// vtest build: amd64 || arm64
|
||||
module context
|
||||
|
||||
fn test_background() {
|
||||
ctx := background()
|
||||
assert '&context.Background' == ctx.str()
|
||||
if _ := ctx.value('') {
|
||||
panic('This should never happen')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_todo() {
|
||||
ctx := todo()
|
||||
assert '&context.TODO' == ctx.str()
|
||||
if _ := ctx.value('') {
|
||||
panic('This should never happen')
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// This module defines the Context type, which carries deadlines, cancellation signals,
|
||||
// and other request-scoped values across API boundaries and between processes.
|
||||
// Based on: https://github.com/golang/go/tree/master/src/context
|
||||
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
|
||||
module context
|
||||
|
||||
import time
|
||||
|
||||
// A ValueContext carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
pub struct ValueContext {
|
||||
key Key
|
||||
value Any
|
||||
mut:
|
||||
context Context
|
||||
}
|
||||
|
||||
// with_value returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The provided key must be comparable and should not be of type
|
||||
// string or any other built-in type to avoid collisions between
|
||||
// packages using context. Users of with_value should define their own
|
||||
// types for keys
|
||||
pub fn with_value(parent Context, key Key, value Any) Context {
|
||||
return &ValueContext{
|
||||
context: parent
|
||||
key: key
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
// deadline returns the deadline from the parent context.
|
||||
pub fn (ctx &ValueContext) deadline() ?time.Time {
|
||||
return ctx.context.deadline()
|
||||
}
|
||||
|
||||
// done returns the done channel from the parent context.
|
||||
pub fn (mut ctx ValueContext) done() chan int {
|
||||
return ctx.context.done()
|
||||
}
|
||||
|
||||
// err returns the error from the parent context.
|
||||
pub fn (mut ctx ValueContext) err() IError {
|
||||
return ctx.context.err()
|
||||
}
|
||||
|
||||
// value returns the value associated with this context for the given key.
|
||||
// If the key matches the one stored in this ValueContext, its value is returned.
|
||||
// Otherwise, the lookup is delegated to the parent context.
|
||||
pub fn (ctx &ValueContext) value(key Key) ?Any {
|
||||
if ctx.key == key {
|
||||
return ctx.value
|
||||
}
|
||||
return ctx.context.value(key)
|
||||
}
|
||||
|
||||
// str returns a string representation of the ValueContext,
|
||||
// showing the parent context name suffixed with '.with_value'.
|
||||
pub fn (ctx &ValueContext) str() string {
|
||||
return context_name(ctx.context) + '.with_value'
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// vtest build: amd64 || arm64
|
||||
import context
|
||||
|
||||
const not_found_value = &Value{
|
||||
val: 'key not found'
|
||||
}
|
||||
|
||||
struct Value {
|
||||
val string
|
||||
}
|
||||
|
||||
// This example demonstrates how a value can be passed to the context
|
||||
// and also how to retrieve it if it exists.
|
||||
fn test_with_value() {
|
||||
f := fn (ctx context.Context, key context.Key) &Value {
|
||||
if value := ctx.value(key) {
|
||||
match value {
|
||||
Value {
|
||||
return value
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return not_found_value
|
||||
}
|
||||
|
||||
key := 'language'
|
||||
value := &Value{
|
||||
val: 'VAL'
|
||||
}
|
||||
ctx := context.with_value(context.background(), key, value)
|
||||
|
||||
assert value == f(ctx, key)
|
||||
assert not_found_value == f(ctx, 'color')
|
||||
}
|
||||
Reference in new issue
Block a user