/
errors.go
170 lines (148 loc) · 5.37 KB
/
errors.go
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package host
// errors.go is responsible for logging the various errors that the host runs
// into related to operations that cannot immediately provide feedback to the
// user. (e.g. network failures, disk failures, etc.). Different errors should
// be handled and logged differently, depending on severity and frequency, such
// that the person reading the logs is able to see all of the major issues
// without having them obstructed by the minor ones.
import (
"errors"
"strings"
"sync/atomic"
"github.com/HyperspaceApp/fastrand"
)
const (
errorCommunicationProbability = 5
errorConnectionProbability = 20
errorConsensusProbability = 1
errorInternalProbability = 3
errorNormalProbability = 20
)
type (
// ErrorCommunication errors are meant to be returned if the host and the
// renter seem to be miscommunicating. For example, if the renter attempts
// to pay an insufficient price, there has been a communication error.
ErrorCommunication string
// ErrorConnection is meant to be used on errors where the network is
// returning unexpected errors. For example, sudden disconnects or
// connection write failures.
ErrorConnection string
// ErrorConsensus errors are meant to be used when there are problems
// related to consensus, such as an inability to submit a storage proof to
// the blockchain, or an inability to get a file contract revision on to
// the blockchain.
ErrorConsensus string
// ErrorInternal errors are meant to be used if an internal process in the
// host is malfunctioning, for example if the disk is failing.
ErrorInternal string
)
// composeErrors will take multiple errors and compose them into a single
// errors with a longer message. Any nil errors used as inputs will be stripped
// out, and if there are zero non-nil inputs then 'nil' will be returned.
//
// The original types of the errors is not preserved at all.
func composeErrors(errs ...error) error {
// Strip out any nil errors.
var errStrings []string
for _, err := range errs {
if err != nil {
errStrings = append(errStrings, err.Error())
}
}
// Return nil if there are no non-nil errors in the input.
if len(errStrings) <= 0 {
return nil
}
// Combine all of the non-nil errors into one larger return value.
return errors.New(strings.Join(errStrings, "; "))
}
// extendErr will return an error that is the same type as the input error, but
// prefixed with the provided context. This only works for the error types
// defined in the host package. If the input error is nil, the extension is
// ignored and nil will be returned.
func extendErr(s string, err error) error {
if err == nil {
return nil
}
switch v := err.(type) {
case ErrorCommunication:
return ErrorCommunication(s) + v
case ErrorConnection:
return ErrorConnection(s) + v
case ErrorConsensus:
return ErrorConsensus(s) + v
case ErrorInternal:
return ErrorInternal(s) + v
default:
return errors.New(s + err.Error())
}
}
// Error satisfies the Error interface for the ErrorCommunication type.
func (ec ErrorCommunication) Error() string {
return "communication error: " + string(ec)
}
// Error satisfies the Error interface for the ErrorConnection type.
func (ec ErrorConnection) Error() string {
return "connection error: " + string(ec)
}
// Error satisfies the Error interface for the ErrorConsensus type.
func (ec ErrorConsensus) Error() string {
return "consensus error: " + string(ec)
}
// Error satisfies the Error interface for the ErrorInternal type.
func (ec ErrorInternal) Error() string {
return "internal error: " + string(ec)
}
// mangedLogError will take an error and log it to the host, depending on the
// type of error and whether or not the DEBUG flag has been set.
func (h *Host) managedLogError(err error) {
// Determine the type of error and the number of times that this error has
// been logged.
var num uint64
var probability int // Error will be logged with 1/probability chance.
switch err.(type) {
case ErrorCommunication:
num = atomic.LoadUint64(&h.atomicCommunicationErrors)
probability = errorCommunicationProbability
case ErrorConnection:
num = atomic.LoadUint64(&h.atomicConnectionErrors)
probability = errorConnectionProbability
case ErrorConsensus:
num = atomic.LoadUint64(&h.atomicConsensusErrors)
probability = errorConsensusProbability
case ErrorInternal:
num = atomic.LoadUint64(&h.atomicInternalErrors)
probability = errorInternalProbability
default:
num = atomic.LoadUint64(&h.atomicNormalErrors)
probability = errorNormalProbability
}
// If num > logFewLimit, substantially decrease the probability that the error
// gets logged.
if num > logFewLimit {
probability = probability * 25
}
// If we've seen less than logAllLimit of that type of error before, log
// the error as a normal logging statement. Otherwise, probabilistically
// log the statement. In debugging mode, log all statements.
shouldLog := num < logAllLimit || fastrand.Intn(probability+1) == probability
if shouldLog {
h.log.Println(err)
} else {
h.log.Debugln(err)
return
}
// Increment the log counter.
switch err.(type) {
case ErrorCommunication:
atomic.AddUint64(&h.atomicCommunicationErrors, 1)
case ErrorConnection:
atomic.AddUint64(&h.atomicConnectionErrors, 1)
case ErrorConsensus:
atomic.AddUint64(&h.atomicConsensusErrors, 1)
case ErrorInternal:
atomic.AddUint64(&h.atomicInternalErrors, 1)
default:
atomic.AddUint64(&h.atomicNormalErrors, 1)
}
}