context: small refactor to always use Context type instead of multiple types (#9705)
parent
66294e359a
commit
909c9c7ee7
|
@ -58,6 +58,7 @@ const (
|
|||
'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/context/deadline_test.v',
|
||||
'vlib/sync/array_rlock_test.v',
|
||||
'vlib/sync/atomic2/atomic_test.v',
|
||||
'vlib/sync/channel_2_test.v',
|
||||
|
|
|
@ -10,10 +10,9 @@ with_deadline, with_timeout, or with_value. When a Context is canceled, all Cont
|
|||
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
|
||||
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. Failing to call the CancelFunc leaks the child
|
||||
and its children until the parent is canceled or the timer fires.
|
||||
and stops any associated timers.
|
||||
|
||||
Programs that use Contexts should follow these rules to keep interfaces consistent
|
||||
across different modules.
|
||||
|
@ -40,29 +39,32 @@ fn example_with_cancel() {
|
|||
// 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 {
|
||||
gen := fn (ctx context.Context) chan int {
|
||||
dst := chan int{}
|
||||
go fn (mut ctx context.CancelerContext, dst chan int) {
|
||||
go fn (ctx context.Context, dst chan int) {
|
||||
mut v := 0
|
||||
ch := ctx.done()
|
||||
loop: for i in 0 .. 5 {
|
||||
for {
|
||||
select {
|
||||
_ := <-ch {
|
||||
// returning not to leak the routine
|
||||
break loop
|
||||
return
|
||||
}
|
||||
dst <- v {
|
||||
v++
|
||||
}
|
||||
dst <- i {}
|
||||
}
|
||||
}
|
||||
}(mut ctx, dst)
|
||||
}(ctx, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
mut ctx := context.with_cancel(context.background())
|
||||
ctx := context.with_cancel(context.background())
|
||||
defer {
|
||||
context.cancel(mut ctx)
|
||||
context.cancel(ctx)
|
||||
}
|
||||
|
||||
ch := gen(mut ctx)
|
||||
ch := gen(ctx)
|
||||
for i in 0 .. 5 {
|
||||
v := <-ch
|
||||
assert i == v
|
||||
|
@ -94,13 +96,13 @@ fn after(dur time.Duration) chan int {
|
|||
// 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)
|
||||
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)
|
||||
context.cancel(ctx)
|
||||
}
|
||||
|
||||
after_ch := after(1 * time.second)
|
||||
|
@ -141,9 +143,9 @@ fn after(dur time.Duration) chan int {
|
|||
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)
|
||||
ctx := context.with_timeout(context.background(), short_duration)
|
||||
defer {
|
||||
context.cancel(mut ctx)
|
||||
context.cancel(ctx)
|
||||
}
|
||||
|
||||
after_ch := after(1 * time.second)
|
||||
|
@ -169,7 +171,7 @@ 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 {
|
||||
f := fn (ctx context.Context, key ValueContextKey) string {
|
||||
if value := ctx.value(key) {
|
||||
if !isnil(value) {
|
||||
return *(&string(value))
|
||||
|
|
|
@ -61,7 +61,7 @@ pub interface Context {
|
|||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
pub fn background() Context {
|
||||
return Context(context.background)
|
||||
return context.background
|
||||
}
|
||||
|
||||
// todo returns an empty Context. Code should use todo when
|
||||
|
@ -69,7 +69,7 @@ pub fn background() Context {
|
|||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter).
|
||||
pub fn todo() Context {
|
||||
return Context(context.todo)
|
||||
return context.todo
|
||||
}
|
||||
|
||||
fn context_name(ctx Context) string {
|
||||
|
|
|
@ -8,10 +8,9 @@ pub interface Canceler {
|
|||
id string
|
||||
cancel(remove_from_parent bool, err string)
|
||||
done() chan int
|
||||
str() string
|
||||
}
|
||||
|
||||
pub fn cancel(mut ctx CancelerContext) {
|
||||
pub fn cancel(ctx Context) {
|
||||
match mut ctx {
|
||||
CancelContext {
|
||||
ctx.cancel(true, canceled)
|
||||
|
@ -19,65 +18,7 @@ pub fn cancel(mut ctx CancelerContext) {
|
|||
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()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,22 +34,16 @@ mut:
|
|||
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 {
|
||||
pub fn with_cancel(parent Context) Context {
|
||||
mut c := new_cancel_context(parent)
|
||||
propagate_cancel(parent, mut c)
|
||||
return c
|
||||
return Context(c)
|
||||
}
|
||||
|
||||
// new_cancel_context returns an initialized CancelContext.
|
||||
|
@ -164,6 +99,7 @@ fn (mut ctx CancelContext) cancel(remove_from_parent bool, err string) {
|
|||
ctx.err = err
|
||||
|
||||
if !ctx.done.closed {
|
||||
ctx.done <- 0
|
||||
ctx.done.close()
|
||||
}
|
||||
|
||||
|
|
|
@ -9,29 +9,32 @@ fn test_with_cancel() {
|
|||
// 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 {
|
||||
gen := fn (ctx context.Context) chan int {
|
||||
dst := chan int{}
|
||||
go fn (mut ctx context.CancelerContext, dst chan int) {
|
||||
go fn (ctx context.Context, dst chan int) {
|
||||
mut v := 0
|
||||
ch := ctx.done()
|
||||
loop: for i in 0 .. 5 {
|
||||
for {
|
||||
select {
|
||||
_ := <-ch {
|
||||
// returning not to leak the routine
|
||||
break loop
|
||||
return
|
||||
}
|
||||
dst <- v {
|
||||
v++
|
||||
}
|
||||
dst <- i {}
|
||||
}
|
||||
}
|
||||
}(mut ctx, dst)
|
||||
}(ctx, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
mut ctx := context.with_cancel(context.background())
|
||||
ctx := context.with_cancel(context.background())
|
||||
defer {
|
||||
context.cancel(mut ctx)
|
||||
context.cancel(ctx)
|
||||
}
|
||||
|
||||
ch := gen(mut ctx)
|
||||
ch := gen(ctx)
|
||||
for i in 0 .. 5 {
|
||||
v := <-ch
|
||||
assert i == v
|
||||
|
|
|
@ -22,7 +22,7 @@ mut:
|
|||
//
|
||||
// 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 {
|
||||
pub fn with_deadline(parent Context, d time.Time) Context {
|
||||
id := rand.uuid_v4()
|
||||
if cur := parent.deadline() {
|
||||
if cur < d {
|
||||
|
@ -40,25 +40,23 @@ pub fn with_deadline(parent Context, d time.Time) &CancelerContext {
|
|||
dur := d - time.now()
|
||||
if dur.nanoseconds() <= 0 {
|
||||
ctx.cancel(true, deadline_exceeded) // deadline has already passed
|
||||
return ctx
|
||||
return Context(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
|
||||
return Context(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 {
|
||||
pub fn with_timeout(parent Context, timeout time.Duration) Context {
|
||||
return with_deadline(parent, time.now().add(timeout))
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
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 test_with_deadline() {
|
||||
dur := time.now().add(short_duration)
|
||||
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(ctx)
|
||||
}
|
||||
|
||||
after_ch := after(1 * time.second)
|
||||
ctx_ch := ctx.done()
|
||||
select {
|
||||
_ := <-after_ch {
|
||||
assert false
|
||||
}
|
||||
_ := <-ctx_ch {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
ctx := context.with_timeout(context.background(), short_duration)
|
||||
defer {
|
||||
context.cancel(ctx)
|
||||
}
|
||||
|
||||
after_ch := after(1 * time.second)
|
||||
ctx_ch := ctx.done()
|
||||
select {
|
||||
_ := <-after_ch {
|
||||
assert false
|
||||
}
|
||||
_ := <-ctx_ch {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,7 @@ mut:
|
|||
// 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')
|
||||
}
|
||||
pub fn with_value(parent Context, key string, value voidptr) Context {
|
||||
return &ValueContext{
|
||||
context: parent
|
||||
key: key
|
||||
|
|
|
@ -5,7 +5,7 @@ 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 {
|
||||
f := fn (ctx context.Context, key ValueContextKey) string {
|
||||
if value := ctx.value(key) {
|
||||
if !isnil(value) {
|
||||
return *(&string(value))
|
||||
|
|
Loading…
Reference in New Issue