Skip to content

Commit 3139b16

Browse files
committed
solver: allow finalizing history record traces
Client can send a finalize update to build record that will complete saving the traces and block until the record has been updated. If no request is sent then the traces will be sent after a 3 second timeout as before. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent f6c85ab commit 3139b16

File tree

6 files changed

+322
-160
lines changed

6 files changed

+322
-160
lines changed

api/services/control/control.pb.go

Lines changed: 189 additions & 147 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/services/control/control.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ message UpdateBuildHistoryRequest {
220220
string Ref = 1;
221221
bool Pinned = 2;
222222
bool Delete = 3;
223+
bool Finalize = 4;
223224
}
224225

225226
message UpdateBuildHistoryResponse {}

control/control.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -283,18 +283,23 @@ func (c *Controller) ListenBuildHistory(req *controlapi.BuildHistoryRequest, srv
283283
}
284284

285285
func (c *Controller) UpdateBuildHistory(ctx context.Context, req *controlapi.UpdateBuildHistoryRequest) (*controlapi.UpdateBuildHistoryResponse, error) {
286-
if !req.Delete {
287-
err := c.history.UpdateRef(ctx, req.Ref, func(r *controlapi.BuildHistoryRecord) error {
288-
if req.Pinned == r.Pinned {
289-
return nil
290-
}
291-
r.Pinned = req.Pinned
292-
return nil
293-
})
286+
if req.Delete {
287+
err := c.history.Delete(ctx, req.Ref)
294288
return &controlapi.UpdateBuildHistoryResponse{}, err
295289
}
296290

297-
err := c.history.Delete(ctx, req.Ref)
291+
if req.Finalize {
292+
err := c.history.Finalize(ctx, req.Ref)
293+
return &controlapi.UpdateBuildHistoryResponse{}, err
294+
}
295+
296+
err := c.history.UpdateRef(ctx, req.Ref, func(r *controlapi.BuildHistoryRecord) error {
297+
if req.Pinned == r.Pinned {
298+
return nil
299+
}
300+
r.Pinned = req.Pinned
301+
return nil
302+
})
298303
return &controlapi.UpdateBuildHistoryResponse{}, err
299304
}
300305

frontend/dockerfile/dockerfile_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ var allTests = integration.TestFuncs(
194194
testSourcePolicyWithNamedContext,
195195
testInvalidJSONCommands,
196196
testHistoryError,
197+
testHistoryFinalizeTrace,
197198
)
198199

199200
// Tests that depend on the `security.*` entitlements
@@ -7521,6 +7522,66 @@ COPY notexist /foo
75217522
}
75227523
}
75237524

7525+
func testHistoryFinalizeTrace(t *testing.T, sb integration.Sandbox) {
7526+
integration.SkipOnPlatform(t, "windows")
7527+
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush)
7528+
ctx := sb.Context()
7529+
7530+
c, err := client.New(ctx, sb.Address())
7531+
require.NoError(t, err)
7532+
defer c.Close()
7533+
7534+
f := getFrontend(t, sb)
7535+
7536+
dockerfile := []byte(`
7537+
FROM scratch
7538+
COPY Dockerfile /foo
7539+
`)
7540+
dir := integration.Tmpdir(
7541+
t,
7542+
fstest.CreateFile("Dockerfile", dockerfile, 0600),
7543+
)
7544+
7545+
ref := identity.NewID()
7546+
7547+
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
7548+
Ref: ref,
7549+
LocalMounts: map[string]fsutil.FS{
7550+
dockerui.DefaultLocalNameDockerfile: dir,
7551+
dockerui.DefaultLocalNameContext: dir,
7552+
},
7553+
}, nil)
7554+
require.NoError(t, err)
7555+
7556+
_, err = c.ControlClient().UpdateBuildHistory(sb.Context(), &controlapi.UpdateBuildHistoryRequest{
7557+
Ref: ref,
7558+
Finalize: true,
7559+
})
7560+
require.NoError(t, err)
7561+
7562+
cl, err := c.ControlClient().ListenBuildHistory(sb.Context(), &controlapi.BuildHistoryRequest{
7563+
EarlyExit: true,
7564+
Ref: ref,
7565+
})
7566+
require.NoError(t, err)
7567+
7568+
got := false
7569+
for {
7570+
resp, err := cl.Recv()
7571+
if err == io.EOF {
7572+
require.Equal(t, true, got)
7573+
break
7574+
}
7575+
require.NoError(t, err)
7576+
got = true
7577+
7578+
trace := resp.Record.Trace
7579+
require.NotEmpty(t, trace)
7580+
7581+
require.NotEmpty(t, trace.Digest)
7582+
}
7583+
}
7584+
75247585
func runShell(dir string, cmds ...string) error {
75257586
for _, args := range cmds {
75267587
var cmd *exec.Cmd

solver/llbsolver/history.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,20 @@ type HistoryQueue struct {
5252
opt HistoryQueueOpt
5353
ps *pubsub[*controlapi.BuildHistoryEvent]
5454
active map[string]*controlapi.BuildHistoryRecord
55+
finalizers map[string]*finalizer
5556
refs map[string]int
5657
deleted map[string]struct{}
5758
hContentStore *containerdsnapshot.Store
5859
hLeaseManager *leaseutil.Manager
5960
}
6061

62+
// finalizer controls completion of saving traces for a
63+
// record and making it immutable
64+
type finalizer struct {
65+
trigger func()
66+
done chan struct{}
67+
}
68+
6169
type StatusImportResult struct {
6270
Descriptor ocispecs.Descriptor
6371
NumCachedSteps int
@@ -77,9 +85,10 @@ func NewHistoryQueue(opt HistoryQueueOpt) (*HistoryQueue, error) {
7785
ps: &pubsub[*controlapi.BuildHistoryEvent]{
7886
m: map[*channel[*controlapi.BuildHistoryEvent]]struct{}{},
7987
},
80-
active: map[string]*controlapi.BuildHistoryRecord{},
81-
refs: map[string]int{},
82-
deleted: map[string]struct{}{},
88+
active: map[string]*controlapi.BuildHistoryRecord{},
89+
refs: map[string]int{},
90+
deleted: map[string]struct{}{},
91+
finalizers: map[string]*finalizer{},
8392
}
8493

8594
ns := h.opt.ContentStore.Namespace()
@@ -570,6 +579,40 @@ func (h *HistoryQueue) update(ctx context.Context, rec controlapi.BuildHistoryRe
570579
})
571580
}
572581

582+
func (h *HistoryQueue) AcquireFinalizer(ref string) (<-chan struct{}, func()) {
583+
h.mu.Lock()
584+
defer h.mu.Unlock()
585+
trigger := make(chan struct{})
586+
f := &finalizer{
587+
trigger: sync.OnceFunc(func() {
588+
close(trigger)
589+
}),
590+
done: make(chan struct{}),
591+
}
592+
h.finalizers[ref] = f
593+
go func() {
594+
<-f.done
595+
h.mu.Lock()
596+
delete(h.finalizers, ref)
597+
h.mu.Unlock()
598+
}()
599+
return trigger, sync.OnceFunc(func() {
600+
close(f.done)
601+
})
602+
}
603+
604+
func (h *HistoryQueue) Finalize(ctx context.Context, ref string) error {
605+
h.mu.Lock()
606+
f, ok := h.finalizers[ref]
607+
h.mu.Unlock()
608+
if !ok {
609+
return nil
610+
}
611+
f.trigger()
612+
<-f.done
613+
return nil
614+
}
615+
573616
func (h *HistoryQueue) Update(ctx context.Context, e *controlapi.BuildHistoryEvent) error {
574617
h.init()
575618
h.mu.Lock()

solver/llbsolver/solver.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,9 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend
385385
releasers = append(releasers, release)
386386
rec.Error = status
387387
}
388+
389+
ready, done := s.history.AcquireFinalizer(rec.Ref)
390+
388391
if err1 := s.history.Update(ctx, &controlapi.BuildHistoryEvent{
389392
Type: controlapi.BuildHistoryEventType_COMPLETE,
390393
Record: rec,
@@ -396,10 +399,17 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend
396399

397400
if stopTrace == nil {
398401
bklog.G(ctx).Warn("no trace recorder found, skipping")
402+
done()
399403
return err
400404
}
401405
go func() {
402-
time.Sleep(3 * time.Second)
406+
defer done()
407+
408+
// if there is no finalizer request then stop tracing after 3 seconds
409+
select {
410+
case <-time.After(3 * time.Second):
411+
case <-ready:
412+
}
403413
spans := stopTrace()
404414

405415
if len(spans) == 0 {

0 commit comments

Comments
 (0)