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
Related
I've created function which needs to run in goroutine, the code is working(this is just simple sample to illustrate the issue)
go rze(ftFilePath, 2)
func rze(ftDataPath,duration time.Duration) error {
}
I want to do something like this
errs := make(chan error, 1)
err := go rze(ftFilePath, 2)
if err != nil{
r <- Result{result, errs}
}
but not sure how to it, most of the examples show
how you do it when you using func
https://tour.golang.org/concurrency/5
You cannot use the return value of a function that is executed with the go keyword. Use an anonymous function instead:
errs := make(chan error, 1)
go func() {
errs <- rze(ftFilePath, 2)
}()
// later:
if err := <-errs; err != nil {
// handle error
}
You can use golang errgroup pkg.
var eg errgroup.Group
eg.Go(func() error {
return rze(ftFilePath, 2)
})
if err := g.Wait(); err != nil {
r <- Result{result, errs}
}
You can handle error from go routines using a separate channel for errors. Create a separate channel specifically for errors. Each child go routine must pass corresponding errors into this channel.
This is one of the ways how it can be done:
func main() {
type err error
items := []string{"1", "2", "3"}
ch := make(chan err, len(items))
for _, f := range items {
go func(f string) {
var e err
e = testFunc(f)
ch <- e
}(f)
}
for range items {
e := <-ch
fmt.Println("Error: ", e)
if e != nil {
// DO Something
}
}
}
func testFunc(item string) error {
if item == "1" {
return errors.New("err")
}
return nil
}
I am trying to achieve some sort of multi-thread processing over here.
func (m *Map) Parse(mapData Node) error {
wg := &sync.WaitGroup{}
for _, node := range mapData.child {
wg.Add(1)
go parseChild(node, m, wg)
}
wg.Wait()
close(errors)
return nil
}
func parseChild(node Node, m *Map, wg *sync.WaitGroup) {
defer wg.Done()
var nodeType uint8
if err := binary.Read(node.data, binary.LittleEndian, &nodeType); err != nil {
errors <- err
}
if nodeType == OTBMNodeTowns {
for _, town := range node.child {
var nodeType uint8
if err := binary.Read(town.data, binary.LittleEndian, &nodeType); err != nil {
errors <- err
return
}
if nodeType != OTBMNodeTown {
errors <- fmt.Errorf("Parsing map towns: expected %v got %v", OTBMNodeTown, nodeType)
return
}
currentTown := Town{}
if err := binary.Read(town.data, binary.LittleEndian, ¤tTown.ID); err != nil {
errors <- err
return
} else if currentTown.Name, err = town.ReadString(); err != nil {
errors <- err
return
} else if currentTown.TemplePosition, err = town.ReadPosition(); err != nil {
errors <- err
return
}
m.Towns = append(m.Towns, currentTown)
errors <- fmt.Errorf("This should be called: %v, nodeType)
return
}
}
}
But my goroutine never sends anything to the errors channel. Seems to be that the main thread is not waiting for the goroutines to even finish
I have no idea what I am missing here. Im waiting for all routines to finish using wg.Wait but doesnt seem to be working as I think it should
And yes. the slice is populated with atleast 3 results. This is the errrors channel
var (
errors = make(chan error, 0)
)
func init() {
go errChannel()
}
func errChannel() {
for {
select {
case err := <-errors:
log.Println(err)
}
}
}
Well, part of my code was working without a method approach, I'm trying to test
append text to a file and reading from goroutines, but I'm stuck here trying to
write it.
What is wrong? the file is created, but I can't append text to it, maybe something obvious, but seems I'm blind, maybe I'm failing understanding some language concepts...
package main
import (
"bufio"
"fmt"
"os"
"sync"
"time"
)
var w sync.WaitGroup
type Buffer struct {
F *os.File
}
func (buff *Buffer) Open(pathName string) (err error) {
buff.F, err = os.OpenFile(pathName, os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return
}
fmt.Println("Open() ok")
return nil
}
func (buff *Buffer) Close() (err error) {
err = buff.F.Close()
if err != nil {
return
}
fmt.Println("Close() ok")
return nil
}
func (buff *Buffer) Push(data string) (err error) {
w := bufio.NewWriter(buff.F)
_, err = fmt.Fprintf(w, "data=%s", data)
if err != nil {
return
}
w.Flush()
fmt.Println("Push() ok")
return nil
}
func checkErr(err error) {
if err != nil {
fmt.Println(err.Error())
}
}
func worker() {
var err error
buffer := new(Buffer)
err = buffer.Open("test")
checkErr(err)
err = buffer.Push("data\n")
checkErr(err)
time.Sleep(5 * time.Second)
err = buffer.Close()
checkErr(err)
w.Done()
}
func main() {
w.Add(2)
go worker()
go worker()
w.Wait()
}
Thanks
Open the file like this:
buff.F, err = os.OpenFile(pathName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
The write flag is required to write to the file.
You missed the write error because the return from bufio Flush is ignored. Change Push to:
func (buff *Buffer) Push(data string) (err error) {
w := bufio.NewWriter(buff.F)
_, err = fmt.Fprintf(w, "data=%s", data)
if err != nil {
return
}
err = w.Flush()
if err != nil {
return err
}
fmt.Println("Push() ok")
return nil
}
To cleanly append data without intermixing with other pushes, the data must be written with a single call to the file Write method. Use a bytes.Buffer instead of a bufio.Writer to ensure a single call to the file Write method:
func (buff *Buffer) Push(data string) (err error) {
var b bytes.Buffer
_, err = fmt.Fprintf(&b, "data=%s", data)
if err != nil {
return
}
_, err := buff.F.Write(b.Bytes())
if err != nil {
return err
}
fmt.Println("Push() ok")
return nil
}
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
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
...
}