mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #71076 from liggitt/preserve-stack
Propagate panics up handler chain
This commit is contained in:
commit
f1e4ec8e48
@ -23,6 +23,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
goruntime "runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
@ -33,7 +35,6 @@ import (
|
|||||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
@ -182,10 +183,17 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
|||||||
panicCh := make(chan interface{}, 1)
|
panicCh := make(chan interface{}, 1)
|
||||||
go func() {
|
go func() {
|
||||||
// panics don't cross goroutine boundaries, so we have to handle ourselves
|
// panics don't cross goroutine boundaries, so we have to handle ourselves
|
||||||
defer utilruntime.HandleCrash(func(panicReason interface{}) {
|
defer func() {
|
||||||
|
panicReason := recover()
|
||||||
|
if panicReason != nil {
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:goruntime.Stack(buf, false)]
|
||||||
|
panicReason = strings.TrimSuffix(fmt.Sprintf("%v\n%s", panicReason, string(buf)), "\n")
|
||||||
|
}
|
||||||
// Propagate to parent goroutine
|
// Propagate to parent goroutine
|
||||||
panicCh <- panicReason
|
panicCh <- panicReason
|
||||||
})
|
}()
|
||||||
|
|
||||||
if result, err := fn(); err != nil {
|
if result, err := fn(); err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -835,13 +836,15 @@ func TestFinishRequest(t *testing.T) {
|
|||||||
successStatusObj := &metav1.Status{Status: metav1.StatusSuccess, Message: "success message"}
|
successStatusObj := &metav1.Status{Status: metav1.StatusSuccess, Message: "success message"}
|
||||||
errorStatusObj := &metav1.Status{Status: metav1.StatusFailure, Message: "error message"}
|
errorStatusObj := &metav1.Status{Status: metav1.StatusFailure, Message: "error message"}
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
timeout time.Duration
|
name string
|
||||||
fn resultFunc
|
timeout time.Duration
|
||||||
expectedObj runtime.Object
|
fn resultFunc
|
||||||
expectedErr error
|
expectedObj runtime.Object
|
||||||
|
expectedErr error
|
||||||
|
expectedPanic string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// Expected obj is returned.
|
name: "Expected obj is returned",
|
||||||
timeout: time.Second,
|
timeout: time.Second,
|
||||||
fn: func() (runtime.Object, error) {
|
fn: func() (runtime.Object, error) {
|
||||||
return exampleObj, nil
|
return exampleObj, nil
|
||||||
@ -850,7 +853,7 @@ func TestFinishRequest(t *testing.T) {
|
|||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Expected error is returned.
|
name: "Expected error is returned",
|
||||||
timeout: time.Second,
|
timeout: time.Second,
|
||||||
fn: func() (runtime.Object, error) {
|
fn: func() (runtime.Object, error) {
|
||||||
return nil, exampleErr
|
return nil, exampleErr
|
||||||
@ -859,7 +862,7 @@ func TestFinishRequest(t *testing.T) {
|
|||||||
expectedErr: exampleErr,
|
expectedErr: exampleErr,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Successful status object is returned as expected.
|
name: "Successful status object is returned as expected",
|
||||||
timeout: time.Second,
|
timeout: time.Second,
|
||||||
fn: func() (runtime.Object, error) {
|
fn: func() (runtime.Object, error) {
|
||||||
return successStatusObj, nil
|
return successStatusObj, nil
|
||||||
@ -868,7 +871,7 @@ func TestFinishRequest(t *testing.T) {
|
|||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Error status object is converted to StatusError.
|
name: "Error status object is converted to StatusError",
|
||||||
timeout: time.Second,
|
timeout: time.Second,
|
||||||
fn: func() (runtime.Object, error) {
|
fn: func() (runtime.Object, error) {
|
||||||
return errorStatusObj, nil
|
return errorStatusObj, nil
|
||||||
@ -876,15 +879,50 @@ func TestFinishRequest(t *testing.T) {
|
|||||||
expectedObj: nil,
|
expectedObj: nil,
|
||||||
expectedErr: apierrors.FromObject(errorStatusObj),
|
expectedErr: apierrors.FromObject(errorStatusObj),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Panic is propagated up",
|
||||||
|
timeout: time.Second,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
panic("my panic")
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedPanic: "my panic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Panic is propagated with stack",
|
||||||
|
timeout: time.Second,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
panic("my panic")
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedPanic: "rest_test.go",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i, tc := range testcases {
|
for i, tc := range testcases {
|
||||||
obj, err := finishRequest(tc.timeout, tc.fn)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if (err == nil && tc.expectedErr != nil) || (err != nil && tc.expectedErr == nil) || (err != nil && tc.expectedErr != nil && err.Error() != tc.expectedErr.Error()) {
|
defer func() {
|
||||||
t.Errorf("%d: unexpected err. expected: %v, got: %v", i, tc.expectedErr, err)
|
r := recover()
|
||||||
}
|
switch {
|
||||||
if !apiequality.Semantic.DeepEqual(obj, tc.expectedObj) {
|
case r == nil && len(tc.expectedPanic) > 0:
|
||||||
t.Errorf("%d: unexpected obj. expected %#v, got %#v", i, tc.expectedObj, obj)
|
t.Errorf("expected panic containing '%s', got none", tc.expectedPanic)
|
||||||
}
|
case r != nil && len(tc.expectedPanic) == 0:
|
||||||
|
t.Errorf("unexpected panic: %v", r)
|
||||||
|
case r != nil && len(tc.expectedPanic) > 0 && !strings.Contains(fmt.Sprintf("%v", r), tc.expectedPanic):
|
||||||
|
t.Errorf("expected panic containing '%s', got '%v'", tc.expectedPanic, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
obj, err := finishRequest(tc.timeout, tc.fn)
|
||||||
|
if (err == nil && tc.expectedErr != nil) || (err != nil && tc.expectedErr == nil) || (err != nil && tc.expectedErr != nil && err.Error() != tc.expectedErr.Error()) {
|
||||||
|
t.Errorf("%d: unexpected err. expected: %v, got: %v", i, tc.expectedErr, err)
|
||||||
|
}
|
||||||
|
if !apiequality.Semantic.DeepEqual(obj, tc.expectedObj) {
|
||||||
|
t.Errorf("%d: unexpected obj. expected %#v, got %#v", i, tc.expectedObj, obj)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user