context: update ValueContext to handle Any value and more Key types, use closures (#11993)

pull/11998/head
Ulises Jeremias Cornejo Fandos 2021-09-27 11:52:20 -03:00 committed by GitHub
parent d6a4bce2ef
commit c151e075e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 90 deletions

View File

@ -99,6 +99,10 @@ const (
'do_not_remove', 'do_not_remove',
] ]
skip_on_windows = [ skip_on_windows = [
'vlib/context/cancel_test.v',
'vlib/context/deadline_test.v',
'vlib/context/empty_test.v',
'vlib/context/value_test.v',
'vlib/orm/orm_test.v', 'vlib/orm/orm_test.v',
'vlib/v/tests/orm_sub_struct_test.v', 'vlib/v/tests/orm_sub_struct_test.v',
'vlib/v/tests/closure_test.v', 'vlib/v/tests/closure_test.v',

View File

@ -59,9 +59,9 @@ fn example_with_cancel() {
return dst return dst
} }
ctx := context.with_cancel(context.background()) ctx, cancel := context.with_cancel(context.background())
defer { defer {
context.cancel(ctx) cancel()
} }
ch := gen(ctx) ch := gen(ctx)
@ -87,13 +87,13 @@ const (
// 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)
ctx := context.with_deadline(context.background(), dur) ctx, cancel := 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(ctx) cancel()
} }
ctx_ch := ctx.done() ctx_ch := ctx.done()
@ -122,9 +122,9 @@ const (
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.
ctx := context.with_timeout(context.background(), short_duration) ctx, cancel := context.with_timeout(context.background(), short_duration)
defer { defer {
context.cancel(ctx) cancel()
} }
ctx_ch := ctx.done() ctx_ch := ctx.done()
@ -142,25 +142,36 @@ fn example_with_timeout() {
```v ```v
import context import context
type ValueContextKey = string 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 // 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.Context, key ValueContextKey) string { f := fn (ctx context.Context, key context.Key) &Value {
if value := ctx.value(key) { if value := ctx.value(key) {
if !isnil(value) { match value {
return *(&string(value)) Value {
return value
}
else {}
} }
} }
return 'key not found' return not_found_value
} }
key := ValueContextKey('language') key := 'language'
value := 'VAL' value := &Value{
ctx := context.with_value(context.background(), key, &value) val: 'VAL'
}
ctx := context.with_value(context.background(), key, value)
assert value == f(ctx, key) assert value == f(ctx, key)
assert 'key not found' == f(ctx, ValueContextKey('color')) assert not_found_value == f(ctx, 'color')
} }
``` ```

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals, // This module defines the Context type, which carries deadlines, cancellation signals,
// and other request-scoped values across API boundaries and between processes. // and other request-scoped values across API boundaries and between processes.
// Based off: https://github.com/golang/go/tree/master/src/context // Based on: https://github.com/golang/go/tree/master/src/context
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 // Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
module context module context
@ -10,7 +10,7 @@ const (
background = EmptyContext(0) background = EmptyContext(0)
todo = EmptyContext(1) todo = EmptyContext(1)
cancel_context_key = 'context.CancelContext' cancel_context_key = Key('context.CancelContext')
// canceled is the error returned by Context.err when the context is canceled. // canceled is the error returned by Context.err when the context is canceled.
canceled = error('context canceled') canceled = error('context canceled')
@ -20,6 +20,24 @@ const (
deadline_exceeded = error('context deadline exceeded') deadline_exceeded = error('context deadline exceeded')
) )
// Key represents the type for the ValueContext key
pub type Key = bool
| byte
| f32
| f64
| i16
| i64
| i8
| int
| string
| u16
| u32
| u64
| voidptr
// Any represents a generic type for the ValueContext
pub interface Any {}
pub interface Context { pub interface Context {
// deadline returns the time when work done on behalf of this context // deadline returns the time when work done on behalf of this context
// should be canceled. deadline returns none when no deadline is // should be canceled. deadline returns none when no deadline is
@ -56,7 +74,7 @@ pub interface Context {
// Context.Value. A key can be any type that supports equality; // Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid // packages should define keys as an unexported type to avoid
// collisions. // collisions.
value(key string) ?voidptr value(key Key) ?Any
str() string str() string
} }

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals, // This module defines the Context type, which carries deadlines, cancellation signals,
// and other request-scoped values across API boundaries and between processes. // and other request-scoped values across API boundaries and between processes.
// Based off: https://github.com/golang/go/tree/master/src/context // Based on: https://github.com/golang/go/tree/master/src/context
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 // Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
module context module context
@ -8,12 +8,15 @@ import rand
import sync import sync
import time import time
pub type CancelFn = fn ()
pub interface Canceler { pub interface Canceler {
id string id string
cancel(remove_from_parent bool, err IError) cancel(remove_from_parent bool, err IError)
done() chan int done() chan int
} }
[deprecated]
pub fn cancel(ctx Context) { pub fn cancel(ctx Context) {
match mut ctx { match mut ctx {
CancelContext { CancelContext {
@ -44,10 +47,12 @@ 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_cancel(parent Context) Context { pub fn with_cancel(parent Context) (Context, CancelFn) {
mut c := new_cancel_context(parent) mut c := new_cancel_context(parent)
propagate_cancel(parent, mut c) propagate_cancel(parent, c)
return Context(c) return Context(c), fn [mut c] () {
c.cancel(true, canceled)
}
} }
// new_cancel_context returns an initialized CancelContext. // new_cancel_context returns an initialized CancelContext.
@ -61,7 +66,7 @@ fn new_cancel_context(parent Context) &CancelContext {
} }
} }
pub fn (ctx CancelContext) deadline() ?time.Time { pub fn (ctx &CancelContext) deadline() ?time.Time {
return none return none
} }
@ -79,14 +84,14 @@ pub fn (mut ctx CancelContext) err() IError {
return err return err
} }
pub fn (ctx CancelContext) value(key string) ?voidptr { pub fn (ctx &CancelContext) value(key Key) ?Any {
if key == cancel_context_key { if key == cancel_context_key {
return voidptr(unsafe { &ctx }) return ctx
} }
return ctx.context.value(key) return ctx.context.value(key)
} }
pub fn (ctx CancelContext) str() string { pub fn (ctx &CancelContext) str() string {
return context_name(ctx.context) + '.with_cancel' return context_name(ctx.context) + '.with_cancel'
} }
@ -122,7 +127,7 @@ fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {
} }
} }
fn propagate_cancel(parent Context, mut child Canceler) { fn propagate_cancel(parent Context, child Canceler) {
done := parent.done() done := parent.done()
select { select {
_ := <-done { _ := <-done {
@ -132,19 +137,19 @@ fn propagate_cancel(parent Context, mut child Canceler) {
} }
} }
mut p := parent_cancel_context(parent) or { mut p := parent_cancel_context(parent) or {
go fn (parent Context, mut child Canceler) { go fn (parent Context, child Canceler) {
pdone := parent.done() pdone := parent.done()
select { select {
_ := <-pdone { _ := <-pdone {
child.cancel(false, parent.err()) child.cancel(false, parent.err())
} }
} }
}(parent, mut child) }(parent, child)
return return
} }
if p.err is none { if p.err is none {
p.children[child.id] = *child p.children[child.id] = child
} else { } else {
// parent has already been canceled // parent has already been canceled
child.cancel(false, p.err) child.cancel(false, p.err)
@ -157,19 +162,20 @@ fn propagate_cancel(parent Context, mut child Canceler) {
// parent.done() matches that CancelContext. (If not, the CancelContext // parent.done() matches that CancelContext. (If not, the CancelContext
// has been wrapped in a custom implementation providing a // has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.) // different done channel, in which case we should not bypass it.)
fn parent_cancel_context(parent Context) ?CancelContext { fn parent_cancel_context(parent Context) ?&CancelContext {
done := parent.done() done := parent.done()
if done.closed { if done.closed {
return none return none
} }
if p_ptr := parent.value(cancel_context_key) { mut p := parent.value(cancel_context_key) ?
if !isnil(p_ptr) { match mut p {
mut p := &CancelContext(p_ptr) CancelContext {
pdone := p.done() pdone := p.done()
if done == pdone { if done == pdone {
return *p return p
} }
} }
else {}
} }
return none return none
} }

View File

@ -30,9 +30,9 @@ fn test_with_cancel() {
return dst return dst
} }
ctx := context.with_cancel(context.background()) ctx, cancel := context.with_cancel(context.background())
defer { defer {
context.cancel(ctx) cancel()
} }
ch := gen(ctx) ch := gen(ctx)

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals, // This module defines the Context type, which carries deadlines, cancellation signals,
// and other request-scoped values across API boundaries and between processes. // and other request-scoped values across API boundaries and between processes.
// Based off: https://github.com/golang/go/tree/master/src/context // Based on: https://github.com/golang/go/tree/master/src/context
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 // Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
module context module context
@ -26,7 +26,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) Context { pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
id := rand.uuid_v4() id := rand.uuid_v4()
if cur := parent.deadline() { if cur := parent.deadline() {
if cur < d { if cur < d {
@ -40,11 +40,13 @@ pub fn with_deadline(parent Context, d time.Time) Context {
deadline: d deadline: d
id: id id: id
} }
propagate_cancel(parent, mut ctx) propagate_cancel(parent, ctx)
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 Context(ctx) return Context(ctx), fn [mut ctx] () {
ctx.cancel(true, canceled)
}
} }
if ctx.err() is none { if ctx.err() is none {
@ -53,18 +55,20 @@ pub fn with_deadline(parent Context, d time.Time) Context {
ctx.cancel(true, deadline_exceeded) ctx.cancel(true, deadline_exceeded)
}(mut ctx, dur) }(mut ctx, dur)
} }
return Context(ctx) return Context(ctx), fn [mut ctx] () {
ctx.cancel(true, canceled)
}
} }
// 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) Context { pub fn with_timeout(parent Context, timeout time.Duration) (Context, CancelFn) {
return with_deadline(parent, time.now().add(timeout)) return with_deadline(parent, time.now().add(timeout))
} }
pub fn (ctx TimerContext) deadline() ?time.Time { pub fn (ctx &TimerContext) deadline() ?time.Time {
return ctx.deadline return ctx.deadline
} }
@ -76,7 +80,7 @@ pub fn (mut ctx TimerContext) err() IError {
return ctx.cancel_ctx.err() return ctx.cancel_ctx.err()
} }
pub fn (ctx TimerContext) value(key string) ?voidptr { pub fn (ctx &TimerContext) value(key Key) ?Any {
return ctx.cancel_ctx.value(key) return ctx.cancel_ctx.value(key)
} }
@ -88,7 +92,7 @@ pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err IError) {
} }
} }
pub fn (ctx TimerContext) str() string { pub fn (ctx &TimerContext) str() string {
return context_name(ctx.cancel_ctx.context) + '.with_deadline(' + ctx.deadline.str() + ' [' + return context_name(ctx.cancel_ctx.context) + '.with_deadline(' + ctx.deadline.str() + ' [' +
(time.now() - ctx.deadline).str() + '])' (time.now() - ctx.deadline).str() + '])'
} }

