Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(backend): 古いユーザーキャッシュを使うことへの対策 #13453

Merged
merged 1 commit into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions packages/backend/src/core/AccountMoveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { CacheService } from '@/core/CacheService.js';
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { MetaService } from '@/core/MetaService.js';
Expand Down Expand Up @@ -60,7 +59,6 @@ export class AccountMoveService {
private instanceChart: InstanceChart,
private metaService: MetaService,
private relayService: RelayService,
private cacheService: CacheService,
private queueService: QueueService,
) {
}
Expand All @@ -84,7 +82,7 @@ export class AccountMoveService {
Object.assign(src, update);

// Update cache
this.cacheService.uriPersonCache.set(srcUri, src);
this.globalEventService.publishInternalEvent('localUserUpdated', src);

const srcPerson = await this.apRendererService.renderPerson(src);
const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src));
Expand Down
4 changes: 3 additions & 1 deletion packages/backend/src/core/CacheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,12 @@ export class CacheService implements OnApplicationShutdown {
switch (type) {
case 'userChangeSuspendedState':
case 'userChangeDeletedState':
case 'remoteUserUpdated': {
case 'remoteUserUpdated':
case 'localUserUpdated': {
const user = await this.usersRepository.findOneBy({ id: body.id });
if (user == null) {
this.userByIdCache.delete(body.id);
this.localUserByIdCache.delete(body.id);
for (const [k, v] of this.uriPersonCache.cache.entries()) {
if (v.value?.id === body.id) {
this.uriPersonCache.delete(k);
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/GlobalEventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export interface InternalEventTypes {
userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; };
remoteUserUpdated: { id: MiUser['id']; };
localUserUpdated: { id: MiUser['id']; };
follow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
Expand Down
27 changes: 9 additions & 18 deletions packages/backend/src/core/UserFollowingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,33 +101,24 @@ export class UserFollowingService implements OnModuleInit {
this.queueService.deliver(followee, content, follower.inbox, false);
}

/**
* ThinUserでなくともユーザーの情報が最新でない場合はこちらを使うべき
*/
@bindThis
public async followByThinUser(
public async follow(
_follower: ThinUser,
_followee: ThinUser,
options: Parameters<typeof this.follow>[2] = {},
) {
const [follower, followee] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: _follower.id }),
this.usersRepository.findOneByOrFail({ id: _followee.id }),
]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser];

await this.follow(follower, followee, options);
}

@bindThis
public async follow(
follower: MiLocalUser | MiRemoteUser,
followee: MiLocalUser | MiRemoteUser,
{ requestId, silent = false, withReplies }: {
requestId?: string,
silent?: boolean,
withReplies?: boolean,
} = {},
): Promise<void> {
/**
* 必ず最新のユーザー情報を取得する
*/
const [follower, followee] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: _follower.id }),
this.usersRepository.findOneByOrFail({ id: _followee.id }),
]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser];

if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isRemoteUser(followee)) {
// What?
throw new Error('Remote user cannot follow remote user.');
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/misc/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ export class RedisSingleCache<T> {
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?

export class MemoryKVCache<T> {
/**
* データを持つマップ
* @deprecated これを直接操作するべきではない
*/
public cache: Map<string, { date: number; value: T; }>;
private lifetime: number;
private gcIntervalHandle: NodeJS.Timeout;
Expand All @@ -201,6 +205,10 @@ export class MemoryKVCache<T> {
}

@bindThis
/**
* Mapにキャッシュをセットします
* @deprecated これを直接呼び出すべきではない。InternalEventなどで変更を全てのプロセス/マシンに通知するべき
*/
public set(key: string, value: T): void {
this.cache.set(key, {
date: Date.now(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class RelationshipProcessorService {
@bindThis
public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> {
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`);
await this.userFollowingService.followByThinUser(job.data.from, job.data.to, {
await this.userFollowingService.follow(job.data.from, job.data.to, {
requestId: job.data.requestId,
silent: job.data.silent,
withReplies: job.data.withReplies,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
withReplies: { type: 'boolean' }
withReplies: { type: 'boolean' },
},
required: ['userId'],
} as const;
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/server/api/endpoints/i/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.hashtagService.updateUsertags(user, tags);
//#endregion

if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates);
if (Object.keys(updates).includes('alsoKnownAs')) {
this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates });
if (Object.keys(updates).length > 0) {
await this.usersRepository.update(user.id, updates);
this.globalEventService.publishInternalEvent('localUserUpdated', { id: user.id });
}

await this.userProfilesRepository.update(user.id, {
Expand Down
Loading