mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-02-15 18:21:37 +00:00
Prevent race conditions in chat storage with semaphores
This commit is contained in:
parent
ff01ed6a3c
commit
aabbb5c2c7
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@ -13,6 +14,19 @@ public static class WorkspaceBehaviour
|
|||||||
{
|
{
|
||||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Semaphores for synchronizing chat storage operations per chat.
|
||||||
|
/// This prevents race conditions when multiple threads try to write
|
||||||
|
/// the same chat file simultaneously.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<string, SemaphoreSlim> CHAT_STORAGE_SEMAPHORES = new();
|
||||||
|
|
||||||
|
private static SemaphoreSlim GetChatSemaphore(Guid workspaceId, Guid chatId)
|
||||||
|
{
|
||||||
|
var key = $"{workspaceId}_{chatId}";
|
||||||
|
return CHAT_STORAGE_SEMAPHORES.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
public static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
@ -36,6 +50,12 @@ public static class WorkspaceBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async Task StoreChat(ChatThread chat)
|
public static async Task StoreChat(ChatThread chat)
|
||||||
|
{
|
||||||
|
// Acquire the semaphore for this specific chat to prevent concurrent writes to the same file:
|
||||||
|
var semaphore = GetChatSemaphore(chat.WorkspaceId, chat.ChatId);
|
||||||
|
await semaphore.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
string chatDirectory;
|
string chatDirectory;
|
||||||
if (chat.WorkspaceId == Guid.Empty)
|
if (chat.WorkspaceId == Guid.Empty)
|
||||||
@ -54,8 +74,19 @@ public static class WorkspaceBehaviour
|
|||||||
var chatPath = Path.Join(chatDirectory, "thread.json");
|
var chatPath = Path.Join(chatDirectory, "thread.json");
|
||||||
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<ChatThread?> LoadChat(LoadChat loadChat)
|
public static async Task<ChatThread?> LoadChat(LoadChat loadChat)
|
||||||
|
{
|
||||||
|
// Acquire the semaphore for this specific chat to prevent concurrent read/writes to the same file:
|
||||||
|
var semaphore = GetChatSemaphore(loadChat.WorkspaceId, loadChat.ChatId);
|
||||||
|
await semaphore.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
||||||
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
||||||
@ -64,8 +95,6 @@ public static class WorkspaceBehaviour
|
|||||||
if(!Directory.Exists(chatPath))
|
if(!Directory.Exists(chatPath))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
||||||
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
||||||
return chat;
|
return chat;
|
||||||
@ -74,6 +103,10 @@ public static class WorkspaceBehaviour
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> LoadWorkspaceName(Guid workspaceId)
|
public static async Task<string> LoadWorkspaceName(Guid workspaceId)
|
||||||
@ -144,8 +177,19 @@ public static class WorkspaceBehaviour
|
|||||||
else
|
else
|
||||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
||||||
|
|
||||||
|
// Acquire the semaphore to prevent deleting while another thread is writing:
|
||||||
|
var semaphore = GetChatSemaphore(workspaceId, chatId);
|
||||||
|
await semaphore.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
Directory.Delete(chatDirectory, true);
|
Directory.Delete(chatDirectory, true);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task EnsureWorkspace(Guid workspaceId, string workspaceName)
|
private static async Task EnsureWorkspace(Guid workspaceId, string workspaceName)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,3 +8,4 @@
|
|||||||
- Fixed a bug with local transcription providers by handling errors correctly when the local provider is unavailable.
|
- Fixed a bug with local transcription providers by handling errors correctly when the local provider is unavailable.
|
||||||
- Fixed a bug with local transcription providers by correctly handling empty model IDs.
|
- Fixed a bug with local transcription providers by correctly handling empty model IDs.
|
||||||
- Fixed a bug affecting the transcription preview: previously, when you stopped music or other media, recorded or dictated text, and then tried to resume playback, the media wouldn’t resume as expected. This behavior is now fixed.
|
- Fixed a bug affecting the transcription preview: previously, when you stopped music or other media, recorded or dictated text, and then tried to resume playback, the media wouldn’t resume as expected. This behavior is now fixed.
|
||||||
|
- Fixed a rare bug that occurred when multiple threads tried to manage the same chat thread.
|
||||||
Loading…
Reference in New Issue
Block a user