ArchiveChat archives a chat family and broadcasts deleted events for each affected chat so watching clients converge without a full refetch. If the target chat is pending or running, it first transitions the chat back to waiting so active processing stops before the archive is broadcast.
(ctx context.Context, chat database.Chat)
| 2198 | // target chat is pending or running, it first transitions the chat back to |
| 2199 | // waiting so active processing stops before the archive is broadcast. |
| 2200 | func (p *Server) ArchiveChat(ctx context.Context, chat database.Chat) error { |
| 2201 | if chat.ID == uuid.Nil { |
| 2202 | return xerrors.New("chat_id is required") |
| 2203 | } |
| 2204 | |
| 2205 | var ( |
| 2206 | archivedChats []database.Chat |
| 2207 | interruptedChats []database.Chat |
| 2208 | ) |
| 2209 | if err := p.db.InTx(func(tx database.Store) error { |
| 2210 | if _, err := tx.GetChatByIDForUpdate(ctx, chat.ID); err != nil { |
| 2211 | return xerrors.Errorf("lock chat for archive: %w", err) |
| 2212 | } |
| 2213 | |
| 2214 | var err error |
| 2215 | archivedChats, err = tx.ArchiveChatByID(ctx, chat.ID) |
| 2216 | if err != nil { |
| 2217 | return xerrors.Errorf("archive chat: %w", err) |
| 2218 | } |
| 2219 | |
| 2220 | for i, archivedChat := range archivedChats { |
| 2221 | if archivedChat.Status != database.ChatStatusPending && |
| 2222 | archivedChat.Status != database.ChatStatusRunning { |
| 2223 | continue |
| 2224 | } |
| 2225 | |
| 2226 | updatedChat, updateErr := tx.UpdateChatStatus(ctx, database.UpdateChatStatusParams{ |
| 2227 | ID: archivedChat.ID, |
| 2228 | Status: database.ChatStatusWaiting, |
| 2229 | WorkerID: uuid.NullUUID{}, |
| 2230 | StartedAt: sql.NullTime{}, |
| 2231 | HeartbeatAt: sql.NullTime{}, |
| 2232 | LastError: pqtype.NullRawMessage{}, |
| 2233 | }) |
| 2234 | if updateErr != nil { |
| 2235 | return xerrors.Errorf("set archived chat waiting before cleanup: %w", updateErr) |
| 2236 | } |
| 2237 | archivedChats[i] = updatedChat |
| 2238 | interruptedChats = append(interruptedChats, updatedChat) |
| 2239 | } |
| 2240 | return nil |
| 2241 | }, nil); err != nil { |
| 2242 | return err |
| 2243 | } |
| 2244 | |
| 2245 | for _, interruptedChat := range interruptedChats { |
| 2246 | p.publishStatus(interruptedChat.ID, interruptedChat.Status, interruptedChat.WorkerID) |
| 2247 | p.publishChatPubsubEvent(interruptedChat, codersdk.ChatWatchEventKindStatusChange, nil) |
| 2248 | } |
| 2249 | |
| 2250 | // Archiving can race with an interrupted worker still flushing its |
| 2251 | // final debug writes. Retry a few times so orphaned rows are |
| 2252 | // removed quickly instead of waiting for the stale sweeper. Source |
| 2253 | // archiveCutoff from the DB-stamped updated_at returned by |
| 2254 | // ArchiveChatByID so the filter uses the same clock that stamps |
| 2255 | // replacement-turn debug rows; subtract debugCleanupClockSkew so |
| 2256 | // replica clock drift cannot let the retry delete a replacement's |
| 2257 | // debug rows if an unarchive races ahead (see the constant for the |