aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-06-19 15:42:29 +0200
committerChocobozzz <me@florianbigard.com>2023-06-29 10:19:55 +0200
commitcf069671f44a4b03d6d5d34f17160b2253f29654 (patch)
treecbdab0f8229b20fe435cd9b052374d47aaf8f6f9 /server
parent2b5dfa2fe00a433ff59c4ca922600f3d860e0903 (diff)
downloadPeerTube-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.ts22
-rw-r--r--server/lib/activitypub/actors/refresh.ts4
-rw-r--r--server/lib/files-cache/shared/abstract-permanent-file-cache.ts35
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 @@
1export class PromiseCache <A, R> { 1export 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
27export 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 @@
1import { logger, loggerTagsFactory } from '@server/helpers/logger' 1import { logger, loggerTagsFactory } from '@server/helpers/logger'
2import { PromiseCache } from '@server/helpers/promise-cache' 2import { CachePromiseFactory } from '@server/helpers/promise-cache'
3import { PeerTubeRequestError } from '@server/helpers/requests' 3import { PeerTubeRequestError } from '@server/helpers/requests'
4import { ActorLoadByUrlType } from '@server/lib/model-loaders' 4import { ActorLoadByUrlType } from '@server/lib/model-loaders'
5import { ActorModel } from '@server/models/actor/actor' 5import { ActorModel } from '@server/models/actor/actor'
@@ -16,7 +16,7 @@ type RefreshOptions <T> = {
16 fetchedType: ActorLoadByUrlType 16 fetchedType: ActorLoadByUrlType
17} 17}
18 18
19const promiseCache = new PromiseCache(doRefresh, (options: RefreshOptions<MActorFull | MActorAccountChannelId>) => options.actor.url) 19const promiseCache = new CachePromiseFactory(doRefresh, (options: RefreshOptions<MActorFull | MActorAccountChannelId>) => options.actor.url)
20 20
21function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (options: RefreshOptions<T>): RefreshResult <T> { 21function 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 @@
1import express from 'express' 1import express from 'express'
2import { LRUCache } from 'lru-cache' 2import { LRUCache } from 'lru-cache'
3import { Model } from 'sequelize'
3import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
5import { CachePromise } from '@server/helpers/promise-cache'
4import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants' 6import { LRU_CACHE, STATIC_MAX_AGE } from '@server/initializers/constants'
5import { downloadImageFromWorker } from '@server/lib/worker/parent-process' 7import { downloadImageFromWorker } from '@server/lib/worker/parent-process'
6import { HttpStatusCode } from '@shared/models' 8import { HttpStatusCode } from '@shared/models'
7import { Model } from 'sequelize'
8 9
9type ImageModel = { 10type 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) {