Skip to content

Commit

Permalink
prevent concurrent builds of same artifact
Browse files Browse the repository at this point in the history
Signed-off-by: Pablo Chacin <[email protected]>
  • Loading branch information
pablochacin committed Sep 19, 2024
1 parent 14ee86c commit 18cc94e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 0 deletions.
20 changes: 20 additions & 0 deletions pkg/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"os"
"sort"
"sync"

"github.com/grafana/k6build"
"github.com/grafana/k6build/pkg/cache"
Expand Down Expand Up @@ -44,6 +45,7 @@ type localBuildSrv struct {
catalog k6catalog.Catalog
builder k6foundry.Builder
cache cache.Cache
mutexes sync.Map
}

// NewBuildService creates a local build service using the given configuration
Expand Down Expand Up @@ -158,6 +160,9 @@ func (b *localBuildSrv) Build(
}
id := fmt.Sprintf("%x", sha1.Sum(hashData.Bytes())) //nolint:gosec

unlock := b.lockArtifact(id)
defer unlock()

artifactObject, err := b.cache.Get(ctx, id)
if err == nil {
return k6build.Artifact{
Expand Down Expand Up @@ -192,3 +197,18 @@ func (b *localBuildSrv) Build(
Platform: platform,
}, nil
}

// lockArtifact obtains a mutex used to prevent concurrent builds of the same artifact and
// returns a function that will unlock the mutex associated to the given id in the cache.
// The lock is also removed from the map. Subsequent calls will get another lock on the same
// id but this is safe as the object should already be in the cache and no further builds are needed.
func (b *localBuildSrv) lockArtifact(id string) func() {
value, _ := b.mutexes.LoadOrStore(id, &sync.Mutex{})
mtx, _ := value.(*sync.Mutex)
mtx.Lock()

return func() {
b.mutexes.Delete(id)
mtx.Unlock()
}
}
61 changes: 61 additions & 0 deletions pkg/local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package local
import (
"context"
"errors"
"sync"
"testing"

"github.com/grafana/k6build"
Expand Down Expand Up @@ -242,3 +243,63 @@ func TestIdempotentBuild(t *testing.T) {
}
})
}

func TestConcurrentBuilds(t *testing.T) {
t.Parallel()
buildsrv, err := SetupTestLocalBuildService(t)
if err != nil {
t.Fatalf("test setup %v", err)
}

builds := []struct {
k6Ver string
deps []k6build.Dependency
}{
{
k6Ver: "v0.1.0",
deps: []k6build.Dependency{
{Name: "k6/x/ext", Constraints: "v0.1.0"},
},
},
{
k6Ver: "v0.1.0",
deps: []k6build.Dependency{
{Name: "k6/x/ext", Constraints: "v0.1.0"},
},
},
{
k6Ver: "v0.2.0",
deps: []k6build.Dependency{
{Name: "k6/x/ext", Constraints: "v0.1.0"},
},
},
}

errch := make(chan error, len(builds))

wg := sync.WaitGroup{}
for _, b := range builds {
wg.Add(1)
go func() {
defer wg.Done()

_, err = buildsrv.Build(
context.TODO(),
"linux/amd64",
b.k6Ver,
b.deps,
)
if err != nil {
errch <- err
}
}()
}

wg.Wait()

select {
case err := <-errch:
t.Fatalf("unexpected %v", err)
default:
}
}

0 comments on commit 18cc94e

Please sign in to comment.