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/value_test.v' /* the following tests need C casts in `sync` and/or thirdparty/stdatomic */,
|
||||||
'vlib/context/empty_test.v',
|
'vlib/context/empty_test.v',
|
||||||
'vlib/context/cancel_test.v',
|
'vlib/context/cancel_test.v',
|
||||||
|
'vlib/context/deadline_test.v',
|
||||||
'vlib/sync/array_rlock_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',
|
||||||
|
|
|
@ -10,10 +10,9 @@ with_deadline, with_timeout, or with_value. When a Context is canceled, all Cont
|
||||||
derived from it are also canceled.
|
derived from it are also canceled.
|
||||||
|
|
||||||
The with_cancel, with_deadline, and with_timeout functions take a Context (the parent)
|
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,
|
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 stops any associated timers.
|
||||||
and its children until the parent is canceled or the timer fires.
|
|
||||||
|
|
||||||
Programs that use Contexts should follow these rules to keep interfaces consistent
|
Programs that use Contexts should follow these rules to keep interfaces consistent
|
||||||
across different modules.
|
across different modules.
|
||||||
|
@ -40,29 +39,32 @@ fn example_with_cancel() {
|
||||||
// The callers of gen need to cancel the context once
|
// The callers of gen need to cancel the context once
|
||||||
// they are done consuming generated integers not to leak
|
// they are done consuming generated integers not to leak
|
||||||
// the internal routine started by gen.
|
// the internal routine started by gen.
|
||||||
gen := fn (mut ctx context.CancelerContext) chan int {
|
gen := fn (ctx context.Context) chan int {
|
||||||
dst := 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()
|
ch := ctx.done()
|
||||||
loop: for i in 0 .. 5 {
|
for {
|
||||||
select {
|
select {
|
||||||
_ := <-ch {
|
_ := <-ch {
|
||||||
// returning not to leak the routine
|
// returning not to leak the routine
|
||||||
break loop
|
return
|
||||||
}
|
}
|
||||||
dst <- i {}
|
dst <- v {
|
||||||
|
v++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(mut ctx, dst)
|
}
|
||||||
|
}(ctx, dst)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
mut ctx := context.with_cancel(context.background())
|
ctx := context.with_cancel(context.background())
|
||||||
defer {
|
defer {
|
||||||
context.cancel(mut ctx)
|
context.cancel(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := gen(mut ctx)
|
ch := gen(ctx)
|
||||||
for i in 0 .. 5 {
|
for i in 0 .. 5 {
|
||||||
v := <-ch
|
v := <-ch
|
||||||
assert i == v
|
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.
|
// function that it should abandon its work as soon as it gets to it.
|
||||||
fn example_with_deadline() {
|
fn example_with_deadline() {
|
||||||
dur := time.now().add(short_duration)
|
dur := time.now().add(short_duration)
|
||||||
mut ctx := context.with_deadline(context.background(), dur)
|
ctx := context.with_deadline(context.background(), dur)
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
// Even though ctx will be expired, it is good practice to call its
|
// 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
|
// cancellation function in any case. Failure to do so may keep the
|
||||||
// context and its parent alive longer than necessary.
|
// context and its parent alive longer than necessary.
|
||||||
context.cancel(mut ctx)
|
context.cancel(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
after_ch := after(1 * time.second)
|
after_ch := after(1 * time.second)
|
||||||
|
@ -141,9 +143,9 @@ fn after(dur time.Duration) chan int {
|
||||||
fn example_with_timeout() {
|
fn example_with_timeout() {
|
||||||
// Pass a context with a timeout to tell a blocking function that it
|
// Pass a context with a timeout to tell a blocking function that it
|
||||||
// should abandon its work after the timeout elapses.
|
// 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 {
|
defer {
|
||||||
context.cancel(mut ctx)
|
context.cancel(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
after_ch := after(1 * time.second)
|
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
|
// This example demonstrates how a value can be passed to the context
|
||||||
// and also how to retrieve it if it exists.
|
// and also how to retrieve it if it exists.
|
||||||
fn example_with_value() {
|
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 value := ctx.value(key) {
|
||||||
if !isnil(value) {
|
if !isnil(value) {
|
||||||
return *(&string(value))
|
return *(&string(value))
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub interface Context {
|
||||||
// initialization, and tests, and as the top-level Context for incoming
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
// requests.
|
// requests.
|
||||||
pub fn background() Context {
|
pub fn background() Context {
|
||||||
return Context(context.background)
|
return context.background
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo returns an empty Context. Code should use todo when
|
// 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
|
// surrounding function has not yet been extended to accept a Context
|
||||||
// parameter).
|
// parameter).
|
||||||
pub fn todo() Context {
|
pub fn todo() Context {
|
||||||
return Context(context.todo)
|
return context.todo
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context_name(ctx Context) string {
|
fn context_name(ctx Context) string {
|
||||||
|
|
|
@ -8,10 +8,9 @@ pub interface Canceler {
|
||||||
id string
|
id string
|
||||||
cancel(remove_from_parent bool, err string)
|
cancel(remove_from_parent bool, err string)
|
||||||
done() chan int
|
done() chan int
|
||||||
str() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cancel(mut ctx CancelerContext) {
|
pub fn cancel(ctx Context) {
|
||||||
match mut ctx {
|
match mut ctx {
|
||||||
CancelContext {
|
CancelContext {
|
||||||
ctx.cancel(true, canceled)
|
ctx.cancel(true, canceled)
|
||||||
|
@ -19,65 +18,7 @@ pub fn cancel(mut ctx CancelerContext) {
|
||||||
TimerContext {
|
TimerContext {
|
||||||
ctx.cancel(true, canceled)
|
ctx.cancel(true, canceled)
|
||||||
}
|
}
|
||||||
}
|
else {}
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,22 +34,16 @@ mut:
|
||||||
err string
|
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
|
// 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
|
// 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.
|
// or when the parent context's done channel is closed, whichever happens first.
|
||||||
//
|
//
|
||||||
// Canceling this context releases resources associated with it, so code should
|
// Canceling this context releases resources associated with it, so code should
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
// 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)
|
mut c := new_cancel_context(parent)
|
||||||
propagate_cancel(parent, mut c)
|
propagate_cancel(parent, mut c)
|
||||||
return c
|
return Context(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// new_cancel_context returns an initialized CancelContext.
|
// 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
|
ctx.err = err
|
||||||
|
|
||||||
if !ctx.done.closed {
|
if !ctx.done.closed {
|
||||||
|
ctx.done <- 0
|
||||||
ctx.done.close()
|
ctx.done.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,29 +9,32 @@ fn test_with_cancel() {
|
||||||
// The callers of gen need to cancel the context once
|
// The callers of gen need to cancel the context once
|
||||||
// they are done consuming generated integers not to leak
|
// they are done consuming generated integers not to leak
|
||||||
// the internal routine started by gen.
|
// the internal routine started by gen.
|
||||||
gen := fn (mut ctx context.CancelerContext) chan int {
|
gen := fn (ctx context.Context) chan int {
|
||||||
dst := 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()
|
ch := ctx.done()
|
||||||
loop: for i in 0 .. 5 {
|
for {
|
||||||
select {
|
select {
|
||||||
_ := <-ch {
|
_ := <-ch {
|
||||||
// returning not to leak the routine
|
// returning not to leak the routine
|
||||||
break loop
|
return
|
||||||
}
|
}
|
||||||
dst <- i {}
|
dst <- v {
|
||||||
|
v++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(mut ctx, dst)
|
}
|
||||||
|
}(ctx, dst)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
mut ctx := context.with_cancel(context.background())
|
ctx := context.with_cancel(context.background())
|
||||||
defer {
|
defer {
|
||||||
context.cancel(mut ctx)
|
context.cancel(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := gen(mut ctx)
|
ch := gen(ctx)
|
||||||
for i in 0 .. 5 {
|
for i in 0 .. 5 {
|
||||||
v := <-ch
|
v := <-ch
|
||||||
assert i == v
|
assert i == v
|
||||||
|
|
|
@ -22,7 +22,7 @@ mut:
|
||||||
//
|
//
|
||||||
// Canceling this context releases resources associated with it, so code should
|
// Canceling this context releases resources associated with it, so code should
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
// 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()
|
id := rand.uuid_v4()
|
||||||
if cur := parent.deadline() {
|
if cur := parent.deadline() {
|
||||||
if cur < d {
|
if cur < d {
|
||||||
|
@ -40,25 +40,23 @@ pub fn with_deadline(parent Context, d time.Time) &CancelerContext {
|
||||||
dur := d - time.now()
|
dur := d - time.now()
|
||||||
if dur.nanoseconds() <= 0 {
|
if dur.nanoseconds() <= 0 {
|
||||||
ctx.cancel(true, deadline_exceeded) // deadline has already passed
|
ctx.cancel(true, deadline_exceeded) // deadline has already passed
|
||||||
return ctx
|
return Context(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.cancel_ctx.err() == '' {
|
if ctx.cancel_ctx.err() == '' {
|
||||||
go fn (mut ctx TimerContext, dur time.Duration) {
|
go fn (mut ctx TimerContext, dur time.Duration) {
|
||||||
time.sleep(dur)
|
time.sleep(dur)
|
||||||
ctx_ch := ctx.done()
|
|
||||||
ctx_ch <- 0
|
|
||||||
ctx.cancel(true, deadline_exceeded)
|
ctx.cancel(true, deadline_exceeded)
|
||||||
}(mut ctx, dur)
|
}(mut ctx, dur)
|
||||||
}
|
}
|
||||||
return ctx
|
return Context(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// with_timeout returns with_deadline(parent, time.now().add(timeout)).
|
// with_timeout returns with_deadline(parent, time.now().add(timeout)).
|
||||||
//
|
//
|
||||||
// Canceling this context releases resources associated with it, so code should
|
// Canceling this context releases resources associated with it, so code should
|
||||||
// call cancel as soon as the operations running in this Context complete
|
// 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))
|
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
|
// string or any other built-in type to avoid collisions between
|
||||||
// packages using context. Users of with_value should define their own
|
// packages using context. Users of with_value should define their own
|
||||||
// types for keys
|
// types for keys
|
||||||
pub fn with_value(parent Context, key string, value voidptr) &ValueContext {
|
pub fn with_value(parent Context, key string, value voidptr) Context {
|
||||||
if isnil(key) {
|
|
||||||
panic('nil key')
|
|
||||||
}
|
|
||||||
return &ValueContext{
|
return &ValueContext{
|
||||||
context: parent
|
context: parent
|
||||||
key: key
|
key: key
|
||||||
|
|
|
@ -5,7 +5,7 @@ type ValueContextKey = string
|
||||||
// This example demonstrates how a value can be passed to the context
|
// This example demonstrates how a value can be passed to the context
|
||||||
// and also how to retrieve it if it exists.
|
// and also how to retrieve it if it exists.
|
||||||
fn test_with_value() {
|
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 value := ctx.value(key) {
|
||||||
if !isnil(value) {
|
if !isnil(value) {
|
||||||
return *(&string(value))
|
return *(&string(value))
|
||||||
|
|
Loading…
Reference in New Issue