This commit is contained in:
MeiMei 2019-09-09 22:46:45 +09:00 committed by syuilo
parent 9b91b92bca
commit 827c378ac1
6 changed files with 100 additions and 60 deletions

View file

@ -199,6 +199,7 @@
"recaptcha-promise": "0.1.3", "recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.2.0", "reconnecting-websocket": "4.2.0",
"redis": "2.8.0", "redis": "2.8.0",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rename": "1.0.4", "rename": "1.0.4",
"request": "2.88.0", "request": "2.88.0",

22
src/misc/app-lock.ts Normal file
View file

@ -0,0 +1,22 @@
import redis from '../db/redis';
import { promisify } from 'util';
/**
* Retry delay (ms) for lock acquisition
*/
const retryDelay = 100;
const lock: (key: string, timeout?: number) => Promise<() => void>
= redis
? promisify(require('redis-lock')(redis, retryDelay))
: async () => () => { };
/**
* Get AP Object lock
* @param uri AP object ID
* @param timeout Lock timeout (ms), The timeout releases previous lock.
* @returns Unlock function
*/
export function getApLock(uri: string, timeout = 30 * 1000) {
return lock(`ap-object:${uri}`, timeout);
}

View file

@ -7,6 +7,7 @@ import { resolvePerson } from '../../models/person';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
import { extractDbHost } from '../../../../misc/convert-host'; import { extractDbHost } from '../../../../misc/convert-host';
import { fetchMeta } from '../../../../misc/fetch-meta'; import { fetchMeta } from '../../../../misc/fetch-meta';
import { getApLock } from '../../../../misc/app-lock';
const logger = apLogger; const logger = apLogger;
@ -25,6 +26,9 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
const meta = await fetchMeta(); const meta = await fetchMeta();
if (meta.blockedHosts.includes(extractDbHost(uri))) return; if (meta.blockedHosts.includes(extractDbHost(uri))) return;
const unlock = await getApLock(uri);
try {
// 既に同じURIを持つものが登録されていないかチェック // 既に同じURIを持つものが登録されていないかチェック
const exist = await fetchNote(uri); const exist = await fetchNote(uri);
if (exist) { if (exist) {
@ -66,6 +70,9 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
visibleUsers, visibleUsers,
uri uri
}); });
} finally {
unlock();
}
} }
type visibility = 'public' | 'home' | 'followers' | 'specified'; type visibility = 'public' | 'home' | 'followers' | 'specified';

View file

@ -1,13 +1,23 @@
import Resolver from '../../resolver'; import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import { createNote, fetchNote } from '../../models/note'; import { createNote, fetchNote } from '../../models/note';
import { getApId } from '../../type';
import { getApLock } from '../../../../misc/app-lock';
/** /**
* 稿 * 稿
*/ */
export default async function(resolver: Resolver, actor: IRemoteUser, note: any, silent = false): Promise<void> { export default async function(resolver: Resolver, actor: IRemoteUser, note: any, silent = false): Promise<void> {
const uri = getApId(note);
const unlock = await getApLock(uri);
try {
const exist = await fetchNote(note); const exist = await fetchNote(note);
if (exist == null) { if (exist == null) {
await createNote(note); await createNote(note);
} }
} finally {
unlock();
}
} }

View file

@ -22,6 +22,7 @@ import { Emoji } from '../../../models/entities/emoji';
import { genId } from '../../../misc/gen-id'; import { genId } from '../../../misc/gen-id';
import { fetchMeta } from '../../../misc/fetch-meta'; import { fetchMeta } from '../../../misc/fetch-meta';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
import { getApLock } from '../../../misc/app-lock';
const logger = apLogger; const logger = apLogger;
@ -257,6 +258,9 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
const meta = await fetchMeta(); const meta = await fetchMeta();
if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 }; if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 };
const unlock = await getApLock(uri);
try {
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す
const exist = await fetchNote(uri); const exist = await fetchNote(uri);
@ -268,19 +272,10 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
// リモートサーバーからフェッチしてきて登録 // リモートサーバーからフェッチしてきて登録
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
return await createNote(uri, resolver, true).catch(e => { return await createNote(uri, resolver, true);
if (e.name === 'duplicated') { } finally {
return fetchNote(uri).then(note => { unlock();
if (note == null) {
throw new Error('something happened');
} else {
return note;
} }
});
} else {
throw e;
}
});
} }
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> { export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {

View file

@ -9561,6 +9561,11 @@ redis-errors@^1.0.0, redis-errors@^1.2.0:
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
redis-lock@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/redis-lock/-/redis-lock-0.1.4.tgz#e83590bee22b5f01cdb65bfbd88d988045356272"
integrity sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==
redis-parser@^2.6.0: redis-parser@^2.6.0:
version "2.6.0" version "2.6.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"