Goroutines 5

goroutines-5 Maria Letta- License

IT’S NOW TIME TO panic ๐Ÿ˜ฑ AHHHHHHHHH! – But wait, before we completely lose our ๐Ÿ’ฉLet’s take a deep breath in ๐Ÿง˜ to recover and exhale ๐ŸŒฌ๏ธ

So if the name didn’t give it away, a panic is about as bad as it gets in Go. This is the absolute worst situation to find yourself in because it means your program has done something unspeakably horrible ๐Ÿ˜ˆ Which is usually a SIGSEGV aka “Seg Fault” aka “Segmentation Violation” and that means it tried to access memory ๐Ÿฅธ that didn’t belong to it ๐Ÿ‘บ Just like in real life, if you take something that doesn’t belong to you, ๐Ÿ‘น you’re going to have to pay the consequences. ๐Ÿ’€ Though this isn’t the only reason for a panic and we can call panic ourselves.

Setup

Let’s make our directory panic_recover and the files we want inside of that directory example_test.go panic_recover.go

mkdir panic_recover
touch panic_recover/example_test.go panic_recover/panic_recover.go

Now let’s open up panic_recover.go and for the very first line we’ll add

package panic_recover

Next for example_test.go for the very first line we’ll add

package panic_recover_test

We can import basics/panic_recover into cmd/main.go and run functions from there with go run cmd/main.go and also to run our example_test.go ๐Ÿ‘ we use go test panic_recover/example_test.go in the commandline.

Panic and Defer

It is incredibly easy to cause a panic you just call the function… Ta-da! ๐Ÿ˜‚ So that begs the question, why would you panic? It is usually done in the main function of a program.

Let’s say that you have a car ๐Ÿš— that you need to drive to work everyday, because your work is an hour drive and there are no buses ๐ŸšŒ or metros ๐Ÿš‡ to take you to work. If you lose the key ๐Ÿ”‘ can you make it to work? ๐Ÿค” The answer is no. Therefore, all the work you were going to do that day? ๐ŸŒ… Not going to happen. So cancel the whole thing!

Let’s say our application has access to a database ๐Ÿ›๏ธ and in order to use the database it has credentials, a username and password, like a key ๐Ÿ”‘ What if the application can’t find those credentials? Or the credentials are wrong? Is there any reason to start the application? No! ๐Ÿ™… All the work it was going to get done with the database ๐Ÿ›๏ธ will never happen because it can’t access it.

In a situation like that ๐Ÿ‘† it’s common to panic. When there is absolutely no moving forward from an error without human ๐Ÿ’ intervention we panic.

Sometimes a panic will happen unexpectedly. Let’s say on your way to work and your wheel ๐Ÿ›ž blows out. You can do some cleanup by calling ๐Ÿค™ your work and let them know you’ll be late, calling someone to come tow ๐Ÿช your car ๐Ÿš— to a shop

In our app we are connecting to the database ๐Ÿ›๏ธ and half way through, the connection ๐Ÿ”Œ drops! Well you still need to cleanup the connection ๐Ÿงน and remove the garbage of the response you were getting.

This is where defer comes in. Even if a function calls panic( ... ) whether you made it do it or not, a defer statement will always be ran.

Coding Time!

https://twitter.com/egonelbre

panic_recover.go

// AfterDefer shows that even if a panic occurs in a function a `defer`
// statement will **always** execute. This is to make sure system resources are
// cleaned up and why we can `recover` in the first place.
func AfterDefer() {
	defer fmt.Println("defer: Still print statement even with a panic")
	panic("๐Ÿ’ฃ TIME TO BLOW UP!!!")
}

example_test.go

Some things just can’t have good output. To see this in action we need to go to our cmd/main.go made all the way back in lesson 0 You’ll need to import our new package and run it. I did it like

import (
  pnr "basics/panic-recover"
)

And then put it in main() to see we actually get defer output!

We’ll keep this example here just in case we ever want to see what we expect.

func ExampleAfterDefer() {
	// NOTE(jay): No way to test output of a panic. That's **not** something you can
	// make an example out of, but we can at least have one to look at by
	// uncommenting ๐Ÿ‘‡ the line below.
	// pnr.AfterDefer()
	// NOTE(jay): Would be Output:
	// defer: Still print statement even with a panic
	// panic: ๐Ÿ’ฃ TIME TO BLOW UP!!!
}

Panic And Recover

Now – you’re ready! This time you have two pairs of keys ๐Ÿ”‘ and you’ve learned how to change a tire ๐Ÿ›ž on a car ๐Ÿš— And it just so happened that day, your keys were hidden by your cat ๐Ÿ˜น and you walk out to your car to see a flat tire! Well no worries, you can recover because you were prepared for the worst to happen.

This time, in the app, instead of having one database ๐Ÿ›๏ธ that we connect to we have two and if neither of them work, we’ll have an in-memory database that will record everything, write it to a local file, and try to connect ๐Ÿ”Œ to the database every 5 minutes to dump the new data to it. These are suboptimal solutions, but if it’s doing work or not, we get work done ๐Ÿ’ช๐Ÿ˜ค

