Managing Producer Consumer deadlock in case of failure - go

say I have a case of reader, manipulator, consumer in different routines:
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
"github.com/pkg/errors"
)
func Reader(ctx context.Context, chanFromReader chan int) error {
defer close(chanFromReader)
for i := 0; i < 100; i++ {
select {
case <-ctx.Done():
return nil
case chanFromReader <- i:
}
}
return nil
}
func Manipulate(ctx context.Context, chanFromReader chan int, chanToWriter chan int) error {
defer close(chanToWriter)
for {
select {
case <-ctx.Done():
return nil
case x, ok := <-chanFromReader:
if !ok {
return nil
}
chanToWriter <- 2 * x
}
}
}
func Writer(ctx context.Context, chanToWriter chan int) error {
for {
select {
case <-ctx.Done():
return nil
case x, ok := <-chanToWriter:
if !ok {
return nil
}
fmt.Println("Writer: ", x)
if x == 10 {
return errors.New("Generate some error in writer")
}
}
}
}
func main() {
g, ctx := errgroup.WithContext(context.Background())
chanFromReader := make(chan int)
chanToWriter := make(chan int)
func(ctx context.Context, chanToWriter chan int) {
g.Go(func() error {
return Writer(ctx, chanToWriter)
})
}(ctx, chanToWriter)
func(ctx context.Context, chanFromReader chan int, chanToWriter chan int) {
g.Go(func() error {
return Manipulate(ctx, chanFromReader, chanToWriter)
})
}(ctx, chanFromReader, chanToWriter)
func(ctx context.Context, chanFromReader chan int) {
g.Go(func() error {
return Reader(ctx, chanFromReader)
})
}(ctx, chanFromReader)
g.Wait()
fmt.Println("Main wait done")
}
https://play.golang.org/p/whslVE3rzel
In case the writer fails for some reason, I'm having trouble aborting the rest of the routines.
In the example above for instance, though they listen on ctx for cancellation they still deadlock on case of fail in writer, is there a workaround this?
I thought of adding this:
func Manipulate(ctx context.Context, chanFromReader chan int, chanToWriter chan int) error {
defer close(chanToWriter)
for {
select {
case <-ctx.Done():
return nil
case x, ok := <-chanFromReader:
if !ok {
return nil
}
select {
case <-ctx.Done():
return nil
case chanToWriter <- 2 * x:
}
}
}
}
which solves it, but it looks so unclean...

I would propose a solution where each channel gets closed only by the code that creates it. This can be enforced by returning a receive-only channel from the function that creates the channel and is responsible for closing it:
(kudos to mh-cbon for further refining this:)
https://play.golang.org/p/Tq4OVW5sSP4
package main
import (
"context"
"fmt"
"log"
"sync"
)
func read(ctx context.Context) (<-chan int, <-chan error) {
ch := make(chan int)
e := make(chan error)
go func() {
defer close(e)
defer close(ch)
for i := 0; i < 12; i++ {
select {
case <-ctx.Done():
return
case ch <- i:
}
}
}()
return ch, e
}
func manipulate(in <-chan int) (<-chan int, <-chan error) {
ch := make(chan int)
e := make(chan error)
go func() {
defer close(e)
defer close(ch)
for n := range in {
ch <- 2 * n
}
}()
return ch, e
}
func write(in <-chan int) <-chan error {
e := make(chan error)
go func() {
defer close(e)
for n := range in {
fmt.Println("written: ", n)
if n == 10 {
e <- fmt.Errorf("output error during write")
}
}
}()
return e
}
func collectErrors(errs ...<-chan error) {
var wg sync.WaitGroup
for i := 0; i < len(errs); i++ {
wg.Add(1)
go func(errs <-chan error) {
defer wg.Done()
for err := range errs {
log.Printf("%v", err)
}
}(errs[i])
}
wg.Wait()
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch1, err1 := read(ctx)
ch2, err2 := manipulate(ch1)
err3 := write(ch2)
collectErrors(err1, err2, err3)
fmt.Println("main wait complete")
}
This way, each channel gets closed reliably, and the I/O errors from write will cause the child context to be cancelled, shutting down the other goroutines.

Related

Stuck in infinite loop in for select in Golang

The code given below is the sample code for my use case. I want to read data from ch1 and ch2 but got stuck into infinite loop.
package main
import "fmt"
func main() {
ch1, ch2 := func() (<-chan int, <-chan int) {
ch_1 := make(chan int)
ch_2 := make(chan int)
go worker_1(ch_1, ch_2)
go worker_2(ch_1, ch_2)
return ch_1, ch_2
}()
// trying to read this way but it is not working
for {
select {
case a := <-ch1:
fmt.Println("from ch1", a)
case a := <-ch2:
fmt.Println("from ch2", a)
default:
fmt.Println("done")
}
}
}
func worker_1(ch1, ch2 chan int) {
for i := 0; i < 100; i++ {
if i%2 == 0 {
ch1 <- i
} else {
ch2 <- i
}
}
}
func worker_2(ch1, ch2 chan int) {
for i := 101; i < 200; i++ {
if i%2 == 0 {
ch1 <- i
} else {
ch2 <- i
}
}
}
Here is one solution:
package main
import (
"fmt"
"sync"
)
func main() {
// Create channels
ch1, ch2 := make(chan int), make(chan int)
// Create workers waitgroup with a counter of 2
wgWorkers := sync.WaitGroup{}
wgWorkers.Add(2)
// Run workers
go worker(&wgWorkers, ch1, ch2, 0, 100) // Worker 1
go worker(&wgWorkers, ch1, ch2, 101, 200) // Worker 2
// Create readers waitgroup with a counter of 2
wgReader := sync.WaitGroup{}
wgReader.Add(2)
// Run readers
go reader(&wgReader, ch1, 1) // Reader 1
go reader(&wgReader, ch2, 2) // Reader 2
// Wait for workers to finish
wgWorkers.Wait()
// Close workers channels
close(ch1) // Makes reader 1 exit after processing the last element in the channel
close(ch2) // Makes reader 2 exit after processing the last element in the channel
// Wait for both readers to finish processing before exiting the program
wgReader.Wait()
}
// Worker function definition
func worker(wg *sync.WaitGroup, ch1, ch2 chan<- int, from, to int) {
// Decrement worker waitgroup counter by one when function returns
defer wg.Done()
for i := from; i < to; i++ {
if i%2 == 0 {
ch1 <- i
} else {
ch2 <- i
}
}
}
// Reader function definition
func reader(wg *sync.WaitGroup, ch <-chan int, chNum int) {
// Decrement reader waitgroup counter by one when function returns
defer wg.Done()
// Here we iterate on the channel fed by worker 1 or worker 2.
// for-range on a channel exits when the channel is closed.
for i := range ch {
fmt.Printf("from ch%d: %d\n", chNum, i)
}
}
Explainations are in the code comments.
Close the channels when the workers are done. Break out of the receive loop after both channels are closed.
package main
import (
"fmt"
"sync"
)
func main() {
ch1, ch2 := func() (<-chan int, <-chan int) {
ch_1 := make(chan int)
ch_2 := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go worker_1(&wg, ch_1, ch_2)
go worker_2(&wg, ch_1, ch_2)
// Close channels after goroutiens complete.
go func() {
wg.Wait()
close(ch_1)
close(ch_2)
}()
return ch_1, ch_2
}()
// While we still have open channels ...
for ch1 != nil || ch2 != nil {
select {
case a, ok := <-ch1:
if ok {
fmt.Println("from ch1", a)
} else {
// note that channel is closed.
ch1 = nil
}
case a, ok := <-ch2:
if ok {
fmt.Println("from ch2", a)
} else {
// note that channel is closed.
ch2 = nil
}
}
}
}
func worker_1(wg *sync.WaitGroup, ch1, ch2 chan int) {
defer wg.Done()
for i := 0; i < 100; i++ {
if i%2 == 0 {
ch1 <- i
} else {
ch2 <- i
}
}
}
func worker_2(wg *sync.WaitGroup, ch1, ch2 chan int) {
defer wg.Done()
for i := 101; i < 200; i++ {
if i%2 == 0 {
ch1 <- i
} else {
ch2 <- i
}
}
}

Golang: Cannot send error to channel in recover()

I try to send an error in the channel on recovery
Why this error is not sent to the channel?
package main
import (
"fmt"
"sync"
"errors"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
batchErrChan := make(chan error)
go func(errchan chan error) {
defer func() {
if r := recover(); r != nil {
errchan <- errors.New("recover err")
}
close(batchErrChan)
wg.Done()
}()
panic("ddd")
}(batchErrChan)
go func() {
for _ = range batchErrChan {
fmt.Println("err in range")
}
}()
wg.Wait()
}
https://play.golang.org/p/0ytunuYDWZU
I expect "err in range" to be printed, but it is not. Why?
Your program ends before the goroutine gets a chance to print the message. Try waiting to it:
...
done:=make(chan struct{})
go func() {
for _ = range batchErrChan {
fmt.Println("err in range")
}
close(done)
}()
wg.Wait()
<-done
}

Simplest concurrent loop with bounded concurrency

I'm looking for the simplest code for looping over a dataset in parallel. The requirements are that the number of goroutines is fixed and that they can return an error code. The following is a quick attempt which doesn't work, since the loops will deadlock as both goroutines are blocking on the error channel
package main
import (
"fmt"
"sync"
)
func worker(wg *sync.WaitGroup, intChan chan int, errChan chan error) {
defer wg.Done()
for i := range intChan {
fmt.Printf("Got %d\n", i)
errChan <- nil
}
}
func main() {
ints := []int{1,2,3,4,5,6,7,8,9,10}
intChan := make(chan int)
errChan := make(chan error)
wg := new(sync.WaitGroup)
for i := 0; i < 2; i++ {
wg.Add(1)
go worker(wg, intChan, errChan)
}
for i := range ints {
intChan <- i
}
for range ints {
err := <- errChan
fmt.Printf("Error: %v\n", err)
}
close(intChan)
wg.Wait()
}
What is the simplest pattern for doing this?
Listen for errors in a goroutine:
go func() {
for err:=range errChan {
// Deal with err
}
}()
for i := 0; i < 2; i++ {
wg.Add(1)
go worker(wg, intChan, errChan)
}
for i := range ints {
intChan <- i
}
close(errChan) // Error listener goroutine terminates after this

Daisy chain input,output channels together in golang

I have the following interface and struct
type PiplineStep interface {
Do(ctx context.Context, in <-chan Message) (<-chan Message, <-chan error, error)
}
type Pipline struct {
Steps []core.PiplineStep
}
Now I am trying to daisy the interfaces to create a pipeline like the following
for _, step := range p.Steps {
out, errc, err := step.Do(ctx, out)
errcList = append(errcList, errc)
if err != nil {
errc <- err
return
}
select {
case outer <- msg:
case <-ctx.Done():
return
}
}
But the compiler says no is this possible?
I get the following Error 'out declared and not used' i have attempted following but it appears that all steps are receiving the same chan
for _, step := range p.Steps {
var tmpOut <-chan core.Message
tmpOut = out
tmpOut, errcTmp, err := step.Do(ctx, tmpOut)
errcList = append(errcList, errcTmp)
if err != nil {
errc <- err
return
}
select {
case out <- msg:
case <-ctx.Done():
return
}
}
You have to declare your channel variable outside the loop if you want to re-use it in each iteration (errors and context omitted for brevity):
package main
import "fmt"
func main() {
var pipeline Pipeline
pipeline.Steps = append(pipeline.Steps,
AddBang{},
AddBang{},
AddBang{},
)
src := make(chan Message)
pipe := src
for _, s := range pipeline.Steps {
pipe = s.Do(pipe)
}
go func() {
src <- "msg 1"
src <- "msg 2"
src <- "msg 3"
}()
fmt.Println(<-pipe)
fmt.Println(<-pipe)
fmt.Println(<-pipe)
}
type Message string
type Pipeline struct {
Steps []PipelineStep
}
type PipelineStep interface {
Do(in chan Message) chan Message
}
type AddBang struct{}
func (AddBang) Do(in chan Message) chan Message {
out := make(chan Message)
go func() {
defer close(out)
for m := range in {
out <- m + "!"
}
}()
return out
}
Try it on the playground: https://play.golang.org/p/ItVLUBRpNA1

which is one better code for goroutine overhead

I want to make Reader.Read concurrent with channel communication.
so I made two ways to run it
1:
type ReturnRead struct {
n int
err error
}
type ReadGoSt struct {
Returnc <-chan ReturnRead
Nextc chan struct{}
}
func (st *ReadGoSt) Close() {
defer func() {
recover()
}()
close(st.Next)
}
func ReadGo(r io.Reader, b []byte) *ReadGoSt {
returnc := make(chan ReturnRead)
nextc := make(chan bool)
go func() {
for range nextc {
n, err := r.Read(b)
returnc <- ReturnRead{n, err}
if err != nil {
return
}
}
}()
return &ReadGoSt{returnc, nextc}
}
2:
func ReadGo(r io.Reader, b []byte) <-chan ReturnRead {
returnc := make(chan ReturnRead)
go func() {
n, err := r.Read(b)
returnc <- ReturnRead{n, err}
}()
return returnc
}
i think code 2 creates too many overhead
which is the better code? 1? 2?
Code 1 is better and probably faster. Code 2 will just read once. But I think both solutions are not the best. you should loop over the read and send back only the bytes readed.
Something like : http://play.golang.org/p/zRPXOtdgWD

Resources