golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/coordinator/remote/remote.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build linux || darwin 6 7 package remote 8 9 import ( 10 "context" 11 "fmt" 12 "log" 13 "sort" 14 "sync" 15 "time" 16 17 "golang.org/x/build/buildlet" 18 "golang.org/x/build/internal" 19 ) 20 21 const ( 22 remoteBuildletIdleTimeout = 30 * time.Minute 23 remoteBuildletCleanInterval = time.Minute 24 ) 25 26 // Session stores the metadata for a remote buildlet Session. 27 type Session struct { 28 BuilderType string // default builder config to use if not overwritten 29 Created time.Time 30 Expires time.Time 31 HostType string 32 ID string // unique identifier for instance "user-bradfitz-linux-amd64-0" 33 OwnerID string // identity aware proxy user id: "accounts.google.com:userIDvalue" 34 buildlet buildlet.Client 35 } 36 37 // renew extends the expiration timestamp for a session. 38 // The SessionPool lock should be held before calling. 39 func (s *Session) renew() { 40 s.Expires = time.Now().Add(remoteBuildletIdleTimeout) 41 } 42 43 // isExpired determines if the remote buildlet session has expired. 44 // The SessionPool lock should be held before calling. 45 func (s *Session) isExpired() bool { 46 return !s.Expires.IsZero() && s.Expires.Before(time.Now()) 47 } 48 49 // SessionPool contains active remote buildlet sessions. 50 type SessionPool struct { 51 mu sync.RWMutex 52 53 once sync.Once 54 pollWait sync.WaitGroup 55 cancelPoll context.CancelFunc 56 m map[string]*Session // keyed by buildletName 57 } 58 59 // NewSessionPool creates a session pool which stores and provides access to active remote buildlet sessions. 60 // Either cancelling the context or calling close on the session pool will terminate any polling functions. 61 func NewSessionPool(ctx context.Context) *SessionPool { 62 ctx, cancel := context.WithCancel(ctx) 63 sp := &SessionPool{ 64 cancelPoll: cancel, 65 m: map[string]*Session{}, 66 } 67 sp.pollWait.Add(1) 68 go func() { 69 internal.PeriodicallyDo(ctx, remoteBuildletCleanInterval, func(ctx context.Context, _ time.Time) { 70 sp.destroyExpiredSessions(ctx) 71 }) 72 sp.pollWait.Done() 73 }() 74 return sp 75 } 76 77 // AddSession adds the provided session to the session pool. 78 func (sp *SessionPool) AddSession(ownerID, username, builderType, hostType string, bc buildlet.Client) (name string) { 79 sp.mu.Lock() 80 defer sp.mu.Unlock() 81 82 for n := 0; ; n++ { 83 name = fmt.Sprintf("%s-%s-%d", username, builderType, n) 84 if _, ok := sp.m[name]; !ok { 85 now := time.Now() 86 sp.m[name] = &Session{ 87 BuilderType: builderType, 88 buildlet: bc, 89 Created: now, 90 Expires: now.Add(remoteBuildletIdleTimeout), 91 HostType: hostType, 92 ID: name, 93 OwnerID: ownerID, 94 } 95 return name 96 } 97 } 98 } 99 100 // IsSession is true if the instance is found in the session pool. The instance name is the not the public 101 // name of the instance. It is the name of the instance as it is tracked in the cloud service. 102 func (sp *SessionPool) IsSession(instName string) bool { 103 sp.mu.RLock() 104 defer sp.mu.RUnlock() 105 106 for _, s := range sp.m { 107 if s.buildlet.InstanceName() == instName { 108 return true 109 } 110 } 111 return false 112 } 113 114 // destroyExpiredSessions destroys all sessions which have expired. 115 func (sp *SessionPool) destroyExpiredSessions(ctx context.Context) { 116 sp.mu.Lock() 117 var ss []*Session 118 for name, s := range sp.m { 119 if s.isExpired() { 120 ss = append(ss, s) 121 delete(sp.m, name) 122 } 123 } 124 sp.mu.Unlock() 125 // the sessions are no longer in the map. They can be mutated. 126 for _, s := range ss { 127 log.Printf("remote: destroying expired buildlet %s", s.ID) 128 if err := s.buildlet.Close(); err != nil { 129 log.Printf("remote: unable to close buildlet connection for %s: %s", s.ID, err) 130 } 131 } 132 } 133 134 // DestroySession destroys a session. 135 func (sp *SessionPool) DestroySession(buildletName string) error { 136 sp.mu.Lock() 137 s, ok := sp.m[buildletName] 138 if ok { 139 delete(sp.m, buildletName) 140 } 141 sp.mu.Unlock() 142 if !ok { 143 return fmt.Errorf("remote buildlet does not exist=%s", buildletName) 144 } 145 if err := s.buildlet.Close(); err != nil { 146 log.Printf("remote: unable to close buildlet connection %s: %s", buildletName, err) 147 } 148 return nil 149 } 150 151 // Close cancels the polling performed by the session pool. It waits for polling to conclude 152 // before returning. 153 func (sp *SessionPool) Close() { 154 sp.once.Do(func() { 155 sp.cancelPoll() 156 sp.pollWait.Wait() 157 }) 158 } 159 160 // List returns a list of all active sessions sorted by session ID. 161 func (sp *SessionPool) List() []*Session { 162 sp.mu.RLock() 163 defer sp.mu.RUnlock() 164 165 var ss []*Session 166 for _, s := range sp.m { 167 ss = append(ss, &Session{ 168 BuilderType: s.BuilderType, 169 Expires: s.Expires, 170 HostType: s.HostType, 171 ID: s.ID, 172 OwnerID: s.OwnerID, 173 Created: s.Created, 174 }) 175 } 176 sort.Slice(ss, func(i, j int) bool { return ss[i].ID < ss[j].ID }) 177 return ss 178 } 179 180 // Len gives a count of how many sessions are in the pool. 181 func (sp *SessionPool) Len() int { 182 sp.mu.RLock() 183 defer sp.mu.RUnlock() 184 185 return len(sp.m) 186 } 187 188 // Session retrieves information about the instance associated with a session from the pool. 189 func (sp *SessionPool) Session(buildletName string) (*Session, error) { 190 sp.mu.Lock() 191 defer sp.mu.Unlock() 192 193 if s, ok := sp.m[buildletName]; ok { 194 s.renew() 195 return &Session{ 196 BuilderType: s.BuilderType, 197 Expires: s.Expires, 198 HostType: s.HostType, 199 ID: s.ID, 200 OwnerID: s.OwnerID, 201 }, nil 202 } 203 return nil, fmt.Errorf("remote buildlet does not exist=%s", buildletName) 204 } 205 206 // BuildletClient returns the buildlet client associated with the Session. 207 func (sp *SessionPool) BuildletClient(buildletName string) (buildlet.Client, error) { 208 sp.mu.RLock() 209 defer sp.mu.RUnlock() 210 211 s, ok := sp.m[buildletName] 212 if !ok { 213 return nil, fmt.Errorf("remote buildlet does not exist=%s", buildletName) 214 } 215 return s.buildlet, nil 216 } 217 218 // KeepAlive will renew the remote buildlet session by extending the expiration value. It will 219 // periodically extend the value until the provided context has been cancelled. 220 func (sp *SessionPool) KeepAlive(ctx context.Context, buildletName string) error { 221 sp.mu.Lock() 222 defer sp.mu.Unlock() 223 224 s, ok := sp.m[buildletName] 225 if !ok { 226 return fmt.Errorf("remote buildlet does not exist=%s", buildletName) 227 } 228 s.renew() 229 go internal.PeriodicallyDo(ctx, time.Minute, func(ctx context.Context, _ time.Time) { 230 sp.mu.Lock() 231 ses, ok := sp.m[buildletName] 232 if !ok { 233 log.Printf("remote: KeepAlive unable to retrieve %s in order to renew the timeout", buildletName) 234 return 235 } 236 ses.renew() 237 sp.mu.Unlock() 238 }) 239 return nil 240 } 241 242 // RenewTimeout will renew the remote buildlet session by extending the expiration value. 243 func (sp *SessionPool) RenewTimeout(buildletName string) error { 244 sp.mu.Lock() 245 defer sp.mu.Unlock() 246 247 s, ok := sp.m[buildletName] 248 if !ok { 249 return fmt.Errorf("remote buildlet does not exist=%s", buildletName) 250 } 251 s.renew() 252 return nil 253 }