diff options
Diffstat (limited to 'v_windows/v/old/vlib/context')
-rw-r--r-- | v_windows/v/old/vlib/context/README.md | 166 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/_context.v | 81 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/cancel.v | 181 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/cancel_test.v | 42 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/deadline.v | 94 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/deadline_test.v | 48 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/empty.v | 42 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/empty_test.v | 17 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/err.v | 12 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/value.v | 57 | ||||
-rw-r--r-- | v_windows/v/old/vlib/context/value_test.v | 23 |
11 files changed, 763 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/context/README.md b/v_windows/v/old/vlib/context/README.md new file mode 100644 index 0000000..7fbf18b --- /dev/null +++ b/v_windows/v/old/vlib/context/README.md @@ -0,0 +1,166 @@ +# Context + +This module defines the Context type, which carries deadlines, cancellation signals, +and other request-scoped values across API boundaries and between processes. + +Incoming requests to a server should create a Context, and outgoing calls to servers +should accept a Context. The chain of function calls between them must propagate the +Context, optionally replacing it with a derived Context created using with_cancel, +with_deadline, with_timeout, or with_value. When a Context is canceled, all Contexts +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). Calling the cancel function +cancels the child and its children, removes the parent's reference to the child, +and stops any associated timers. + +Programs that use Contexts should follow these rules to keep interfaces consistent +across different modules. + +Do not store Contexts inside a struct type; instead, pass a Context explicitly +to each function that needs it. The Context should be the first parameter, +typically named ctx, just to make it more consistent. + +## Examples + +In this section you can see some usage examples for this module + +### Context With Cancellation + +```v +import context + +// This example demonstrates the use of a cancelable context to prevent a +// routine leak. By the end of the example function, the routine started +// by gen will return without leaking. +fn example_with_cancel() { + // gen generates integers in a separate routine and + // sends them to the returned channel. + // 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 (ctx context.Context) chan int { + dst := chan int{} + go fn (ctx context.Context, dst chan int) { + mut v := 0 + ch := ctx.done() + for { + select { + _ := <-ch { + // returning not to leak the routine + return + } + dst <- v { + v++ + } + } + } + }(ctx, dst) + return dst + } + + ctx := context.with_cancel(context.background()) + defer { + context.cancel(ctx) + } + + ch := gen(ctx) + for i in 0 .. 5 { + v := <-ch + assert i == v + } +} +``` + +### Context With Deadline + +```v +import context +import time + +const ( + // a reasonable duration to block in an example + short_duration = 1 * time.millisecond +) + +// 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 example_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) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} +``` + +### Context With Timeout + +```v +import context +import time + +const ( + // a reasonable duration to block in an example + short_duration = 1 * time.millisecond +) + +// This example passes a context with a timeout to tell a blocking function that +// it should abandon its work after the timeout elapses. +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) + defer { + context.cancel(ctx) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} +``` + +### Context With Value + +```v +import context + +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.Context, key ValueContextKey) string { + if value := ctx.value(key) { + if !isnil(value) { + return *(&string(value)) + } + } + return 'key not found' + } + + key := ValueContextKey('language') + value := 'VAL' + ctx := context.with_value(context.background(), key, &value) + + assert value == f(ctx, key) + assert 'key not found' == f(ctx, ValueContextKey('color')) +} +``` diff --git a/v_windows/v/old/vlib/context/_context.v b/v_windows/v/old/vlib/context/_context.v new file mode 100644 index 0000000..5d4e2d1 --- /dev/null +++ b/v_windows/v/old/vlib/context/_context.v @@ -0,0 +1,81 @@ +// 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 +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import time + +const ( + background = EmptyContext(0) + todo = EmptyContext(1) + + cancel_context_key = 'context.CancelContext' + + // canceled is the error returned by Context.err when the context is canceled. + canceled = error('context canceled') + + // deadline_exceeded is the error returned by Context.err when the context's + // deadline passes. + deadline_exceeded = error('context deadline exceeded') +) + +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 + // set. Successive calls to deadline return the same results. + deadline() ?time.Time + // done returns a channel that's closed when work done on behalf of this + // context should be canceled. done may return nil if this context can + // never be canceled. Successive calls to done return the same value. + // The close of the done channel may happen asynchronously, + // after the cancel function returns. + // + // with_cancel arranges for done to be closed when cancel is called; + // with_deadline arranges for done to be closed when the deadline + // expires; with_timeout arranges for done to be closed when the timeout + // elapses. + done() chan int + // If done is not yet closed, err returns nil. + // If done is closed, err returns a non-nil error explaining why: + // canceled if the context was canceled + // or deadline_exceeded if the context's deadline passed. + // After err returns a non-nil error, successive calls to err return the same error. + err() IError + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.with_value and + // 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 + str() string +} + +// background returns an empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +pub fn background() Context { + return context.background +} + +// todo returns an empty Context. Code should use todo when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). +pub fn todo() Context { + return context.todo +} + +fn context_name(ctx Context) string { + return typeof(ctx) +} diff --git a/v_windows/v/old/vlib/context/cancel.v b/v_windows/v/old/vlib/context/cancel.v new file mode 100644 index 0000000..2972b17 --- /dev/null +++ b/v_windows/v/old/vlib/context/cancel.v @@ -0,0 +1,181 @@ +// 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 +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import rand +import sync +import time + +pub interface Canceler { + id string + cancel(remove_from_parent bool, err IError) + done() chan int +} + +pub fn cancel(ctx Context) { + match mut ctx { + CancelContext { + ctx.cancel(true, canceled) + } + TimerContext { + ctx.cancel(true, canceled) + } + else {} + } +} + +// A CancelContext can be canceled. When canceled, it also cancels any children +// that implement Canceler. +pub struct CancelContext { + id string +mut: + context Context + mutex &sync.Mutex + done chan int + children map[string]Canceler + err IError +} + +// 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) Context { + mut c := new_cancel_context(parent) + propagate_cancel(parent, mut c) + return Context(c) +} + +// new_cancel_context returns an initialized CancelContext. +fn new_cancel_context(parent Context) &CancelContext { + return &CancelContext{ + id: rand.uuid_v4() + context: parent + mutex: sync.new_mutex() + done: chan int{cap: 2} + err: none + } +} + +pub fn (ctx CancelContext) deadline() ?time.Time { + return none +} + +pub fn (mut ctx CancelContext) done() chan int { + ctx.mutex.@lock() + done := ctx.done + ctx.mutex.unlock() + return done +} + +pub fn (mut ctx CancelContext) err() IError { + ctx.mutex.@lock() + err := ctx.err + ctx.mutex.unlock() + return err +} + +pub fn (ctx CancelContext) value(key string) ?voidptr { + if key == cancel_context_key { + return voidptr(unsafe { &ctx }) + } + return ctx.context.value(key) +} + +pub fn (ctx CancelContext) str() string { + return context_name(ctx.context) + '.with_cancel' +} + +fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) { + if err is none { + panic('context: internal error: missing cancel error') + } + + ctx.mutex.@lock() + if !(ctx.err is none) { + ctx.mutex.unlock() + // already canceled + return + } + + ctx.err = err + + if !ctx.done.closed { + ctx.done <- 0 + ctx.done.close() + } + + for _, child in ctx.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + + ctx.children = map[string]Canceler{} + ctx.mutex.unlock() + + if remove_from_parent { + remove_child(ctx.context, ctx) + } +} + +fn propagate_cancel(parent Context, mut child Canceler) { + done := parent.done() + select { + _ := <-done { + // parent is already canceled + child.cancel(false, parent.err()) + return + } + } + mut p := parent_cancel_context(parent) or { + go fn (parent Context, mut child Canceler) { + pdone := parent.done() + select { + _ := <-pdone { + child.cancel(false, parent.err()) + } + } + }(parent, mut child) + return + } + + if p.err is none { + p.children[child.id] = *child + } else { + // parent has already been canceled + child.cancel(false, p.err) + } +} + +// parent_cancel_context returns the underlying CancelContext for parent. +// It does this by looking up parent.value(&cancel_context_key) to find +// the innermost enclosing CancelContext and then checking whether +// 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 { + 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) + pdone := p.done() + if done == pdone { + return *p + } + } + } + return none +} + +// remove_child removes a context from its parent. +fn remove_child(parent Context, child Canceler) { + mut p := parent_cancel_context(parent) or { return } + p.children.delete(child.id) +} diff --git a/v_windows/v/old/vlib/context/cancel_test.v b/v_windows/v/old/vlib/context/cancel_test.v new file mode 100644 index 0000000..6b9fdaa --- /dev/null +++ b/v_windows/v/old/vlib/context/cancel_test.v @@ -0,0 +1,42 @@ +import context + +// This example demonstrates the use of a cancelable context to prevent a +// routine leak. By the end of the example function, the routine started +// by gen will return without leaking. +fn test_with_cancel() { + // gen generates integers in a separate routine and + // sends them to the returned channel. + // 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 (ctx context.Context) chan int { + dst := chan int{} + go fn (ctx context.Context, dst chan int) { + mut v := 0 + ch := ctx.done() + for { + select { + _ := <-ch { + // returning not to leak the routine + return + } + dst <- v { + v++ + } + } + } + }(ctx, dst) + return dst + } + + ctx := context.with_cancel(context.background()) + defer { + context.cancel(ctx) + } + + ch := gen(ctx) + for i in 0 .. 5 { + v := <-ch + assert i == v + } +} diff --git a/v_windows/v/old/vlib/context/deadline.v b/v_windows/v/old/vlib/context/deadline.v new file mode 100644 index 0000000..43fd056 --- /dev/null +++ b/v_windows/v/old/vlib/context/deadline.v @@ -0,0 +1,94 @@ +// 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 +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import rand +import time + +// A TimerContext carries a timer and a deadline. It embeds a CancelContext to +// implement done and err. It implements cancel by stopping its timer then +// delegating to CancelContext.cancel +pub struct TimerContext { + id string +mut: + cancel_ctx CancelContext + deadline time.Time +} + +// with_deadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// with_deadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, 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_deadline(parent Context, d time.Time) Context { + id := rand.uuid_v4() + if cur := parent.deadline() { + if cur < d { + // The current deadline is already sooner than the new one. + return with_cancel(parent) + } + } + cancel_ctx := new_cancel_context(parent) + mut ctx := &TimerContext{ + cancel_ctx: cancel_ctx + deadline: d + id: id + } + propagate_cancel(parent, mut ctx) + dur := d - time.now() + if dur.nanoseconds() <= 0 { + ctx.cancel(true, deadline_exceeded) // deadline has already passed + return Context(ctx) + } + + if ctx.err() is none { + go fn (mut ctx TimerContext, dur time.Duration) { + time.sleep(dur) + ctx.cancel(true, deadline_exceeded) + }(mut ctx, dur) + } + 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) Context { + return with_deadline(parent, time.now().add(timeout)) +} + +pub fn (ctx TimerContext) deadline() ?time.Time { + return ctx.deadline +} + +pub fn (mut ctx TimerContext) done() chan int { + return ctx.cancel_ctx.done() +} + +pub fn (mut ctx TimerContext) err() IError { + return ctx.cancel_ctx.err() +} + +pub fn (ctx TimerContext) value(key string) ?voidptr { + return ctx.cancel_ctx.value(key) +} + +pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err IError) { + ctx.cancel_ctx.cancel(false, err) + if remove_from_parent { + // Remove this TimerContext from its parent CancelContext's children. + remove_child(ctx.cancel_ctx.context, ctx) + } +} + +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/v_windows/v/old/vlib/context/deadline_test.v b/v_windows/v/old/vlib/context/deadline_test.v new file mode 100644 index 0000000..e4d7280 --- /dev/null +++ b/v_windows/v/old/vlib/context/deadline_test.v @@ -0,0 +1,48 @@ +import context +import time + +const ( + // a reasonable duration to block in an example + short_duration = 1 * time.millisecond +) + +// 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) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} + +// 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) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} diff --git a/v_windows/v/old/vlib/context/empty.v b/v_windows/v/old/vlib/context/empty.v new file mode 100644 index 0000000..335369a --- /dev/null +++ b/v_windows/v/old/vlib/context/empty.v @@ -0,0 +1,42 @@ +// 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 +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import time + +// An EmptyContext is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +pub type EmptyContext = int + +pub fn (ctx EmptyContext) deadline() ?time.Time { + return none +} + +pub fn (ctx EmptyContext) done() chan int { + ch := chan int{} + defer { + ch.close() + } + return ch +} + +pub fn (ctx EmptyContext) err() IError { + // TODO: Change this to `none` + return none_ +} + +pub fn (ctx EmptyContext) value(key string) ?voidptr { + return none +} + +pub fn (ctx EmptyContext) str() string { + if ctx == background { + return 'context.Background' + } + if ctx == todo { + return 'context.TODO' + } + return 'unknown empty Context' +} diff --git a/v_windows/v/old/vlib/context/empty_test.v b/v_windows/v/old/vlib/context/empty_test.v new file mode 100644 index 0000000..433d8c8 --- /dev/null +++ b/v_windows/v/old/vlib/context/empty_test.v @@ -0,0 +1,17 @@ +module context + +fn test_background() { + ctx := background() + assert 'context.Background' == ctx.str() + if _ := ctx.value('') { + panic('This should never happen') + } +} + +fn test_todo() { + ctx := todo() + assert 'context.TODO' == ctx.str() + if _ := ctx.value('') { + panic('This should never happen') + } +} diff --git a/v_windows/v/old/vlib/context/err.v b/v_windows/v/old/vlib/context/err.v new file mode 100644 index 0000000..23cfe56 --- /dev/null +++ b/v_windows/v/old/vlib/context/err.v @@ -0,0 +1,12 @@ +module context + +const none_ = IError(&None{}) + +struct None { + msg string + code int +} + +fn (_ None) str() string { + return 'none' +} diff --git a/v_windows/v/old/vlib/context/value.v b/v_windows/v/old/vlib/context/value.v new file mode 100644 index 0000000..19a2289 --- /dev/null +++ b/v_windows/v/old/vlib/context/value.v @@ -0,0 +1,57 @@ +// 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 +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +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 +mut: + context Context +} + +// with_value returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The provided key must be comparable and should not be of type +// 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 { + return &ValueContext{ + context: parent + key: key + value: value + } +} + +pub fn (ctx ValueContext) deadline() ?time.Time { + return ctx.context.deadline() +} + +pub fn (ctx ValueContext) done() chan int { + return ctx.context.done() +} + +pub fn (ctx ValueContext) err() IError { + return ctx.context.err() +} + +pub fn (ctx ValueContext) value(key string) ?voidptr { + if ctx.key == key { + return ctx.value + } + return ctx.context.value(key) +} + +pub fn (ctx ValueContext) str() string { + return context_name(ctx.context) + '.with_value' +} diff --git a/v_windows/v/old/vlib/context/value_test.v b/v_windows/v/old/vlib/context/value_test.v new file mode 100644 index 0000000..a8ed5b5 --- /dev/null +++ b/v_windows/v/old/vlib/context/value_test.v @@ -0,0 +1,23 @@ +import context + +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.Context, key ValueContextKey) string { + if value := ctx.value(key) { + if !isnil(value) { + return *(&string(value)) + } + } + return 'key not found' + } + + key := ValueContextKey('language') + value := 'VAL' + ctx := context.with_value(context.background(), key, &value) + + assert value == f(ctx, key) + assert 'key not found' == f(ctx, ValueContextKey('color')) +} |