fixed unhandled panic and wrapped all internal goroutines

This commit is contained in:
Gani Georgiev committed 2026-07-15 13:41:10 +03:00
1 parent a6deb6ab90
commit 30b4184305
11 files changed
+76 -22

No files matched your search

+4 -2
View File
@@ -13,6 +13,8 @@ import (
"fmt"
"sync"
"time"
"github.com/pocketbase/pocketbase/tools/routine"
)
type job struct {
@@ -170,7 +172,7 @@ func (c *Cron) Start() {
c.runDue(time.Now())
// run after each tick
go func() {
routine.FireAndForget(func() {
for {
select {
case <-c.tickerDone:
@@ -179,7 +181,7 @@ func (c *Cron) Start() {
c.runDue(t)
}
}
}()
})
})
c.Unlock()
}
+3 -2
View File
@@ -85,6 +85,7 @@ import (
s3managerv2 "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
s3v2 "github.com/aws/aws-sdk-go-v2/service/s3"
typesv2 "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/pocketbase/pocketbase/tools/routine"
"github.com/aws/smithy-go"
"gocloud.dev/blob"
@@ -362,7 +363,7 @@ func (w *writer) Upload(r io.Reader) error {
// error uploading to S3.
func (w *writer) open(r io.Reader, closePipeOnError bool) {
// This goroutine will keep running until Close, unless there's an error.
go func() {
routine.FireAndForget(func() {
defer close(w.donec)
if r == nil {
@@ -379,7 +380,7 @@ func (w *writer) open(r io.Reader, closePipeOnError bool) {
}
w.err = err
}
}()
})
}
// Close completes the writer and closes it. Any error occurring during write
+15
View File
@@ -1,6 +1,7 @@
package routine
import (
"fmt"
"log"
"runtime/debug"
"sync"
@@ -30,3 +31,17 @@ func FireAndForget(f func(), wg ...*sync.WaitGroup) {
f()
}()
}
// SafeWrap wraps the provided function with auto panic recover handling
// and returns any eventual panic as regular error.
func SafeWrap(f func() error) func() error {
return func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("[SafeWrap] recovered from panic: %v", r)
}
}()
return f()
}
}
+26 -2
View File
@@ -1,6 +1,7 @@
package routine_test
import (
"strings"
"sync"
"testing"
@@ -12,7 +13,7 @@ func TestFireAndForget(t *testing.T) {
fn := func() {
called = true
panic("test")
panic("test_recover")
}
wg := &sync.WaitGroup{}
@@ -22,6 +23,29 @@ func TestFireAndForget(t *testing.T) {
wg.Wait()
if !called {
t.Error("Expected fn to be called.")
t.Fatal("Expected fn to be called.")
}
}
func TestSafeWrap(t *testing.T) {
called := false
fn := func() error {
called = true
panic("test_recover")
}
err := routine.SafeWrap(fn)()
if !called {
t.Fatal("Expected fn to be called.")
}
if err == nil {
t.Fatal("Expected fn panic to be converted to error")
}
if !strings.Contains(err.Error(), "test_recover") {
t.Fatal("Expected the returned error to contain the recovered panic value")
}
}
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"strconv"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/tools/routine"
"golang.org/x/sync/errgroup"
)
@@ -268,8 +269,8 @@ func (s *Provider) Exec(items any) (*Result, error) {
// execute the 2 queries concurrently
errg := new(errgroup.Group)
errg.SetLimit(2)
errg.Go(countExec)
errg.Go(modelsExec)
errg.Go(routine.SafeWrap(countExec))
errg.Go(routine.SafeWrap(modelsExec))
if err := errg.Wait(); err != nil {
return nil, err
}