go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/sink/test_exoneration_channel.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package sink 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 "github.com/google/uuid" 25 26 "go.chromium.org/luci/common/sync/dispatcher" 27 "go.chromium.org/luci/common/sync/dispatcher/buffer" 28 pb "go.chromium.org/luci/resultdb/proto/v1" 29 sinkpb "go.chromium.org/luci/resultdb/sink/proto/v1" 30 ) 31 32 // unexpectedPassChannel is the channel for unexpected passes which will be exonerated. 33 type unexpectedPassChannel struct { 34 ch dispatcher.Channel 35 cfg *ServerConfig 36 37 // wgActive indicates if there are active goroutines invoking reportTestExonerations. 38 // 39 // reportTestExonerations can be invoked by multiple goroutines in parallel. wgActive is used 40 // to ensure that all active goroutines finish enqueuing messages to the channel before 41 // closeAndDrain closes and drains the channel. 42 wgActive sync.WaitGroup 43 44 // 1 indicates that unexpectedPassChannel started the process of closing and draining 45 // the channel. 0, otherwise. 46 closed int32 47 } 48 49 func newTestExonerationChannel(ctx context.Context, cfg *ServerConfig) *unexpectedPassChannel { 50 var err error 51 c := &unexpectedPassChannel{cfg: cfg} 52 opts := &dispatcher.Options{ 53 Buffer: buffer.Options{ 54 // BatchRequest can include up to 500 requests. KEEP BatchItemsMax <= 500 55 // to keep report() simple. For more details, visit 56 // https://godoc.org/go.chromium.org/luci/resultdb/proto/v1#BatchCreateTestExonerations 57 BatchItemsMax: 500, 58 BatchAgeMax: time.Second, 59 FullBehavior: &buffer.BlockNewItems{MaxItems: 8000}, 60 }, 61 } 62 c.ch, err = dispatcher.NewChannel(ctx, opts, func(b *buffer.Batch) error { 63 return c.report(ctx, b) 64 }) 65 if err != nil { 66 panic(fmt.Sprintf("failed to create a channel for TestExoneration: %s", err)) 67 } 68 return c 69 } 70 71 func (c *unexpectedPassChannel) closeAndDrain(ctx context.Context) { 72 // announce that it is in the process of closeAndDrain. 73 if !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { 74 return 75 } 76 // wait for all the active sessions to finish enqueueing tests exonerations to the channel 77 c.wgActive.Wait() 78 c.ch.CloseAndDrain(ctx) 79 } 80 81 func (c *unexpectedPassChannel) schedule(trs ...*sinkpb.TestResult) { 82 c.wgActive.Add(1) 83 defer c.wgActive.Done() 84 // if the channel already has been closed, drop the test exonerations. 85 if atomic.LoadInt32(&c.closed) == 1 { 86 return 87 } 88 for _, tr := range trs { 89 c.ch.C <- tr 90 } 91 } 92 93 func (c *unexpectedPassChannel) report(ctx context.Context, b *buffer.Batch) error { 94 if b.Meta == nil { 95 reqs := make([]*pb.CreateTestExonerationRequest, len(b.Data)) 96 for i, d := range b.Data { 97 tr := d.Item.(*sinkpb.TestResult) 98 reqs[i] = &pb.CreateTestExonerationRequest{ 99 TestExoneration: &pb.TestExoneration{ 100 TestId: tr.GetTestId(), 101 Variant: c.cfg.BaseVariant, 102 ExplanationHtml: "Unexpected passes are exonerated", 103 Reason: pb.ExonerationReason_UNEXPECTED_PASS, 104 }, 105 } 106 } 107 b.Meta = &pb.BatchCreateTestExonerationsRequest{ 108 Invocation: c.cfg.Invocation, 109 // a random UUID 110 RequestId: uuid.New().String(), 111 Requests: reqs, 112 } 113 } 114 _, err := c.cfg.Recorder.BatchCreateTestExonerations(ctx, b.Meta.(*pb.BatchCreateTestExonerationsRequest)) 115 return err 116 }