From c151e075e190ac8d4929fbcae9ce7d4254d800a9 Mon Sep 17 00:00:00 2001 From: Ulises Jeremias Cornejo Fandos Date: Mon, 27 Sep 2021 11:52:20 -0300 Subject: [PATCH] context: update ValueContext to handle Any value and more Key types, use closures (#11993) --- cmd/tools/vtest-self.v | 4 ++++ vlib/context/README.md | 41 +++++++++++++++++++++++------------- vlib/context/_context.v | 24 ++++++++++++++++++--- vlib/context/cancel.v | 40 ++++++++++++++++++++--------------- vlib/context/cancel_test.v | 4 ++-- vlib/context/deadline.v | 22 +++++++++++-------- vlib/context/deadline_test.v | 8 +++---- vlib/context/empty.v | 15 ++++++------- vlib/context/err.v | 12 ----------- vlib/context/value.v | 18 ++++++++-------- vlib/context/value_test.v | 29 +++++++++++++++++-------- 11 files changed, 129 insertions(+), 88 deletions(-) delete mode 100644 vlib/context/err.v diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index d632d1f3b..8ef97ee78 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -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', diff --git a/vlib/context/README.md b/vlib/context/README.md index 7fbf18b75..43f640399 100644 --- a/vlib/context/README.md +++ b/vlib/context/README.md @@ -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') } ``` diff --git a/vlib/context/_context.v b/vlib/context/_context.v index 5d4e2d1e6..61e820cd9 100644 --- a/vlib/context/_context.v +++ b/vlib/context/_context.v @@ -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 } diff --git a/vlib/context/cancel.v b/vlib/context/cancel.v index 1a0ae5701..497f17c89 100644 --- a/vlib/context/cancel.v +++ b/vlib/context/cancel.v @@ -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 } diff --git a/vlib/context/cancel_test.v b/vlib/context/cancel_test.v index c54603176..314e2da5c 100644 --- a/vlib/context/cancel_test.v +++ b/vlib/context/cancel_test.v @@ -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) diff --git a/vlib/context/deadline.v b/vlib/context/deadline.v index 43fd056bb..a402277fc 100644 --- a/vlib/context/deadline.v +++ b/vlib/context/deadline.v @@ -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() + '])' } diff --git a/vlib/context/deadline_test.v b/vlib/context/deadline_test.v index e4d7280d7..92dd46483 100644 --- a/vlib/context/deadline_test.v +++ b/vlib/context/deadline_test.v @@ -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() diff --git a/vlib/context/empty.v b/vlib/context/empty.v index 335369a9a..e5fe2f736 100644 --- a/vlib/context/empty.v +++ b/vlib/context/empty.v @@ -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) err() IError { + return none } -pub fn (ctx EmptyContext) value(key string) ?voidptr { +pub fn (ctx &EmptyContext) value(key Key) ?Any { return none } -pub fn (ctx EmptyContext) str() string { +pub fn (ctx &EmptyContext) str() string { if ctx == background { return 'context.Background' } diff --git a/vlib/context/err.v b/vlib/context/err.v deleted file mode 100644 index 23cfe568e..000000000 --- a/vlib/context/err.v +++ /dev/null @@ -1,12 +0,0 @@ -module context - -const none_ = IError(&None{}) - -struct None { - msg string - code int -} - -fn (_ None) str() string { - return 'none' -} diff --git a/vlib/context/value.v b/vlib/context/value.v index 19a228920..f4a3bcd28 100644 --- a/vlib/context/value.v +++ b/vlib/context/value.v @@ -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' } diff --git a/vlib/context/value_test.v b/vlib/context/value_test.v index a8ed5b523..687411a91 100644 --- a/vlib/context/value_test.v +++ b/vlib/context/value_test.v @@ -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') } -- 2.30.2