fix(backend): リモートサーバーの情報が更新できなくなっていた問題を修正 (#13507)
* fix(backend): fetchInstanceMetadataのLockが永遠に解除されない問題を修正 Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> * fix test * fix * comment * comment * improve test --------- Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
This commit is contained in:
parent
983480131b
commit
9542cb8d62
@ -51,21 +51,35 @@ export class FetchInstanceMetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async tryLock(host: string): Promise<boolean> {
|
// public for test
|
||||||
const mutex = await this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '1', 'GET');
|
public async tryLock(host: string): Promise<string | null> {
|
||||||
return mutex !== '1';
|
// TODO: マイグレーションなのであとで消す (2024.3.1)
|
||||||
|
this.redisClient.del(`fetchInstanceMetadata:mutex:${host}`);
|
||||||
|
|
||||||
|
return await this.redisClient.set(
|
||||||
|
`fetchInstanceMetadata:mutex:v2:${host}`, '1',
|
||||||
|
'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395
|
||||||
|
'GET' // 古い値を返す(なかったらnull)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public unlock(host: string): Promise<'OK'> {
|
// public for test
|
||||||
return this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '0');
|
public unlock(host: string): Promise<number> {
|
||||||
|
return this.redisClient.del(`fetchInstanceMetadata:mutex:v2:${host}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetchInstanceMetadata(instance: MiInstance, force = false): Promise<void> {
|
public async fetchInstanceMetadata(instance: MiInstance, force = false): Promise<void> {
|
||||||
const host = instance.host;
|
const host = instance.host;
|
||||||
// Acquire mutex to ensure no parallel runs
|
|
||||||
if (!await this.tryLock(host)) return;
|
// finallyでunlockされてしまうのでtry内でロックチェックをしない
|
||||||
|
// (returnであってもfinallyは実行される)
|
||||||
|
if (!force && await this.tryLock(host) === '1') {
|
||||||
|
// 1が返ってきていたらロックされているという意味なので、何もしない
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const _instance = await this.federatedInstanceService.fetch(host);
|
const _instance = await this.federatedInstanceService.fetch(host);
|
||||||
|
@ -56,6 +56,7 @@ describe('FetchInstanceMetadataService', () => {
|
|||||||
} else if (token === DI.redis) {
|
} else if (token === DI.redis) {
|
||||||
return mockRedis;
|
return mockRedis;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
})
|
})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ describe('FetchInstanceMetadataService', () => {
|
|||||||
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
||||||
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
||||||
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
||||||
|
|
||||||
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
||||||
expect(tryLockSpy).toHaveBeenCalledTimes(1);
|
expect(tryLockSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(unlockSpy).toHaveBeenCalledTimes(1);
|
expect(unlockSpy).toHaveBeenCalledTimes(1);
|
||||||
@ -92,6 +94,7 @@ describe('FetchInstanceMetadataService', () => {
|
|||||||
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
||||||
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
||||||
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
||||||
|
|
||||||
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
||||||
expect(tryLockSpy).toHaveBeenCalledTimes(1);
|
expect(tryLockSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(unlockSpy).toHaveBeenCalledTimes(1);
|
expect(unlockSpy).toHaveBeenCalledTimes(1);
|
||||||
@ -104,13 +107,30 @@ describe('FetchInstanceMetadataService', () => {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
|
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
|
||||||
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
||||||
|
await fetchInstanceMetadataService.tryLock('example.com');
|
||||||
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
||||||
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
||||||
await fetchInstanceMetadataService.tryLock('example.com');
|
|
||||||
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
||||||
expect(tryLockSpy).toHaveBeenCalledTimes(2);
|
expect(tryLockSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(unlockSpy).toHaveBeenCalledTimes(0);
|
expect(unlockSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0);
|
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0);
|
||||||
expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
|
expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Do when lock not acquired but forced', async () => {
|
||||||
|
redisClient.set = mockRedis();
|
||||||
|
const now = Date.now();
|
||||||
|
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
|
||||||
|
httpRequestService.getJson.mockImplementation(() => { throw Error(); });
|
||||||
|
await fetchInstanceMetadataService.tryLock('example.com');
|
||||||
|
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
|
||||||
|
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
|
||||||
|
|
||||||
|
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true);
|
||||||
|
expect(tryLockSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(unlockSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0);
|
||||||
|
expect(httpRequestService.getJson).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user