This commit is contained in:
tamaina 2023-04-22 15:37:17 +00:00
parent d2ea04fbf2
commit 239824c187
8 changed files with 54 additions and 37 deletions

View file

@ -135,10 +135,14 @@ id: 'aid'
# Job concurrency per worker # Job concurrency per worker
#deliverJobConcurrency: 128 #deliverJobConcurrency: 128
#inboxJobConcurrency: 16 #inboxJobConcurrency: 16
#relashionshipJobConcurrency: 16
# What's relashionshipJob?:
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
# Job rate limiter # Job rate limiter
#deliverJobPerSec: 128 #deliverJobPerSec: 128
#inboxJobPerSec: 16 #inboxJobPerSec: 16
#relashionshipJobPerSec: 64
# Job attempts # Job attempts
#deliverJobMaxAttempts: 12 #deliverJobMaxAttempts: 12

View file

@ -6,5 +6,6 @@
"files.associations": { "files.associations": {
"*.test.ts": "typescript" "*.test.ts": "typescript"
}, },
"jest.jestCommandLine": "pnpm run jest",
"jest.autoRun": "off" "jest.autoRun": "off"
} }

View file

@ -84,8 +84,10 @@ export type Source = {
deliverJobConcurrency?: number; deliverJobConcurrency?: number;
inboxJobConcurrency?: number; inboxJobConcurrency?: number;
relashionshipJobConcurrency?: number;
deliverJobPerSec?: number; deliverJobPerSec?: number;
inboxJobPerSec?: number; inboxJobPerSec?: number;
relashionshipJobPerSec?: number;
deliverJobMaxAttempts?: number; deliverJobMaxAttempts?: number;
inboxJobMaxAttempts?: number; inboxJobMaxAttempts?: number;

View file

@ -96,14 +96,14 @@ export class AccountMoveService {
const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true }); const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true });
this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj); this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
// Unfollow // Unfollow after 24 hours
const followings = await this.followingsRepository.findBy({ const followings = await this.followingsRepository.findBy({
followerId: src.id, followerId: src.id,
}); });
this.queueService.createUnfollowJob(followings.map(following => ({ this.queueService.createDelayedUnfollowJob(followings.map(following => ({
from: { id: src.id }, from: { id: src.id },
to: { id: following.followeeId }, to: { id: following.followeeId },
}))); })), process.env.NODE_ENV === 'test' ? 10000 : 1000 * 60 * 60 * 24);
await this.postMoveProcess(src, dst); await this.postMoveProcess(src, dst);

View file

@ -78,7 +78,7 @@ const $db: Provider = {
const $relationship: Provider = { const $relationship: Provider = {
provide: 'queue:relationship', provide: 'queue:relationship',
useFactory: (config: Config) => q(config, 'relationship'), useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64),
inject: [DI.config], inject: [DI.config],
}; };

View file

@ -258,6 +258,12 @@ export class QueueService {
return this.relationshipQueue.addBulk(jobs); return this.relationshipQueue.addBulk(jobs);
} }
@bindThis
public createDelayedUnfollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string }[], delay: number) {
const jobs = followings.map(rel => this.generateRelationshipJobData('unfollow', rel, { delay }));
return this.relationshipQueue.addBulk(jobs);
}
@bindThis @bindThis
public createBlockJob(blockings: { from: ThinUser, to: ThinUser, silent?: boolean }[]) { public createBlockJob(blockings: { from: ThinUser, to: ThinUser, silent?: boolean }[]) {
const jobs = blockings.map(rel => this.generateRelationshipJobData('block', rel)); const jobs = blockings.map(rel => this.generateRelationshipJobData('block', rel));
@ -271,7 +277,7 @@ export class QueueService {
} }
@bindThis @bindThis
private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData): { private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts?: Bull.JobOptions): {
name: string, name: string,
data: RelationshipJobData, data: RelationshipJobData,
opts: Bull.JobOptions, opts: Bull.JobOptions,
@ -287,6 +293,7 @@ export class QueueService {
opts: { opts: {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: true,
...opts,
}, },
}; };
} }

View file

@ -17,7 +17,7 @@ export class RelationshipQueueProcessorsService {
@bindThis @bindThis
public start(q: Bull.Queue): void { public start(q: Bull.Queue): void {
const maxJobs = (this.config.deliverJobConcurrency ?? 128) / 4; // conservative? const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job)); q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job)); q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job)); q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));

View file

@ -266,11 +266,12 @@ describe('Account Move', () => {
await sleep(1000 * 3); // wait for jobs to finish await sleep(1000 * 3); // wait for jobs to finish
// Unfollow delayed?
const aliceFollowings = await api('/users/following', { const aliceFollowings = await api('/users/following', {
userId: alice.id, userId: alice.id,
}, alice); }, alice);
assert.strictEqual(aliceFollowings.status, 200); assert.strictEqual(aliceFollowings.status, 200);
assert.strictEqual(aliceFollowings.body.length, 0); assert.strictEqual(aliceFollowings.body.length, 3);
const carolFollowings = await api('/users/following', { const carolFollowings = await api('/users/following', {
userId: carol.id, userId: carol.id,
@ -304,16 +305,35 @@ describe('Account Move', () => {
assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id)); assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id));
}); });
test('Unable to move if the destination account has already moved.', async () => { test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => {
await api('/i/move', { await successfulApiCall({
moveToAccount: `@bob@${url.hostname}`, endpoint: '/following/create',
parameters: {
userId: frank.id,
},
user: bob,
});
const followers = await api('/users/followers', {
userId: frank.id,
}, frank);
assert.strictEqual(followers.status, 200);
assert.strictEqual(followers.body.length, 2);
assert.strictEqual(followers.body[0].followerId, bob.id);
});
test('Unfollowed after 10 sec (24 hours in production).', async () => {
await sleep(1000 * 8);
const following = await api('/users/following', {
userId: alice.id,
}, alice); }, alice);
const newAlice = await Users.findOneByOrFail({ id: alice.id }); assert.strictEqual(following.status, 200);
assert.strictEqual(newAlice.movedToUri, `${url.origin}/users/${bob.id}`); assert.strictEqual(following.body.length, 0);
assert.strictEqual(newAlice.alsoKnownAs?.length, 1); });
assert.strictEqual(newAlice.alsoKnownAs[0], `${url.origin}/users/${bob.id}`);
test('Unable to move if the destination account has already moved.', async () => {
const res = await api('/i/move', { const res = await api('/i/move', {
moveToAccount: `@alice@${url.hostname}`, moveToAccount: `@alice@${url.hostname}`,
}, bob); }, bob);
@ -345,23 +365,6 @@ describe('Account Move', () => {
assert.strictEqual(newEve.followersCount, 1); assert.strictEqual(newEve.followersCount, 1);
}); });
test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => {
await successfulApiCall({
endpoint: '/following/create',
parameters: {
userId: frank.id,
},
user: bob,
});
const followers = await api('/users/followers', {
userId: frank.id,
}, frank);
assert.strictEqual(followers.status, 200);
assert.strictEqual(followers.body.length, 2);
assert.strictEqual(followers.body[0].followerId, bob.id);
});
test.each([ test.each([
'/antennas/create', '/antennas/create',
'/channels/create', '/channels/create',