/
operation.go
154 lines (134 loc) · 4.68 KB
/
operation.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
package rados
// #include <stdlib.h>
import "C"
import (
"fmt"
"strings"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
// The file operation.go exists to support both read op and write op types that
// have some pretty common behaviors between them. In C/C++ its assumed that
// the buffer types and other pointers will not be freed between passing them
// to the action setup calls (things like rados_write_op_write or
// rados_read_op_omap_get_vals2) and the call to Operate(...). Since there's
// nothing stopping one from sleeping for hours between these calls, or passing
// the op to other functions and calling Operate there, we want a mechanism
// that's (fairly) simple to understand and won't run afoul of Go's garbage
// collection. That's one reason the operation type tracks the steps (the
// parts that track complex inputs and outputs) so that as long as the op
// exists it will have a reference to the step, which will have references
// to the C language types.
type opKind string
const (
readOp opKind = "read"
writeOp opKind = "write"
)
// OperationError is an error type that may be returned by an Operate call.
// It captures the error from the operate call itself and any errors from
// steps that can return an error.
type OperationError struct {
kind opKind
OpError error
StepErrors map[int]error
}
func (e OperationError) Error() string {
subErrors := []string{}
if e.OpError != nil {
subErrors = append(subErrors,
fmt.Sprintf("op=%s", e.OpError))
}
for idx, es := range e.StepErrors {
subErrors = append(subErrors,
fmt.Sprintf("Step#%d=%s", idx, es))
}
return fmt.Sprintf(
"%s operation error: %s",
e.kind,
strings.Join(subErrors, ", "))
}
// opStep provides an interface for types that are tied to the management of
// data being input or output from write ops and read ops. The steps are
// meant to simplify the internals of the ops themselves and be exportable when
// appropriate. If a step is not being exported it should not be returned
// from an ops action function. If the step is exported it should be
// returned from an ops action function.
//
// Not all types implementing opStep are expected to need all the functions
// in the interface. However, for the sake of simplicity on the op side, we use
// the same interface for all cases and expect those implementing opStep
// just embed the without* types that provide no-op implementation of
// functions that make up this interface.
type opStep interface {
// update the state of the step after the call to Operate.
// It can be used to convert values from C and cache them and/or
// communicate a failure of the action associated with the step. The
// update call will only be made once. Implementations are not required to
// handle this call being made more than once.
update() error
// free will be called to free any resources, especially C memory, that
// the step is managing. The behavior of free should be idempotent and
// handle being called more than once.
free()
}
// operation represents some of the shared underlying mechanisms for
// both read and write op types.
type operation struct {
steps []opStep
}
// free will call the free method of all the steps this operation
// contains.
func (o *operation) free() {
for i := range o.steps {
o.steps[i].free()
}
}
// update the operation and the steps it contains. The top-level result
// of the rados call is passed in as ret and used to construct errors.
// The update call of each step is used to update the contents of each
// step and gather any errors from those steps.
func (o *operation) update(kind opKind, ret C.int) error {
stepErrors := map[int]error{}
for i := range o.steps {
if err := o.steps[i].update(); err != nil {
stepErrors[i] = err
}
}
if ret == 0 && len(stepErrors) == 0 {
return nil
}
return OperationError{
kind: kind,
OpError: getError(ret),
StepErrors: stepErrors,
}
}
func opStepFinalizer(s opStep) {
if s != nil {
log.Warnf("unreachable opStep object found. Cleaning up.")
s.free()
}
}
// withoutUpdate can be embedded in a struct to help indicate
// the type implements the opStep interface but has a no-op
// update function.
type withoutUpdate struct{}
func (*withoutUpdate) update() error { return nil }
// withoutFree can be embedded in a struct to help indicate
// the type implements the opStep interface but has a no-op
// free function.
type withoutFree struct{}
func (*withoutFree) free() {}
// withRefs is a embeddable type to help track and free C memory.
type withRefs struct {
refs []unsafe.Pointer
}
func (w *withRefs) free() {
for i := range w.refs {
C.free(w.refs[i])
}
w.refs = nil
}
func (w *withRefs) add(ptr unsafe.Pointer) {
w.refs = append(w.refs, ptr)
}