pq/error.go (107 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package pq import ( "fmt" "github.com/elastic/go-txfile" "github.com/elastic/go-txfile/internal/strbld" "github.com/elastic/go-txfile/txerr" ) // ErrKind provides the pq related error kinds type ErrKind int // reason is used as package internal error type. It's used to guarantee all // package level errors generated or returned by txfile are compatible to txerr.Error. type reason interface { txerr.Error } // Error is the actual error type returned by all functions/methods within the // pq package. The Error is compatible to error and txerr.Error, but adds a // few additional meta-data for applications to report and handle errors. type Error struct { op string kind error cause error ctx errorCtx msg string } type errorCtx struct { // internal queue ID for correlating errors, changes between restarts. id queueID // page number an error was detected for page txfile.PageID isPage bool // set if the page id is valid } //go:generate stringer -type=ErrKind -linecomment=true const ( NoError ErrKind = iota // no error InitFailed // failed to initialize queue InvalidParam // invalid parameter InvalidPageSize // invalid page size InvalidConfig // invalid queue config QueueClosed // queue is already closed ReaderClosed // reader is already closed WriterClosed // writer is already closed NoQueueRoot // no queue root InvalidQueueRoot // queue root is invalid QueueVersion // unsupported queue version ACKEmptyQueue // invalid ack on empty queue ACKTooMany // too many events acked SeekFail // failed to seek to next page ReadFail // failed to read page InactiveTx // no active transaction UnexpectedActiveTx // unexpected active transaction ) // Error returns a user readable error message. func (k ErrKind) Error() string { return k.String() } // Error returns the error message. The cause will not be included in the error // string. Use fmt with %+v to create a formatted multiline error. func (e *Error) Error() string { return txerr.Report(e, false) } // Format adds support for fmt.Formatter to Error. // The format patterns %v and %s print the top-level error message only // (similar to `(*Error).Error()`). The format pattern "q" is similar to "%s", // but adds double quotes before and after the message. // Use %+v to create a multiline string containing the full trace of errors. func (e *Error) Format(s fmt.State, c rune) { txerr.Format(e, s, c) } // Op returns the operation the error occured at. Returns "" if the error value // is used to wrap another error. Better use `txerr.GetOp(err)` to query an // error value for the causing operation. func (e *Error) Op() string { return e.op } // Kind returns the error kind of the error. The kind should be used by // applications to check if it is possible to recover from an error condition. // Kind return nil if the error value does not define a kind. Better use // `txerr.Is` or `txerr.GetKind` to query the error kind. func (e *Error) Kind() error { return e.kind } // Context returns a formatted string of the related meta-data as key/value // pairs. func (e *Error) Context() string { return e.ctx.String() } // Message returns the user-focused error message. func (e *Error) Message() string { return e.msg } // Cause returns the causing error, if any. func (e *Error) Cause() error { return e.cause } // Errors is similar to `Cause()`, but returns a slice of errors. This way the // error value can be consumed and formatted by zap (and propably other // loggers). func (e *Error) Errors() []error { if e.cause == nil { return nil } return []error{e.cause} } func (ctx *errorCtx) String() string { buf := &strbld.Builder{} if ctx.id != 0 { buf.Fmt("queueID=%v", ctx.id) } if ctx.isPage { buf.Pad(" ") buf.Fmt("page=%v", ctx.page) } return buf.String() } // IsQueueCorrupt checks if the error value indicates a corrupted queue, which // can not be used anymore. func IsQueueCorrupt(err error) bool { for _, kind := range []ErrKind{InvalidQueueRoot, SeekFail} { if txerr.Is(kind, err) { return true } } return false } func errOp(op string) *Error { return &Error{op: op} } func wrapErr(op string, cause error) *Error { return errOp(op).causedBy(cause) } func (e *Error) of(k ErrKind) *Error { e.kind = k return e } // causedBy adds a cause to e and returns the modified e itself. // The error contexts are merged (duplicates are removed from the cause), if // the cause is `*Error`. func (e *Error) causedBy(cause error) *Error { e.cause = cause other, ok := cause.(*Error) if !ok { return e } // merge error and cause context such that the cause context only reports // fields that differ from the current context. errCtx := &e.ctx causeCtx := &other.ctx if errCtx.id == causeCtx.id { causeCtx.id = 0 // delete common queue id from cause context } if errCtx.isPage && causeCtx.isPage && errCtx.page == causeCtx.page { causeCtx.isPage = false // delete common page id from cause context } return e } func (e *Error) report(m string) *Error { e.msg = m return e }