go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/gerrit/poller/notify.go (about)

     1  // Copyright 2020 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 poller
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  	"go.chromium.org/luci/common/logging"
    23  	gerritpb "go.chromium.org/luci/common/proto/gerrit"
    24  	"go.chromium.org/luci/common/retry/transient"
    25  	"go.chromium.org/luci/common/sync/parallel"
    26  	"go.chromium.org/luci/gae/service/datastore"
    27  
    28  	"go.chromium.org/luci/cv/internal/changelist"
    29  	"go.chromium.org/luci/cv/internal/common"
    30  )
    31  
    32  // maxLoadCLBatchSize limits how many CL entities are loaded at once for
    33  // notifying PM.
    34  const maxLoadCLBatchSize = 100
    35  
    36  func (p *Poller) notifyOnMatchedCLs(ctx context.Context, luciProject, host string, changes []*gerritpb.ChangeInfo, forceNotifyPM bool, requester changelist.UpdateCLTask_Requester) error {
    37  	if len(changes) == 0 {
    38  		return nil
    39  	}
    40  	// TODO(tandrii): optimize by checking if CV is interested in the
    41  	// (host,project,ref) of these changes from before triggering tasks.
    42  	logging.Debugf(ctx, "scheduling %d CLUpdate tasks (forceNotifyPM: %t)", len(changes), forceNotifyPM)
    43  
    44  	if forceNotifyPM {
    45  		changeNumbers := make([]int64, len(changes))
    46  		for i, c := range changes {
    47  			changeNumbers[i] = c.GetNumber()
    48  		}
    49  		if err := p.notifyPMifKnown(ctx, luciProject, host, changeNumbers, maxLoadCLBatchSize); err != nil {
    50  			return err
    51  		}
    52  	}
    53  
    54  	errs := parallel.WorkPool(min(10, len(changes)), func(work chan<- func() error) {
    55  		for _, c := range changes {
    56  			payload := &changelist.UpdateCLTask{
    57  				LuciProject: luciProject,
    58  				ExternalId:  string(changelist.MustGobID(host, c.GetNumber())),
    59  				Hint:        &changelist.UpdateCLTask_Hint{ExternalUpdateTime: c.GetUpdated()},
    60  				Requester:   requester,
    61  			}
    62  			work <- func() error {
    63  				return p.clUpdater.Schedule(ctx, payload)
    64  			}
    65  		}
    66  	})
    67  	return common.MostSevereError(errs)
    68  }
    69  
    70  func (p *Poller) notifyOnUnmatchedCLs(ctx context.Context, luciProject, host string, changes []int64, requester changelist.UpdateCLTask_Requester) error {
    71  	if len(changes) == 0 {
    72  		return nil
    73  	}
    74  	logging.Debugf(ctx, "notifying CL Updater and PM on %d no longer matched CLs", len(changes))
    75  
    76  	if err := p.notifyPMifKnown(ctx, luciProject, host, changes, maxLoadCLBatchSize); err != nil {
    77  		return err
    78  	}
    79  	errs := parallel.WorkPool(min(10, len(changes)), func(work chan<- func() error) {
    80  		for i, c := range changes {
    81  			payload := &changelist.UpdateCLTask{
    82  				LuciProject: luciProject,
    83  				ExternalId:  string(changelist.MustGobID(host, c)),
    84  				Requester:   requester,
    85  			}
    86  			// Distribute these tasks in time to avoid high peaks (e.g. see
    87  			// https://crbug.com/1211057).
    88  			delay := (fullPollInterval * time.Duration(i)) / time.Duration(len(changes))
    89  			work <- func() error {
    90  				return p.clUpdater.ScheduleDelayed(ctx, payload, delay)
    91  			}
    92  		}
    93  	})
    94  	return common.MostSevereError(errs)
    95  }
    96  
    97  // notifyPMifKnown notifies PM to update its CLs for each Gerrit Change
    98  // with existing CL entity.
    99  //
   100  // For Gerrit Changes without a CL entity, either:
   101  //   - the Gerrit CL Updater will create it in the future and hence also notify
   102  //     the PM;
   103  //   - or if the Gerrit CL updater doesn't do it, then there is no point
   104  //     notifying the PM anyway.
   105  //
   106  // Obtains EVersion of each CL before notify a PM. Unfortunately, this loads a
   107  // lot of information we don't need, such as Snapshot. So, load CLs in batches
   108  // to avoid large memory footprint.
   109  //
   110  // In practice, most of these CLs would be already dscache-ed, so loading them
   111  // is fast.
   112  func (p *Poller) notifyPMifKnown(ctx context.Context, luciProject, host string, changes []int64, maxBatchSize int) error {
   113  	eids := make([]changelist.ExternalID, len(changes))
   114  	for i, c := range changes {
   115  		eids[i] = changelist.MustGobID(host, c)
   116  	}
   117  	clids, err := changelist.Lookup(ctx, eids)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	cls := make([]*changelist.CL, 0, maxBatchSize)
   123  	flush := func() error {
   124  		if err := datastore.Get(ctx, cls); err != nil {
   125  			return errors.Annotate(common.MostSevereError(err), "failed to load CLs").Tag(transient.Tag).Err()
   126  		}
   127  		return p.pm.NotifyCLsUpdated(ctx, luciProject, changelist.ToUpdatedEvents(cls...))
   128  	}
   129  
   130  	for _, clid := range clids {
   131  		switch l := len(cls); {
   132  		case clid == 0:
   133  		case l == maxBatchSize:
   134  			if err := flush(); err != nil {
   135  				return err
   136  			}
   137  			cls = cls[:0]
   138  		default:
   139  			cls = append(cls, &changelist.CL{ID: clid})
   140  		}
   141  	}
   142  	if len(cls) > 0 {
   143  		return flush()
   144  	}
   145  	return nil
   146  }