Skip to content

Commit 5686ead

Browse files
authored
Release v1.42.2 (#1024)
* Add additional logging for worker status * Add retries for Postman email and SMS
2 parents bb38ab4 + 38dfab8 commit 5686ead

File tree

12 files changed

+175
-32
lines changed

12 files changed

+175
-32
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,5 @@
106106
"tsconfig-paths": "^4.2.0",
107107
"type-fest": "4.10.3"
108108
},
109-
"version": "1.42.1"
109+
"version": "1.42.2"
110110
}

packages/backend/src/apps/postman-sms/__tests__/request-error-handler.test.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,43 @@ describe('Postman SMS request error handler', () => {
7373
})
7474

7575
describe('Other errors', () => {
76-
it('throws StepError with generic error message', async () => {
76+
it.each([500, 502, 503])('should retry on %s', async (status) => {
7777
const axiosError = {
7878
isAxiosError: true,
7979
name: 'AxiosError',
8080
message: 'Internal server error',
8181
response: {
82-
status: 500,
82+
status,
8383
},
8484
} as unknown as AxiosError
8585

8686
const error = new HttpError(axiosError)
8787

88-
await expect(requestErrorHandler($, error)).rejects.toThrow(StepError)
88+
await expect(requestErrorHandler($, error)).rejects.toThrow(
89+
RetriableError,
90+
)
91+
await expect(requestErrorHandler($, error)).rejects.toMatchObject({
92+
delayType: 'step',
93+
delayInMs: 3000,
94+
})
95+
})
96+
97+
it('should retry on read ETIMEDOUT', async () => {
98+
const axiosError = {
99+
isAxiosError: true,
100+
name: 'AxiosError',
101+
message: 'read ETIMEDOUT',
102+
} as unknown as AxiosError
103+
104+
const error = new HttpError(axiosError)
105+
106+
await expect(requestErrorHandler($, error)).rejects.toThrow(
107+
RetriableError,
108+
)
109+
await expect(requestErrorHandler($, error)).rejects.toMatchObject({
110+
delayType: 'step',
111+
delayInMs: 3000,
112+
})
89113
})
90114
})
91115
})

packages/backend/src/apps/postman-sms/common/request-error-handler.ts

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,53 @@ const handle429: ThrowingHandler = (_, error): never => {
2121
})
2222
}
2323

24+
const handle500and502and503: ThrowingHandler = (_, error): never => {
25+
const status = error.response.status
26+
throw new RetriableError({
27+
error: `Retrying HTTP ${status} from Postman SMS`,
28+
delayType: 'step',
29+
delayInMs: 'default',
30+
})
31+
}
32+
2433
const requestErrorHandler: IApp['requestErrorHandler'] = async function (
2534
$,
2635
error,
2736
) {
28-
if (error.response.status === 429) {
29-
return handle429($, error)
30-
}
37+
switch (error.response.status) {
38+
case 429:
39+
return handle429($, error)
40+
case 500:
41+
case 502:
42+
case 503:
43+
return handle500and502and503($, error)
44+
default:
45+
if (error.message === 'read ETIMEDOUT') {
46+
throw new RetriableError({
47+
error: `Retrying ${error.message} from Postman SMS`,
48+
// pausing the entire queue here is not a good idea because we wont be able to fully utilize
49+
// the throughput that the campaign supports, so we throw step error here instead of group
50+
delayType: 'step',
51+
delayInMs: 'default',
52+
})
53+
}
3154

32-
if (
33-
error.response.status === 400 &&
34-
error.response.data.error?.code === 'parameter_invalid'
35-
) {
36-
throw new StepError(
37-
'Campaign template was not set up correctly',
38-
'Ensure that you have followed the instructions in our guide to set up your campaign template.',
39-
$.step.position,
40-
$.app.name,
41-
)
42-
}
55+
if (error.response.data.error?.code === 'parameter_invalid') {
56+
throw new StepError(
57+
'Campaign template was not set up correctly',
58+
'Ensure that you have followed the instructions in our guide to set up your campaign template.',
59+
$.step.position,
60+
$.app.name,
61+
)
62+
}
4363

44-
throw new StepError(
45-
'Error sending SMS',
46-
error.message,
47-
$.step.position,
48-
$.app.name,
49-
)
64+
throw new StepError(
65+
'Error sending SMS',
66+
error.message,
67+
$.step.position,
68+
$.app.name,
69+
)
70+
}
5071
}
5172

5273
export default requestErrorHandler

packages/backend/src/apps/postman/__tests__/actions/send-transactional-email.test.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,13 @@ describe('send transactional email', () => {
350350
})
351351
})
352352

