Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding fixing save chat session overwriting #6457

Merged
merged 3 commits into from
Dec 27, 2024

Conversation

arafatkatze
Copy link
Contributor

@arafatkatze arafatkatze commented Dec 24, 2024

Linear Issue

Problem

Previously, whenever a user deleted a chat, disposeChat would call clearAndRestartSession(), which in turn triggered saveSession(). This saved the just-deleted chat back into local storage, negating the deletion.

Solution

Introduce a skipSave boolean to conditionally skip the clearAndRestartSession() flow during deletion. If skipSave is true (e.g., for a deletion), clearAndRestartSession() is not called. This prevents the race condition of re-saving an already-deleted chat.

Why It Works

1.	Avoid Conflicts – If the user explicitly wants to remove a chat, we no longer re-create or re-save the session.
2.	Maintain Clean State – Normal operations (skipSave = false) still clear and restart sessions. Deletions (skipSave = true) skip that path to maintain data consistency.
3.	Prevent Race Conditions – The skip logic ensures no stale data overwrites the freshly updated local storage.

Using the skipSave flag to conditionally skip clearAndRestartSession() is safe because:

  1. During normal operations (when skipSave is false), you want to clear and restart the session to maintain a clean state

  2. During deletion (when skipSave is true), you want to skip this step because:
    The chat is being deleted anyway
    clearAndRestartSession() includes a saveSession() call which could race with the deletion

  3. There's no need to create a new session when we're removing the chat
    The condition helps prevent potential issues where:

  • The deletion operation is trying to remove chat history
  • While simultaneously, clearAndRestartSession() might try to save the session
  • Which could lead to inconsistent state or race conditions

So yes, using the skipSave flag to conditionally skip clearAndRestartSession() during deletion is a good practice for maintaining data consistency.

Test plan

Run this code in Vscode/Jetbrains debugger and delete a chat from history

Changelog

Copy link
Contributor

@abeatrix abeatrix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this! Left a comment in line, will do a proper review once I'm back on my computer later.

Would be great if we can have a test covering this behavior!

@@ -446,8 +446,10 @@ export class ChatsController implements vscode.Disposable {
return
}

// For single chat deletion
await chatHistory.deleteChat(authStatus, chatID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't looked into the code yet but wonder if it would work if we dispose the chat before we deleting the chat?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great if we can have a test covering this behavior!

So I could possibly write some kind of "number of times called" kind of test on chatcontroller but I would ideally love to have webview based UI test(see slack)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if it would work if we dispose the chat before we deleting the chat?

While disposing before deleting seems like a good approach to prevent race conditions, it doesn't fully solve the problem. Here's why:

private async clearHistory(chatID?: string): Promise<void> {
    // ... other code ...

    // Even if we do this:
    this.disposeChat(chatID, true)  // Step 1: Dispose first
    await chatHistory.deleteChat(authStatus, chatID)  // Step 2: Then delete

    // The issue is that disposeChat() itself triggers a save operation:
    // 1. When disposing the chat controller
    // 2. When clearing/restarting the session

The race condition still exists because:
disposeChat() triggers a save operation as part of its cleanup
Then we immediately try to delete the chat history
These operations can overlap since saves are asynchronous
That's why the current solution uses a skipSave flag:

this.disposeChat(chatID, true, { skipSave: true }) // Prevents the save during disposal
await chatHistory.deleteChat(authStatus, chatID)

This ensures we're not trying to save and delete the same chat data simultaneously. Simply changing the order of operations (dispose then delete) isn't enough to prevent the race condition.

@arafatkatze arafatkatze enabled auto-merge (squash) December 27, 2024 18:32
Copy link
Contributor

@abeatrix abeatrix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@@ -34,3 +34,41 @@ test.extend<ExpectedV2Events>({
await expect(newHistoryItem).toBeVisible()
await newHistoryItem.click()
})

test.extend<ExpectedV2Events>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

@arafatkatze arafatkatze merged commit 1234884 into main Dec 27, 2024
20 of 21 checks passed
@arafatkatze arafatkatze deleted the fix-history-deletion-method branch December 27, 2024 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants