go-ceph/rados/operation.go

155 lines
4.7 KiB
Go

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)
}