go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/prjmanager/state/state.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 state 16 17 import ( 18 "go.chromium.org/luci/cv/internal/configs/prjcfg" 19 "go.chromium.org/luci/cv/internal/gerrit/cfgmatcher" 20 "go.chromium.org/luci/cv/internal/prjmanager/prjpb" 21 ) 22 23 // State is a state of Project Manager. 24 // 25 // The state object must not be re-used except for serializing public state 26 // after its public methods returned a modified State or an error. 27 // This allows for efficient evolution of cached helper datastructures which 28 // would otherwise have to be copied, too. 29 // 30 // To illustrate correct and incorrect usages: 31 // 32 // s0 := &State{...} 33 // s1, _, err := s0.Mut1() 34 // if err != nil { 35 // // ... := s0.Mut2() // NOT OK, 2nd call on s0 36 // return proto.Marshal(s0.PB) // OK 37 // } 38 // // ... := s0.Mut2() // NOT OK, 2nd call on s0 39 // s2, _, err := s1.Mut2() // OK, s1 may be s0 if Mut1() was noop 40 // if err != nil { 41 // // return proto.Marshal(s0.PB) // OK 42 // return proto.Marshal(s0.PB) // OK 43 // } 44 type State struct { 45 // PB is the serializable part of State mutated using copy-on-write approach 46 // https://en.wikipedia.org/wiki/Copy-on-write 47 PB *prjpb.PState 48 49 // LogReasons is append-only accumulation of reasons to record this state for 50 // posterity. 51 LogReasons []prjpb.LogReason 52 53 // Helper private fields used during mutations. 54 55 // alreadyCloned is set to true after state is cloned to prevent incorrect 56 // usage. 57 alreadyCloned bool 58 // configGroups are cacehd config groups. 59 configGroups []*prjcfg.ConfigGroup 60 // cfgMatcher is lazily created, cached, and passed on to State clones. 61 cfgMatcher *cfgmatcher.Matcher 62 // pclIndex provides O(1) check if PCL exists for a CL. 63 // 64 // lazily created, see ensurePCLIndex(). 65 pclIndex pclIndex // CLID => index in PB.Pcls slice. 66 } 67 68 // cloneShallow returns cloned state ready for in-place mutation. 69 func (s *State) cloneShallow(reasons ...prjpb.LogReason) *State { 70 ret := &State{} 71 *ret = *s 72 if len(reasons) > 0 { 73 ret.LogReasons = append(ret.LogReasons, reasons...) 74 } 75 76 // Don't use proto.merge to avoid deep copy. 77 ret.PB = &prjpb.PState{ 78 LuciProject: s.PB.GetLuciProject(), 79 Status: s.PB.GetStatus(), 80 ConfigHash: s.PB.GetConfigHash(), 81 ConfigGroupNames: s.PB.GetConfigGroupNames(), 82 Pcls: s.PB.GetPcls(), 83 Components: s.PB.GetComponents(), 84 RepartitionRequired: s.PB.GetRepartitionRequired(), 85 CreatedPruns: s.PB.GetCreatedPruns(), 86 NextEvalTime: s.PB.GetNextEvalTime(), 87 PurgingCls: s.PB.GetPurgingCls(), 88 TriggeringClDeps: s.PB.GetTriggeringClDeps(), 89 } 90 91 s.alreadyCloned = true 92 return ret 93 } 94 95 func (s *State) ensureNotYetCloned() { 96 if s.alreadyCloned { 97 panic("Incorrect use. This State object has already been cloned. See State doc") 98 } 99 } 100 101 // UpgradeIfNecessary upgrades old state to new format if necessary. 102 // 103 // Returns the new state, or this state if nothing was changed. 104 func (s *State) UpgradeIfNecessary() *State { 105 if !s.needUpgrade() { 106 return s 107 } 108 s = s.cloneShallow() 109 s.upgrade() 110 return s 111 }