(t time.Time)
| 168 | } |
| 169 | |
| 170 | func (e *Executor) runOnce(t time.Time) Stats { |
| 171 | stats := Stats{ |
| 172 | Transitions: make(map[uuid.UUID]database.WorkspaceTransition), |
| 173 | Errors: make(map[uuid.UUID]error), |
| 174 | } |
| 175 | // we build the map of transitions concurrently, so need a mutex to serialize writes to the map |
| 176 | statsMu := sync.Mutex{} |
| 177 | defer func() { |
| 178 | stats.Elapsed = time.Since(t) |
| 179 | }() |
| 180 | currentTick := t.Truncate(time.Minute) |
| 181 | |
| 182 | // TTL is set at the workspace level, and deadline at the workspace build level. |
| 183 | // When a workspace build is created, its deadline initially starts at zero. |
| 184 | // When provisionerd successfully completes a provision job, the deadline is |
| 185 | // set to now + TTL if the associated workspace has a TTL set. This deadline |
| 186 | // is what we compare against when performing autostop operations, rounded down |
| 187 | // to the minute. |
| 188 | // |
| 189 | // NOTE: If a workspace build is created with a given TTL and then the user either |
| 190 | // changes or unsets the TTL, the deadline for the workspace build will not |
| 191 | // have changed. This behavior is as expected per #2229. |
| 192 | workspaces, err := e.db.GetWorkspacesEligibleForTransition(e.ctx, currentTick) |
| 193 | if err != nil { |
| 194 | e.log.Error(e.ctx, "get workspaces for autostart or autostop", slog.Error(err)) |
| 195 | return stats |
| 196 | } |
| 197 | |
| 198 | // Sort the workspaces by build template version ID so that we can group |
| 199 | // identical template versions together. This is a slight (and imperfect) |
| 200 | // optimization. |
| 201 | // |
| 202 | // `wsbuilder` needs to load the terraform files for a given template version |
| 203 | // into memory. If 2 workspaces are using the same template version, they will |
| 204 | // share the same files in the FileCache. This only happens if the builds happen |
| 205 | // in parallel. |
| 206 | // TODO: Actually make sure the cache has the files in the cache for the full |
| 207 | // set of identical template versions. Then unload the files when the builds |
| 208 | // are done. Right now, this relies on luck for the 10 goroutine workers to |
| 209 | // overlap and keep the file reference in the cache alive. |
| 210 | slices.SortFunc(workspaces, func(a, b database.GetWorkspacesEligibleForTransitionRow) int { |
| 211 | return strings.Compare(a.BuildTemplateVersionID.UUID.String(), b.BuildTemplateVersionID.UUID.String()) |
| 212 | }) |
| 213 | |
| 214 | // We only use errgroup here for convenience of API, not for early |
| 215 | // cancellation. This means we only return nil errors in th eg.Go. |
| 216 | eg := errgroup.Group{} |
| 217 | // Limit the concurrency to avoid overloading the database. |
| 218 | eg.SetLimit(10) |
| 219 | |
| 220 | for _, ws := range workspaces { |
| 221 | wsID := ws.ID |
| 222 | wsName := ws.Name |
| 223 | log := e.log.With( |
| 224 | slog.F("workspace_id", wsID), |
| 225 | slog.F("workspace_name", wsName), |
| 226 | ) |
| 227 |
no test coverage detected