Skip to content

Commit

Permalink
Catch panic from bbolt open connection
Browse files Browse the repository at this point in the history
  • Loading branch information
gandarez committed Aug 7, 2023
1 parent 0f41c3b commit 373d824
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 33 deletions.
20 changes: 20 additions & 0 deletions pkg/offline/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package offline

import (
"fmt"
)

// ErrOpenDB is an error returned when the database cannot be opened.
type ErrOpenDB struct {
Err error
}

// Error method to implement error interface.
func (e ErrOpenDB) Error() string {
return e.Err.Error()
}

// Message method to implement wakaerror.Error interface.
func (e ErrOpenDB) Message() string {
return fmt.Sprintf("failed to open db connection: %s", e.Err)
}
83 changes: 50 additions & 33 deletions pkg/offline/offline.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math"
"net/http"
"path/filepath"
"runtime/debug"
"time"

"github.com/wakatime/wakatime-cli/pkg/api"
Expand Down Expand Up @@ -66,10 +67,17 @@ func WithQueue(filepath string) heartbeat.HandleOption {

requeueErr := pushHeartbeatsWithRetry(filepath, hh)
if requeueErr != nil {
if errors.Is(requeueErr, &ErrOpenDB{}) {
log.Errorf("failed to push heartbeats to queue: %s", requeueErr.(ErrOpenDB).Message())

return nil, nil
}

return nil, fmt.Errorf(
"failed to push heartbeats to queue after api error: %s. error: %s",
requeueErr,
err)
err,
)
}

return nil, err
Expand Down Expand Up @@ -158,15 +166,15 @@ func Sync(filepath string, syncLimit int) func(next heartbeat.Handle) error {
if err != nil {
requeueErr := pushHeartbeatsWithRetry(filepath, hh)
if requeueErr != nil {
log.Warnf("failed to push heatbeats to queue after api error: %s", requeueErr)
log.Warnf("failed to push heartbeats to queue after api error: %s", requeueErr)
}

return err
}

err = handleResults(filepath, results, hh)
if err != nil {
return fmt.Errorf("failed to handle heatbeats api results: %s", err)
return fmt.Errorf("failed to handle heartbeats api results: %s", err)
}
}

Expand Down Expand Up @@ -213,7 +221,7 @@ func handleResults(filepath string, results []heartbeat.Result, hh []heartbeat.H

err = pushHeartbeatsWithRetry(filepath, withInvalidStatus)
if err != nil {
log.Warnf("failed to push heatbeats with invalid status to queue: %s", err)
log.Warnf("failed to push heartbeats with invalid status to queue: %s", err)
}
}

Expand All @@ -226,24 +234,20 @@ func handleResults(filepath string, results []heartbeat.Result, hh []heartbeat.H

err = pushHeartbeatsWithRetry(filepath, hh[start:])
if err != nil {
log.Warnf("failed to push leftover heatbeats to queue: %s", err)
log.Warnf("failed to push leftover heartbeats to queue: %s", err)
}
}

return err
}

func popHeartbeats(filepath string, limit int) ([]heartbeat.Heartbeat, error) {
db, err := bolt.Open(filepath, 0600, &bolt.Options{Timeout: 10 * time.Minute})
db, close, err := openDB(filepath)
if err != nil {
return nil, fmt.Errorf("failed to open db connection: %s", err)
return nil, err
}

defer func() {
if err := db.Close(); err != nil {
log.Debugf("failed to close db file: %s", err)
}
}()
defer close()

tx, err := db.Begin(true)
if err != nil {
Expand Down Expand Up @@ -308,16 +312,12 @@ func pushHeartbeatsWithRetry(filepath string, hh []heartbeat.Heartbeat) error {
}

func pushHeartbeats(filepath string, hh []heartbeat.Heartbeat) error {
db, err := bolt.Open(filepath, 0600, &bolt.Options{Timeout: 10 * time.Minute})
db, close, err := openDB(filepath)
if err != nil {
return fmt.Errorf("failed to open db connection: %s", err)
return err
}

defer func() {
if err := db.Close(); err != nil {
log.Debugf("failed to close db file: %s", err)
}
}()
defer close()

tx, err := db.Begin(true)
if err != nil {
Expand All @@ -340,16 +340,12 @@ func pushHeartbeats(filepath string, hh []heartbeat.Heartbeat) error {

// CountHeartbeats returns the total number of heartbeats in the offline db.
func CountHeartbeats(filepath string) (int, error) {
db, err := bolt.Open(filepath, 0600, &bolt.Options{Timeout: 30 * time.Second})
db, close, err := openDB(filepath)
if err != nil {
return 0, fmt.Errorf("failed to open db connection: %s", err)
return 0, err
}

defer func() {
if err := db.Close(); err != nil {
log.Debugf("failed to close db file: %s", err)
}
}()
defer close()

tx, err := db.Begin(true)
if err != nil {
Expand All @@ -375,16 +371,12 @@ func CountHeartbeats(filepath string) (int, error) {

// ReadHeartbeats reads the informed heartbeats in the offline db.
func ReadHeartbeats(filepath string, limit int) ([]heartbeat.Heartbeat, error) {
db, err := bolt.Open(filepath, 0600, &bolt.Options{Timeout: 30 * time.Second})
db, close, err := openDB(filepath)
if err != nil {
return nil, fmt.Errorf("failed to open db connection: %s", err)
return nil, err
}

defer func() {
if err := db.Close(); err != nil {
log.Debugf("failed to close db file: %s", err)
}
}()
defer close()

tx, err := db.Begin(true)
if err != nil {
Expand All @@ -410,6 +402,31 @@ func ReadHeartbeats(filepath string, limit int) ([]heartbeat.Heartbeat, error) {
return hh, nil
}

// openDB opens a connection to the offline db.
// It returns the pointer to bolt.DB, a function to close the connection and an error.
func openDB(filepath string) (*bolt.DB, func(), error) {
var err error

defer func() {
if r := recover(); r != nil {
err = ErrOpenDB{Err: fmt.Errorf("panicked: %v", r)}

log.Errorf("%s. Stack: %s", err, string(debug.Stack()))
}
}()

db, err := bolt.Open(filepath, 0600, &bolt.Options{Timeout: 30 * time.Second})
if err != nil {
return nil, nil, fmt.Errorf("failed to open db connection: %s", err)
}

return db, func() {
if err := db.Close(); err != nil {
log.Debugf("failed to close db file: %s", err)
}
}, err
}

// Queue is a db client to temporarily store heartbeats in bolt db, in case heartbeat
// sending to wakatime api is not possible. Transaction handling is left to the user
// via the passed in transaction.
Expand Down

0 comments on commit 373d824

Please sign in to comment.