context: add a new `context` module, based on Golang's context, intended to be used in webservers (#9563)
parent
b54188dfea
commit
07a6f4e445
|
@ -30,7 +30,6 @@ const (
|
||||||
]
|
]
|
||||||
skip_with_fsanitize_undefined = []string{}
|
skip_with_fsanitize_undefined = []string{}
|
||||||
skip_with_werror = [
|
skip_with_werror = [
|
||||||
'vlib/sync/array_rlock_test.v',
|
|
||||||
'vlib/clipboard/clipboard_test.v',
|
'vlib/clipboard/clipboard_test.v',
|
||||||
'vlib/eventbus/eventbus_test.v',
|
'vlib/eventbus/eventbus_test.v',
|
||||||
'vlib/gx/color_test.v',
|
'vlib/gx/color_test.v',
|
||||||
|
@ -56,6 +55,10 @@ const (
|
||||||
'vlib/strconv/atof_test.v',
|
'vlib/strconv/atof_test.v',
|
||||||
'vlib/strconv/f32_f64_to_string_test.v',
|
'vlib/strconv/f32_f64_to_string_test.v',
|
||||||
'vlib/strconv/number_to_base_test.v',
|
'vlib/strconv/number_to_base_test.v',
|
||||||
|
'vlib/context/value_test.v' /* the following tests need C casts in `sync` and/or thirdparty/stdatomic */,
|
||||||
|
'vlib/context/empty_test.v',
|
||||||
|
'vlib/context/cancel_test.v',
|
||||||
|
'vlib/sync/array_rlock_test.v',
|
||||||
'vlib/sync/atomic2/atomic_test.v',
|
'vlib/sync/atomic2/atomic_test.v',
|
||||||
'vlib/sync/channel_2_test.v',
|
'vlib/sync/channel_2_test.v',
|
||||||
'vlib/sync/channel_1_test.v',
|
'vlib/sync/channel_1_test.v',
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
# 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) and a CancelFunc. Calling the CancelFunc
|
||||||
|
cancels the child and its children, removes the parent's reference to the child,
|
||||||
|
and stops any associated timers. Failing to call the CancelFunc leaks the child
|
||||||
|
and its children until the parent is canceled or the timer fires.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
In this section you can see some usage examples for this module
|
||||||
|
|
||||||
|
### Context With Cancellation
|
||||||
|
|
||||||
|
```v
|
||||||
|
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 example_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.CancelerContext) chan int {
|
||||||
|
dst := chan int{}
|
||||||
|
go fn (mut ctx context.CancelerContext, dst chan int) {
|
||||||
|
ch := ctx.done()
|
||||||
|
loop: for i in 0 .. 5 {
|
||||||
|
select {
|
||||||
|
_ := <-ch {
|
||||||
|
// returning not to leak the routine
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
dst <- i {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(mut ctx, dst)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
mut ctx := context.with_cancel(context.background())
|
||||||
|
defer {
|
||||||
|
context.cancel(mut ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := gen(mut ctx)
|
||||||
|
for i in 0 .. 5 {
|
||||||
|
v := <-ch
|
||||||
|
assert i == v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context With Deadline
|
||||||
|
|
||||||
|
```v
|
||||||
|
import context
|
||||||
|
import time
|
||||||
|
|
||||||
|
const (
|
||||||
|
// a reasonable duration to block in an example
|
||||||
|
short_duration = 1 * time.millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
fn after(dur time.Duration) chan int {
|
||||||
|
dst := chan int{}
|
||||||
|
go fn (dur time.Duration, dst chan int) {
|
||||||
|
time.sleep(dur)
|
||||||
|
dst <- 0
|
||||||
|
}(dur, dst)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 example_with_deadline() {
|
||||||
|
dur := time.now().add(short_duration)
|
||||||
|
mut ctx := context.with_deadline(context.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.
|
||||||
|
context.cancel(mut ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
after_ch := after(1 * time.second)
|
||||||
|
ctx_ch := ctx.done()
|
||||||
|
select {
|
||||||
|
_ := <-after_ch {
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
_ := <-ctx_ch {
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context With Timeout
|
||||||
|
|
||||||
|
```v
|
||||||
|
import context
|
||||||
|
import time
|
||||||
|
|
||||||
|
const (
|
||||||
|
// a reasonable duration to block in an example
|
||||||
|
short_duration = 1 * time.millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
fn after(dur time.Duration) chan int {
|
||||||
|
dst := chan int{}
|
||||||
|
go fn (dur time.Duration, dst chan int) {
|
||||||
|
time.sleep(dur)
|
||||||
|
dst <- 0
|
||||||
|
}(dur, dst)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example passes a context with a timeout to tell a blocking function that
|
||||||
|
// it should abandon its work after the timeout elapses.
|
||||||
|
fn example_with_timeout() {
|
||||||
|
// Pass a context with a timeout to tell a blocking function that it
|
||||||
|
// should abandon its work after the timeout elapses.
|
||||||
|
mut ctx := context.with_timeout(context.background(), short_duration)
|
||||||
|
defer {
|
||||||
|
context.cancel(mut ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
after_ch := after(1 * time.second)
|
||||||
|
ctx_ch := ctx.done()
|
||||||
|
select {
|
||||||
|
_ := <-after_ch {
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
_ := <-ctx_ch {
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context With Value
|
||||||
|
|
||||||
|
```v
|
||||||
|
import context
|
||||||
|
|
||||||
|
type ValueContextKey = string
|
||||||
|
|
||||||
|
// This example demonstrates how a value can be passed to the context
|
||||||
|
// and also how to retrieve it if it exists.
|
||||||
|
fn example_with_value() {
|
||||||
|
f := fn (ctx context.ValueContext, key ValueContextKey) string {
|
||||||
|
if value := ctx.value(key) {
|
||||||
|
if !isnil(value) {
|
||||||
|
return *(&string(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'key not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ValueContextKey('language')
|
||||||
|
value := 'VAL'
|
||||||
|
ctx := context.with_value(context.background(), key, &value)
|
||||||
|
|
||||||
|
assert value == f(ctx, key)
|
||||||
|
assert 'key not found' == f(ctx, ValueContextKey('color'))
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,77 @@
|
||||||
|
module context
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
pub const (
|
||||||
|
background = EmptyContext(0)
|
||||||
|
todo = EmptyContext(1)
|
||||||
|
|
||||||
|
cancel_context_key = 'context.CancelContext'
|
||||||
|
|
||||||
|
// canceled is the error returned by Context.err when the context is canceled.
|
||||||
|
canceled = 'context canceled'
|
||||||
|
|
||||||
|
// deadline_exceeded is the error returned by Context.err when the context's
|
||||||
|
// deadline passes.
|
||||||
|
deadline_exceeded = 'context deadline exceeded'
|
||||||
|
)
|
||||||
|
|
||||||
|
pub interface 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.
|
||||||
|
deadline() ?time.Time
|
||||||
|
// done returns a channel that's closed when work done on behalf of this
|
||||||
|
// context should be canceled. done may return nil 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.
|
||||||
|
done() chan int
|
||||||
|
// If done is not yet closed, err returns nil.
|
||||||
|
// If done is closed, err returns a non-nil error explaining why:
|
||||||
|
// canceled if the context was canceled
|
||||||
|
// or deadline_exceeded if the context's deadline passed.
|
||||||
|
// After err returns a non-nil error, successive calls to err return the same error.
|
||||||
|
err() string
|
||||||
|
// Value returns the value associated with this context for key, or nil
|
||||||
|
// 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;
|
||||||
|
// packages should define keys as an unexported type to avoid
|
||||||
|
// collisions.
|
||||||
|
value(key string) ?voidptr
|
||||||
|
str() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Context(context.background)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Context(context.todo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context_name(ctx Context) string {
|
||||||
|
return typeof(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
module context
|
||||||
|
|
||||||
|
import rand
|
||||||
|
import sync
|
||||||
|
import time
|
||||||
|
|
||||||
|
pub interface Canceler {
|
||||||
|
id string
|
||||||
|
cancel(remove_from_parent bool, err string)
|
||||||
|
done() chan int
|
||||||
|
str() string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(mut ctx CancelerContext) {
|
||||||
|
match mut ctx {
|
||||||
|
CancelContext {
|
||||||
|
ctx.cancel(true, canceled)
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
ctx.cancel(true, canceled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelerContext implements the Canceler intarface for both
|
||||||
|
// struct types: CancelContext and TimerContext
|
||||||
|
pub type CancelerContext = CancelContext | TimerContext
|
||||||
|
|
||||||
|
pub fn (mut ctx CancelerContext) done() chan int {
|
||||||
|
match mut ctx {
|
||||||
|
CancelContext {
|
||||||
|
return ctx.done()
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
return ctx.done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx CancelerContext) err() string {
|
||||||
|
match mut ctx {
|
||||||
|
CancelContext {
|
||||||
|
return ctx.err()
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
return ctx.err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx CancelerContext) value(key string) ?voidptr {
|
||||||
|
match ctx {
|
||||||
|
CancelContext {
|
||||||
|
return ctx.value(key)
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
return ctx.value(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx CancelerContext) cancel(remove_from_parent bool, err string) {
|
||||||
|
match mut ctx {
|
||||||
|
CancelContext {
|
||||||
|
ctx.cancel(remove_from_parent, err)
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
ctx.cancel(remove_from_parent, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx CancelerContext) str() string {
|
||||||
|
match ctx {
|
||||||
|
CancelContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
done chan int
|
||||||
|
children map[string]Canceler
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// A CancelFunc may be called by multiple goroutines simultaneously.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
// pub type CancelFunc = fn (c Canceler)
|
||||||
|
|
||||||
|
// 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(parent Context) &CancelerContext {
|
||||||
|
mut c := new_cancel_context(parent)
|
||||||
|
propagate_cancel(parent, mut c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx CancelContext) deadline() ?time.Time {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx CancelContext) done() chan int {
|
||||||
|
ctx.mutex.@lock()
|
||||||
|
done := ctx.done
|
||||||
|
ctx.mutex.unlock()
|
||||||
|
return done
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx CancelContext) err() string {
|
||||||
|
ctx.mutex.@lock()
|
||||||
|
err := ctx.err
|
||||||
|
ctx.mutex.unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx CancelContext) value(key string) ?voidptr {
|
||||||
|
if key == cancel_context_key {
|
||||||
|
return voidptr(&ctx)
|
||||||
|
}
|
||||||
|
return ctx.context.value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx CancelContext) str() string {
|
||||||
|
return context_name(ctx.context) + '.with_cancel'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut ctx CancelContext) cancel(remove_from_parent bool, err string) {
|
||||||
|
if err == '' {
|
||||||
|
panic('context: internal error: missing cancel error')
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.mutex.@lock()
|
||||||
|
if ctx.err != '' {
|
||||||
|
ctx.mutex.unlock()
|
||||||
|
// already canceled
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.err = err
|
||||||
|
|
||||||
|
if !ctx.done.closed {
|
||||||
|
ctx.done.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child in ctx.children {
|
||||||
|
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||||
|
child.cancel(false, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.children = map[string]Canceler{}
|
||||||
|
ctx.mutex.unlock()
|
||||||
|
|
||||||
|
if remove_from_parent {
|
||||||
|
remove_child(ctx.context, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propagate_cancel(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(parent) or {
|
||||||
|
go fn (parent Context, mut child Canceler) {
|
||||||
|
pdone := parent.done()
|
||||||
|
cdone := child.done()
|
||||||
|
select {
|
||||||
|
_ := <-pdone {
|
||||||
|
child.cancel(false, parent.err())
|
||||||
|
}
|
||||||
|
_ := <-cdone {}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}(parent, mut child)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.err != '' {
|
||||||
|
// parent has already been canceled
|
||||||
|
child.cancel(false, p.err)
|
||||||
|
} else {
|
||||||
|
p.children[child.id] = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(parent Context) ?CancelContext {
|
||||||
|
done := parent.done()
|
||||||
|
if done.closed {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
if p_ptr := parent.value(cancel_context_key) {
|
||||||
|
if !isnil(p_ptr) {
|
||||||
|
mut p := &CancelContext(p_ptr)
|
||||||
|
pdone := p.done()
|
||||||
|
if done == pdone {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove_child removes a context from its parent.
|
||||||
|
fn remove_child(parent Context, child Canceler) {
|
||||||
|
mut p := parent_cancel_context(parent) or { return }
|
||||||
|
p.children.delete(child.id)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
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.CancelerContext) chan int {
|
||||||
|
dst := chan int{}
|
||||||
|
go fn (mut ctx context.CancelerContext, dst chan int) {
|
||||||
|
ch := ctx.done()
|
||||||
|
loop: for i in 0 .. 5 {
|
||||||
|
select {
|
||||||
|
_ := <-ch {
|
||||||
|
// returning not to leak the routine
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
dst <- i {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(mut ctx, dst)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
mut ctx := context.with_cancel(context.background())
|
||||||
|
defer {
|
||||||
|
context.cancel(mut ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := gen(mut ctx)
|
||||||
|
for i in 0 .. 5 {
|
||||||
|
v := <-ch
|
||||||
|
assert i == v
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
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(parent Context, d time.Time) &CancelerContext {
|
||||||
|
id := rand.uuid_v4()
|
||||||
|
if cur := parent.deadline() {
|
||||||
|
if cur < d {
|
||||||
|
// The current deadline is already sooner than the new one.
|
||||||
|
return with_cancel(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel_ctx := new_cancel_context(parent)
|
||||||
|
mut ctx := &TimerContext{
|
||||||
|
cancel_ctx: cancel_ctx
|
||||||
|
deadline: d
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
propagate_cancel(parent, mut ctx)
|
||||||
|
dur := d - time.now()
|
||||||
|
if dur.nanoseconds() <= 0 {
|
||||||
|
ctx.cancel(true, deadline_exceeded) // deadline has already passed
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.cancel_ctx.err() == '' {
|
||||||
|
go fn (mut ctx TimerContext, dur time.Duration) {
|
||||||
|
time.sleep(dur)
|
||||||
|
ctx_ch := ctx.done()
|
||||||
|
ctx_ch <- 0
|
||||||
|
ctx.cancel(true, deadline_exceeded)
|
||||||
|
}(mut ctx, dur)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(parent Context, timeout time.Duration) &CancelerContext {
|
||||||
|
return with_deadline(parent, time.now().add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx TimerContext) deadline() ?time.Time {
|
||||||
|
return ctx.deadline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx TimerContext) done() chan int {
|
||||||
|
return ctx.cancel_ctx.done()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx TimerContext) err() string {
|
||||||
|
return ctx.cancel_ctx.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx TimerContext) value(key string) ?voidptr {
|
||||||
|
return ctx.cancel_ctx.value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err string) {
|
||||||
|
ctx.cancel_ctx.cancel(false, err)
|
||||||
|
if remove_from_parent {
|
||||||
|
// Remove this TimerContext from its parent CancelContext's children.
|
||||||
|
remove_child(ctx.cancel_ctx.context, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,37 @@
|
||||||
|
module context
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
// An EmptyContext is never canceled, has no values, and has no deadline. It is not
|
||||||
|
// struct{}, since vars of this type must have distinct addresses.
|
||||||
|
pub type EmptyContext = int
|
||||||
|
|
||||||
|
pub fn (ctx EmptyContext) deadline() ?time.Time {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx EmptyContext) done() chan int {
|
||||||
|
ch := chan int{}
|
||||||
|
defer {
|
||||||
|
ch.close()
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx EmptyContext) err() string {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx EmptyContext) value(key string) ?voidptr {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx EmptyContext) str() string {
|
||||||
|
if ctx == background {
|
||||||
|
return 'context.Background'
|
||||||
|
}
|
||||||
|
if ctx == todo {
|
||||||
|
return 'context.TODO'
|
||||||
|
}
|
||||||
|
return 'unknown empty Context'
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
module context
|
||||||
|
|
||||||
|
fn test_background() {
|
||||||
|
ctx := background()
|
||||||
|
assert 'context.Background' == ctx.str()
|
||||||
|
if _ := ctx.value('') {
|
||||||
|
println('This should not happen')
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_todo() {
|
||||||
|
ctx := todo()
|
||||||
|
assert 'context.TODO' == ctx.str()
|
||||||
|
if _ := ctx.value('') {
|
||||||
|
println('This should not happen')
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
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 string
|
||||||
|
value voidptr
|
||||||
|
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 string, value voidptr) &ValueContext {
|
||||||
|
if isnil(key) {
|
||||||
|
panic('nil key')
|
||||||
|
}
|
||||||
|
return &ValueContext{
|
||||||
|
context: parent
|
||||||
|
key: key
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx ValueContext) deadline() ?time.Time {
|
||||||
|
return ctx.context.deadline()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx ValueContext) done() chan int {
|
||||||
|
return ctx.context.done()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx ValueContext) err() string {
|
||||||
|
return ctx.context.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx ValueContext) value(key string) ?voidptr {
|
||||||
|
if ctx.key == key {
|
||||||
|
return ctx.value
|
||||||
|
}
|
||||||
|
return ctx.context.value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (ctx ValueContext) str() string {
|
||||||
|
return context_name(ctx.context) + '.with_value'
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import context
|
||||||
|
|
||||||
|
type ValueContextKey = 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.ValueContext, key ValueContextKey) string {
|
||||||
|
if value := ctx.value(key) {
|
||||||
|
if !isnil(value) {
|
||||||
|
return *(&string(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'key not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ValueContextKey('language')
|
||||||
|
value := 'VAL'
|
||||||
|
ctx := context.with_value(context.background(), key, &value)
|
||||||
|
|
||||||
|
assert value == f(ctx, key)
|
||||||
|
assert 'key not found' == f(ctx, ValueContextKey('color'))
|
||||||
|
}
|
Loading…
Reference in New Issue