Coordinating error states for multiple goroutines?

35 Views Asked by At

I have three goroutines which depend on each other, and I am coordinating them with channels. Two of the routines are writers and one is a reader to a named pipe.

However, each of these routines could have an error. For example, os.OpenFile could fail in several of these routines. If this happens, we want to exit the routine immediately. However, if we do that, then the other routines will block because the channels will never be consumed.

How do I coordinate and handle error states across these three routines? Do I need an error channel for every single action, and then interleave checking them in every other routine?

func main() {
    syscall.Mkfifo("p.pipe", 0666)
    writer, _ := os.Create("output.csv")

    queryDone := make(chan error)
    waitingDone := make(chan error)
    copyDone := make(chan error)
    go func() {
        // Open for reading and copy to http handler
        r, _ := os.OpenFile("p.pipe", os.O_RDONLY, os.ModeNamedPipe)
        n, _ := io.Copy(writer, r)
        e := <-waitingDone
        r.Close()
        copyDone <- e
    }()

    go func() {
        // Open for writing
        pipe, _ := os.OpenFile("p.pipe", os.O_WRONLY|os.O_APPEND, os.ModeNamedPipe)
        e := <-queryDone
        _ = pipe.Close()
        waitingDone <- e
    }()

    go func() {
        // DB query
        db, _ := sql.Open("duckdb", "")
        defer db.Close()
        _, err = db.Exec("COPY (select 1) TO 'p.pipe' (FORMAT CSV)")
        queryDone <- err
    }()

    e := <-copyDone
    fmt.Println("Done", e)
}
1

There are 1 best solutions below

2
Burak Serdar On

Use a cancelable context, and cancel it if error happens:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Channel to capture the first error
errCh:=make(chan error,1)

// Template for goroutines
go func() {
   for {
     err:=doSometing()
     if err!=nil {
        // Record the error in the channel
        select {
           case errCh<-err:
           default:
        }
        // cancel all goroutines
        cancel()
        return
     }
     if ctx.Err()!=nil {
         return // Error happened somewhere else
     }
}()
...
// Check if error happened. This will collect the first error
var err error
select {
  case err=<-errCh:
  default:
}
if err!=nil {
   // Handle error
}

There are also third part libraries that can help with coordinating error state among multiple goroutines.