context: add a new `context` module, based on Golang's context, intended to be used in webservers (#9563)
							parent
							
								
									b54188dfea
								
							
						
					
					
						commit
						07a6f4e445
					
				|  | @ -30,7 +30,6 @@ const ( | ||||||
| 	] | 	] | ||||||
| 	skip_with_fsanitize_undefined = []string{} | 	skip_with_fsanitize_undefined = []string{} | ||||||
| 	skip_with_werror              = [ | 	skip_with_werror              = [ | ||||||
| 		'vlib/sync/array_rlock_test.v', |  | ||||||
| 		'vlib/clipboard/clipboard_test.v', | 		'vlib/clipboard/clipboard_test.v', | ||||||
| 		'vlib/eventbus/eventbus_test.v', | 		'vlib/eventbus/eventbus_test.v', | ||||||
| 		'vlib/gx/color_test.v', | 		'vlib/gx/color_test.v', | ||||||
|  | @ -56,6 +55,10 @@ const ( | ||||||
| 		'vlib/strconv/atof_test.v', | 		'vlib/strconv/atof_test.v', | ||||||
| 		'vlib/strconv/f32_f64_to_string_test.v', | 		'vlib/strconv/f32_f64_to_string_test.v', | ||||||
| 		'vlib/strconv/number_to_base_test.v', | 		'vlib/strconv/number_to_base_test.v', | ||||||
|  | 		'vlib/context/value_test.v' /* the following tests need C casts in `sync` and/or thirdparty/stdatomic */, | ||||||
|  | 		'vlib/context/empty_test.v', | ||||||
|  | 		'vlib/context/cancel_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', | ||||||
| 		'vlib/sync/channel_1_test.v', | 		'vlib/sync/channel_1_test.v', | ||||||
|  |  | ||||||
|  | @ -0,0 +1,188 @@ | ||||||
|  | # 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) and a CancelFunc. Calling the CancelFunc | ||||||
|  | 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 its children until the parent is canceled or the timer fires. | ||||||
|  | 
 | ||||||
|  | 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 (mut ctx context.CancelerContext) chan int { | ||||||
|  | 		dst := chan int{} | ||||||
|  | 		go fn (mut ctx context.CancelerContext, dst chan int) { | ||||||
|  | 			ch := ctx.done() | ||||||
|  | 			loop: for i in 0 .. 5 { | ||||||
|  | 				select { | ||||||
|  | 					_ := <-ch { | ||||||
|  | 						// returning not to leak the routine | ||||||
|  | 						break loop | ||||||
|  | 					} | ||||||
|  | 					dst <- i {} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}(mut ctx, dst) | ||||||
|  | 		return dst | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mut ctx := context.with_cancel(context.background()) | ||||||
|  | 	defer { | ||||||
|  | 		context.cancel(mut ctx) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ch := gen(mut 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 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 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 example_with_deadline() { | ||||||
|  | 	dur := time.now().add(short_duration) | ||||||
|  | 	mut 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(mut ctx) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	after_ch := after(1 * time.second) | ||||||
|  | 	ctx_ch := ctx.done() | ||||||
|  | 	select { | ||||||
|  | 		_ := <-after_ch { | ||||||
|  | 			assert false | ||||||
|  | 		} | ||||||
|  | 		_ := <-ctx_ch { | ||||||
|  | 			assert true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Context With Timeout | ||||||
|  | 
 | ||||||
|  | ```v | ||||||
|  | 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 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. | ||||||
|  | 	mut ctx := context.with_timeout(context.background(), short_duration) | ||||||
|  | 	defer { | ||||||
|  | 		context.cancel(mut ctx) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	after_ch := after(1 * time.second) | ||||||
|  | 	ctx_ch := ctx.done() | ||||||
|  | 	select { | ||||||
|  | 		_ := <-after_ch { | ||||||
|  | 			assert false | ||||||
|  | 		} | ||||||
|  | 		_ := <-ctx_ch { | ||||||
|  | 			assert true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### 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.ValueContext, 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')) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | @ -0,0 +1,77 @@ | ||||||
|  | module context | ||||||
|  | 
 | ||||||
|  | import time | ||||||
|  | 
 | ||||||
|  | pub 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           = 'context canceled' | ||||||
|  | 
 | ||||||
|  | 	// deadline_exceeded is the error returned by Context.err when the context's
 | ||||||
|  | 	// deadline passes.
 | ||||||
|  | 	deadline_exceeded  = '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() string | ||||||
|  | 	// 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(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(context.todo) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn context_name(ctx Context) string { | ||||||
|  | 	return typeof(ctx) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,243 @@ | ||||||
|  | module context | ||||||
|  | 
 | ||||||
|  | import rand | ||||||
|  | import sync | ||||||
|  | import time | ||||||
|  | 
 | ||||||
|  | pub interface Canceler { | ||||||
|  | 	id string | ||||||
|  | 	cancel(remove_from_parent bool, err string) | ||||||
|  | 	done() chan int | ||||||
|  | 	str() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn cancel(mut ctx CancelerContext) { | ||||||
|  | 	match mut ctx { | ||||||
|  | 		CancelContext { | ||||||
|  | 			ctx.cancel(true, canceled) | ||||||
|  | 		} | ||||||
|  | 		TimerContext { | ||||||
|  | 			ctx.cancel(true, canceled) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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      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
 | ||||||
|  | // 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) &CancelerContext { | ||||||
|  | 	mut c := new_cancel_context(parent) | ||||||
|  | 	propagate_cancel(parent, mut c) | ||||||
|  | 	return 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() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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() string { | ||||||
|  | 	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(&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 string) { | ||||||
|  | 	if err == '' { | ||||||
|  | 		panic('context: internal error: missing cancel error') | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.mutex.@lock() | ||||||
|  | 	if ctx.err != '' { | ||||||
|  | 		ctx.mutex.unlock() | ||||||
|  | 		// already canceled
 | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.err = err | ||||||
|  | 
 | ||||||
|  | 	if !ctx.done.closed { | ||||||
|  | 		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 | ||||||
|  | 		} | ||||||
|  | 		else {} | ||||||
|  | 	} | ||||||
|  | 	mut p := parent_cancel_context(parent) or { | ||||||
|  | 		go fn (parent Context, mut child Canceler) { | ||||||
|  | 			pdone := parent.done() | ||||||
|  | 			cdone := child.done() | ||||||
|  | 			select { | ||||||
|  | 				_ := <-pdone { | ||||||
|  | 					child.cancel(false, parent.err()) | ||||||
|  | 				} | ||||||
|  | 				_ := <-cdone {} | ||||||
|  | 				else {} | ||||||
|  | 			} | ||||||
|  | 		}(parent, mut child) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if p.err != '' { | ||||||
|  | 		// parent has already been canceled
 | ||||||
|  | 		child.cancel(false, p.err) | ||||||
|  | 	} else { | ||||||
|  | 		p.children[child.id] = child | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | 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 (mut ctx context.CancelerContext) chan int { | ||||||
|  | 		dst := chan int{} | ||||||
|  | 		go fn (mut ctx context.CancelerContext, dst chan int) { | ||||||
|  | 			ch := ctx.done() | ||||||
|  | 			loop: for i in 0 .. 5 { | ||||||
|  | 				select { | ||||||
|  | 					_ := <-ch { | ||||||
|  | 						// returning not to leak the routine
 | ||||||
|  | 						break loop | ||||||
|  | 					} | ||||||
|  | 					dst <- i {} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}(mut ctx, dst) | ||||||
|  | 		return dst | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mut ctx := context.with_cancel(context.background()) | ||||||
|  | 	defer { | ||||||
|  | 		context.cancel(mut ctx) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ch := gen(mut ctx) | ||||||
|  | 	for i in 0 .. 5 { | ||||||
|  | 		v := <-ch | ||||||
|  | 		assert i == v | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | 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) &CancelerContext { | ||||||
|  | 	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 ctx | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ctx.cancel_ctx.err() == '' { | ||||||
|  | 		go fn (mut ctx TimerContext, dur time.Duration) { | ||||||
|  | 			time.sleep(dur) | ||||||
|  | 			ctx_ch := ctx.done() | ||||||
|  | 			ctx_ch <- 0 | ||||||
|  | 			ctx.cancel(true, deadline_exceeded) | ||||||
|  | 		}(mut ctx, dur) | ||||||
|  | 	} | ||||||
|  | 	return 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) &CancelerContext { | ||||||
|  | 	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() string { | ||||||
|  | 	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 string) { | ||||||
|  | 	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() + '])' | ||||||
|  | } | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | 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() string { | ||||||
|  | 	return '' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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' | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | module context | ||||||
|  | 
 | ||||||
|  | fn test_background() { | ||||||
|  | 	ctx := background() | ||||||
|  | 	assert 'context.Background' == ctx.str() | ||||||
|  | 	if _ := ctx.value('') { | ||||||
|  | 		println('This should not happen') | ||||||
|  | 		assert false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn test_todo() { | ||||||
|  | 	ctx := todo() | ||||||
|  | 	assert 'context.TODO' == ctx.str() | ||||||
|  | 	if _ := ctx.value('') { | ||||||
|  | 		println('This should not happen') | ||||||
|  | 		assert false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,56 @@ | ||||||
|  | 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) &ValueContext { | ||||||
|  | 	if isnil(key) { | ||||||
|  | 		panic('nil key') | ||||||
|  | 	} | ||||||
|  | 	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() string { | ||||||
|  | 	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' | ||||||
|  | } | ||||||
|  | @ -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.ValueContext, 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')) | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue