gorecoverpanic

Can't recover from panic - defer is entirely skipped


When using io.Copy with an invalid writer, I get a panic - that's expected. However I can't recover when this is the case. My deferred recover is completely bypassed. Here is the code:

package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "os"
    "runtime"
    "runtime/debug"

    "cloud.google.com/go/storage"
)

func main() {
    var (
        ctx      = context.Background()
        fromFile = "blah.txt"
        bucket   = "blah-bucket"
        path     = "blah-path"
    )

    defer func() {
        if result := recover(); result != nil {
            buf := make([]byte, 1<<16)
            length := runtime.Stack(buf, false)
            log.Fatalf("PANIC RECOVER: %v\nSTACK: \n%s", result, buf[:length])
            debug.PrintStack()
        }
    }()

    err := FakeUpload(ctx, fromFile, bucket, path)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("HELLO")
}

func FakeUpload(ctx context.Context, fromFile, toBucket, toPath string) (err error) {
    var (
        file   *os.File
        client *storage.Client
        wc     *storage.Writer
    )

    defer func() {
        for _, c := range []io.Closer{wc, file} {
            if c != nil {
                err = c.Close()
                if err != nil {
                    return
                }
            }
        }
    }()

    file, err = os.Open(fromFile)
    if err != nil {
        err = fmt.Errorf("problem opening file %v: %v", fromFile, err)
        return
    }

    wc = client.Bucket(toBucket).Object(toPath).NewWriter(ctx)

    _, err = io.Copy(wc, file) // THE UNRECOVERABLE PANIC HAPPENS HERE

    return
}

The panic being:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x9e0fa8]

goroutine 21 [running]:
cloud.google.com/go/storage.(*Writer).open.func1(0xc000161200, 0xc0002721e0, 0xc000150388, 0xc000290e20, 0x1, 0x1)
    C:/Users/xxxx/go/pkg/mod/cloud.google.com/go/storage@v1.14.0/writer.go:128 +0x248
created by cloud.google.com/go/storage.(*Writer).open
    C:/Users/xxxx/go/pkg/mod/cloud.google.com/go/storage@v1.14.0/writer.go:118 +0x6ce
Process exiting with code: 0
module main

go 1.15

require cloud.google.com/go/storage v1.14.0
go version go1.15.10 windows/amd64

The kicker is, if it panics elsewhere, for example I point to an invalid file, it'll panic, and the deferred recover properly captures it.

This has me baffled. Any idea?


Solution

  • The answer, as you can read in the comments above, is that cloud.google.com/go/storage writer is creating a goroutine, and throwing the panic in there. GO does not allow you to recover from another goroutine.