Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per-artifact build results v2: use channels as futures to send results from builders to runner #2077

Closed

Conversation

nkubala
Copy link
Contributor

@nkubala nkubala commented May 6, 2019

NOTE: this PR is an extension of #2000, which will be closed in favor of this. please read the description and comments before reading this for context!

this change uses channels as makeshift futures to send build results back from builders to the runner asynchronously. this has the advantage of giving the runner the ability to immediately dispatch events when results are received, rather than needing to wait for all build threads to finish before dispatching events.

the event changes are NOT included in this PR: they will come in a follow up change.

the signature for the builder methods now becomes

Build(ctx context.Context, out io.Writer, tags tag.ImageTags, artifacts []*latest.Artifact) ([]chan Result, error)

the runner receives a slice of channels back from the builder, each promising to return a result for its corresponding build. the runner will block until it has received a result from each channel, and then continue with the rest of the pipeline.

Copy link
Member

@tejal29 tejal29 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am little bit worried about the "InSequence" flow.
I found this interesting book with Go Design Patterns

A snippet of [book] (https://learning.oreilly.com/library/view/go-design-patterns/9781786466204/ch09.html
) here:--

"Now that we are familiar with the concepts of concurrency and parallelism, and we have understood how to achieve them by using Go's concurrency primitives, we can see some patterns regarding concurrent work and parallel execution. In this chapter we'll see the following patterns:

  1. Barrier is a very common pattern, especially when we have to wait for more than one response from different Goroutines before letting the program continue.
  2. Future pattern allows us to write an algorithm that will be executed eventually in time (or not) by the same Goroutine or a different one
  3. Pipeline is a powerful pattern to build complex synchronous flows of Goroutines that are connected with each other according to some logic"

Looks like we are merging 2 patterns

  • Barrier (since runner waits for the responses for all build)
  • Future (fire and forget pattern)

Trying to think how we can simplify this.

cmd/skaffold/app/cmd/build.go Show resolved Hide resolved
cmd/skaffold/app/cmd/build_test.go Outdated Show resolved Hide resolved
pkg/skaffold/build/parallel.go Show resolved Hide resolved
Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first round of reviews on the top files. I'm continuing.

cmd/skaffold/app/cmd/build.go Outdated Show resolved Hide resolved
cmd/skaffold/app/cmd/build_test.go Show resolved Hide resolved
cmd/skaffold/app/cmd/build.go Show resolved Hide resolved
cmd/skaffold/app/cmd/build_test.go Outdated Show resolved Hide resolved
cmd/skaffold/app/cmd/deploy_test.go Show resolved Hide resolved
Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review round 2

pkg/skaffold/build/cache/save.go Outdated Show resolved Hide resolved
pkg/skaffold/build/cluster/cluster.go Outdated Show resolved Hide resolved
pkg/skaffold/build/cluster/cluster.go Outdated Show resolved Hide resolved
pkg/skaffold/build/cluster/cluster.go Outdated Show resolved Hide resolved
pkg/skaffold/build/cluster/cluster.go Outdated Show resolved Hide resolved
pkg/skaffold/build/cache/save.go Outdated Show resolved Hide resolved
@nkubala nkubala force-pushed the build-res-channel branch 2 times, most recently from 9c7de4c to bb8a577 Compare May 7, 2019 20:19
Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some nits around parallel

pkg/skaffold/build/parallel.go Outdated Show resolved Hide resolved
pkg/skaffold/build/parallel.go Show resolved Hide resolved
@codecov-io
Copy link

codecov-io commented May 7, 2019

Codecov Report

Merging #2077 into master will increase coverage by 0.21%.
The diff coverage is 78.57%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2077      +/-   ##
==========================================
+ Coverage   56.12%   56.33%   +0.21%     
==========================================
  Files         180      180              
  Lines        7756     7810      +54     
