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',
]
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/v/tests/orm_sub_struct_test.v',
'vlib/v/tests/closure_test.v',

View File

@ -59,9 +59,9 @@ fn example_with_cancel() {
return dst
}
ctx := context.with_cancel(context.background())
ctx, cancel := context.with_cancel(context.background())
defer {
context.cancel(ctx)
cancel()
}
ch := gen(ctx)
@ -87,13 +87,13 @@ const (
// function that it should abandon its work as soon as it gets to it.
fn example_with_deadline() {
dur := time.now().add(short_duration)
ctx := context.with_deadline(context.background(), dur)
ctx, cancel := 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)
cancel()
}
ctx_ch := ctx.done()
@ -122,9 +122,9 @@ const (
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.
ctx := context.with_timeout(context.background(), short_duration)
ctx, cancel := context.with_timeout(context.background(), short_duration)
defer {
context.cancel(ctx)
cancel()
}
ctx_ch := ctx.done()
@ -142,25 +142,36 @@ fn example_with_timeout() {
```v
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
// and also how to retrieve it if it exists.
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 !isnil(value) {
return *(&string(value))
match value {
Value {
return value
}
else {}
}
}
return 'key not found'
return not_found_value
}
key := ValueContextKey('language')
value := 'VAL'
ctx := context.with_value(context.background(), key, &value)
key := 'language'
value := &Value{
val: 'VAL'
}
ctx := context.with_value(context.background(), key, value)
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,
// 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
module context
@ -10,7 +10,7 @@ const (
background = EmptyContext(0)
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 = error('context canceled')
@ -20,6 +20,24 @@ const (
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 {
// deadline returns the time when work done on behalf of this context
// 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;
// packages should define keys as an unexported type to avoid
// collisions.
value(key string) ?voidptr
value(key Key) ?Any
str() string
}

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals,
// 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
module context
@ -8,12 +8,15 @@ import rand
import sync
import time
pub type CancelFn = fn ()
pub interface Canceler {
id string
cancel(remove_from_parent bool, err IError)
done() chan int
}
[deprecated]
pub fn cancel(ctx Context) {
match mut ctx {
CancelContext {
@ -44,10 +47,12 @@ 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_cancel(parent Context) Context {
pub fn with_cancel(parent Context) (Context, CancelFn) {
mut c := new_cancel_context(parent)
propagate_cancel(parent, mut c)
return Context(c)
propagate_cancel(parent, c)
return Context(c), fn [mut c] () {
c.cancel(true, canceled)
}
}
// 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
}
@ -79,14 +84,14 @@ pub fn (mut ctx CancelContext) err() IError {
return err
}
pub fn (ctx CancelContext) value(key string) ?voidptr {
pub fn (ctx &CancelContext) value(key Key) ?Any {
if key == cancel_context_key {
return voidptr(unsafe { &ctx })
return ctx
}
return ctx.context.value(key)
}
pub fn (ctx CancelContext) str() string {
pub fn (ctx &CancelContext) str() string {
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()
select {
_ := <-done {
@ -132,19 +137,19 @@ fn propagate_cancel(parent Context, mut child Canceler) {
}
}
mut p := parent_cancel_context(parent) or {
go fn (parent Context, mut child Canceler) {
go fn (parent Context, child Canceler) {
pdone := parent.done()
select {
_ := <-pdone {
child.cancel(false, parent.err())
}
}
}(parent, mut child)
}(parent, child)
return
}
if p.err is none {
p.children[child.id] = *child
p.children[child.id] = child
} else {
// parent has already been canceled
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
// 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 {
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)
mut p := parent.value(cancel_context_key) ?
match mut p {
CancelContext {
pdone := p.done()
if done == pdone {
return *p
return p
}
}
else {}
}
return none
}

View File

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

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals,
// 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
module context
@ -26,7 +26,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) Context {
pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
id := rand.uuid_v4()
if cur := parent.deadline() {
if cur < d {
@ -40,11 +40,13 @@ pub fn with_deadline(parent Context, d time.Time) Context {
deadline: d
id: id
}
propagate_cancel(parent, mut ctx)
propagate_cancel(parent, ctx)
dur := d - time.now()
if dur.nanoseconds() <= 0 {
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 {
@ -53,18 +55,20 @@ pub fn with_deadline(parent Context, d time.Time) Context {
ctx.cancel(true, deadline_exceeded)
}(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)).
//
// 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) Context {
pub fn with_timeout(parent Context, timeout time.Duration) (Context, CancelFn) {
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
}
@ -76,7 +80,7 @@ pub fn (mut ctx TimerContext) err() IError {
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)
}
@ -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() + ' [' +
(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.
fn test_with_deadline() {
dur := time.now().add(short_duration)
ctx := context.with_deadline(context.background(), dur)
ctx, cancel := 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)
cancel()
}
ctx_ch := ctx.done()
@ -33,9 +33,9 @@ fn test_with_deadline() {
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)
ctx, cancel := context.with_timeout(context.background(), short_duration)
defer {
context.cancel(ctx)
cancel()
}
ctx_ch := ctx.done()

View File

@ -1,6 +1,6 @@
// This module defines the Context type, which carries deadlines, cancellation signals,
// 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
module context
@ -10,11 +10,11 @@ import time
// struct{}, since vars of this type must have distinct addresses.
pub type EmptyContext = int
pub fn (ctx EmptyContext) deadline() ?time.Time {
pub fn (ctx &EmptyContext) deadline() ?time.Time {
return none
}
pub fn (ctx EmptyContext) done() chan int {
pub fn (ctx &EmptyContext) done() chan int {
ch := chan int{}
defer {
ch.close()
@ -22,16 +22,15 @@ pub fn (ctx EmptyContext) done() chan int {
return ch
}
pub fn (ctx EmptyContext) err() IError {
// TODO: Change this to `none`
return none_
}
pub fn (ctx EmptyContext) value(key string) ?voidptr {
pub fn (ctx &EmptyContext) err() IError {
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 {
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,
// 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
module context
@ -9,8 +9,8 @@ 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
key Key
value Any
mut:
context Context
}
@ -25,7 +25,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) Context {
pub fn with_value(parent Context, key Key, value Any) Context {
return &ValueContext{
context: parent
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()
}
pub fn (ctx ValueContext) done() chan int {
pub fn (ctx &ValueContext) done() chan int {
return ctx.context.done()
}
pub fn (ctx ValueContext) err() IError {
pub fn (ctx &ValueContext) err() IError {
return ctx.context.err()
}
pub fn (ctx ValueContext) value(key string) ?voidptr {
pub fn (ctx &ValueContext) value(key Key) ?Any {
if ctx.key == key {
return ctx.value
}
return ctx.context.value(key)
}
pub fn (ctx ValueContext) str() string {
pub fn (ctx &ValueContext) str() string {
return context_name(ctx.context) + '.with_value'
}

View File

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