mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Merge pull request #75853 from roycaihw/fix/use-standard-stacktrace
apimachinery & apiserver: use stacktrace in the stdlib
This commit is contained in:
		@@ -62,27 +62,18 @@ func HandleCrash(additionalHandlers ...func(interface{})) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// logPanic logs the caller tree when a panic occurs.
 | 
					// logPanic logs the caller tree when a panic occurs.
 | 
				
			||||||
func logPanic(r interface{}) {
 | 
					func logPanic(r interface{}) {
 | 
				
			||||||
	callers := getCallers(r)
 | 
						// Same as stdlib http server code. Manually allocate stack trace buffer size
 | 
				
			||||||
 | 
						// to prevent excessively large logs
 | 
				
			||||||
 | 
						const size = 64 << 10
 | 
				
			||||||
 | 
						stacktrace := make([]byte, size)
 | 
				
			||||||
 | 
						stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]
 | 
				
			||||||
	if _, ok := r.(string); ok {
 | 
						if _, ok := r.(string); ok {
 | 
				
			||||||
		klog.Errorf("Observed a panic: %s\n%v", r, callers)
 | 
							klog.Errorf("Observed a panic: %s\n%s", r, stacktrace)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		klog.Errorf("Observed a panic: %#v (%v)\n%v", r, r, callers)
 | 
							klog.Errorf("Observed a panic: %#v (%v)\n%s", r, r, stacktrace)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getCallers(r interface{}) string {
 | 
					 | 
				
			||||||
	callers := ""
 | 
					 | 
				
			||||||
	for i := 0; true; i++ {
 | 
					 | 
				
			||||||
		_, file, line, ok := runtime.Caller(i)
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		callers = callers + fmt.Sprintf("%v:%v\n", file, line)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return callers
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrorHandlers is a list of functions which will be invoked when an unreturnable
 | 
					// ErrorHandlers is a list of functions which will be invoked when an unreturnable
 | 
				
			||||||
// error occurs.
 | 
					// error occurs.
 | 
				
			||||||
// TODO(lavalamp): for testability, this and the below HandleError function
 | 
					// TODO(lavalamp): for testability, this and the below HandleError function
 | 
				
			||||||
@@ -155,13 +146,17 @@ func GetCaller() string {
 | 
				
			|||||||
// handlers to handle errors and panics the same way.
 | 
					// handlers to handle errors and panics the same way.
 | 
				
			||||||
func RecoverFromPanic(err *error) {
 | 
					func RecoverFromPanic(err *error) {
 | 
				
			||||||
	if r := recover(); r != nil {
 | 
						if r := recover(); r != nil {
 | 
				
			||||||
		callers := getCallers(r)
 | 
							// Same as stdlib http server code. Manually allocate stack trace buffer size
 | 
				
			||||||
 | 
							// to prevent excessively large logs
 | 
				
			||||||
 | 
							const size = 64 << 10
 | 
				
			||||||
 | 
							stacktrace := make([]byte, size)
 | 
				
			||||||
 | 
							stacktrace = stacktrace[:runtime.Stack(stacktrace, false)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		*err = fmt.Errorf(
 | 
							*err = fmt.Errorf(
 | 
				
			||||||
			"recovered from panic %q. (err=%v) Call stack:\n%v",
 | 
								"recovered from panic %q. (err=%v) Call stack:\n%s",
 | 
				
			||||||
			r,
 | 
								r,
 | 
				
			||||||
			*err,
 | 
								*err,
 | 
				
			||||||
			callers)
 | 
								stacktrace)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,12 @@ limitations under the License.
 | 
				
			|||||||
package runtime
 | 
					package runtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,3 +74,66 @@ func TestCustomHandleError(t *testing.T) {
 | 
				
			|||||||
		t.Errorf("did not receive custom handler")
 | 
							t.Errorf("did not receive custom handler")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestHandleCrashLog(t *testing.T) {
 | 
				
			||||||
 | 
						log, err := captureStderr(func() {
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								if r := recover(); r == nil {
 | 
				
			||||||
 | 
									t.Fatalf("expected a panic to recover from")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
							defer HandleCrash()
 | 
				
			||||||
 | 
							panic("test panic")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("%v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Example log:
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// ...] Observed a panic: test panic
 | 
				
			||||||
 | 
						// goroutine 6 [running]:
 | 
				
			||||||
 | 
						// command-line-arguments.logPanic(0x..., 0x...)
 | 
				
			||||||
 | 
						// 	.../src/k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go:69 +0x...
 | 
				
			||||||
 | 
						lines := strings.Split(log, "\n")
 | 
				
			||||||
 | 
						if len(lines) < 4 {
 | 
				
			||||||
 | 
							t.Fatalf("panic log should have 1 line of message, 1 line per goroutine and 2 lines per function call")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if match, _ := regexp.MatchString("Observed a panic: test panic", lines[0]); !match {
 | 
				
			||||||
 | 
							t.Errorf("mismatch panic message: %s", lines[0])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// The following regexp's verify that Kubernetes panic log matches Golang stdlib
 | 
				
			||||||
 | 
						// stacktrace pattern. We need to update these regexp's if stdlib changes its pattern.
 | 
				
			||||||
 | 
						if match, _ := regexp.MatchString(`goroutine [0-9]+ \[.+\]:`, lines[1]); !match {
 | 
				
			||||||
 | 
							t.Errorf("mismatch goroutine: %s", lines[1])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if match, _ := regexp.MatchString(`logPanic(.*)`, lines[2]); !match {
 | 
				
			||||||
 | 
							t.Errorf("mismatch symbolized function name: %s", lines[2])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if match, _ := regexp.MatchString(`runtime\.go:[0-9]+ \+0x`, lines[3]); !match {
 | 
				
			||||||
 | 
							t.Errorf("mismatch file/line/offset information: %s", lines[3])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// captureStderr redirects stderr to result string, and then restore stderr from backup
 | 
				
			||||||
 | 
					func captureStderr(f func()) (string, error) {
 | 
				
			||||||
 | 
						r, w, err := os.Pipe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						bak := os.Stderr
 | 
				
			||||||
 | 
						os.Stderr = w
 | 
				
			||||||
 | 
						defer func() { os.Stderr = bak }()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resultCh := make(chan string)
 | 
				
			||||||
 | 
						// copy the output in a separate goroutine so printing can't block indefinitely
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							var buf bytes.Buffer
 | 
				
			||||||
 | 
							io.Copy(&buf, r)
 | 
				
			||||||
 | 
							resultCh <- buf.String()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f()
 | 
				
			||||||
 | 
						w.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return <-resultCh, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,6 @@ import (
 | 
				
			|||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	goruntime "runtime"
 | 
						goruntime "runtime"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
						"k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
@@ -195,10 +194,12 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
 | 
				
			|||||||
		defer func() {
 | 
							defer func() {
 | 
				
			||||||
			panicReason := recover()
 | 
								panicReason := recover()
 | 
				
			||||||
			if panicReason != nil {
 | 
								if panicReason != nil {
 | 
				
			||||||
 | 
									// Same as stdlib http server code. Manually allocate stack
 | 
				
			||||||
 | 
									// trace buffer size to prevent excessively large logs
 | 
				
			||||||
				const size = 64 << 10
 | 
									const size = 64 << 10
 | 
				
			||||||
				buf := make([]byte, size)
 | 
									buf := make([]byte, size)
 | 
				
			||||||
				buf = buf[:goruntime.Stack(buf, false)]
 | 
									buf = buf[:goruntime.Stack(buf, false)]
 | 
				
			||||||
				panicReason = strings.TrimSuffix(fmt.Sprintf("%v\n%s", panicReason, string(buf)), "\n")
 | 
									panicReason = fmt.Sprintf("%v\n%s", panicReason, buf)
 | 
				
			||||||
				// Propagate to parent goroutine
 | 
									// Propagate to parent goroutine
 | 
				
			||||||
				panicCh <- panicReason
 | 
									panicCh <- panicReason
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,23 +92,27 @@ func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errCh := make(chan interface{})
 | 
						// resultCh is used as both errCh and stopCh
 | 
				
			||||||
 | 
						resultCh := make(chan interface{})
 | 
				
			||||||
	tw := newTimeoutWriter(w)
 | 
						tw := newTimeoutWriter(w)
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		defer func() {
 | 
							defer func() {
 | 
				
			||||||
			err := recover()
 | 
								err := recover()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
 | 
									// Same as stdlib http server code. Manually allocate stack
 | 
				
			||||||
 | 
									// trace buffer size to prevent excessively large logs
 | 
				
			||||||
				const size = 64 << 10
 | 
									const size = 64 << 10
 | 
				
			||||||
				buf := make([]byte, size)
 | 
									buf := make([]byte, size)
 | 
				
			||||||
				buf = buf[:runtime.Stack(buf, false)]
 | 
									buf = buf[:runtime.Stack(buf, false)]
 | 
				
			||||||
				err = fmt.Sprintf("%v\n%s", err, buf)
 | 
									err = fmt.Sprintf("%v\n%s", err, buf)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			errCh <- err
 | 
								resultCh <- err
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
		t.handler.ServeHTTP(tw, r)
 | 
							t.handler.ServeHTTP(tw, r)
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
	select {
 | 
						select {
 | 
				
			||||||
	case err := <-errCh:
 | 
						case err := <-resultCh:
 | 
				
			||||||
 | 
							// panic if error occurs; stop otherwise
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			panic(err)
 | 
								panic(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user