It’s important to recognize that without defer you cannot recover As we looked at above, a defer statement will run even if there is a panic. recover is just a function. It will execute on the line it’s asked to run and check that no panic has occurred. We don’t want to check for a panic before a panic occurs, we want to check after. And how do we make sure that our function is the last thing ran before the return statement? A defer ๐Ÿ˜

Coding Time!

https://twitter.com/egonelbre

panic_recover.go

// KeepCalm shows how we can `recover` from a `panic` by using a `defer`
// statement that calls `recover()`. You **must** put recover in a `defer`
// statement or else it won't work.
func KeepCalm() {
	defer recuperate()
	// NOTE(jay): This will not stop the panic
	// recover()
	panic("๐Ÿ˜ฑ AWWW ๐Ÿ’ฉWE'RE GOING DOWN!")
}

func recuperate() {
	if err := recover(); err != nil {
		fmt.Println("recovered from:", err)
	}
}

example_test.go

func ExampleKeepCalm() {
	pnr.KeepCalm()
	// Output:
	// recovered from: ๐Ÿ˜ฑ AWWW ๐Ÿ’ฉWE'RE GOING DOWN!
}

I Didn’t Do That!

More often then causing a panic, we’ll encounter a panic ๐Ÿ‘€ through means we hadn’t expected or hoped for. ๐Ÿ˜ฉ This usually presents itself with the ever popular nil pointer or index out of bounds runtime errors. ๐Ÿ™ƒ

Coding Time!

https://twitter.com/egonelbre

panic_recover.go

type myStruct struct{ cantAccess string }

func (s *myStruct) CausePanic() string { return s.cantAccess }

func NilPointer() {
	s := new(myStruct)
	s = nil // NOTE(jay): Obviously dangerous, but it happens in mysterious ways.
	fmt.Println(s.CausePanic())
}

func NewMap() {
	m := new(map[string]string)
	(*m)["nil map"] = "causes panic!"
	// We actually want:
	// ma := make(map[string]string)
	// ma["not nil"] = "works"
}

func IndexOut() {
	daBomb := []string{"set", "us", "up", "da bomb."}
	fmt.Println(daBomb[len(daBomb)])
	// We actually want:
	// fmt.Println(daBomb[len(daBomb)-1])
}

Understanding Panic Stack Trace

Let’s cause a panic with pnr.IndexOut() to get an index out of range.

panic: runtime error: index out of range [4] with length 4

goroutine 1 [running]:
main.main()
        /home/jay/basics/cmd/main.go:7 +0x1b
exit status 2

This is a rather tame ๐Ÿงธ panic, let’s go through each line regardless.


This is what the actual panic had to say about why it halted the execution of the entire program.

panic: runtime error: index out of range [4] with length 4

There can be more than one goroutine running. We will cover goroutines next lesson, just know this is the first one (main goroutine) and they are lightweight threads of execution.

goroutine 1 [running]:

This shows the package and function that had a panic

main.main()

This shows the absolute path to the file that was a part of the stack trace /home/jay/basics/cmd/main.go and what line it happened on :7 and where this function was on the stack +0x1b in hexadecimal. For some knowledge the stack fills up like a bucket ๐Ÿชฃ Meaning 0x00 is the bottom and 0x1b is very close to the bottom. It’s 27 in decimal.

        /home/jay/basics/cmd/main.go:7 +0x1b

Anything other than an exit status of 0 means something went wrong.

exit status 2

That wasn’t too bad right? ๐Ÿ˜€ Well I’d be lying if I said that’s what you’ll normally get when you see a panic ๐Ÿ˜ฌ We can cause a real looking panic with our example_test.go

Coding Time!

https://twitter.com/egonelbre

example_test.go

func ExampleNilPointer() {
	// NOTE(jay): No way to test output of a panic. That's **not** something you can
	// make an example out of, but we can at least have one to look at by
	// uncommenting ๐Ÿ‘‡ the line below.
	// pnr.NilPointer()
	// NOTE(jay): Would be Output:
	// panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	//         panic: runtime error: invalid memory address or nil pointer dereference
	// [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4ef6f6]
	//
	// goroutine 1 [running]:
	// testing.(*InternalExample).processRunResult(0xc000057c68, {0x0, 0x0}, 0xc00007eb60?, 0x0, {0x5027e0, 0x5ef9f0})
	//         /usr/lib/go/src/testing/example.go:91 +0x4e5
	// testing.runExample.func2()
	//         /usr/lib/go/src/testing/run_example.go:59 +0x11c
	// panic({0x5027e0, 0x5ef9f0})
	//         /usr/lib/go/src/runtime/panic.go:838 +0x207
	// basics/panic-recover.(*myStruct).CausePanic(...)
	//         /home/jay/basics/panic-recover/panic_recover.go:25
	// basics/panic-recover.NilPointer()
	//         /home/jay/basics/panic-recover/panic_recover.go:30 +0x16
	// basics/panic-recover_test.ExampleNilPointer()
	//         /home/jay/basics/panic-recover/example_test.go:18 +0x17
	// testing.runExample({{0x5201b1, 0x13}, 0x527dd0, {0x0, 0x0}, 0x0})
	//         /usr/lib/go/src/testing/run_example.go:63 +0x28d
	// testing.runExamples(0xc000057e58, {0x5f4360?, 0x3, 0x0?})
	//         /usr/lib/go/src/testing/example.go:44 +0x186
	// testing.(*M).Run(0xc00010c140)
	//         /usr/lib/go/src/testing/testing.go:1721 +0x689
	// main.main()
	//         _testmain.go:53 +0x1aa
	// exit status 2
}

