diff options
author | Chocobozzz <me@florianbigard.com> | 2023-06-19 15:42:29 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-06-29 10:19:55 +0200 |
commit | cf069671f44a4b03d6d5d34f17160b2253f29654 (patch) | |
tree | cbdab0f8229b20fe435cd9b052374d47aaf8f6f9 /server | |
parent | 2b5dfa2fe00a433ff59c4ca922600f3d860e0903 (diff) | |
download | PeerTube-cf069671f44a4b03d6d5d34f17160b2253f29654.tar.gz PeerTube-cf069671f44a4b03d6d5d34f17160b2253f29654.tar.zst PeerTube-cf069671f44a4b03d6d5d34f17160b2253f29654.zip |
Use promise cache to load remote thumbnails
Diffstat (limited to 'server')
-rw-r--r-- | server/helpers/promise-cache.ts | 22 | ||||
-rw-r--r-- | server/lib/activitypub/actors/refresh.ts | 4 | ||||
-rw-r--r-- | server/lib/files-cache/shared/abstract-permanent-file-cache.ts | 35 |
3 files changed, 45 insertions, 16 deletions
diff --git a/server/helpers/promise-cache.ts b/server/helpers/promise-cache.ts index 07e8a9962..303bab976 100644 --- a/server/helpers/promise-cache.ts +++ b/server/helpers/promise-cache.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export class PromiseCache <A, R> { | 1 | export class CachePromiseFactory <A, R> { |
2 | private readonly running = new Map<string, Promise<R>>() | 2 | private readonly running = new Map<string, Promise<R>>() |
3 | 3 | ||
4 | constructor ( | 4 | constructor ( |
@@ -8,14 +8,32 @@ export class PromiseCache <A, R> { | |||
8 | } | 8 | } |
9 | 9 | ||
10 | run (arg: A) { | 10 | run (arg: A) { |
11 | return this.runWithContext(null, arg) | ||
12 | } | ||
13 | |||
14 | runWithContext (ctx: any, arg: A) { | ||
11 | const key = this.keyBuilder(arg) | 15 | const key = this.keyBuilder(arg) |
12 | 16 | ||
13 | if (this.running.has(key)) return this.running.get(key) | 17 | if (this.running.has(key)) return this.running.get(key) |
14 | 18 | ||
15 | const p = this.fn(arg) | 19 | const p = this.fn.apply(ctx || this, [ arg ]) |
16 | 20 | ||
17 | this.running.set(key, p) | 21 | this.running.set(key, p) |
18 | 22 | ||
19 | return p.finally(() => this.running.delete(key)) | 23 | return p.finally(() => this.running.delete(key)) |
20 | } | 24 | } |
21 | } | 25 | } |
26 | |||
27 | export function CachePromise (options: { | ||
28 | keyBuilder: (...args: any[]) => string | ||
29 | }) { | ||
30 | return function (_target, _key, descriptor: PropertyDescriptor) { | ||
31 | const promiseCache = new CachePromiseFactory(descriptor.value, options.keyBuilder) | ||
32 | |||
33 | descriptor.value = function () { | ||
34 | if (arguments.length !== 1) throw new Error('Cache promise only support methods with 1 argument') | ||
35 | |||
36 | return promiseCache.runWithContext(this, arguments[0]) | ||
37 | } | ||
38 | } | ||
39 | } | ||
diff --git a/server/lib/activitypub/actors/refresh.ts b/server/lib/activitypub/actors/refresh.ts index 6d8428d66..d15cb5e90 100644 --- a/server/lib/activitypub/actors/refresh.ts +++ b/server/lib/activitypub/actors/refresh.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
2 | import { PromiseCache } from '@server/helpers/promise-cache' | 2 | import { CachePromiseFactory } from '@server/helpers/promise-cache' |
3 | import { PeerTubeRequestError } from '@server/helpers/requests' | 3 | import { PeerTubeRequestError } from '@server/helpers/requests' |
4 | import { ActorLoadByUrlType } from '@server/lib/model-loaders' | 4 | import { ActorLoadByUrlType } from '@server/lib/model-loaders' |
5 | import { ActorModel } from '@server/models/actor/actor' | 5 | import { ActorModel } from '@server/models/actor/actor' |
@@ -16,7 +16,7 @@ type RefreshOptions <T> = { | |||
16 | fetchedType: ActorLoadByUrlType | 16 | fetchedType: ActorLoadByUrlType |
17 | } | 17 | } |
18 | 18 | ||
19 | const promiseCache = new PromiseCache(doRefresh, (options: RefreshOptions<MActorFull | MActorAccountChannelId>) => options.actor.url) | 19 | const promiseCache = new CachePromiseFactory(doRefresh, (options: RefreshOptions<MActorFull | MActorAccountChannelId>) => options.actor.url) |
20 | 20 | ||
21 | function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (options: RefreshOptions<T>): RefreshResult <T> { | 21 | function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (options: RefreshOptions<T>): RefreshResult <T> { |
22 | const actorArg = options.actor | 22 | const actorArg = options.actor |
diff --git a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts index 297461035..f990e9872 100644 --- a/server/lib/files-cache/shared/abstract-permanent-file-cache.ts +++ b/server/lib/files-cache/shared/abstract-permanent-file-cache.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { LRUCache } from 'lru-cache' | 2 | import { LRUCache } from 'lru-cache' |
3 | import { Model } from 'sequelize' | ||
3 | import { logger } from '@server/helpers/logger' | 4 | import { logger } from '@server/helpers/logger' |
5 | import { CachePromise } from '@server/helpers/promise-cache' | ||
4 | import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants' | 6 | import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants' |
5 | import { downloadImageFromWorker } from '@server/lib/worker/parent-process' | 7 | import { downloadImageFromWorker } from '@server/lib/worker/parent-process' |
6 | import { HttpStatusCode } from '@shared/models' | 8 | import { HttpStatusCode } from '@shared/models' |
7 | import { Model } from 'sequelize' | ||
8 | 9 | ||
9 | type ImageModel = { | 10 | type ImageModel = { |
10 | fileUrl: string | 11 | fileUrl: string |
@@ -41,29 +42,39 @@ export abstract class AbstractPermanentFileCache <M extends ImageModel> { | |||
41 | return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER }) | 42 | return res.sendFile(this.filenameToPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER }) |
42 | } | 43 | } |
43 | 44 | ||
44 | const image = await this.loadModel(filename) | 45 | const image = await this.lazyLoadIfNeeded(filename) |
45 | if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end() | 46 | if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end() |
46 | 47 | ||
48 | const path = image.getPath() | ||
49 | this.filenameToPathUnsafeCache.set(filename, path) | ||
50 | |||
51 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }, (err: any) => { | ||
52 | if (!err) return | ||
53 | |||
54 | this.onServeError({ err, image, next, filename }) | ||
55 | }) | ||
56 | } | ||
57 | |||
58 | @CachePromise({ | ||
59 | keyBuilder: filename => filename | ||
60 | }) | ||
61 | private async lazyLoadIfNeeded (filename: string) { | ||
62 | const image = await this.loadModel(filename) | ||
63 | if (!image) return undefined | ||
64 | |||
47 | if (image.onDisk === false) { | 65 | if (image.onDisk === false) { |
48 | if (!image.fileUrl) return res.status(HttpStatusCode.NOT_FOUND_404).end() | 66 | if (!image.fileUrl) return undefined |
49 | 67 | ||
50 | try { | 68 | try { |
51 | await this.downloadRemoteFile(image) | 69 | await this.downloadRemoteFile(image) |
52 | } catch (err) { | 70 | } catch (err) { |
53 | logger.warn('Cannot process remote image %s.', image.fileUrl, { err }) | 71 | logger.warn('Cannot process remote image %s.', image.fileUrl, { err }) |
54 | 72 | ||
55 | return res.status(HttpStatusCode.NOT_FOUND_404).end() | 73 | return undefined |
56 | } | 74 | } |
57 | } | 75 | } |
58 | 76 | ||
59 | const path = image.getPath() | 77 | return image |
60 | this.filenameToPathUnsafeCache.set(filename, path) | ||
61 | |||
62 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }, (err: any) => { | ||
63 | if (!err) return | ||
64 | |||
65 | this.onServeError({ err, image, next, filename }) | ||
66 | }) | ||
67 | } | 78 | } |
68 | 79 | ||
69 | async downloadRemoteFile (image: M) { | 80 | async downloadRemoteFile (image: M) { |