update
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
This commit is contained in:
176
utils/pl/pipelines.go
Normal file
176
utils/pl/pipelines.go
Normal file
@@ -0,0 +1,176 @@
|
||||
// Package pl implements some Data Pipeline helper functions.
|
||||
// Reference: https://medium.com/amboss/applying-modern-go-concurrency-patterns-to-data-pipelines-b3b5327908d4#3a80
|
||||
//
|
||||
// See also:
|
||||
//
|
||||
// https://www.oreilly.com/library/view/concurrency-in-go/9781491941294/ch04.html#fano_fani
|
||||
// https://www.youtube.com/watch?v=f6kdp27TYZs
|
||||
// https://www.youtube.com/watch?v=QDDwwePbDtw
|
||||
package pl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
func Stage[In any, Out any](
|
||||
ctx context.Context,
|
||||
maxWorkers int,
|
||||
inputChannel <-chan In,
|
||||
fn func(context.Context, In) (Out, error),
|
||||
) (chan Out, chan error) {
|
||||
outputChannel := make(chan Out)
|
||||
errorChannel := make(chan error)
|
||||
|
||||
limit := int64(maxWorkers)
|
||||
sem1 := semaphore.NewWeighted(limit)
|
||||
|
||||
go func() {
|
||||
defer close(outputChannel)
|
||||
defer close(errorChannel)
|
||||
|
||||
for s := range ReadOrDone(ctx, inputChannel) {
|
||||
if err := sem1.Acquire(ctx, 1); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
log.Error(ctx, "Failed to acquire semaphore", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
go func(s In) {
|
||||
defer sem1.Release(1)
|
||||
|
||||
result, err := fn(ctx, s)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
errorChannel <- err
|
||||
}
|
||||
} else {
|
||||
outputChannel <- result
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
// By using context.Background() here we are assuming the fn will stop when the context
|
||||
// is canceled. This is required so we can wait for the workers to finish and avoid closing
|
||||
// the outputChannel before they are done.
|
||||
if err := sem1.Acquire(context.Background(), limit); err != nil {
|
||||
log.Error(ctx, "Failed waiting for workers", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return outputChannel, errorChannel
|
||||
}
|
||||
|
||||
func Sink[In any](
|
||||
ctx context.Context,
|
||||
maxWorkers int,
|
||||
inputChannel <-chan In,
|
||||
fn func(context.Context, In) error,
|
||||
) chan error {
|
||||
results, errC := Stage(ctx, maxWorkers, inputChannel, func(ctx context.Context, in In) (bool, error) {
|
||||
err := fn(ctx, in)
|
||||
return false, err // Only err is important, results will be discarded
|
||||
})
|
||||
|
||||
// Discard results
|
||||
go func() {
|
||||
for range ReadOrDone(ctx, results) {
|
||||
}
|
||||
}()
|
||||
|
||||
return errC
|
||||
}
|
||||
|
||||
func Merge[T any](ctx context.Context, cs ...<-chan T) <-chan T {
|
||||
var wg sync.WaitGroup
|
||||
out := make(chan T)
|
||||
|
||||
output := func(c <-chan T) {
|
||||
defer wg.Done()
|
||||
for v := range ReadOrDone(ctx, c) {
|
||||
select {
|
||||
case out <- v:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Add(len(cs))
|
||||
for _, c := range cs {
|
||||
go output(c)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(out)
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func SendOrDone[T any](ctx context.Context, out chan<- T, v T) {
|
||||
select {
|
||||
case out <- v:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func ReadOrDone[T any](ctx context.Context, in <-chan T) <-chan T {
|
||||
valStream := make(chan T)
|
||||
go func() {
|
||||
defer close(valStream)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case v, ok := <-in:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case valStream <- v:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return valStream
|
||||
}
|
||||
|
||||
func Tee[T any](ctx context.Context, in <-chan T) (<-chan T, <-chan T) {
|
||||
out1 := make(chan T)
|
||||
out2 := make(chan T)
|
||||
go func() {
|
||||
defer close(out1)
|
||||
defer close(out2)
|
||||
for val := range ReadOrDone(ctx, in) {
|
||||
var out1, out2 = out1, out2
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case out1 <- val:
|
||||
out1 = nil
|
||||
case out2 <- val:
|
||||
out2 = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return out1, out2
|
||||
}
|
||||
|
||||
func FromSlice[T any](ctx context.Context, in []T) <-chan T {
|
||||
output := make(chan T, len(in))
|
||||
for _, c := range in {
|
||||
output <- c
|
||||
}
|
||||
close(output)
|
||||
return output
|
||||
}
|
||||
Reference in New Issue
Block a user