summaryrefslogtreecommitdiff
path: root/vendor/github.com/getlantern/ops
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/getlantern/ops')
-rw-r--r--vendor/github.com/getlantern/ops/ops.go154
-rw-r--r--vendor/github.com/getlantern/ops/ops_test.go76
2 files changed, 230 insertions, 0 deletions
diff --git a/vendor/github.com/getlantern/ops/ops.go b/vendor/github.com/getlantern/ops/ops.go
new file mode 100644
index 0000000..136302d
--- /dev/null
+++ b/vendor/github.com/getlantern/ops/ops.go
@@ -0,0 +1,154 @@
+// Package ops provides a facility for tracking the processing of operations,
+// including contextual metadata about the operation and their final success or
+// failure. An op is assumed to have succeeded if by the time of calling Exit()
+// no errors have been reported. The final status can be reported to a metrics
+// facility.
+package ops
+
+import (
+ "sync"
+ "sync/atomic"
+
+ "github.com/getlantern/context"
+)
+
+var (
+ cm = context.NewManager()
+ reporters []Reporter
+ reportersMutex sync.RWMutex
+)
+
+// Reporter is a function that reports the success or failure of an Op. If
+// failure is nil, the Op can be considered successful.
+type Reporter func(failure error, ctx map[string]interface{})
+
+// Op represents an operation that's being performed. It mimics the API of
+// context.Context.
+type Op interface {
+ // Begin marks the beginning of an Op under this Op.
+ Begin(name string) Op
+
+ // Go starts the given function on a new goroutine.
+ Go(fn func())
+
+ // End marks the end of this op, at which point the Op will report its success
+ // or failure to all registered Reporters.
+ End()
+
+ // Cancel cancels this op so that even if End() is called later, it will not
+ // report its success or failure.
+ Cancel()
+
+ // Set puts a key->value pair into the current Op's context.
+ Set(key string, value interface{}) Op
+
+ // SetDynamic puts a key->value pair into the current Op's context, where the
+ // value is generated by a function that gets evaluated at every Read.
+ SetDynamic(key string, valueFN func() interface{}) Op
+
+ // FailIf marks this Op as failed if the given err is not nil. If FailIf is
+ // called multiple times, the latest error will be reported as the failure.
+ // Returns the original error for convenient chaining.
+ FailIf(err error) error
+}
+
+type op struct {
+ ctx context.Context
+ canceled bool
+ failure atomic.Value
+}
+
+// RegisterReporter registers the given reporter.
+func RegisterReporter(reporter Reporter) {
+ reportersMutex.Lock()
+ reporters = append(reporters, reporter)
+ reportersMutex.Unlock()
+}
+
+// Begin marks the beginning of a new Op.
+func Begin(name string) Op {
+ return &op{ctx: cm.Enter().Put("op", name).PutIfAbsent("root_op", name)}
+}
+
+func (o *op) Begin(name string) Op {
+ return &op{ctx: o.ctx.Enter().Put("op", name).PutIfAbsent("root_op", name)}
+}
+
+func (o *op) Go(fn func()) {
+ o.ctx.Go(fn)
+}
+
+// Go mimics the method from context.Manager.
+func Go(fn func()) {
+ cm.Go(fn)
+}
+
+func (o *op) Cancel() {
+ o.canceled = true
+}
+
+func (o *op) End() {
+ if o.canceled {
+ return
+ }
+
+ var reportersCopy []Reporter
+ reportersMutex.RLock()
+ if len(reporters) > 0 {
+ reportersCopy = make([]Reporter, len(reporters))
+ copy(reportersCopy, reporters)
+ }
+ reportersMutex.RUnlock()
+
+ if len(reportersCopy) > 0 {
+ var failure error
+ _failure := o.failure.Load()
+ ctx := o.ctx.AsMap(_failure, true)
+ if _failure != nil {
+ failure = _failure.(error)
+ _, errorSet := ctx["error"]
+ if !errorSet {
+ ctx["error"] = failure.Error()
+ }
+ }
+ for _, reporter := range reportersCopy {
+ reporter(failure, ctx)
+ }
+ }
+
+ o.ctx.Exit()
+}
+
+func (o *op) Set(key string, value interface{}) Op {
+ o.ctx.Put(key, value)
+ return o
+}
+
+// SetGlobal puts a key->value pair into the global context, which is inherited
+// by all Ops.
+func SetGlobal(key string, value interface{}) {
+ cm.PutGlobal(key, value)
+}
+
+func (o *op) SetDynamic(key string, valueFN func() interface{}) Op {
+ o.ctx.PutDynamic(key, valueFN)
+ return o
+}
+
+// SetGlobalDynamic is like SetGlobal but uses a function to derive the value
+// at read time.
+func SetGlobalDynamic(key string, valueFN func() interface{}) {
+ cm.PutGlobalDynamic(key, valueFN)
+}
+
+// AsMap mimics the method from context.Manager.
+func AsMap(obj interface{}, includeGlobals bool) context.Map {
+ return cm.AsMap(obj, includeGlobals)
+}
+
+func (o *op) FailIf(err error) error {
+ if err != nil {
+ o.failure.Store(err)
+ }
+ return err
+}
diff --git a/vendor/github.com/getlantern/ops/ops_test.go b/vendor/github.com/getlantern/ops/ops_test.go
new file mode 100644
index 0000000..1c16e73
--- /dev/null
+++ b/vendor/github.com/getlantern/ops/ops_test.go
@@ -0,0 +1,76 @@
+package ops_test
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/getlantern/errors"
+ "github.com/getlantern/ops"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSuccess(t *testing.T) {
+ var reportedFailure error
+ var reportedCtx map[string]interface{}
+ report := func(failure error, ctx map[string]interface{}) {
+ reportedFailure = failure
+ reportedCtx = ctx
+ }
+
+ ops.RegisterReporter(report)
+ ops.SetGlobal("g", "g1")
+ op := ops.Begin("test_success").Set("a", 1).SetDynamic("b", func() interface{} { return 2 })
+ defer op.End()
+ innerOp := op.Begin("inside")
+ innerOp.FailIf(nil)
+ innerOp.End()
+
+ assert.Nil(t, reportedFailure)
+ expectedCtx := map[string]interface{}{
+ "op": "inside",
+ "root_op": "test_success",
+ "g": "g1",
+ "a": 1,
+ "b": 2,
+ }
+ assert.Equal(t, expectedCtx, reportedCtx)
+}
+
+func TestFailure(t *testing.T) {
+ doTestFailure(t, false)
+}
+
+func TestCancel(t *testing.T) {
+ doTestFailure(t, true)
+}
+
+func doTestFailure(t *testing.T, cancel bool) {
+ var reportedFailure error
+ var reportedCtx map[string]interface{}
+ report := func(failure error, ctx map[string]interface{}) {
+ reportedFailure = failure
+ reportedCtx = ctx
+ }
+
+ ops.RegisterReporter(report)
+ op := ops.Begin("test_failure")
+ var wg sync.WaitGroup
+ wg.Add(1)
+ op.Go(func() {
+ op.FailIf(errors.New("I failed").With("errorcontext", 5))
+ wg.Done()
+ })
+ wg.Wait()
+ if cancel {
+ op.Cancel()
+ }
+ op.End()
+
+ if cancel {
+ assert.Nil(t, reportedFailure)
+ assert.Nil(t, reportedCtx)
+ } else {
+ assert.Contains(t, reportedFailure.Error(), "I failed")
+ assert.Equal(t, 5, reportedCtx["errorcontext"])
+ }
+}