353-
it('should retry on 502, 504, 520', async () => {
353+
it('should retry on 500, 502, 504, 520, 524', async () => {
354354
const recipients = [
355355
356356
357357
358+
359+
358360
]
359361
$.step.parameters.destinationEmail = recipients.join(',')
360362
$.http.post = vi
@@ -387,11 +389,72 @@ describe('send transactional email', () => {
387389
},
388390
} as AxiosError),
389391
)
392+
.mockRejectedValueOnce(
393+
new HttpError({
394+
response: {
395+
data: '<html>cloudflare error</html>',
396+
status: 520,
397+
statusText: 'Web server is returning an unknown error',
398+
},
399+
} as AxiosError),
400+
)
401+
.mockRejectedValueOnce(
402+
new HttpError({
403+
response: {
404+
data: '<html>cloudflare error</html>',
405+
status: 524,
406+
statusText: 'A timeout occurred',
407+
},
408+
} as AxiosError),
409+
)
410+
411+
await expect(sendTransactionalEmail.run($)).rejects.toThrow(RetriableError)
412+
expect($.setActionItem).toHaveBeenCalledWith({
413+
raw: {
414+
status: [
415+
'ACCEPTED',
416+
'INTERMITTENT-ERROR',
417+
'INTERMITTENT-ERROR',
418+
'INTERMITTENT-ERROR',
419+
'INTERMITTENT-ERROR',
420+
],
421+
recipient: recipients,
422+
subject: 'test subject',
423+
body: 'test body',
424+
from: 'jack',
425+
reply_to: '[email protected]',
426+
},
427+
})
428+
})
390429

430+
it('should retry on socket hang up', async () => {
431+
const recipients = ['[email protected]', '[email protected]']
432+
$.step.parameters.destinationEmail = recipients.join(',')
433+
$.http.post = vi
434+
.fn()
435+
.mockResolvedValueOnce({
436+
data: {
437+
params: {
438+
body: 'test body',
439+
subject: 'test subject',
440+
from: 'jack',
441+
reply_to: '[email protected]',
442+
},
443+
},
444+
})
445+
.mockRejectedValueOnce(
446+
new HttpError({
447+
response: {
448+
data: 'socket hang up',
449+
status: 400,
450+
statusText: 'socket hang up',
451+
},
452+
} as AxiosError),
453+
)
391454
await expect(sendTransactionalEmail.run($)).rejects.toThrow(RetriableError)
392455
expect($.setActionItem).toHaveBeenCalledWith({
393456
raw: {
394-
status: ['ACCEPTED', 'ERROR', 'INTERMITTENT-ERROR'],
457+
status: ['ACCEPTED', 'ERROR'],
395458
recipient: recipients,
396459
subject: 'test subject',
397460
body: 'test body',

packages/backend/src/apps/postman/common/throw-errors.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type PostmanApiErrorData = {
1616
// These are HTTP error codes returned by Cloudflare, which likely indicate
1717
// that Postman's origin server did not receive the request.
1818
// Until this is fixed, we will retry these requests on behalf of the user
19-
const POSTMAN_RETRIABLE_HTTP_CODES = [502, 504, 520]
19+
const POSTMAN_RETRIABLE_HTTP_CODES = [500, 502, 504, 520, 524]
2020

2121
export function getPostmanErrorStatus(
2222
error: HttpError,
@@ -121,6 +121,14 @@ export function throwPostmanStepError({
121121
})
122122
case 'ERROR':
123123
default:
124+
if (error.message === 'socket hang up') {
125+
throw new RetriableError({
126+
error: `Retrying ${error.message} from Postman`,
127+
delayInMs: 'default',
128+
delayType: 'step',
129+
})
130+
}
131+
124132
throw new StepError(
125133
'Something went wrong',
126134
'Please contact [email protected] for assistance.',

packages/backend/src/config/redis.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import ioRedis from 'ioredis'
22

3+
import logger from '@/helpers/logger'
4+
35
import appConfig from './app'
46

57
// Maximum of 16; be careful when adding!
@@ -13,6 +15,7 @@ export const REDIS_DB_INDEX = {
1315

1416
function reconnectOnError(err: Error) {
1517
const targetError = 'READONLY'
18+
logger.error('Redis connection error', err)
1619
if (err.message.includes(targetError)) {
1720
// Only reconnect when the error contains "READONLY"
1821
// during node failover, this is thrown: 149: -READONLY You can't write against a read only replica.

packages/backend/src/workers/flow.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ worker.on('failed', (job, err) => {
6767
)
6868
})
6969

70+
worker.on('ready', () => {
71+
logger.info('Flow worker is ready!')
72+
})
73+
74+
worker.on('closed', () => {
75+
logger.info('Flow worker is closed!')
76+
})
77+
7078
worker.on('error', (err) => {
7179
if (!err) {
7280
logger.error('Worker undefined error')

packages/backend/src/workers/helpers/make-action-worker.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,14 @@ export function makeActionWorker(
278278
}
279279
})
280280

281+
worker.on('ready', () => {
282+
logger.info(`[${queueName}] Worker is ready!`)
283+
})
284+
285+
worker.on('closed', () => {
286+
logger.info(`[${queueName}] Worker is closed!`)
287+
})
288+
281289
worker.on('error', (err) => {
282290
if (!err) {
283291
logger.error(`[${queueName}] Worker had undefined error`)

packages/backend/src/workers/trigger.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ worker.on('failed', (job, err) => {
6666
)
6767
})
6868

69+
worker.on('ready', () => {
70+
logger.info('Trigger worker is ready!')
71+
})
72+
73+
worker.on('closed', () => {
74+
logger.info('Trigger worker is closed!')
75+
})
76+
6977
worker.on('error', (err) => {
7078
if (!err) {
7179
logger.error('Worker undefined error')

0 commit comments

Comments
 (0)