The VFS calls backends from goroutines of its own. A panic on any of these cannot be recovered. So a backend panicking on a single file killed the whole process, taking down a mount or every user of a serve command, even for servers such as serve http whose library recovers panics raised on its own request goroutines. Recover panics at those goroutines and log them with a stack trace. Where the surrounding code already handles a failure, recover around the backend call itself rather than the whole goroutine, so a panicking upload is retried like any other failed upload and a panicking download is reported to the waiters, instead of abandoning the work part way through and leaving the bookkeeping inconsistent. Addresses GHSA-6jcg-q3wp-x2f4
42 lines
999 B
Go
42 lines
999 B
Go
package vfscommon
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRecoverPanic(t *testing.T) {
|
|
// Panic recovered into err
|
|
err := func() (err error) {
|
|
defer RecoverPanic("test", &err)
|
|
panic("boom")
|
|
}()
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "boom")
|
|
|
|
// No panic leaves err alone
|
|
err = func() (err error) {
|
|
defer RecoverPanic("test", nil)
|
|
return nil
|
|
}()
|
|
assert.NoError(t, err)
|
|
|
|
// A nil error target is allowed, for goroutines with nothing to report to
|
|
assert.NotPanics(t, func() {
|
|
defer RecoverPanic("test", nil)
|
|
panic("boom")
|
|
})
|
|
}
|
|
|
|
func TestRecoverCall(t *testing.T) {
|
|
assert.ErrorContains(t, RecoverCall("test", func() error { panic("boom") }), "boom")
|
|
|
|
// Ordinary errors and results pass through untouched
|
|
errFoo := errors.New("foo")
|
|
assert.Equal(t, errFoo, RecoverCall("test", func() error { return errFoo }))
|
|
assert.NoError(t, RecoverCall("test", func() error { return nil }))
|
|
}
|