-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
spec: guarantee non-nil return value from recover #25448
Comments
Dear runtime, if I perform Less the magic, the less exceptional rules I have to think about. It makes me more productive and my code more comprehensible to my future self. Thanks. edit: typo |
An alternative solution would be to allow My personal leaning at the moment is in favor of the proposal as stated. |
Is any program out there using nil panics intentionally? A simple search for In any case, I agree with the sentiment and the proposed solution. Perhaps
|
FTR: I admit this is a real problem. I just prefer explicitly handling it. |
Is this a real problem, though? I doubt it. What if the implementation of |
I've had to deal with it at least twice. net/http had hangs when people panicked with nil. @mpvl was talking about error handling the other day and was showing some examples of how defensive code should ideally look like (and how it's hard to get right), and he forgot the nil panic case, showing it's even harder than it seems.
That's what I'm proposing, except with a variable (which could have a String method with that text). But I'm fine (but less fine) with it being that string value exactly, as matching on strings is a little gross. |
This proposal makes total sense--for a language which prides itself on its simplicity and obviousness, it is perplexing that the only way to check for a |
While I believe that this is a very good thing to be able to handle, I think that a So then recover would look like: defer func() {
if e, ok := recover(); ok {
// do recovery stuff
}
}(); |
@deanveloper I like your idea for being backwards compatible. |
That turns out not to be correct either: As far as I can tell, there is no way for a |
As it turns out, we can use the fact that a |
Change https://golang.org/cl/134395 mentions this issue: |
I think we should treat |
Perhaps you're using a badly-written library, and you don't have control over it. I've seen worse before 🤷♂️ |
Our team spent 3 hours today hunting down what we now know to be a This proposal as-is would work as the common Making |
Sometimes. people may call [edit] in this example, the panic value is not nil, but it can be. |
@go101, eliminating code like that would be an added bonus. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
One minor change from the accepted proposal: writing the text in the spec it turns out to be simplest to say that panic(nil) causes a run-time error, in which case the type should be runtime.PanicNilError, not runtime.PanicNil. |
This is a good point. I still don't like the change being global, but it is definitely true that there is almost definitely more code out there failing to handle a recover from a |
Change https://go.dev/cl/461956 mentions this issue: |
Change https://go.dev/cl/462293 mentions this issue: |
panic(nil) now panics with runtime.PanicNilError. For #25448 Change-Id: I58994aa80d4d11f0c5fcd988714f7b4b45c6b5ee Reviewed-on: https://go-review.googlesource.com/c/go/+/462293 Run-TryBot: Ian Lance Taylor <iant@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Austin Clements <austin@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
As a point of reference, libraries which already explicitly handled nil panics may need to make changes like go-check/check#136 to treat a nil panic prior to go1.21 and a *runtime.NilPanicError result in go1.21+ as equivalent. |
I don't think we explicitly decided the question of whether to change net/http to continue to accept That is, I don't think we decided whether to include a change like the one in this file from the draft CL: Given that we have the GODEBUG and that http.ErrAbortHandler has existed since Go 1.8, I am inclined to leave that change out. The only impact will be that panic(nil) will cause new logging on servers running in Go 1.21 mode. |
Change https://go.dev/cl/517036 mentions this issue: |
For golang/go#25448. Change-Id: I775ad6d8d8952da8fa94c39c44a2420501b63b57 Reviewed-on: https://go-review.googlesource.com/c/website/+/517036 Run-TryBot: Michael Knyszek <mknyszek@google.com> Reviewed-by: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Michael Knyszek <mknyszek@google.com>
Long ago we decided that panic(nil) was too unlikely to bother making a special case for purposes of recover. Unfortunately, it has turned out not to be a special case. There are many examples of code in the Go ecosystem where an author has written panic(nil) because they want to panic and don't care about the panic value. Using panic(nil) in this case has the unfortunate behavior of making recover behave as though the goroutine isn't panicking. As a result, code like: func f() { defer func() { if err := recover(); err != nil { log.Fatalf("panicked! %v", err) } }() call1() call2() } looks like it guarantees that call2 has been run any time f returns, but that turns out not to be strictly true. If call1 does panic(nil), then f returns "successfully", having recovered the panic, but without calling call2. Instead you have to write something like: func f() { done := false defer func() { if err := recover(); !done { log.Fatalf("panicked! %v", err) } }() call1() call2() done = true } which defeats nearly the whole point of recover. No one does this, with the result that almost all uses of recover are subtly broken. One specific broken use along these lines is in net/http, which recovers from panics in handlers and sends back an HTTP error. Users discovered in the early days of Go that panic(nil) was a convenient way to jump out of a handler up to the serving loop without sending back an HTTP error. This was a bug, not a feature. Go 1.8 added panic(http.ErrAbortHandler) as a better way to access the feature. Any lingering code that uses panic(nil) to abort an HTTP handler without a failure message should be changed to use http.ErrAbortHandler. Programs that need the old, unintended behavior from net/http or other packages can set GODEBUG=panicnil=1 to stop the run-time error. Uses of recover that want to detect panic(nil) in new programs can check for recover returning a value of type *runtime.PanicNilError. Because the new GODEBUG is used inside the runtime, we can't import internal/godebug, so there is some new machinery to cross-connect those in this CL, to allow a mutable GODEBUG setting. That won't be necessary if we add any other mutable GODEBUG settings in the future. The CL also corrects the handling of defaulted GODEBUG values in the runtime, for golang#56986. Fixes golang#25448. Change-Id: I2b39c7e83e4f7aa308777dabf2edae54773e03f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/461956 Reviewed-by: Robert Griesemer <gri@google.com> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Russ Cox <rsc@golang.org>
Long ago we decided that panic(nil) was too unlikely to bother making a special case for purposes of recover. Unfortunately, it has turned out not to be a special case. There are many examples of code in the Go ecosystem where an author has written panic(nil) because they want to panic and don't care about the panic value. Using panic(nil) in this case has the unfortunate behavior of making recover behave as though the goroutine isn't panicking. As a result, code like: func f() { defer func() { if err := recover(); err != nil { log.Fatalf("panicked! %v", err) } }() call1() call2() } looks like it guarantees that call2 has been run any time f returns, but that turns out not to be strictly true. If call1 does panic(nil), then f returns "successfully", having recovered the panic, but without calling call2. Instead you have to write something like: func f() { done := false defer func() { if err := recover(); !done { log.Fatalf("panicked! %v", err) } }() call1() call2() done = true } which defeats nearly the whole point of recover. No one does this, with the result that almost all uses of recover are subtly broken. One specific broken use along these lines is in net/http, which recovers from panics in handlers and sends back an HTTP error. Users discovered in the early days of Go that panic(nil) was a convenient way to jump out of a handler up to the serving loop without sending back an HTTP error. This was a bug, not a feature. Go 1.8 added panic(http.ErrAbortHandler) as a better way to access the feature. Any lingering code that uses panic(nil) to abort an HTTP handler without a failure message should be changed to use http.ErrAbortHandler. Programs that need the old, unintended behavior from net/http or other packages can set GODEBUG=panicnil=1 to stop the run-time error. Uses of recover that want to detect panic(nil) in new programs can check for recover returning a value of type *runtime.PanicNilError. Because the new GODEBUG is used inside the runtime, we can't import internal/godebug, so there is some new machinery to cross-connect those in this CL, to allow a mutable GODEBUG setting. That won't be necessary if we add any other mutable GODEBUG settings in the future. The CL also corrects the handling of defaulted GODEBUG values in the runtime, for golang#56986. Fixes golang#25448. Change-Id: I2b39c7e83e4f7aa308777dabf2edae54773e03f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/461956 Reviewed-by: Robert Griesemer <gri@google.com> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Russ Cox <rsc@golang.org>
Long ago we decided that panic(nil) was too unlikely to bother making a special case for purposes of recover. Unfortunately, it has turned out not to be a special case. There are many examples of code in the Go ecosystem where an author has written panic(nil) because they want to panic and don't care about the panic value. Using panic(nil) in this case has the unfortunate behavior of making recover behave as though the goroutine isn't panicking. As a result, code like: func f() { defer func() { if err := recover(); err != nil { log.Fatalf("panicked! %v", err) } }() call1() call2() } looks like it guarantees that call2 has been run any time f returns, but that turns out not to be strictly true. If call1 does panic(nil), then f returns "successfully", having recovered the panic, but without calling call2. Instead you have to write something like: func f() { done := false defer func() { if err := recover(); !done { log.Fatalf("panicked! %v", err) } }() call1() call2() done = true } which defeats nearly the whole point of recover. No one does this, with the result that almost all uses of recover are subtly broken. One specific broken use along these lines is in net/http, which recovers from panics in handlers and sends back an HTTP error. Users discovered in the early days of Go that panic(nil) was a convenient way to jump out of a handler up to the serving loop without sending back an HTTP error. This was a bug, not a feature. Go 1.8 added panic(http.ErrAbortHandler) as a better way to access the feature. Any lingering code that uses panic(nil) to abort an HTTP handler without a failure message should be changed to use http.ErrAbortHandler. Programs that need the old, unintended behavior from net/http or other packages can set GODEBUG=panicnil=1 to stop the run-time error. Uses of recover that want to detect panic(nil) in new programs can check for recover returning a value of type *runtime.PanicNilError. Because the new GODEBUG is used inside the runtime, we can't import internal/godebug, so there is some new machinery to cross-connect those in this CL, to allow a mutable GODEBUG setting. That won't be necessary if we add any other mutable GODEBUG settings in the future. The CL also corrects the handling of defaulted GODEBUG values in the runtime, for golang#56986. Fixes golang#25448. Change-Id: I2b39c7e83e4f7aa308777dabf2edae54773e03f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/461956 Reviewed-by: Robert Griesemer <gri@google.com> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Russ Cox <rsc@golang.org>
Change https://go.dev/cl/536955 mentions this issue: |
Updates #25448 Change-Id: Ia1b7a376f5175f67e14ad4bd065d6e8ad5250d38 Reviewed-on: https://go-review.googlesource.com/c/go/+/536955 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Keith Randall <khr@golang.org>
Updates golang#25448 Change-Id: Ia1b7a376f5175f67e14ad4bd065d6e8ad5250d38 Reviewed-on: https://go-review.googlesource.com/c/go/+/536955 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Keith Randall <khr@golang.org>
In the past, using My question regarding this change is the following: |
According to https://go.dev/doc/godebug#go-121 there is no plan to remove the That said, you can keep using var ignorePanic byte
func mayPanic() {
...
panic(&ignorePanic)
...
}
func callsCodeThatMayPanic() {
defer func() {
if r := recover(); r != nil && r != &ignorePanic {
// handle real panic
}
}()
...
} |
Great, thank you! My question was more about handling this via a module, and making that module "safe to use" (i.e., it would not inadvertently trigger any properly-written logic regarding Consider the following error handling package: package errhndl
...
func Catch() {
err := recover()
if err != nil { // panic if the argument passed to panic was not nil
panic(err)
}
}
func Wrap(err error) *errWrapper {
return &errWrapper{
err: err,
}
}
func (w *errWrapper) OnErrThrow(retErrAddr *error) {
if w.err != nil {
*retErrAddr = w.err
panic(nil)
}
} Now, assume the above package is used in the following functions: package somepackage
import "some/module/pkg/errhndl"
// the function whose error we want to handle
func mayReturnError() error {
return errors.New("oops, something happened")
}
// one example of error handling
func someOtherFunction() (e error) {
defer errhndl.Catch()
...
// here, OnErrThrow() jumps to the deferred functions, stopping the panicking sequence
// at either the nearest Catch(), or any recover() encountered before it.
errhndl.Wrap(mayReturnError()).OnErrThrow(&e)
...
} In this case, if the func thisFunctionIsMoreComplex() (e error) {
defer errhndl.Catch()
defer func() {
if err := recover(); err != nil {
...
log.Errorf("oops, some error occurred - %s", err)
...
}
}()
// here, OnErrThrow() jumps to the deferred functions, stopping the panicking sequence
// at either the nearest Catch(), or any recover() encountered before it.
errhndl.Wrap(mayReturnError()).OnErrThrow(&e)
...
} Prior to Go 1.21, the OnErrThrow() and Catch() worked flawlessly with existing Historically, Am I understanding this correctly, that the main goal of this change was to ensure that developers using |
@the-zucc First I want to say, in case it wasn't clear, that we aren't going to reverse this decision. There were many years of discussion and a lot of analysis of existing Go code, as you can see in the issue comments. General Go style is that code should not panic across package boundaries, and code should not try to recover panics that they are not expecting. I think you are suggesting a different Go style. That is fine, of course, but I think it's reasonable to suggest that people who want to use that style should use a special error sentinel to mean "panic but no error." You already have to use an errhndl package; it's not unreasonable to panic with a sentinel value defined in errhndl. One could argue that that is actually easier to understand for the code reader. The main goal of the change is that Note that almost nobody will see this discussion. It's more useful to discuss Go changes in a forum rather than the issue tracker. See https://go.dev/wiki/Questions. |
Calling
panic
with anil
panic value is allowed in Go 1, but weird.Almost all code checks for panics with:
... which is not correct in the case of
panic(nil)
.The proper way is more like:
Proposal: make the runtime
panic
function promote its panic value fromnil
to something like aruntime.NilPanic
global value of private, unassignable type:Probably Go2.
The text was updated successfully, but these errors were encountered: