Commit 00a3c085 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 60125d35
// Copyright (C) 2017 Nexedi SA and Contributors. // Copyright (C) 2017-2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com> // Kirill Smelkov <kirr@nexedi.com>
// //
// This program is free software: you can Use, Study, Modify and Redistribute // This program is free software: you can Use, Study, Modify and Redistribute
...@@ -27,7 +27,7 @@ import ( ...@@ -27,7 +27,7 @@ import (
"lab.nexedi.com/kirr/go123/xerr" "lab.nexedi.com/kirr/go123/xerr"
) )
// Task represents currently running operation // Task represents currently running operation.
type Task struct { type Task struct {
Parent *Task Parent *Task
Name string Name string
...@@ -35,17 +35,18 @@ type Task struct { ...@@ -35,17 +35,18 @@ type Task struct {
type taskKey struct{} type taskKey struct{}
// Running creates new task and returns new context with that task set to current // Running creates new task and returns new context with that task set to current.
func Running(ctx context.Context, name string) context.Context { func Running(ctx context.Context, name string) context.Context {
return context.WithValue(ctx, taskKey{}, &Task{Parent: Current(ctx), Name: name}) return context.WithValue(ctx, taskKey{}, &Task{Parent: Current(ctx), Name: name})
} }
// Runningf is Running cousin with formatting support // Runningf is Running cousin with formatting support.
func Runningf(ctx context.Context, format string, argv ...interface{}) context.Context { func Runningf(ctx context.Context, format string, argv ...interface{}) context.Context {
return Running(ctx, fmt.Sprintf(format, argv...)) return Running(ctx, fmt.Sprintf(format, argv...))
} }
// Current returns current task represented by context. // Current returns current task represented by context.
//
// if there is no current task - it returns nil. // if there is no current task - it returns nil.
func Current(ctx context.Context) *Task { func Current(ctx context.Context) *Task {
task, _ := ctx.Value(taskKey{}).(*Task) task, _ := ctx.Value(taskKey{}).(*Task)
...@@ -53,6 +54,7 @@ func Current(ctx context.Context) *Task { ...@@ -53,6 +54,7 @@ func Current(ctx context.Context) *Task {
} }
// ErrContext adds current task name to error on error return. // ErrContext adds current task name to error on error return.
//
// To work as intended it should be called under defer like this: // To work as intended it should be called under defer like this:
// //
// func myfunc(ctx, ...) (..., err error) { // func myfunc(ctx, ...) (..., err error) {
......
// Copyright (C) 2017 Nexedi SA and Contributors. // Copyright (C) 2017-2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com> // Kirill Smelkov <kirr@nexedi.com>
// //
// This program is free software: you can Use, Study, Modify and Redistribute // This program is free software: you can Use, Study, Modify and Redistribute
...@@ -18,6 +18,35 @@ ...@@ -18,6 +18,35 @@
// See https://www.nexedi.com/licensing for rationale and options. // See https://www.nexedi.com/licensing for rationale and options.
// Package xcontext provides addons to std package context. // Package xcontext provides addons to std package context.
//
// Merging contexts
//
// Merge and MergeChan could be handy in situations where spawned job needs to
// be canceled whenever any of 2 contexts becomes done. This frequently arises
// with service methods which accept context as argument, and the service
// itself, on another control line, could be instructed to become
// non-operational. For example:
//
// func (srv *Service) DoSomething(ctx context.Context) error {
// // srv.down is chan struct{} that becomes ready when service is closed.
// ctxDown, down := xcontext.MergeChan(ctx, srv.down)
// defer down()
//
// err := doJob(ctxDown)
// if ctxDown.Err() != nil && ctx.Err() == nil {
// err = ErrDueToServiceDown
// }
//
// ...
// }
//
//
//
// XXX docs:
// - Canceled
// - Merge
//
// - WhenDone
package xcontext package xcontext
import ( import (
...@@ -26,6 +55,17 @@ import ( ...@@ -26,6 +55,17 @@ import (
"time" "time"
) )
// mergeCtx represents 2 context merged into 1.
type mergeCtx struct {
ctx1, ctx2 context.Context
done chan struct{}
doneErr error
cancelCh chan struct{}
cancelOnce sync.Once
}
// Merge merges 2 contexts into 1. // Merge merges 2 contexts into 1.
// //
// The result context: // The result context:
...@@ -36,9 +76,6 @@ import ( ...@@ -36,9 +76,6 @@ import (
// //
// Canceling this context releases resources associated with it, so code should // Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete. // call cancel as soon as the operations running in this Context complete.
//
// XXX let Merge do only merge, not create another cancel; optimize it for
// cases when a source context is not cancellable
func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) { func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
mc := &mergeCtx{ mc := &mergeCtx{
ctx1: ctx1, ctx1: ctx1,
...@@ -47,15 +84,6 @@ func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) { ...@@ -47,15 +84,6 @@ func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
cancelCh: make(chan struct{}), cancelCh: make(chan struct{}),
} }
/*
// ctx1 will never be canceled?
switch ctx1.Done() {
case nil, context.Background().Done():
bg1 = true
}
// ----//---- same for ctx2?
*/
// if src ctx is already cancelled - make mc cancelled right after creation // if src ctx is already cancelled - make mc cancelled right after creation
// //
// this saves goroutine spawn and makes // this saves goroutine spawn and makes
...@@ -80,16 +108,6 @@ func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) { ...@@ -80,16 +108,6 @@ func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
return mc, mc.cancel return mc, mc.cancel
} }
type mergeCtx struct {
ctx1, ctx2 context.Context
done chan struct{}
doneErr error
cancelCh chan struct{}
cancelOnce sync.Once
}
// wait waits when .ctx1 or .ctx2 is done and then mark mergeCtx as done // wait waits when .ctx1 or .ctx2 is done and then mark mergeCtx as done
func (mc *mergeCtx) wait() { func (mc *mergeCtx) wait() {
select { select {
...@@ -106,18 +124,21 @@ func (mc *mergeCtx) wait() { ...@@ -106,18 +124,21 @@ func (mc *mergeCtx) wait() {
close(mc.done) close(mc.done)
} }
// cancel sends signal to wait to shutdown // cancel sends signal to wait to shutdown.
// cancel is the context.CancelFunc returned for mergeCtx by Merge //
// cancel is the context.CancelFunc returned for mergeCtx by Merge.
func (mc *mergeCtx) cancel() { func (mc *mergeCtx) cancel() {
mc.cancelOnce.Do(func() { mc.cancelOnce.Do(func() {
close(mc.cancelCh) close(mc.cancelCh)
}) })
} }
// Done implements context.Context .
func (mc *mergeCtx) Done() <-chan struct{} { func (mc *mergeCtx) Done() <-chan struct{} {
return mc.done return mc.done
} }
// Err implements context.Context .
func (mc *mergeCtx) Err() error { func (mc *mergeCtx) Err() error {
// synchronize on .done to avoid .doneErr read races // synchronize on .done to avoid .doneErr read races
select { select {
...@@ -132,6 +153,7 @@ func (mc *mergeCtx) Err() error { ...@@ -132,6 +153,7 @@ func (mc *mergeCtx) Err() error {
return mc.doneErr return mc.doneErr
} }
// Deadline implements context.Context .
func (mc *mergeCtx) Deadline() (time.Time, bool) { func (mc *mergeCtx) Deadline() (time.Time, bool) {
d1, ok1 := mc.ctx1.Deadline() d1, ok1 := mc.ctx1.Deadline()
d2, ok2 := mc.ctx2.Deadline() d2, ok2 := mc.ctx2.Deadline()
...@@ -147,6 +169,7 @@ func (mc *mergeCtx) Deadline() (time.Time, bool) { ...@@ -147,6 +169,7 @@ func (mc *mergeCtx) Deadline() (time.Time, bool) {
} }
} }
// Value implements context.Context .
func (mc *mergeCtx) Value(key interface{}) interface{} { func (mc *mergeCtx) Value(key interface{}) interface{} {
v := mc.ctx1.Value(key) v := mc.ctx1.Value(key)
if v != nil { if v != nil {
......
// Copyright (C) 2017 Nexedi SA and Contributors. // Copyright (C) 2017-2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com> // Kirill Smelkov <kirr@nexedi.com>
// //
// This program is free software: you can Use, Study, Modify and Redistribute // This program is free software: you can Use, Study, Modify and Redistribute
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment