Skip to main content

Comparison to Our Original Approach

·2 mins

… continued from the previous post.

Refactor Our Original Approach #

What we have done to the previously can also be done to the approach using an error channel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "context"

type Group struct {
	errc   chan error
	cancel context.CancelCauseFunc
	count  int
}

func NewGroup(cancel context.CancelCauseFunc) *Group {
	return &Group{errc: make(chan error, 1), cancel: cancel}
}

func (g *Group) Go(f func() error) {
	g.count++
	go func() {
		g.errc <- f()
	}()
}

func (g *Group) Wait() error {
	var err error
	for range g.count {
		if e := <-g.errc; e != nil && err == nil {
			err = e
			g.cancel(e)
		}
	}

	return err
}

Making our function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"context"

	"fillmore-labs.com/blog/structured/pkg/task"
)

func doWork(ctx context.Context) error {
	ctx, cancel := context.WithCancelCause(ctx)
	defer cancel(nil)

	g := NewGroup(cancel)

	g.Go(func() error {
		return task.Task(ctx, "task1", processingTime/3, nil)
	})

	g.Go(func() error {
		return task.Task(ctx, "task2", processingTime/2, errFail)
	})

	g.Go(func() error {
		return task.Task(ctx, "task3", processingTime, nil)
	})

	return g.Wait()
}

As we see we get a nearly identical result for the main function, witht the API neatly abstracting our soulution. One difference is that we have to call all subtasks asynchronously, since we need Group.Wait working on the error channel.

Summary #

We have seen two approaches to structured concurrency with nearly identical APIs.

… continued in the next post.