aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-06-13 14:27:40 +0200
committerChocobozzz <me@florianbigard.com>2018-06-13 14:27:40 +0200
commit90d4bb8125e80c8060416d4d135ddeaf0a622ede (patch)
treeb3b7181329a08ecc930b54fe7b48095c4155393c
parent3cd0734fd9b0ff21aaef02317a874e8f1c06e027 (diff)
downloadPeerTube-90d4bb8125e80c8060416d4d135ddeaf0a622ede.tar.gz
PeerTube-90d4bb8125e80c8060416d4d135ddeaf0a622ede.tar.zst
PeerTube-90d4bb8125e80c8060416d4d135ddeaf0a622ede.zip
Refractor retry transaction function
-rw-r--r--server/controllers/api/users.ts52
-rw-r--r--server/controllers/api/video-channel.ts55
-rw-r--r--server/controllers/api/videos/abuse.ts25
-rw-r--r--server/controllers/api/videos/comment.ts68
-rw-r--r--server/controllers/api/videos/index.ts62
-rw-r--r--server/controllers/api/videos/rate.ts18
-rw-r--r--server/helpers/database-utils.ts49
-rw-r--r--server/lib/activitypub/actor.ts12
-rw-r--r--server/lib/activitypub/process/process-announce.ts13
-rw-r--r--server/lib/activitypub/process/process-create.ts37
-rw-r--r--server/lib/activitypub/process/process-delete.ts46
-rw-r--r--server/lib/activitypub/process/process-follow.ts13
-rw-r--r--server/lib/activitypub/process/process-like.ts16
-rw-r--r--server/lib/activitypub/process/process-undo.ts48
-rw-r--r--server/lib/activitypub/process/process-update.ts26
-rw-r--r--server/lib/activitypub/videos.ts7
-rw-r--r--server/lib/job-queue/handlers/activitypub-follow.ts6
-rw-r--r--server/lib/job-queue/handlers/video-file.ts12
-rw-r--r--server/middlewares/async.ts12
19 files changed, 162 insertions, 415 deletions
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 2b40c44d9..0aeb77964 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -4,7 +4,6 @@ import { extname, join } from 'path'
4import * as uuidv4 from 'uuid/v4' 4import * as uuidv4 from 'uuid/v4'
5import * as RateLimit from 'express-rate-limit' 5import * as RateLimit from 'express-rate-limit'
6import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' 6import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
7import { retryTransactionWrapper } from '../../helpers/database-utils'
8import { processImage } from '../../helpers/image-utils' 7import { processImage } from '../../helpers/image-utils'
9import { logger } from '../../helpers/logger' 8import { logger } from '../../helpers/logger'
10import { getFormattedObjects } from '../../helpers/utils' 9import { getFormattedObjects } from '../../helpers/utils'
@@ -16,6 +15,7 @@ import { Redis } from '../../lib/redis'
16import { createUserAccountAndChannel } from '../../lib/user' 15import { createUserAccountAndChannel } from '../../lib/user'
17import { 16import {
18 asyncMiddleware, 17 asyncMiddleware,
18 asyncRetryTransactionMiddleware,
19 authenticate, 19 authenticate,
20 ensureUserHasRight, 20 ensureUserHasRight,
21 ensureUserRegistrationAllowed, 21 ensureUserRegistrationAllowed,
@@ -102,14 +102,14 @@ usersRouter.post('/',
102 authenticate, 102 authenticate,
103 ensureUserHasRight(UserRight.MANAGE_USERS), 103 ensureUserHasRight(UserRight.MANAGE_USERS),
104 asyncMiddleware(usersAddValidator), 104 asyncMiddleware(usersAddValidator),
105 asyncMiddleware(createUserRetryWrapper) 105 asyncRetryTransactionMiddleware(createUser)
106) 106)
107 107
108usersRouter.post('/register', 108usersRouter.post('/register',
109 asyncMiddleware(ensureUserRegistrationAllowed), 109 asyncMiddleware(ensureUserRegistrationAllowed),
110 ensureUserRegistrationAllowedForIP, 110 ensureUserRegistrationAllowedForIP,
111 asyncMiddleware(usersRegisterValidator), 111 asyncMiddleware(usersRegisterValidator),
112 asyncMiddleware(registerUserRetryWrapper) 112 asyncRetryTransactionMiddleware(registerUser)
113) 113)
114 114
115usersRouter.put('/me', 115usersRouter.put('/me',
@@ -178,26 +178,7 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
178 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) 178 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
179} 179}
180 180
181async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 181async function createUser (req: express.Request, res: express.Response) {
182 const options = {
183 arguments: [ req ],
184 errorMessage: 'Cannot insert the user with many retries.'
185 }
186
187 const { user, account } = await retryTransactionWrapper(createUser, options)
188
189 return res.json({
190 user: {
191 id: user.id,
192 account: {
193 id: account.id,
194 uuid: account.Actor.uuid
195 }
196 }
197 }).end()
198}
199
200async function createUser (req: express.Request) {
201 const body: UserCreate = req.body 182 const body: UserCreate = req.body
202 const userToCreate = new UserModel({ 183 const userToCreate = new UserModel({
203 username: body.username, 184 username: body.username,
@@ -213,21 +194,18 @@ async function createUser (req: express.Request) {
213 194
214 logger.info('User %s with its channel and account created.', body.username) 195 logger.info('User %s with its channel and account created.', body.username)
215 196
216 return { user, account } 197 return res.json({
217} 198 user: {
218 199 id: user.id,
219async function registerUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 200 account: {
220 const options = { 201 id: account.id,
221 arguments: [ req ], 202 uuid: account.Actor.uuid
222 errorMessage: 'Cannot insert the user with many retries.' 203 }
223 } 204 }
224 205 }).end()
225 await retryTransactionWrapper(registerUser, options)
226
227 return res.type('json').status(204).end()
228} 206}
229 207
230async function registerUser (req: express.Request) { 208async function registerUser (req: express.Request, res: express.Response) {
231 const body: UserCreate = req.body 209 const body: UserCreate = req.body
232 210
233 const user = new UserModel({ 211 const user = new UserModel({
@@ -243,6 +221,8 @@ async function registerUser (req: express.Request) {
243 await createUserAccountAndChannel(user) 221 await createUserAccountAndChannel(user)
244 222
245 logger.info('User %s with its channel and account registered.', body.username) 223 logger.info('User %s with its channel and account registered.', body.username)
224
225 return res.type('json').status(204).end()
246} 226}
247 227
248async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 228async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 263eb2a8a..61e72125f 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -2,6 +2,7 @@ import * as express from 'express'
2import { getFormattedObjects, resetSequelizeInstance } from '../../helpers/utils' 2import { getFormattedObjects, resetSequelizeInstance } from '../../helpers/utils'
3import { 3import {
4 asyncMiddleware, 4 asyncMiddleware,
5 asyncRetryTransactionMiddleware,
5 authenticate, 6 authenticate,
6 optionalAuthenticate, 7 optionalAuthenticate,
7 paginationValidator, 8 paginationValidator,
@@ -20,7 +21,6 @@ import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
20import { createVideoChannel } from '../../lib/video-channel' 21import { createVideoChannel } from '../../lib/video-channel'
21import { isNSFWHidden } from '../../helpers/express-utils' 22import { isNSFWHidden } from '../../helpers/express-utils'
22import { setAsyncActorKeys } from '../../lib/activitypub' 23import { setAsyncActorKeys } from '../../lib/activitypub'
23import { retryTransactionWrapper } from '../../helpers/database-utils'
24import { AccountModel } from '../../models/account/account' 24import { AccountModel } from '../../models/account/account'
25import { sequelizeTypescript } from '../../initializers' 25import { sequelizeTypescript } from '../../initializers'
26import { logger } from '../../helpers/logger' 26import { logger } from '../../helpers/logger'
@@ -39,19 +39,19 @@ videoChannelRouter.get('/',
39videoChannelRouter.post('/', 39videoChannelRouter.post('/',
40 authenticate, 40 authenticate,
41 videoChannelsAddValidator, 41 videoChannelsAddValidator,
42 asyncMiddleware(addVideoChannelRetryWrapper) 42 asyncRetryTransactionMiddleware(addVideoChannel)
43) 43)
44 44
45videoChannelRouter.put('/:id', 45videoChannelRouter.put('/:id',
46 authenticate, 46 authenticate,
47 asyncMiddleware(videoChannelsUpdateValidator), 47 asyncMiddleware(videoChannelsUpdateValidator),
48 updateVideoChannelRetryWrapper 48 asyncRetryTransactionMiddleware(updateVideoChannel)
49) 49)
50 50
51videoChannelRouter.delete('/:id', 51videoChannelRouter.delete('/:id',
52 authenticate, 52 authenticate,
53 asyncMiddleware(videoChannelsRemoveValidator), 53 asyncMiddleware(videoChannelsRemoveValidator),
54 asyncMiddleware(removeVideoChannelRetryWrapper) 54 asyncRetryTransactionMiddleware(removeVideoChannel)
55) 55)
56 56
57videoChannelRouter.get('/:id', 57videoChannelRouter.get('/:id',
@@ -83,23 +83,6 @@ async function listVideoChannels (req: express.Request, res: express.Response, n
83 return res.json(getFormattedObjects(resultList.data, resultList.total)) 83 return res.json(getFormattedObjects(resultList.data, resultList.total))
84} 84}
85 85
86// Wrapper to video channel add that retry the async function if there is a database error
87// We need this because we run the transaction in SERIALIZABLE isolation that can fail
88async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
89 const options = {
90 arguments: [ req, res ],
91 errorMessage: 'Cannot insert the video video channel with many retries.'
92 }
93
94 const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
95 return res.json({
96 videoChannel: {
97 id: videoChannel.id,
98 uuid: videoChannel.Actor.uuid
99 }
100 }).end()
101}
102
103async function addVideoChannel (req: express.Request, res: express.Response) { 86async function addVideoChannel (req: express.Request, res: express.Response) {
104 const videoChannelInfo: VideoChannelCreate = req.body 87 const videoChannelInfo: VideoChannelCreate = req.body
105 const account: AccountModel = res.locals.oauth.token.User.Account 88 const account: AccountModel = res.locals.oauth.token.User.Account
@@ -113,18 +96,12 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
113 96
114 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid) 97 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
115 98
116 return videoChannelCreated 99 return res.json({
117} 100 videoChannel: {
118 101 id: videoChannelCreated.id,
119async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 102 uuid: videoChannelCreated.Actor.uuid
120 const options = { 103 }
121 arguments: [ req, res ], 104 }).end()
122 errorMessage: 'Cannot update the video with many retries.'
123 }
124
125 await retryTransactionWrapper(updateVideoChannel, options)
126
127 return res.type('json').status(204).end()
128} 105}
129 106
130async function updateVideoChannel (req: express.Request, res: express.Response) { 107async function updateVideoChannel (req: express.Request, res: express.Response) {
@@ -157,15 +134,6 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
157 134
158 throw err 135 throw err
159 } 136 }
160}
161
162async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
163 const options = {
164 arguments: [ req, res ],
165 errorMessage: 'Cannot remove the video channel with many retries.'
166 }
167
168 await retryTransactionWrapper(removeVideoChannel, options)
169 137
170 return res.type('json').status(204).end() 138 return res.type('json').status(204).end()
171} 139}
@@ -173,12 +141,13 @@ async function removeVideoChannelRetryWrapper (req: express.Request, res: expres
173async function removeVideoChannel (req: express.Request, res: express.Response) { 141async function removeVideoChannel (req: express.Request, res: express.Response) {
174 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 142 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
175 143
176 return sequelizeTypescript.transaction(async t => { 144 await sequelizeTypescript.transaction(async t => {
177 await videoChannelInstance.destroy({ transaction: t }) 145 await videoChannelInstance.destroy({ transaction: t })
178 146
179 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) 147 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
180 }) 148 })
181 149
150 return res.type('json').status(204).end()
182} 151}
183 152
184async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 153async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index 61ff3af4f..3413ae894 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -1,12 +1,18 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight, VideoAbuseCreate } from '../../../../shared' 2import { UserRight, VideoAbuseCreate } from '../../../../shared'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
5import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
6import { sequelizeTypescript } from '../../../initializers' 5import { sequelizeTypescript } from '../../../initializers'
7import { sendVideoAbuse } from '../../../lib/activitypub/send' 6import { sendVideoAbuse } from '../../../lib/activitypub/send'
8import { 7import {
9 asyncMiddleware, authenticate, ensureUserHasRight, paginationValidator, setDefaultSort, setDefaultPagination, videoAbuseReportValidator, 8 asyncMiddleware,
9 asyncRetryTransactionMiddleware,
10 authenticate,
11 ensureUserHasRight,
12 paginationValidator,
13 setDefaultPagination,
14 setDefaultSort,
15 videoAbuseReportValidator,
10 videoAbusesSortValidator 16 videoAbusesSortValidator
11} from '../../../middlewares' 17} from '../../../middlewares'
12import { AccountModel } from '../../../models/account/account' 18import { AccountModel } from '../../../models/account/account'
@@ -27,7 +33,7 @@ abuseVideoRouter.get('/abuse',
27abuseVideoRouter.post('/:id/abuse', 33abuseVideoRouter.post('/:id/abuse',
28 authenticate, 34 authenticate,
29 asyncMiddleware(videoAbuseReportValidator), 35 asyncMiddleware(videoAbuseReportValidator),
30 asyncMiddleware(reportVideoAbuseRetryWrapper) 36 asyncRetryTransactionMiddleware(reportVideoAbuse)
31) 37)
32 38
33// --------------------------------------------------------------------------- 39// ---------------------------------------------------------------------------
@@ -44,17 +50,6 @@ async function listVideoAbuses (req: express.Request, res: express.Response, nex
44 return res.json(getFormattedObjects(resultList.data, resultList.total)) 50 return res.json(getFormattedObjects(resultList.data, resultList.total))
45} 51}
46 52
47async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
48 const options = {
49 arguments: [ req, res ],
50 errorMessage: 'Cannot report abuse to the video with many retries.'
51 }
52
53 await retryTransactionWrapper(reportVideoAbuse, options)
54
55 return res.type('json').status(204).end()
56}
57
58async function reportVideoAbuse (req: express.Request, res: express.Response) { 53async function reportVideoAbuse (req: express.Request, res: express.Response) {
59 const videoInstance = res.locals.video as VideoModel 54 const videoInstance = res.locals.video as VideoModel
60 const reporterAccount = res.locals.oauth.token.User.Account as AccountModel 55 const reporterAccount = res.locals.oauth.token.User.Account as AccountModel
@@ -77,4 +72,6 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
77 }) 72 })
78 73
79 logger.info('Abuse report for video %s created.', videoInstance.name) 74 logger.info('Abuse report for video %s created.', videoInstance.name)
75
76 return res.type('json').status(204).end()
80} 77}
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index f8a669e35..bbeb0d557 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -1,15 +1,24 @@
1import * as express from 'express' 1import * as express from 'express'
2import { ResultList } from '../../../../shared/models' 2import { ResultList } from '../../../../shared/models'
3import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' 3import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model'
4import { retryTransactionWrapper } from '../../../helpers/database-utils'
5import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
6import { getFormattedObjects } from '../../../helpers/utils' 5import { getFormattedObjects } from '../../../helpers/utils'
7import { sequelizeTypescript } from '../../../initializers' 6import { sequelizeTypescript } from '../../../initializers'
8import { buildFormattedCommentTree, createVideoComment } from '../../../lib/video-comment' 7import { buildFormattedCommentTree, createVideoComment } from '../../../lib/video-comment'
9import { asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination } from '../../../middlewares' 8import {
9 asyncMiddleware,
10 asyncRetryTransactionMiddleware,
11 authenticate,
12 paginationValidator,
13 setDefaultPagination,
14 setDefaultSort
15} from '../../../middlewares'
10import { videoCommentThreadsSortValidator } from '../../../middlewares/validators' 16import { videoCommentThreadsSortValidator } from '../../../middlewares/validators'
11import { 17import {
12 addVideoCommentReplyValidator, addVideoCommentThreadValidator, listVideoCommentThreadsValidator, listVideoThreadCommentsValidator, 18 addVideoCommentReplyValidator,
19 addVideoCommentThreadValidator,
20 listVideoCommentThreadsValidator,
21 listVideoThreadCommentsValidator,
13 removeVideoCommentValidator 22 removeVideoCommentValidator
14} from '../../../middlewares/validators/video-comments' 23} from '../../../middlewares/validators/video-comments'
15import { VideoModel } from '../../../models/video/video' 24import { VideoModel } from '../../../models/video/video'
@@ -33,17 +42,17 @@ videoCommentRouter.get('/:videoId/comment-threads/:threadId',
33videoCommentRouter.post('/:videoId/comment-threads', 42videoCommentRouter.post('/:videoId/comment-threads',
34 authenticate, 43 authenticate,
35 asyncMiddleware(addVideoCommentThreadValidator), 44 asyncMiddleware(addVideoCommentThreadValidator),
36 asyncMiddleware(addVideoCommentThreadRetryWrapper) 45 asyncRetryTransactionMiddleware(addVideoCommentThread)
37) 46)
38videoCommentRouter.post('/:videoId/comments/:commentId', 47videoCommentRouter.post('/:videoId/comments/:commentId',
39 authenticate, 48 authenticate,
40 asyncMiddleware(addVideoCommentReplyValidator), 49 asyncMiddleware(addVideoCommentReplyValidator),
41 asyncMiddleware(addVideoCommentReplyRetryWrapper) 50 asyncRetryTransactionMiddleware(addVideoCommentReply)
42) 51)
43videoCommentRouter.delete('/:videoId/comments/:commentId', 52videoCommentRouter.delete('/:videoId/comments/:commentId',
44 authenticate, 53 authenticate,
45 asyncMiddleware(removeVideoCommentValidator), 54 asyncMiddleware(removeVideoCommentValidator),
46 asyncMiddleware(removeVideoCommentRetryWrapper) 55 asyncRetryTransactionMiddleware(removeVideoComment)
47) 56)
48 57
49// --------------------------------------------------------------------------- 58// ---------------------------------------------------------------------------
@@ -86,23 +95,10 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
86 return res.json(buildFormattedCommentTree(resultList)) 95 return res.json(buildFormattedCommentTree(resultList))
87} 96}
88 97
89async function addVideoCommentThreadRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 98async function addVideoCommentThread (req: express.Request, res: express.Response) {
90 const options = {
91 arguments: [ req, res ],
92 errorMessage: 'Cannot insert the video comment thread with many retries.'
93 }
94
95 const comment = await retryTransactionWrapper(addVideoCommentThread, options)
96
97 res.json({
98 comment: comment.toFormattedJSON()
99 }).end()
100}
101
102function addVideoCommentThread (req: express.Request, res: express.Response) {
103 const videoCommentInfo: VideoCommentCreate = req.body 99 const videoCommentInfo: VideoCommentCreate = req.body
104 100
105 return sequelizeTypescript.transaction(async t => { 101 const comment = await sequelizeTypescript.transaction(async t => {
106 return createVideoComment({ 102 return createVideoComment({
107 text: videoCommentInfo.text, 103 text: videoCommentInfo.text,
108 inReplyToComment: null, 104 inReplyToComment: null,
@@ -110,25 +106,16 @@ function addVideoCommentThread (req: express.Request, res: express.Response) {
110 account: res.locals.oauth.token.User.Account 106 account: res.locals.oauth.token.User.Account
111 }, t) 107 }, t)
112 }) 108 })
113}
114 109
115async function addVideoCommentReplyRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 110 return res.json({
116 const options = {
117 arguments: [ req, res ],
118 errorMessage: 'Cannot insert the video comment reply with many retries.'
119 }
120
121 const comment = await retryTransactionWrapper(addVideoCommentReply, options)
122
123 res.json({
124 comment: comment.toFormattedJSON() 111 comment: comment.toFormattedJSON()
125 }).end() 112 }).end()
126} 113}
127 114
128function addVideoCommentReply (req: express.Request, res: express.Response, next: express.NextFunction) { 115async function addVideoCommentReply (req: express.Request, res: express.Response) {
129 const videoCommentInfo: VideoCommentCreate = req.body 116 const videoCommentInfo: VideoCommentCreate = req.body
130 117
131 return sequelizeTypescript.transaction(async t => { 118 const comment = await sequelizeTypescript.transaction(async t => {
132 return createVideoComment({ 119 return createVideoComment({
133 text: videoCommentInfo.text, 120 text: videoCommentInfo.text,
134 inReplyToComment: res.locals.videoComment, 121 inReplyToComment: res.locals.videoComment,
@@ -136,17 +123,10 @@ function addVideoCommentReply (req: express.Request, res: express.Response, next
136 account: res.locals.oauth.token.User.Account 123 account: res.locals.oauth.token.User.Account
137 }, t) 124 }, t)
138 }) 125 })
139}
140
141async function removeVideoCommentRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
142 const options = {
143 arguments: [ req, res ],
144 errorMessage: 'Cannot remove the video comment with many retries.'
145 }
146 126
147 await retryTransactionWrapper(removeVideoComment, options) 127 return res.json({
148 128 comment: comment.toFormattedJSON()
149 return res.type('json').status(204).end() 129 }).end()
150} 130}
151 131
152async function removeVideoComment (req: express.Request, res: express.Response) { 132async function removeVideoComment (req: express.Request, res: express.Response) {
@@ -157,4 +137,6 @@ async function removeVideoComment (req: express.Request, res: express.Response)
157 }) 137 })
158 138
159 logger.info('Video comment %d deleted.', videoCommentInstance.id) 139 logger.info('Video comment %d deleted.', videoCommentInstance.id)
140
141 return res.type('json').status(204).end()
160} 142}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 9d9b2b0e1..78963d89b 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { extname, join } from 'path' 2import { extname, join } from 'path'
3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' 3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared'
4import { renamePromise } from '../../../helpers/core-utils' 4import { renamePromise } from '../../../helpers/core-utils'
5import { retryTransactionWrapper } from '../../../helpers/database-utils'
6import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 5import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
7import { processImage } from '../../../helpers/image-utils' 6import { processImage } from '../../../helpers/image-utils'
8import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
@@ -30,6 +29,7 @@ import { JobQueue } from '../../../lib/job-queue'
30import { Redis } from '../../../lib/redis' 29import { Redis } from '../../../lib/redis'
31import { 30import {
32 asyncMiddleware, 31 asyncMiddleware,
32 asyncRetryTransactionMiddleware,
33 authenticate, 33 authenticate,
34 optionalAuthenticate, 34 optionalAuthenticate,
35 paginationValidator, 35 paginationValidator,
@@ -104,13 +104,13 @@ videosRouter.put('/:id',
104 authenticate, 104 authenticate,
105 reqVideoFileUpdate, 105 reqVideoFileUpdate,
106 asyncMiddleware(videosUpdateValidator), 106 asyncMiddleware(videosUpdateValidator),
107 asyncMiddleware(updateVideoRetryWrapper) 107 asyncRetryTransactionMiddleware(updateVideo)
108) 108)
109videosRouter.post('/upload', 109videosRouter.post('/upload',
110 authenticate, 110 authenticate,
111 reqVideoFileAdd, 111 reqVideoFileAdd,
112 asyncMiddleware(videosAddValidator), 112 asyncMiddleware(videosAddValidator),
113 asyncMiddleware(addVideoRetryWrapper) 113 asyncRetryTransactionMiddleware(addVideo)
114) 114)
115 115
116videosRouter.get('/:id/description', 116videosRouter.get('/:id/description',
@@ -129,7 +129,7 @@ videosRouter.post('/:id/views',
129videosRouter.delete('/:id', 129videosRouter.delete('/:id',
130 authenticate, 130 authenticate,
131 asyncMiddleware(videosRemoveValidator), 131 asyncMiddleware(videosRemoveValidator),
132 asyncMiddleware(removeVideoRetryWrapper) 132 asyncRetryTransactionMiddleware(removeVideo)
133) 133)
134 134
135// --------------------------------------------------------------------------- 135// ---------------------------------------------------------------------------
@@ -156,25 +156,8 @@ function listVideoPrivacies (req: express.Request, res: express.Response) {
156 res.json(VIDEO_PRIVACIES) 156 res.json(VIDEO_PRIVACIES)
157} 157}
158 158
159// Wrapper to video add that retry the function if there is a database error 159async function addVideo (req: express.Request, res: express.Response) {
160// We need this because we run the transaction in SERIALIZABLE isolation that can fail 160 const videoPhysicalFile = req.files['videofile'][0]
161async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
162 const options = {
163 arguments: [ req, res, req.files['videofile'][0] ],
164 errorMessage: 'Cannot insert the video with many retries.'
165 }
166
167 const video = await retryTransactionWrapper(addVideo, options)
168
169 res.json({
170 video: {
171 id: video.id,
172 uuid: video.uuid
173 }
174 }).end()
175}
176
177async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
178 const videoInfo: VideoCreate = req.body 161 const videoInfo: VideoCreate = req.body
179 162
180 // Prepare data so we don't block the transaction 163 // Prepare data so we don't block the transaction
@@ -272,18 +255,12 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
272 await JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) 255 await JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput })
273 } 256 }
274 257
275 return videoCreated 258 return res.json({
276} 259 video: {
277 260 id: videoCreated.id,
278async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 261 uuid: videoCreated.uuid
279 const options = { 262 }
280 arguments: [ req, res ], 263 }).end()
281 errorMessage: 'Cannot update the video with many retries.'
282 }
283
284 await retryTransactionWrapper(updateVideo, options)
285
286 return res.type('json').status(204).end()
287} 264}
288 265
289async function updateVideo (req: express.Request, res: express.Response) { 266async function updateVideo (req: express.Request, res: express.Response) {
@@ -360,6 +337,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
360 337
361 throw err 338 throw err
362 } 339 }
340
341 return res.type('json').status(204).end()
363} 342}
364 343
365function getVideo (req: express.Request, res: express.Response) { 344function getVideo (req: express.Request, res: express.Response) {
@@ -414,17 +393,6 @@ async function listVideos (req: express.Request, res: express.Response, next: ex
414 return res.json(getFormattedObjects(resultList.data, resultList.total)) 393 return res.json(getFormattedObjects(resultList.data, resultList.total))
415} 394}
416 395
417async function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
418 const options = {
419 arguments: [ req, res ],
420 errorMessage: 'Cannot remove the video with many retries.'
421 }
422
423 await retryTransactionWrapper(removeVideo, options)
424
425 return res.type('json').status(204).end()
426}
427
428async function removeVideo (req: express.Request, res: express.Response) { 396async function removeVideo (req: express.Request, res: express.Response) {
429 const videoInstance: VideoModel = res.locals.video 397 const videoInstance: VideoModel = res.locals.video
430 398
@@ -433,6 +401,8 @@ async function removeVideo (req: express.Request, res: express.Response) {
433 }) 401 })
434 402
435 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid) 403 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
404
405 return res.type('json').status(204).end()
436} 406}
437 407
438async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 408async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index 23e9de9f3..9d63b5821 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,10 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { UserVideoRateUpdate } from '../../../../shared'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
5import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers' 4import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers'
6import { sendVideoRateChange } from '../../../lib/activitypub' 5import { sendVideoRateChange } from '../../../lib/activitypub'
7import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares' 6import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoRateValidator } from '../../../middlewares'
8import { AccountModel } from '../../../models/account/account' 7import { AccountModel } from '../../../models/account/account'
9import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 8import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
10import { VideoModel } from '../../../models/video/video' 9import { VideoModel } from '../../../models/video/video'
@@ -14,7 +13,7 @@ const rateVideoRouter = express.Router()
14rateVideoRouter.put('/:id/rate', 13rateVideoRouter.put('/:id/rate',
15 authenticate, 14 authenticate,
16 asyncMiddleware(videoRateValidator), 15 asyncMiddleware(videoRateValidator),
17 asyncMiddleware(rateVideoRetryWrapper) 16 asyncRetryTransactionMiddleware(rateVideo)
18) 17)
19 18
20// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
@@ -25,17 +24,6 @@ export {
25 24
26// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
27 26
28async function rateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
29 const options = {
30 arguments: [ req, res ],
31 errorMessage: 'Cannot update the user video rate.'
32 }
33
34 await retryTransactionWrapper(rateVideo, options)
35
36 return res.type('json').status(204).end()
37}
38
39async function rateVideo (req: express.Request, res: express.Response) { 27async function rateVideo (req: express.Request, res: express.Response) {
40 const body: UserVideoRateUpdate = req.body 28 const body: UserVideoRateUpdate = req.body
41 const rateType = body.rating 29 const rateType = body.rating
@@ -87,4 +75,6 @@ async function rateVideo (req: express.Request, res: express.Response) {
87 }) 75 })
88 76
89 logger.info('Account video rate for video %s of account %s updated.', videoInstance.name, accountInstance.name) 77 logger.info('Account video rate for video %s of account %s updated.', videoInstance.name, accountInstance.name)
78
79 return res.type('json').status(204).end()
90} 80}
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts
index b3ff42a37..9b861a88c 100644
--- a/server/helpers/database-utils.ts
+++ b/server/helpers/database-utils.ts
@@ -3,35 +3,54 @@ import * as Bluebird from 'bluebird'
3import { Model } from 'sequelize-typescript' 3import { Model } from 'sequelize-typescript'
4import { logger } from './logger' 4import { logger } from './logger'
5 5
6type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] } 6function retryTransactionWrapper <T, A, B, C> (
7 functionToRetry: (arg1: A, arg2: B, arg3: C) => Promise<T> | Bluebird<T>,
8 arg1: A,
9 arg2: B,
10 arg3: C
11): Promise<T>
12
13function retryTransactionWrapper <T, A, B> (
14 functionToRetry: (arg1: A, arg2: B) => Promise<T> | Bluebird<T>,
15 arg1: A,
16 arg2: B
17): Promise<T>
18
19function retryTransactionWrapper <T, A> (
20 functionToRetry: (arg1: A) => Promise<T> | Bluebird<T>,
21 arg1: A
22): Promise<T>
23
7function retryTransactionWrapper <T> ( 24function retryTransactionWrapper <T> (
8 functionToRetry: (...args) => Promise<T> | Bluebird<T>, 25 functionToRetry: (...args: any[]) => Promise<T> | Bluebird<T>,
9 options: RetryTransactionWrapperOptions 26 ...args: any[]
10): Promise<T> { 27): Promise<T> {
11 const args = options.arguments ? options.arguments : []
12
13 return transactionRetryer<T>(callback => { 28 return transactionRetryer<T>(callback => {
14 functionToRetry.apply(this, args) 29 functionToRetry.apply(this, args)
15 .then((result: T) => callback(null, result)) 30 .then((result: T) => callback(null, result))
16 .catch(err => callback(err)) 31 .catch(err => callback(err))
17 }) 32 })
18 .catch(err => { 33 .catch(err => {
19 logger.error(options.errorMessage, { err }) 34 logger.error('Cannot execute %s with many retries.', functionToRetry.toString(), { err })
20 throw err 35 throw err
21 }) 36 })
22} 37}
23 38
24function transactionRetryer <T> (func: (err: any, data: T) => any) { 39function transactionRetryer <T> (func: (err: any, data: T) => any) {
25 return new Promise<T>((res, rej) => { 40 return new Promise<T>((res, rej) => {
26 retry({ 41 retry(
27 times: 5, 42 {
28 43 times: 5,
29 errorFilter: err => { 44
30 const willRetry = (err.name === 'SequelizeDatabaseError') 45 errorFilter: err => {
31 logger.debug('Maybe retrying the transaction function.', { willRetry, err }) 46 const willRetry = (err.name === 'SequelizeDatabaseError')
32 return willRetry 47 logger.debug('Maybe retrying the transaction function.', { willRetry, err })
33 } 48 return willRetry
34 }, func, (err, data) => err ? rej(err) : res(data)) 49 }
50 },
51 func,
52 (err, data) => err ? rej(err) : res(data)
53 )
35 }) 54 })
36} 55}
37 56
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index f27733418..9257d7d20 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -62,18 +62,10 @@ async function getOrCreateActorAndServerAndModel (activityActor: string | Activi
62 } 62 }
63 } 63 }
64 64
65 const options = { 65 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, result, ownerActor)
66 arguments: [ result, ownerActor ],
67 errorMessage: 'Cannot save actor and server with many retries.'
68 }
69 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options)
70 } 66 }
71 67
72 const options = { 68 return retryTransactionWrapper(refreshActorIfNeeded, actor)
73 arguments: [ actor ],
74 errorMessage: 'Cannot refresh actor if needed with many retries.'
75 }
76 return retryTransactionWrapper(refreshActorIfNeeded, options)
77} 69}
78 70
79function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { 71function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) {
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index 4e50da8d2..d8ca59425 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -11,7 +11,7 @@ import { getOrCreateAccountAndVideoAndChannel } from '../videos'
11async function processAnnounceActivity (activity: ActivityAnnounce) { 11async function processAnnounceActivity (activity: ActivityAnnounce) {
12 const actorAnnouncer = await getOrCreateActorAndServerAndModel(activity.actor) 12 const actorAnnouncer = await getOrCreateActorAndServerAndModel(activity.actor)
13 13
14 return processVideoShare(actorAnnouncer, activity) 14 return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity)
15} 15}
16 16
17// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
@@ -22,16 +22,7 @@ export {
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { 25async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
26 const options = {
27 arguments: [ actorAnnouncer, activity ],
28 errorMessage: 'Cannot share the video activity with many retries.'
29 }
30
31 return retryTransactionWrapper(shareVideo, options)
32}
33
34async function shareVideo (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
35 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id 26 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
36 let video: VideoModel 27 let video: VideoModel
37 28
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 38dacf772..6364bf135 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -21,13 +21,13 @@ async function processCreateActivity (activity: ActivityCreate) {
21 if (activityType === 'View') { 21 if (activityType === 'View') {
22 return processCreateView(actor, activity) 22 return processCreateView(actor, activity)
23 } else if (activityType === 'Dislike') { 23 } else if (activityType === 'Dislike') {
24 return processCreateDislike(actor, activity) 24 return retryTransactionWrapper(processCreateDislike, actor, activity)
25 } else if (activityType === 'Video') { 25 } else if (activityType === 'Video') {
26 return processCreateVideo(actor, activity) 26 return processCreateVideo(actor, activity)
27 } else if (activityType === 'Flag') { 27 } else if (activityType === 'Flag') {
28 return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject) 28 return retryTransactionWrapper(processCreateVideoAbuse, actor, activityObject as VideoAbuseObject)
29 } else if (activityType === 'Note') { 29 } else if (activityType === 'Note') {
30 return processCreateVideoComment(actor, activity) 30 return retryTransactionWrapper(processCreateVideoComment, actor, activity)
31 } 31 }
32 32
33 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) 33 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -54,15 +54,6 @@ async function processCreateVideo (
54} 54}
55 55
56async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) { 56async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
57 const options = {
58 arguments: [ byActor, activity ],
59 errorMessage: 'Cannot dislike the video with many retries.'
60 }
61
62 return retryTransactionWrapper(createVideoDislike, options)
63}
64
65async function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) {
66 const dislike = activity.object as DislikeObject 57 const dislike = activity.object as DislikeObject
67 const byAccount = byActor.Account 58 const byAccount = byActor.Account
68 59
@@ -109,16 +100,7 @@ async function processCreateView (byActor: ActorModel, activity: ActivityCreate)
109 } 100 }
110} 101}
111 102
112function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) { 103async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
113 const options = {
114 arguments: [ actor, videoAbuseToCreateData ],
115 errorMessage: 'Cannot insert the remote video abuse with many retries.'
116 }
117
118 return retryTransactionWrapper(addRemoteVideoAbuse, options)
119}
120
121async function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
122 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) 104 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
123 105
124 const account = actor.Account 106 const account = actor.Account
@@ -139,16 +121,7 @@ async function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: V
139 }) 121 })
140} 122}
141 123
142function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) { 124async function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) {
143 const options = {
144 arguments: [ byActor, activity ],
145 errorMessage: 'Cannot create video comment with many retries.'
146 }
147
148 return retryTransactionWrapper(createVideoComment, options)
149}
150
151async function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
152 const comment = activity.object as VideoCommentObject 125 const comment = activity.object as VideoCommentObject
153 const byAccount = byActor.Account 126 const byAccount = byActor.Account
154 127
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 8310b70f0..ff0caa343 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -21,12 +21,12 @@ async function processDeleteActivity (activity: ActivityDelete) {
21 if (!actor.Account) throw new Error('Actor ' + actor.url + ' is a person but we cannot find it in database.') 21 if (!actor.Account) throw new Error('Actor ' + actor.url + ' is a person but we cannot find it in database.')
22 22
23 actor.Account.Actor = await actor.Account.$get('Actor') as ActorModel 23 actor.Account.Actor = await actor.Account.$get('Actor') as ActorModel
24 return processDeleteAccount(actor.Account) 24 return retryTransactionWrapper(processDeleteAccount, actor.Account)
25 } else if (actor.type === 'Group') { 25 } else if (actor.type === 'Group') {
26 if (!actor.VideoChannel) throw new Error('Actor ' + actor.url + ' is a group but we cannot find it in database.') 26 if (!actor.VideoChannel) throw new Error('Actor ' + actor.url + ' is a group but we cannot find it in database.')
27 27
28 actor.VideoChannel.Actor = await actor.VideoChannel.$get('Actor') as ActorModel 28 actor.VideoChannel.Actor = await actor.VideoChannel.$get('Actor') as ActorModel
29 return processDeleteVideoChannel(actor.VideoChannel) 29 return retryTransactionWrapper(processDeleteVideoChannel, actor.VideoChannel)
30 } 30 }
31 } 31 }
32 32
@@ -34,14 +34,14 @@ async function processDeleteActivity (activity: ActivityDelete) {
34 { 34 {
35 const videoCommentInstance = await VideoCommentModel.loadByUrlAndPopulateAccount(objectUrl) 35 const videoCommentInstance = await VideoCommentModel.loadByUrlAndPopulateAccount(objectUrl)
36 if (videoCommentInstance) { 36 if (videoCommentInstance) {
37 return processDeleteVideoComment(actor, videoCommentInstance, activity) 37 return retryTransactionWrapper(processDeleteVideoComment, actor, videoCommentInstance, activity)
38 } 38 }
39 } 39 }
40 40
41 { 41 {
42 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(objectUrl) 42 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(objectUrl)
43 if (videoInstance) { 43 if (videoInstance) {
44 return processDeleteVideo(actor, videoInstance) 44 return retryTransactionWrapper(processDeleteVideo, actor, videoInstance)
45 } 45 }
46 } 46 }
47 47
@@ -57,15 +57,6 @@ export {
57// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
58 58
59async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) { 59async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) {
60 const options = {
61 arguments: [ actor, videoToDelete ],
62 errorMessage: 'Cannot remove the remote video with many retries.'
63 }
64
65 await retryTransactionWrapper(deleteRemoteVideo, options)
66}
67
68async function deleteRemoteVideo (actor: ActorModel, videoToDelete: VideoModel) {
69 logger.debug('Removing remote video "%s".', videoToDelete.uuid) 60 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
70 61
71 await sequelizeTypescript.transaction(async t => { 62 await sequelizeTypescript.transaction(async t => {
@@ -80,15 +71,6 @@ async function deleteRemoteVideo (actor: ActorModel, videoToDelete: VideoModel)
80} 71}
81 72
82async function processDeleteAccount (accountToRemove: AccountModel) { 73async function processDeleteAccount (accountToRemove: AccountModel) {
83 const options = {
84 arguments: [ accountToRemove ],
85 errorMessage: 'Cannot remove the remote account with many retries.'
86 }
87
88 await retryTransactionWrapper(deleteRemoteAccount, options)
89}
90
91async function deleteRemoteAccount (accountToRemove: AccountModel) {
92 logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid) 74 logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid)
93 75
94 await sequelizeTypescript.transaction(async t => { 76 await sequelizeTypescript.transaction(async t => {
@@ -99,15 +81,6 @@ async function deleteRemoteAccount (accountToRemove: AccountModel) {
99} 81}
100 82
101async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { 83async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
102 const options = {
103 arguments: [ videoChannelToRemove ],
104 errorMessage: 'Cannot remove the remote video channel with many retries.'
105 }
106
107 await retryTransactionWrapper(deleteRemoteVideoChannel, options)
108}
109
110async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel) {
111 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid) 84 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid)
112 85
113 await sequelizeTypescript.transaction(async t => { 86 await sequelizeTypescript.transaction(async t => {
@@ -117,16 +90,7 @@ async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel
117 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid) 90 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
118} 91}
119 92
120async function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { 93function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
121 const options = {
122 arguments: [ byActor, videoComment, activity ],
123 errorMessage: 'Cannot remove the remote video comment with many retries.'
124 }
125
126 await retryTransactionWrapper(deleteRemoteVideoComment, options)
127}
128
129function deleteRemoteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
130 logger.debug('Removing remote video comment "%s".', videoComment.url) 94 logger.debug('Removing remote video comment "%s".', videoComment.url)
131 95
132 return sequelizeTypescript.transaction(async t => { 96 return sequelizeTypescript.transaction(async t => {
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index dc1d542b5..f34fd66cc 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -11,7 +11,7 @@ async function processFollowActivity (activity: ActivityFollow) {
11 const activityObject = activity.object 11 const activityObject = activity.object
12 const actor = await getOrCreateActorAndServerAndModel(activity.actor) 12 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
13 13
14 return processFollow(actor, activityObject) 14 return retryTransactionWrapper(processFollow, actor, activityObject)
15} 15}
16 16
17// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
@@ -22,16 +22,7 @@ export {
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25function processFollow (actor: ActorModel, targetActorURL: string) { 25async function processFollow (actor: ActorModel, targetActorURL: string) {
26 const options = {
27 arguments: [ actor, targetActorURL ],
28 errorMessage: 'Cannot follow with many retries.'
29 }
30
31 return retryTransactionWrapper(follow, options)
32}
33
34async function follow (actor: ActorModel, targetActorURL: string) {
35 await sequelizeTypescript.transaction(async t => { 26 await sequelizeTypescript.transaction(async t => {
36 const targetActor = await ActorModel.loadByUrl(targetActorURL, t) 27 const targetActor = await ActorModel.loadByUrl(targetActorURL, t)
37 28
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index f1642f038..d0865b78c 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -4,14 +4,13 @@ import { sequelizeTypescript } from '../../../initializers'
4import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 4import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/activitypub/actor'
6import { getOrCreateActorAndServerAndModel } from '../actor' 6import { getOrCreateActorAndServerAndModel } from '../actor'
7import { forwardActivity, forwardVideoRelatedActivity } from '../send/utils' 7import { forwardVideoRelatedActivity } from '../send/utils'
8import { getOrCreateAccountAndVideoAndChannel } from '../videos' 8import { getOrCreateAccountAndVideoAndChannel } from '../videos'
9import { getActorsInvolvedInVideo } from '../audience'
10 9
11async function processLikeActivity (activity: ActivityLike) { 10async function processLikeActivity (activity: ActivityLike) {
12 const actor = await getOrCreateActorAndServerAndModel(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
13 12
14 return processLikeVideo(actor, activity) 13 return retryTransactionWrapper(processLikeVideo, actor, activity)
15} 14}
16 15
17// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
@@ -22,16 +21,7 @@ export {
22 21
23// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
24 23
25async function processLikeVideo (actor: ActorModel, activity: ActivityLike) { 24async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) {
26 const options = {
27 arguments: [ actor, activity ],
28 errorMessage: 'Cannot like the video with many retries.'
29 }
30
31 return retryTransactionWrapper(createVideoLike, options)
32}
33
34async function createVideoLike (byActor: ActorModel, activity: ActivityLike) {
35 const videoUrl = activity.object 25 const videoUrl = activity.object
36 26
37 const byAccount = byActor.Account 27 const byAccount = byActor.Account
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index 37db58e1a..b6de107ad 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -18,13 +18,13 @@ async function processUndoActivity (activity: ActivityUndo) {
18 const actorUrl = getActorUrl(activity.actor) 18 const actorUrl = getActorUrl(activity.actor)
19 19
20 if (activityToUndo.type === 'Like') { 20 if (activityToUndo.type === 'Like') {
21 return processUndoLike(actorUrl, activity) 21 return retryTransactionWrapper(processUndoLike, actorUrl, activity)
22 } else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') { 22 } else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') {
23 return processUndoDislike(actorUrl, activity) 23 return retryTransactionWrapper(processUndoDislike, actorUrl, activity)
24 } else if (activityToUndo.type === 'Follow') { 24 } else if (activityToUndo.type === 'Follow') {
25 return processUndoFollow(actorUrl, activityToUndo) 25 return retryTransactionWrapper(processUndoFollow, actorUrl, activityToUndo)
26 } else if (activityToUndo.type === 'Announce') { 26 } else if (activityToUndo.type === 'Announce') {
27 return processUndoAnnounce(actorUrl, activityToUndo) 27 return retryTransactionWrapper(processUndoAnnounce, actorUrl, activityToUndo)
28 } 28 }
29 29
30 logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id }) 30 logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
@@ -40,16 +40,7 @@ export {
40 40
41// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
42 42
43function processUndoLike (actorUrl: string, activity: ActivityUndo) { 43async function processUndoLike (actorUrl: string, activity: ActivityUndo) {
44 const options = {
45 arguments: [ actorUrl, activity ],
46 errorMessage: 'Cannot undo like with many retries.'
47 }
48
49 return retryTransactionWrapper(undoLike, options)
50}
51
52async function undoLike (actorUrl: string, activity: ActivityUndo) {
53 const likeActivity = activity.object as ActivityLike 44 const likeActivity = activity.object as ActivityLike
54 45
55 const { video } = await getOrCreateAccountAndVideoAndChannel(likeActivity.object) 46 const { video } = await getOrCreateAccountAndVideoAndChannel(likeActivity.object)
@@ -73,16 +64,7 @@ async function undoLike (actorUrl: string, activity: ActivityUndo) {
73 }) 64 })
74} 65}
75 66
76function processUndoDislike (actorUrl: string, activity: ActivityUndo) { 67async function processUndoDislike (actorUrl: string, activity: ActivityUndo) {
77 const options = {
78 arguments: [ actorUrl, activity ],
79 errorMessage: 'Cannot undo dislike with many retries.'
80 }
81
82 return retryTransactionWrapper(undoDislike, options)
83}
84
85async function undoDislike (actorUrl: string, activity: ActivityUndo) {
86 const dislike = activity.object.object as DislikeObject 68 const dislike = activity.object.object as DislikeObject
87 69
88 const { video } = await getOrCreateAccountAndVideoAndChannel(dislike.object) 70 const { video } = await getOrCreateAccountAndVideoAndChannel(dislike.object)
@@ -107,15 +89,6 @@ async function undoDislike (actorUrl: string, activity: ActivityUndo) {
107} 89}
108 90
109function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) { 91function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) {
110 const options = {
111 arguments: [ actorUrl, followActivity ],
112 errorMessage: 'Cannot undo follow with many retries.'
113 }
114
115 return retryTransactionWrapper(undoFollow, options)
116}
117
118function undoFollow (actorUrl: string, followActivity: ActivityFollow) {
119 return sequelizeTypescript.transaction(async t => { 92 return sequelizeTypescript.transaction(async t => {
120 const follower = await ActorModel.loadByUrl(actorUrl, t) 93 const follower = await ActorModel.loadByUrl(actorUrl, t)
121 const following = await ActorModel.loadByUrl(followActivity.object, t) 94 const following = await ActorModel.loadByUrl(followActivity.object, t)
@@ -130,15 +103,6 @@ function undoFollow (actorUrl: string, followActivity: ActivityFollow) {
130} 103}
131 104
132function processUndoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) { 105function processUndoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) {
133 const options = {
134 arguments: [ actorUrl, announceActivity ],
135 errorMessage: 'Cannot undo announce with many retries.'
136 }
137
138 return retryTransactionWrapper(undoAnnounce, options)
139}
140
141function undoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) {
142 return sequelizeTypescript.transaction(async t => { 106 return sequelizeTypescript.transaction(async t => {
143 const byAccount = await AccountModel.loadByUrl(actorUrl, t) 107 const byAccount = await AccountModel.loadByUrl(actorUrl, t)
144 if (!byAccount) throw new Error('Unknown account ' + actorUrl) 108 if (!byAccount) throw new Error('Unknown account ' + actorUrl)
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 1ebda46d3..73db461c3 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -25,9 +25,9 @@ async function processUpdateActivity (activity: ActivityUpdate) {
25 const objectType = activity.object.type 25 const objectType = activity.object.type
26 26
27 if (objectType === 'Video') { 27 if (objectType === 'Video') {
28 return processUpdateVideo(actor, activity) 28 return retryTransactionWrapper(processUpdateVideo, actor, activity)
29 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') { 29 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
30 return processUpdateActor(actor, activity) 30 return retryTransactionWrapper(processUpdateActor, actor, activity)
31 } 31 }
32 32
33 return undefined 33 return undefined
@@ -41,16 +41,7 @@ export {
41 41
42// --------------------------------------------------------------------------- 42// ---------------------------------------------------------------------------
43 43
44function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) { 44async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
45 const options = {
46 arguments: [ actor, activity ],
47 errorMessage: 'Cannot update the remote video with many retries'
48 }
49
50 return retryTransactionWrapper(updateRemoteVideo, options)
51}
52
53async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
54 const videoObject = activity.object as VideoTorrentObject 45 const videoObject = activity.object as VideoTorrentObject
55 46
56 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { 47 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@@ -136,16 +127,7 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
136 } 127 }
137} 128}
138 129
139function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) { 130async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
140 const options = {
141 arguments: [ actor, activity ],
142 errorMessage: 'Cannot update the remote actor with many retries'
143 }
144
145 return retryTransactionWrapper(updateRemoteActor, options)
146}
147
148async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
149 const actorAttributesToUpdate = activity.object as ActivityPubActor 131 const actorAttributesToUpdate = activity.object as ActivityPubActor
150 132
151 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid) 133 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 7ec8ca193..a16828fda 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -228,12 +228,7 @@ async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentOb
228 228
229 const channelActor = await getOrCreateVideoChannel(videoObject) 229 const channelActor = await getOrCreateVideoChannel(videoObject)
230 230
231 const options = { 231 const video = await retryTransactionWrapper(getOrCreateVideo, videoObject, channelActor)
232 arguments: [ videoObject, channelActor ],
233 errorMessage: 'Cannot insert the remote video with many retries.'
234 }
235
236 const video = await retryTransactionWrapper(getOrCreateVideo, options)
237 232
238 // Process outside the transaction because we could fetch remote data 233 // Process outside the transaction because we could fetch remote data
239 logger.info('Adding likes of video %s.', video.uuid) 234 logger.info('Adding likes of video %s.', video.uuid)
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts
index 6764a4037..286e343f2 100644
--- a/server/lib/job-queue/handlers/activitypub-follow.ts
+++ b/server/lib/job-queue/handlers/activitypub-follow.ts
@@ -26,12 +26,8 @@ async function processActivityPubFollow (job: kue.Job) {
26 const targetActor = await getOrCreateActorAndServerAndModel(actorUrl) 26 const targetActor = await getOrCreateActorAndServerAndModel(actorUrl)
27 27
28 const fromActor = await getServerActor() 28 const fromActor = await getServerActor()
29 const options = {
30 arguments: [ fromActor, targetActor ],
31 errorMessage: 'Cannot follow with many retries.'
32 }
33 29
34 return retryTransactionWrapper(follow, options) 30 return retryTransactionWrapper(follow, fromActor, targetActor)
35} 31}
36// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
37 33
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts
index f5ad076a6..a5c6bf300 100644
--- a/server/lib/job-queue/handlers/video-file.ts
+++ b/server/lib/job-queue/handlers/video-file.ts
@@ -52,19 +52,11 @@ async function processVideoFile (job: kue.Job) {
52 if (payload.resolution) { 52 if (payload.resolution) {
53 await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode) 53 await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode)
54 54
55 const options = { 55 await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, video)
56 arguments: [ video ],
57 errorMessage: 'Cannot execute onVideoFileTranscoderOrImportSuccess with many retries.'
58 }
59 await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, options)
60 } else { 56 } else {
61 await video.optimizeOriginalVideofile() 57 await video.optimizeOriginalVideofile()
62 58
63 const options = { 59 await retryTransactionWrapper(onVideoFileOptimizerSuccess, video, payload.isNewVideo)
64 arguments: [ video, payload.isNewVideo ],
65 errorMessage: 'Cannot execute onVideoFileOptimizerSuccess with many retries.'
66 }
67 await retryTransactionWrapper(onVideoFileOptimizerSuccess, options)
68 } 60 }
69 61
70 return video 62 return video
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts
index dd209b115..f770bc120 100644
--- a/server/middlewares/async.ts
+++ b/server/middlewares/async.ts
@@ -1,5 +1,6 @@
1import { eachSeries } from 'async' 1import { eachSeries } from 'async'
2import { NextFunction, Request, RequestHandler, Response } from 'express' 2import { NextFunction, Request, RequestHandler, Response } from 'express'
3import { retryTransactionWrapper } from '../helpers/database-utils'
3 4
4// Syntactic sugar to avoid try/catch in express controllers 5// Syntactic sugar to avoid try/catch in express controllers
5// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016 6// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
@@ -20,8 +21,17 @@ function asyncMiddleware (fun: RequestPromiseHandler | RequestPromiseHandler[])
20 } 21 }
21} 22}
22 23
24function asyncRetryTransactionMiddleware (fun: RequestPromiseHandler) {
25 return (req: Request, res: Response, next: NextFunction) => {
26 return Promise.resolve(
27 retryTransactionWrapper(fun, req, res, next)
28 ).catch(err => next(err))
29 }
30}
31
23// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
24 33
25export { 34export {
26 asyncMiddleware 35 asyncMiddleware,
36 asyncRetryTransactionMiddleware
27} 37}