==========================================
+ Hits         4353     4400      +47     
- Misses       2987     2993       +6     
- Partials      416      417       +1
Impacted Files Coverage Δ
pkg/skaffold/build/gcb/cloud_build.go 0% <0%> (ø) ⬆️
pkg/skaffold/build/cluster/cluster.go 0% <0%> (ø) ⬆️
pkg/skaffold/test/test.go 14.7% <0%> (ø) ⬆️
pkg/skaffold/runner/timings.go 14.58% <0%> (+0.58%) ⬆️
pkg/skaffold/build/local/local.go 37.31% <100%> (ø) ⬆️
pkg/skaffold/build/util.go 100% <100%> (ø) ⬆️
pkg/skaffold/build/sequence.go 100% <100%> (ø) ⬆️
pkg/skaffold/build/cache/retrieve.go 82.14% <47.61%> (-4.68%) ⬇️
pkg/skaffold/build/cache/save.go 20.68% <66.66%> (ø) ⬆️
pkg/skaffold/runner/runner.go 65.76% <75.86%> (+0.98%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ceda301...fb59015. Read the comment docs.

pkg/skaffold/build/sequence.go Outdated Show resolved Hide resolved
pkg/skaffold/build/sequence.go Show resolved Hide resolved
pkg/skaffold/build/sequence.go Outdated Show resolved Hide resolved
Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more nits

func InSequence(ctx context.Context, out io.Writer, tags tag.ImageTags, artifacts []*latest.Artifact, buildArtifact artifactBuilder) ([]Artifact, error) {
var builds []Artifact
func InSequence(ctx context.Context, out io.Writer, tags tag.ImageTags, artifacts []*latest.Artifact, buildArtifact artifactBuilder) ([]chan Result, error) {
resultChans := make([]chan Result, len(artifacts))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue that this does the same:

	resultChans := make([]chan Result, len(artifacts))
	
	for i, a := range artifacts {
		resultChan := make(chan Result, 1)
		resultChans[i] = resultChan
		resultChan <- doBuild(ctx, out, tags, a, buildArtifact)
	}

	return resultChans, nil

@tejal29
Copy link
Member

tejal29 commented May 8, 2019

@balopat and @nkubala, I have this prototype to implement this using a single channel
tejal29/gosink#1

Let me know what you think.

@balopat
Copy link
Contributor

balopat commented May 8, 2019

I think it's worthwhile to experiment with it. I'll check it out.
One argument towards separate channels per artifact would be the better ability to start doing per artifact progress bar later.

var built []Artifact

for i, artifact := range artifacts {
for i := range artifacts {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we left this here, the parallel builder won't return until all the outputs are consumed - which means that the eventing logic is going to be "batched" until all builds are done.

So:

  1. we should write a test to ensure "realtimeness" of the events somehow
  2. we should move the output handling to another go routine

@balopat
Copy link
Contributor

balopat commented May 8, 2019

I think it's worthwhile to experiment with it. I'll check it out.
One argument towards separate channels per artifact would be the better ability to start doing per artifact progress bar later.

#2091

if len(artifacts) == 1 {
return InSequence(ctx, out, tags, artifacts, buildArtifact)
}

resultChans := make([]chan Result, len(artifacts))

ctx, cancel := context.WithCancel(ctx)
defer cancel()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a subtle bug here: we need to make sure that this cancel() happens only after all the builds are happened. Otherwise we might run into issues that this method returns while the builds are happening, and they are going to get cancelled. I fixed it in #2091.

@balopat
Copy link
Contributor

balopat commented May 14, 2019

After multiple days invested in this direction I still feel that it is not worthwhile to go down this direction: the complexity of adding go channels adds a lot of complexity and the design more error prone for subtle bugs. The current arguments for going towards this direction is the Event API redesign to centralize event management in the Runner. This in itself is a valuable idea but at this point the current design is simpler to reason about this new design with the go channels, plus if we want to extend the events sent by builders, e.g. progress bar events, we will have to send data through the go channels anyway that represents those events and convert them to events, or directly call the event package - at which point the complexity of "handling events from the builders" is going to be the same.
I am open to introduce a streaming/reactive design in Skaffold on the long run if we have a strong enough argument/featureset for it. For this redesign I don't see that it is worthwhile. I am closing this and the related PRs.

Thank you @nkubala and @tejal29 for all the hard work in exploring this avenue in detail in code with testing and thoughtful conversations!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants