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  }