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_werror              = [ | ||||
| 		'vlib/sync/array_rlock_test.v', | ||||
| 		'vlib/clipboard/clipboard_test.v', | ||||
| 		'vlib/eventbus/eventbus_test.v', | ||||
| 		'vlib/gx/color_test.v', | ||||
|  | @ -56,6 +55,10 @@ const ( | |||
| 		'vlib/strconv/atof_test.v', | ||||
| 		'vlib/strconv/f32_f64_to_string_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/channel_2_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