View File

@ -10,13 +10,13 @@ const (
// 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 test_with_deadline() { fn test_with_deadline() {
dur := time.now().add(short_duration) dur := time.now().add(short_duration)
ctx := context.with_deadline(context.background(), dur) ctx, cancel := 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(ctx) cancel()
} }
ctx_ch := ctx.done() ctx_ch := ctx.done()
@ -33,9 +33,9 @@ fn test_with_deadline() {
fn test_with_timeout() { fn test_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.
ctx := context.with_timeout(context.background(), short_duration) ctx, cancel := context.with_timeout(context.background(), short_duration)
defer { defer {
context.cancel(ctx) cancel()
} }
ctx_ch := ctx.done() ctx_ch := ctx.done()

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals, // This module defines the Context type, which carries deadlines, cancellation signals,
// and other request-scoped values across API boundaries and between processes. // and other request-scoped values across API boundaries and between processes.
// Based off: https://github.com/golang/go/tree/master/src/context // Based on: https://github.com/golang/go/tree/master/src/context
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 // Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
module context module context
@ -10,11 +10,11 @@ import time
// struct{}, since vars of this type must have distinct addresses. // struct{}, since vars of this type must have distinct addresses.
pub type EmptyContext = int pub type EmptyContext = int
pub fn (ctx EmptyContext) deadline() ?time.Time { pub fn (ctx &EmptyContext) deadline() ?time.Time {
return none return none
} }
pub fn (ctx EmptyContext) done() chan int { pub fn (ctx &EmptyContext) done() chan int {
ch := chan int{} ch := chan int{}
defer { defer {
ch.close() ch.close()
@ -22,16 +22,15 @@ pub fn (ctx EmptyContext) done() chan int {
return ch return ch
} }
pub fn (ctx EmptyContext) err() IError { pub fn (ctx &EmptyContext) err() IError {
// TODO: Change this to `none`
return none_
}
pub fn (ctx EmptyContext) value(key string) ?voidptr {
return none return none
} }
pub fn (ctx EmptyContext) str() string { pub fn (ctx &EmptyContext) value(key Key) ?Any {
return none
}
pub fn (ctx &EmptyContext) str() string {
if ctx == background { if ctx == background {
return 'context.Background' return 'context.Background'
} }

View File

@ -1,12 +0,0 @@
module context
const none_ = IError(&None{})
struct None {
msg string
code int
}
fn (_ None) str() string {
return 'none'
}

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals, // This module defines the Context type, which carries deadlines, cancellation signals,
// and other request-scoped values across API boundaries and between processes. // and other request-scoped values across API boundaries and between processes.
// Based off: https://github.com/golang/go/tree/master/src/context // Based on: https://github.com/golang/go/tree/master/src/context
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 // Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
module context module context
@ -9,8 +9,8 @@ import time
// A ValueContext carries a key-value pair. It implements Value for that key and // A ValueContext carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context. // delegates all other calls to the embedded Context.
pub struct ValueContext { pub struct ValueContext {
key string key Key
value voidptr value Any
mut: mut:
context Context context Context
} }
@ -25,7 +25,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) Context { pub fn with_value(parent Context, key Key, value Any) Context {
return &ValueContext{ return &ValueContext{
context: parent context: parent
key: key key: key
@ -33,25 +33,25 @@ pub fn with_value(parent Context, key string, value voidptr) Context {
} }
} }
pub fn (ctx ValueContext) deadline() ?time.Time { pub fn (ctx &ValueContext) deadline() ?time.Time {
return ctx.context.deadline() return ctx.context.deadline()
} }
pub fn (ctx ValueContext) done() chan int { pub fn (ctx &ValueContext) done() chan int {
return ctx.context.done() return ctx.context.done()
} }
pub fn (ctx ValueContext) err() IError { pub fn (ctx &ValueContext) err() IError {
return ctx.context.err() return ctx.context.err()
} }
pub fn (ctx ValueContext) value(key string) ?voidptr { pub fn (ctx &ValueContext) value(key Key) ?Any {
if ctx.key == key { if ctx.key == key {
return ctx.value return ctx.value
} }
return ctx.context.value(key) return ctx.context.value(key)
} }
pub fn (ctx ValueContext) str() string { pub fn (ctx &ValueContext) str() string {
return context_name(ctx.context) + '.with_value' return context_name(ctx.context) + '.with_value'
} }

View File

@ -1,23 +1,34 @@
import context import context
type ValueContextKey = string 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 // 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.Context, key ValueContextKey) string { f := fn (ctx context.Context, key context.Key) &Value {
if value := ctx.value(key) { if value := ctx.value(key) {
if !isnil(value) { match value {
return *(&string(value)) Value {
return value
}
else {}
} }
} }
return 'key not found' return not_found_value
} }
key := ValueContextKey('language') key := 'language'
value := 'VAL' value := &Value{
ctx := context.with_value(context.background(), key, &value) val: 'VAL'
}
ctx := context.with_value(context.background(), key, value)
assert value == f(ctx, key) assert value == f(ctx, key)
assert 'key not found' == f(ctx, ValueContextKey('color')) assert not_found_value == f(ctx, 'color')
} }