mirror of https://github.com/ceph/go-ceph
152 lines
4.6 KiB
Go
152 lines
4.6 KiB
Go
package rados
|
|
|
|
// #include <stdlib.h>
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unsafe"
|
|
)
|
|
|
|
// 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 {
|
|
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)
|
|
}
|