๐Ÿ˜ฒ Let’s go through this stack trace! Usually when you see something like the above you should look for what you’ve done. Meaning what functions and files and directories look familiar to you? That’s probably where you should start trying to fix your panic message.

So I see basics/panic-recover.(*myStruct).CausePanic(...) โ— Hey That’s myStruct I know that and /home/jay/basics/panic-recover/panic_recover.go:25 that’s my go file! I’d look there on line 25 to see what the problem is, but we’re not here just to see that. Let’s go through the whole stack trace.


panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4ef6f6]

goroutine 1 [running]:

This is telling us that there was a nil pointer dereference, nil.someValue ๐Ÿ‘ˆ We did this and that caused a segmentation violation SIGSEGV also known as a segfault with code=1 address=0 and program counter=5175030

A program counter is a register in a computer processor that contains the address of the instruction being executed at the current time. We can see that whether the pc is in hexadecimal or decimal it doesn’t make sense to us humans. It doesn’t need to. It’s just an address. You can see 1234 Square Rd ๐Ÿ  and understand it’s an address, but does it mean anything to you? Not at all.

The information we can really gather from the above text is what happened?


testing.(*InternalExample).processRunResult(0xc000057c68, {0x0, 0x0}, 0xc00007eb60?, 0x0, {0x5027e0, 0x5ef9f0})
        /usr/lib/go/src/testing/example.go:91 +0x4e5
testing.runExample.func2()
        /usr/lib/go/src/testing/run_example.go:59 +0x11c

The InternalExample struct panicked at line 91 high up on the stack trace at 0x4e5 or 1253 All of the hexadecimal values in between processRunResult(0xc000057c68, {0x0, 0x0}, 0xc00007eb60?, 0x0, {0x5027e0, 0x5ef9f0}) are pointers too all of the arguments that the method takes and it happened in example.go which is in the standard library testing package.

And an anonymous function on line 59 in the run_example.go file panicked because the above InternalExample panicked.


panic({0x5027e0, 0x5ef9f0})
        /usr/lib/go/src/runtime/panic.go:838 +0x207
basics/panic-recover.(*myStruct).CausePanic(...)
        /home/jay/basics/panic-recover/panic_recover.go:25
basics/panic-recover.NilPointer()
        /home/jay/basics/panic-recover/panic_recover.go:30 +0x16
basics/panic-recover_test.ExampleNilPointer()
        /home/jay/basics/panic-recover/example_test.go:18 +0x17

The place where panic is defined in the runtime package in the file panic.go at line 838 and fairly high on the stack trace at 0x207 or 519 in decimal.

Next we see all of our code that has files, directories and packages that we made! This is where we should check. We have control over all of these values. Everything we’ve seen up until now has been done for us by a different library. This is what we control, however. So when looking at a stack trace look for what you did and what you care about. We can see how close to the bottom of the stack both of the functions are at 0x16 and 0x17 or 22 and 23 respectively.


testing.runExample({{0x5201b1, 0x13}, 0x527dd0, {0x0, 0x0}, 0x0})
        /usr/lib/go/src/testing/run_example.go:63 +0x28d
testing.runExamples(0xc000057e58, {0x5f4360?, 0x3, 0x0?})
        /usr/lib/go/src/testing/example.go:44 +0x186
testing.(*M).Run(0xc00010c140)
        /usr/lib/go/src/testing/testing.go:1721 +0x689
main.main()
        _testmain.go:53 +0x1aa
exit status 2

We see a runExample function call that has some pointers to the passed in arguments that happened in the testing package in the run_example.go file on line 63. Much higher than our function calls at 0x28d or 653 in decimal.

Another function call to runExamples a different function from the above in the same package in example.go on line 44 and closer to the bottom of the stack at 0x186 or 390 in decimal.

Finally we see our last actual call of a library using the testing M struct’s Run method with one pointer to a code argument which is an int. This happens on line 1721!!!! Of the file at 0x689!!! Or 1673 in decimal, very high up on the stack.

And every Go program must have a main function. It is made for us by the builtin testing package. Thanks Go! ๐Ÿ˜๐Ÿ‘

Source File ๐Ÿ“„

The Source File

Test File ๐Ÿ“

The Test File