mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-08 04:54:32 +00:00
avoid race conditions in meta / instance insert
This commit is contained in:
parent
c0ead9cf11
commit
3e7ab07b3c
2 changed files with 37 additions and 41 deletions
|
@ -5,15 +5,13 @@
|
||||||
|
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { DataSource, QueryFailedError } from 'typeorm';
|
|
||||||
import type { InstancesRepository } from '@/models/_.js';
|
import type { InstancesRepository } from '@/models/_.js';
|
||||||
import { MiInstance } from '@/models/Instance.js';
|
import type { MiInstance } from '@/models/Instance.js';
|
||||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FederatedInstanceService implements OnApplicationShutdown {
|
export class FederatedInstanceService implements OnApplicationShutdown {
|
||||||
|
@ -26,9 +24,6 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
||||||
@Inject(DI.instancesRepository)
|
@Inject(DI.instancesRepository)
|
||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
@Inject(DI.db)
|
|
||||||
private readonly db: DataSource,
|
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
|
@ -58,11 +53,11 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
||||||
const cached = await this.federatedInstanceCache.get(host);
|
const cached = await this.federatedInstanceCache.get(host);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
return await this.db.transaction(async tem => {
|
let index = await this.instancesRepository.findOneBy({ host });
|
||||||
let index = await tem.findOneBy(MiInstance, { host });
|
if (index == null) {
|
||||||
|
await this.instancesRepository.createQueryBuilder('instance')
|
||||||
if (index == null) {
|
.insert()
|
||||||
await tem.insert(MiInstance, {
|
.values({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
host,
|
host,
|
||||||
firstRetrievedAt: new Date(),
|
firstRetrievedAt: new Date(),
|
||||||
|
@ -71,14 +66,15 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
||||||
isMediaSilenced: this.utilityService.isMediaSilencedHost(host),
|
isMediaSilenced: this.utilityService.isMediaSilencedHost(host),
|
||||||
isAllowListed: this.utilityService.isAllowListedHost(host),
|
isAllowListed: this.utilityService.isAllowListedHost(host),
|
||||||
isBubbled: this.utilityService.isBubbledHost(host),
|
isBubbled: this.utilityService.isBubbledHost(host),
|
||||||
});
|
})
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
|
||||||
index = await tem.findOneByOrFail(MiInstance, { host });
|
index = await this.instancesRepository.findOneByOrFail({ host });
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.federatedInstanceCache.set(host, index);
|
await this.federatedInstanceCache.set(host, index);
|
||||||
return index;
|
return index;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -14,6 +14,7 @@ import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { MiInstance } from '@/models/Instance.js';
|
import { MiInstance } from '@/models/Instance.js';
|
||||||
import { diffArrays } from '@/misc/diff-arrays.js';
|
import { diffArrays } from '@/misc/diff-arrays.js';
|
||||||
|
import type { MetasRepository } from '@/models/_.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -28,6 +29,9 @@ export class MetaService implements OnApplicationShutdown {
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@Inject(DI.metasRepository)
|
||||||
|
private readonly metasRepository: MetasRepository,
|
||||||
|
|
||||||
private featuredService: FeaturedService,
|
private featuredService: FeaturedService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
) {
|
) {
|
||||||
|
@ -69,35 +73,31 @@ export class MetaService implements OnApplicationShutdown {
|
||||||
public async fetch(noCache = false): Promise<MiMeta> {
|
public async fetch(noCache = false): Promise<MiMeta> {
|
||||||
if (!noCache && this.cache) return this.cache;
|
if (!noCache && this.cache) return this.cache;
|
||||||
|
|
||||||
return await this.db.transaction(async transactionalEntityManager => {
|
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
|
||||||
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
|
let meta = await this.metasRepository.findOne({
|
||||||
const metas = await transactionalEntityManager.find(MiMeta, {
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!meta) {
|
||||||
|
await this.metasRepository.createQueryBuilder('meta')
|
||||||
|
.insert()
|
||||||
|
.values({
|
||||||
|
id: 'x',
|
||||||
|
})
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
meta = await this.metasRepository.findOneOrFail({
|
||||||
order: {
|
order: {
|
||||||
id: 'DESC',
|
id: 'DESC',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const meta = metas[0];
|
this.cache = meta;
|
||||||
|
return meta;
|
||||||
if (meta) {
|
|
||||||
this.cache = meta;
|
|
||||||
return meta;
|
|
||||||
} else {
|
|
||||||
// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
|
|
||||||
const saved = await transactionalEntityManager
|
|
||||||
.upsert(
|
|
||||||
MiMeta,
|
|
||||||
{
|
|
||||||
id: 'x',
|
|
||||||
},
|
|
||||||
['id'],
|
|
||||||
)
|
|
||||||
.then((x) => transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0]));
|
|
||||||
|
|
||||||
this.cache = saved;
|
|
||||||
return saved;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
Loading…
Add table
Reference in a new issue