getting panic() argument in defer function in GO lang - go

I have a function A calling function B which would sometime call panic based on invalid data.
In function A defer function, I would like to know the message function B passed to the panic() so that I can report the error in json over the network back to the client.
e.g.
func A( abc data) result string{
defer func(){
// get panic args and return result.
}
xx = B( abc[0] );
yy = B( abc[1] );
...
}
The reason function B use panic is to avoid a very large amount of
err := B(abc)
if err != nil {
...
}
in function A and make the code easier to read and maintain.

For example:
package main
import (
"errors"
"fmt"
)
func A(s string) (result string, err error) {
defer func() {
if e := recover(); e != nil {
switch x := e.(type) {
case error:
err = x
default:
err = fmt.Errorf("%v", x)
}
}
}()
B(s)
return "returned", nil
}
func B(s string) {
switch s {
case "ok":
return
case "fail":
panic(errors.New("failed"))
case "fail miserably":
panic(42)
default:
a, b := 1, 0
if a/b != 0 {
panic("ouch")
}
}
}
func main() {
s, err := A("ok")
fmt.Printf("%q, %T(%#v)\n", s, err, err)
s, err = A("fail")
fmt.Printf("%q, %T(%#v)\n", s, err, err)
s, err = A("fail miserably")
fmt.Printf("%q, %T(%#v)\n", s, err, err)
s, err = A("")
fmt.Printf("%q, %T(%#v)\n", s, err, err)
}
Playground
Output:
"returned", <nil>(<nil>)
"", *errors.errorString(&errors.errorString{s:"failed"})
"", *errors.errorString(&errors.errorString{s:"42"})
"", runtime.errorString("integer divide by zero")

What you want is the recover function. You're right to want to defer it - recover only works properly in a deferred function (if you call it in the body, it will either return nil if there was no panic, or get skipped over when a panic does happen). Recover returns the value that was panicked in an empty interface:
func A(abc data) result string {
defer func() {
p := recover() // p is an interface{} value, and will be nil if there was no panic
}() // You have to call the function
...
}

Related

Check if all goroutines have finished without using wg.Wait()

Let's say I have a function IsAPrimaryColour() which works by calling three other functions IsRed(), IsGreen() and IsBlue(). Since the three functions are quite independent of one another, they can run concurrently. The return conditions are:
If any of the three functions returns true, IsAPrimaryColour()
should also return true. There is no need to wait for the other
functions to finish. That is: IsPrimaryColour() is true if IsRed() is true OR IsGreen() is true OR IsBlue() is true
If all functions return false, IsAPrimaryColour() should also return
false. That is: IsPrimaryColour() is false if IsRed() is false AND IsGreen() is false AND IsBlue() is false
If any of the three functions returns an error, IsAPrimaryColour()
should also return the error. There is no need to wait for the other
functions to finish, or to collect any other errors.
The thing I'm struggling with is how to exit the function if any other three functions return true, but also to wait for all three to finish if they all return false. If I use a sync.WaitGroup object, I will need to wait for all 3 go routines to finish before I can return from the calling function.
Therefore, I'm using a loop counter to keep track of how many times I have received a message on a channel and existing the program once I have received all 3 messages.
https://play.golang.org/p/kNfqWVq4Wix
package main
import (
"errors"
"fmt"
"time"
)
func main() {
x := "something"
result, err := IsAPrimaryColour(x)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %v\n", result)
}
}
func IsAPrimaryColour(value interface{}) (bool, error) {
found := make(chan bool, 3)
errors := make(chan error, 3)
defer close(found)
defer close(errors)
var nsec int64 = time.Now().UnixNano()
//call the first function, return the result on the 'found' channel and any errors on the 'errors' channel
go func() {
result, err := IsRed(value)
if err != nil {
errors <- err
} else {
found <- result
}
fmt.Printf("IsRed done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
}()
//call the second function, return the result on the 'found' channel and any errors on the 'errors' channel
go func() {
result, err := IsGreen(value)
if err != nil {
errors <- err
} else {
found <- result
}
fmt.Printf("IsGreen done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
}()
//call the third function, return the result on the 'found' channel and any errors on the 'errors' channel
go func() {
result, err := IsBlue(value)
if err != nil {
errors <- err
} else {
found <- result
}
fmt.Printf("IsBlue done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
}()
//loop counter which will be incremented every time we read a value from the 'found' channel
var counter int
for {
select {
case result := <-found:
counter++
fmt.Printf("received a value on the results channel after %f nanoseconds. Value of counter is %d\n", float64(time.Now().UnixNano()-nsec), counter)
if result {
fmt.Printf("some goroutine returned true\n")
return true, nil
}
case err := <-errors:
if err != nil {
fmt.Printf("some goroutine returned an error\n")
return false, err
}
default:
}
//check if we have received all 3 messages on the 'found' channel. If so, all 3 functions must have returned false and we can thus return false also
if counter == 3 {
fmt.Printf("all goroutines have finished and none of them returned true\n")
return false, nil
}
}
}
func IsRed(value interface{}) (bool, error) {
return false, nil
}
func IsGreen(value interface{}) (bool, error) {
time.Sleep(time.Millisecond * 100) //change this to a value greater than 200 to make this function take longer than IsBlue()
return true, nil
}
func IsBlue(value interface{}) (bool, error) {
time.Sleep(time.Millisecond * 200)
return false, errors.New("something went wrong")
}
Although this works well enough, I wonder if I'm not overlooking some language feature to do this in a better way?
errgroup.WithContext can help simplify the concurrency here.
You want to stop all of the goroutines if an error occurs, or if a result is found. If you can express “a result is found” as a distinguished error (along the lines of io.EOF), then you can use errgroup's built-in “cancel on first error” behavior to shut down the whole group:
func IsAPrimaryColour(ctx context.Context, value interface{}) (bool, error) {
var nsec int64 = time.Now().UnixNano()
errFound := errors.New("result found")
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
result, err := IsRed(ctx, value)
if result {
err = errFound
}
fmt.Printf("IsRed done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
return err
})
…
err := g.Wait()
if err == errFound {
fmt.Printf("some goroutine returned errFound\n")
return true, nil
}
if err != nil {
fmt.Printf("some goroutine returned an error\n")
return false, err
}
fmt.Printf("all goroutines have finished and none of them returned true\n")
return false, nil
}
(https://play.golang.org/p/MVeeBpDv4Mn)
some remarks,
you dont need to close the channels, you know before hand the expected count of signals to read. This is sufficient for an exit condition.
you dont need to duplicate manual function calls, use a slice.
since you use a slice, you dont even need a counter, or a static value of 3, just look at the length of your func slice.
that default case into the switch is useless. just block on the input you are waiting for.
So once you got ride of all the fat, the code looks like
func IsAPrimaryColour(value interface{}) (bool, error) {
fns := []func(interface{}) (bool, error){IsRed, IsGreen, IsBlue}
found := make(chan bool, len(fns))
errors := make(chan error, len(fns))
for i := 0; i < len(fns); i++ {
fn := fns[i]
go func() {
result, err := fn(value)
if err != nil {
errors <- err
return
}
found <- result
}()
}
for i := 0; i < len(fns); i++ {
select {
case result := <-found:
if result {
return true, nil
}
case err := <-errors:
if err != nil {
return false, err
}
}
}
return false, nil
}
you dont need to obsereve the time at the each and every async calls, just observe the time the overall caller took to return.
func main() {
now := time.Now()
x := "something"
result, err := IsAPrimaryColour(x)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %v\n", result)
}
fmt.Println("it took", time.Since(now))
}
https://play.golang.org/p/bARHS6c6m1c
The idiomatic way to handle multiple concurrent function calls, and cancel any outstanding after a condition, is with the use of a context value. Something like this:
func operation1(ctx context.Context) bool { ... }
func operation2(ctx context.Context) bool { ... }
func operation3(ctx context.Context) bool { ... }
func atLeastOneSuccess() bool {
ctx, cancel := context.WithCancel(context.Background()
defer cancel() // Ensure any functions still running get the signal to stop
results := make(chan bool, 3) // A channel to send results
go func() {
results <- operation1(ctx)
}()
go func() {
results <- operation2(ctx)
}()
go func() {
results <- operation3(ctx)
}()
for i := 0; i < 3; i++ {
result := <-results
if result {
// One of the operations returned success, so we'll return that
// and let the deferred call to cancel() tell any outstanding
// functions to abort.
return true
}
}
// We've looped through all return values, and they were all false
return false
}
Of course this assumes that each of the operationN functions actually honors a canceled context. This answer discusses how to do that.
You don't have to block the main goroutine on the Wait, you could block something else, for example:
doneCh := make(chan struct{}{})
go func() {
wg.Wait()
close(doneCh)
}()
Then you can wait on doneCh in your select to see if all the routines have finished.

Best approach to getting results out of goroutines

I have two functions that I cannot change (see first() and second() below). They are returning some data and errors (the output data is different, but in the examples below I use (string, error) for simplicity)
I would like to run them in separate goroutines - my approach:
package main
import (
"fmt"
"os"
)
func first(name string) (string, error) {
if name == "" {
return "", fmt.Errorf("empty name is not allowed")
}
fmt.Println("processing first")
return fmt.Sprintf("First hello %s", name), nil
}
func second(name string) (string, error) {
if name == "" {
return "", fmt.Errorf("empty name is not allowed")
}
fmt.Println("processing second")
return fmt.Sprintf("Second hello %s", name), nil
}
func main() {
firstCh := make(chan string)
secondCh := make(chan string)
go func() {
defer close(firstCh)
res, err := first("one")
if err != nil {
fmt.Printf("Failed to run first: %v\n", err)
}
firstCh <- res
}()
go func() {
defer close(secondCh)
res, err := second("two")
if err != nil {
fmt.Printf("Failed to run second: %v\n", err)
}
secondCh <- res
}()
resultsOne := <-firstCh
resultsTwo := <-secondCh
// It's important for my app to do error checking and stop if errors exist.
if resultsOne == "" || resultsTwo == "" {
fmt.Println("There was an ERROR")
os.Exit(1)
}
fmt.Println("ONE:", resultsOne)
fmt.Println("TWO:", resultsTwo)
}
I believe one caveat is that resultsOne := <- firstCh blocks until first goroutine finishes, but I don't care too much about this.
Can you please confirm that my approach is good? What other approaches would be better in my situation?
The example looks mostly good. A couple improvements are:
declaring your channels as buffered
firstCh := make(chan string, 1)
secondCh := make(chan string, 1)
With unbuffered channels, send operations block (until someone receives). If your goroutine #2 is much faster than the first, it will have to wait until the first finishes as well, since you receive in sequence:
resultsOne := <-firstCh // waiting on this one first
resultsTwo := <-secondCh // sender blocked because the main thread hasn't reached this point
use "golang.org/x/sync/errgroup".Group. The program will feel "less native" but it dispenses you from managing channels by hand — which trades, in a non-contrived setting, for sync'ing writes on the results:
func main() {
var (
resultsOne string
resultsTwo string
)
g := errgroup.Group{}
g.Go(func() error {
res, err := first("one")
if err != nil {
return err
}
resultsOne = res
return nil
})
g.Go(func() error {
res, err := second("two")
if err != nil {
return err
}
resultsTwo = res
return nil
})
err := g.Wait()
// ... handle err

Simulate an HTTP request with success or failure using retry logic

I want to simulate a re-try option with http like:
first two http attempts with error (using some faulty urls)
the third with success (with valid url)
This is a bit tricky any idea how to do it? I try with loop on the doSomething method with different url but it doesn't make the point,
which is for example, retry at least 3 times until you get http 200, (success) any idea how could I simulate it?
maybe run in loop on following...
www.stackoverflow.com2
www.stackoverflow.com1
www.stackoverflow.com
https://play.golang.org/p/dblPh1T0XBu
package main
import (
`fmt`
`log`
"net/http"
`time`
`github.com/cenkalti/backoff/v4`
)
func main() {
b := backoff.NewExponentialBackOff()
b.MaxElapsedTime = 3 * time.Second
retryable := func() error {
val, err := doSomething("https://www.google.com1")
if err != nil {
return err
}
fmt.Println(val)
return nil
}
notify := func(err error, t time.Duration) {
log.Printf("error: %v happened at time: %v", err, t)
}
err := backoff.RetryNotify(retryable, b, notify)
if err != nil {
fmt.Errorf("error after retrying: %v", err)
}
}
func doSomething(url string) (int, error) {
res, e := http.Get(url)
if e != nil {
fmt.Println("error occurred: ", e)
return 500, e
}
return res.StatusCode, nil
}
The idea on the comment below is part of the problem, I need to use the http calls
https://play.golang.org/p/FTR7J2r-QB7
package main
import (
`fmt`
`log`
`time`
`github.com/cenkalti/backoff/v4`
)
func main() {
b := backoff.NewExponentialBackOff()
b.MaxElapsedTime = 3 * time.Second
retrybuilder := func (count int) func() error {
return func() error {
var succeed bool
count -= 1
if count == 0 {
succeed = true
}
val, err := doSomething(succeed)
if err != nil {
fmt.Println("response: ", val)
}
return err
}
}
notify := func(err error, t time.Duration) {
log.Printf("error: %v happened at time: %v", err, t)
}
err := backoff.RetryNotify(retrybuilder(3), b, notify)
if err != nil {
fmt.Printf("error after retrying: %v", err)
}
}
func doSomething(succeed bool) (int, error) {
if !succeed {
return 500, fmt.Errorf("E_SIMULATED: sim error")
}
return 200, nil
}

Setting variables in defer on recovery

Per examples (e.g. getting panic() argument in defer function in GO lang) I've seen, I'm expecting this to work, but it isn't. When forcing an error, the err return string remains blank, although printing the err string shows the expected error.
I'm sure I'm missing something obvious, but can't find it. A little help?
// expected error example:
// chk, err := equal("a", map[string]string{"a"})
//
func Equal(a interface{}, b interface{}) (check bool, err string) {
defer func() {
if catch := recover(); catch != nil {
check = false
// this prints
fmt.Printf("%v\n", catch)
err = fmt.Sprint(catch)
}
}()
return a == b, ""
}
BTW:
go version go1.2.1 linux/amd64
As #nos pointed out, there's no panic, here.
This example works as expected:
package main
import "fmt"
func equal(a interface{}, b interface{}) (check bool, err string) {
defer func() {
if catch := recover(); catch != nil {
check = false
fmt.Printf("recover: %v\n", catch)
err = fmt.Sprint(catch)
} else {
fmt.Printf("recover: none\n")
}
}()
return a == b, ""
}
func main() {
chk, err := equal("a", "a")
fmt.Printf("a == a\n")
fmt.Printf("chk: %v\n", chk)
fmt.Printf("err: %v\n", err)
fmt.Println()
chk, err = equal("a", 1)
fmt.Printf("a == 1\n")
fmt.Printf("chk: %v\n", chk)
fmt.Printf("err: %v\n", err)
fmt.Println()
chk, err = equal([]int{1}, []int{1})
fmt.Printf("[]int{1}, []int{1}\n")
fmt.Printf("chk: %v\n", chk)
fmt.Printf("err: %v\n", err)
}
// Output:
//
// recover: none
// a == a
// chk: true
// err:
// recover: none
//
// a == 1
// chk: false
// err:
// recover: runtime error: comparing uncomparable type []int
//
// []int{1}, []int{1}
// chk: false
// err: runtime error: comparing uncomparable type []int

Go: returning from defer

I want to return an error from a function if it panics (in Go):
func getReport(filename string) (rep report, err error) {
rep.data = make(map[string]float64)
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
err, _ = r.(error)
return nil, err
}
}()
panic("Report format not recognized.")
// rest of the getReport function, which can try to out-of-bound-access a slice
...
}
I appear to have misunderstood the very concept of panic and defer. Can anybody enlighten me?
In a deferred function you can alter the returned parameters, but you can't return a new set. So a simple change to what you have will make it work.
There is another problem with what you wrote, namely that the you've paniced with a string but are expecting an error in your type assertion.
Here is a fix for both of those (Play)
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
// find out exactly what the error was and set err
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("Unknown panic")
}
// invalidate rep
rep = nil
// return the modified err and rep
}
}()
have a look at this
package main
import "fmt"
func iWillPanic() {
panic("ops, panic")
}
func runner() (rtnValue string) {
rtnValue := ""
defer func() {
if r := recover(); r != nil {
// and your logs or something here, log nothing with panic is not a good idea
rtnValue = "don't panic" // modify the return value, and it will return
}
}()
iWillPanic()
return rtnValue
}
func main() {
fmt.Println("Return Value:", runner())
}
func TestReturnFromPanic(t *testing.T) {
fn := func(filename string) (rep string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic in getReport %s", r)
}
}()
return filename[100:], nil
}
t.Log(fn(``))
}
The named return parameter err is the trick.
https://play.golang.org/p/jpaCa9j2iAf

Resources