Compare commits

...

2 Commits

Author SHA1 Message Date
bo0tzz 6fec82b772 fix: no lock on queue duplicates 2026-05-14 10:20:50 +02:00
bo0tzz e2e9dd425f fix: locking around concurrency-1 jobs 2026-05-13 15:18:33 +02:00
3 changed files with 167 additions and 158 deletions
+2
View File
@@ -915,6 +915,8 @@ export enum DatabaseLock {
MaintenanceOperation = 621, MaintenanceOperation = 621,
MemoryCreation = 777, MemoryCreation = 777,
VersionCheck = 800, VersionCheck = 800,
FacialRecognition = 900,
DuplicateDetection = 1000,
} }
export enum MaintenanceAction { export enum MaintenanceAction {
+3 -1
View File
@@ -5,7 +5,7 @@ import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset
import { MapAsset, mapAsset } from 'src/dtos/asset-response.dto'; import { MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { DuplicateResolveDto, DuplicateResolveGroupDto, DuplicateResponseDto } from 'src/dtos/duplicate.dto'; import { DuplicateResolveDto, DuplicateResolveGroupDto, DuplicateResponseDto } from 'src/dtos/duplicate.dto';
import { AssetStatus, AssetVisibility, JobName, JobStatus, Permission, QueueName } from 'src/enum'; import { AssetStatus, AssetVisibility, DatabaseLock, JobName, JobStatus, Permission, QueueName } from 'src/enum';
import { AssetDuplicateResult } from 'src/repositories/search.repository'; import { AssetDuplicateResult } from 'src/repositories/search.repository';
import { BaseService } from 'src/services/base.service'; import { BaseService } from 'src/services/base.service';
import { JobItem, JobOf } from 'src/types'; import { JobItem, JobOf } from 'src/types';
@@ -326,6 +326,7 @@ export class DuplicateService extends BaseService {
@OnJob({ name: JobName.AssetDetectDuplicates, queue: QueueName.DuplicateDetection }) @OnJob({ name: JobName.AssetDetectDuplicates, queue: QueueName.DuplicateDetection })
async handleSearchDuplicates({ id }: JobOf<JobName.AssetDetectDuplicates>): Promise<JobStatus> { async handleSearchDuplicates({ id }: JobOf<JobName.AssetDetectDuplicates>): Promise<JobStatus> {
return this.databaseRepository.withLock(DatabaseLock.DuplicateDetection, async () => {
const { machineLearning } = await this.getConfig({ withCache: true }); const { machineLearning } = await this.getConfig({ withCache: true });
if (!isDuplicateDetectionEnabled(machineLearning)) { if (!isDuplicateDetectionEnabled(machineLearning)) {
return JobStatus.Skipped; return JobStatus.Skipped;
@@ -380,6 +381,7 @@ export class DuplicateService extends BaseService {
await this.assetRepository.upsertJobStatus(...assetIds.map((assetId) => ({ assetId, duplicatesDetectedAt }))); await this.assetRepository.upsertJobStatus(...assetIds.map((assetId) => ({ assetId, duplicatesDetectedAt })));
return JobStatus.Success; return JobStatus.Success;
});
} }
private async updateDuplicates( private async updateDuplicates(
+5
View File
@@ -25,6 +25,7 @@ import {
import { import {
AssetVisibility, AssetVisibility,
CacheControl, CacheControl,
DatabaseLock,
JobName, JobName,
JobStatus, JobStatus,
Permission, Permission,
@@ -402,6 +403,7 @@ export class PersonService extends BaseService {
@OnJob({ name: JobName.FacialRecognitionQueueAll, queue: QueueName.FacialRecognition }) @OnJob({ name: JobName.FacialRecognitionQueueAll, queue: QueueName.FacialRecognition })
async handleQueueRecognizeFaces({ force, nightly }: JobOf<JobName.FacialRecognitionQueueAll>): Promise<JobStatus> { async handleQueueRecognizeFaces({ force, nightly }: JobOf<JobName.FacialRecognitionQueueAll>): Promise<JobStatus> {
return this.databaseRepository.withLock(DatabaseLock.FacialRecognition, async () => {
const { machineLearning } = await this.getConfig({ withCache: false }); const { machineLearning } = await this.getConfig({ withCache: false });
if (!isFacialRecognitionEnabled(machineLearning)) { if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.Skipped; return JobStatus.Skipped;
@@ -456,10 +458,12 @@ export class PersonService extends BaseService {
await this.systemMetadataRepository.set(SystemMetadataKey.FacialRecognitionState, { lastRun }); await this.systemMetadataRepository.set(SystemMetadataKey.FacialRecognitionState, { lastRun });
return JobStatus.Success; return JobStatus.Success;
});
} }
@OnJob({ name: JobName.FacialRecognition, queue: QueueName.FacialRecognition }) @OnJob({ name: JobName.FacialRecognition, queue: QueueName.FacialRecognition })
async handleRecognizeFaces({ id, deferred }: JobOf<JobName.FacialRecognition>): Promise<JobStatus> { async handleRecognizeFaces({ id, deferred }: JobOf<JobName.FacialRecognition>): Promise<JobStatus> {
return this.databaseRepository.withLock(DatabaseLock.FacialRecognition, async () => {
const { machineLearning } = await this.getConfig({ withCache: true }); const { machineLearning } = await this.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) { if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.Skipped; return JobStatus.Skipped;
@@ -540,6 +544,7 @@ export class PersonService extends BaseService {
} }
return JobStatus.Success; return JobStatus.Success;
});
} }
@OnJob({ name: JobName.PersonFileMigration, queue: QueueName.Migration }) @OnJob({ name: JobName.PersonFileMigration, queue: QueueName.Migration })