summaryrefslogtreecommitdiff
path: root/vendor/github.com/getlantern/ops/ops.go
blob: 136302db9d587f99fdc86c43e5a53bfc3fca3a8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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
}