1
0
mirror of https://github.com/bitwarden/server synced 2026-01-31 00:33:17 +00:00

First pass at semaphore

This commit is contained in:
Thomas Rittson
2025-12-30 14:21:38 +10:00
parent 34b4dc3985
commit f069fafea1
13 changed files with 785 additions and 2 deletions

View File

@@ -368,6 +368,29 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
return;
}
await using var connection = new SqlConnection(ConnectionString);
await connection.OpenAsync();
var organizationUserIdsJson = JsonSerializer.Serialize(organizationUserIds);
await connection.ExecuteAsync(
"[dbo].[Collection_UpsertDefaultCollections]",
new
{
OrganizationId = organizationId,
DefaultCollectionName = defaultCollectionName,
OrganizationUserIdsJson = organizationUserIdsJson
},
commandType: CommandType.StoredProcedure);
}
public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
{
organizationUserIds = organizationUserIds.ToList();
if (!organizationUserIds.Any())
{
return;
}
await using var connection = new SqlConnection(ConnectionString);
connection.Open();
await using var transaction = connection.BeginTransaction();
@@ -384,6 +407,17 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
return;
}
// CRITICAL: Insert semaphore entries BEFORE collections
// TODO: this will result in a creation date of the semaphore AFTER that of the collection, which is weird
var now = DateTime.UtcNow;
var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore
{
OrganizationId = organizationId,
OrganizationUserId = c.OrganizationUserId,
CreationDate = now
}).ToList();
await BulkInsertDefaultCollectionSemaphoresAsync(connection, transaction, semaphores);
await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections);
await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers);
@@ -455,6 +489,56 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
return (collectionUsers, collections);
}
private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection connection, SqlTransaction transaction, List<DefaultCollectionSemaphore> semaphores)
{
if (!semaphores.Any())
{
return;
}
// Sort by composite key to reduce deadlocks
var sortedSemaphores = semaphores
.OrderBy(s => s.OrganizationId)
.ThenBy(s => s.OrganizationUserId)
.ToList();
using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction);
bulkCopy.DestinationTableName = "[dbo].[DefaultCollectionSemaphore]";
bulkCopy.BatchSize = 500;
bulkCopy.BulkCopyTimeout = 120;
bulkCopy.EnableStreaming = true;
var dataTable = new DataTable("DefaultCollectionSemaphoreDataTable");
var organizationIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationId), typeof(Guid));
dataTable.Columns.Add(organizationIdColumn);
var organizationUserIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationUserId), typeof(Guid));
dataTable.Columns.Add(organizationUserIdColumn);
var creationDateColumn = new DataColumn(nameof(DefaultCollectionSemaphore.CreationDate), typeof(DateTime));
dataTable.Columns.Add(creationDateColumn);
foreach (DataColumn col in dataTable.Columns)
{
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
var keys = new DataColumn[2];
keys[0] = organizationIdColumn;
keys[1] = organizationUserIdColumn;
dataTable.PrimaryKey = keys;
foreach (var semaphore in sortedSemaphores)
{
var row = dataTable.NewRow();
row[organizationIdColumn] = semaphore.OrganizationId;
row[organizationUserIdColumn] = semaphore.OrganizationUserId;
row[creationDateColumn] = semaphore.CreationDate;
dataTable.Rows.Add(row);
}
await bulkCopy.WriteToServerAsync(dataTable);
}
public class CollectionWithGroupsAndUsers : Collection
{
public CollectionWithGroupsAndUsers() { }