package main
import "errors"
import "log"
import "net"
func main() {
listener, err := net.Listen("tcp", ":12345")
if err != nil {
log.Fatalln(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
}
// Handle each client connection
// in a new goroutine.
go ClientHandler(conn)
}
}
func ClientHandler(c net.Conn) {
defer func() {
if v := recover(); v != nil {
log.Println("capture a panic:", v)
log.Println("avoid crashing the program")
}
c.Close()
}()
panic(errors.New("just a demo.")) // a demo-purpose panic
}
telnet localhost 12345
in another terminal, we can observe that the server will not crash down for the panics created in each client handler goroutine.
package main
import "log"
import "time"
func shouldNotExit() {
for {
// Simulate a workload.
time.Sleep(time.Second)
// Simulate an unexpected panic.
if time.Now().UnixNano() & 0x3 == 0 {
panic("unexpected situation")
}
}
}
func NeverExit(name string, f func()) {
defer func() {
if v := recover(); v != nil {
// A panic is detected.
log.Println(name, "is crashed. Restart it now.")
go NeverExit(name, f) // restart
}
}()
f()
}
func main() {
log.SetFlags(0)
go NeverExit("job#A", shouldNotExit)
go NeverExit("job#B", shouldNotExit)
select{} // block here for ever
}
panic
/recover
Calls to Simulate Long Jump Statements
package main
import "fmt"
func main() {
n := func () (result int) {
defer func() {
if v := recover(); v != nil {
if n, ok := v.(int); ok {
result = n
}
}
}()
func () {
func () {
func () {
// ...
panic(123) // panic on succeeded
}()
// ...
}()
}()
// ...
return 0
}()
fmt.Println(n) // 123
}
panic
/recover
Calls to Reduce Error Checks
func doSomething() (err error) {
defer func() {
switch e := recover().(type) {
case nil:
case error:
err = e
default:
panic(e) // re-throw the panic
}
}()
doStep1()
doStep2()
doStep3()
doStep4()
doStep5()
return
}
// In reality, the prototypes of the doStepN functions
// might be different. Here, for each of them,
// * panic with nil for success and no needs to continue.
// * panic with error for failure and no needs to continue.
// * not panic for continuing.
func doStepN() {
...
if err != nil {
panic(err)
}
...
if done {
panic(nil)
}
}
func doSomething() (err error) {
shouldContinue, err := doStep1()
if !shouldContinue {
return err
}
shouldContinue, err = doStep2()
if !shouldContinue {
return err
}
shouldContinue, err = doStep3()
if !shouldContinue {
return err
}
shouldContinue, err = doStep4()
if !shouldContinue {
return err
}
shouldContinue, err = doStep5()
if !shouldContinue {
return err
}
return
}
// If err is not nil, then shouldContinue must be true.
// If shouldContinue is true, err might be nil or non-nil.
func doStepN() (shouldContinue bool, err error) {
...
if err != nil {
return false, err
}
...
if done {
return false, nil
}
return true, nil
}
panic(nil)
call will become eqivalent to panic(new(runtime.PanicNilError))
. So since Go 1.21, the above deferred function call should be written as
func doSomething() (err error) {
defer func() {
switch e := recover().(type) {
case nil, *runtime.PanicNilError:
case error:
err = e
default:
panic(e) // re-throw the panic
}
}()
doStep1()
...
}
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect
standard package.sync
standard package.sync/atomic
standard package.