qdone 2.0.34-alpha → 2.0.36-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commonjs/src/cache.js +3 -2
- package/commonjs/src/dedup.js +264 -0
- package/commonjs/src/defaults.js +42 -5
- package/commonjs/src/enqueue.js +192 -114
- package/commonjs/src/qrlCache.js +2 -1
- package/commonjs/src/scheduler/jobExecutor.js +3 -0
- package/commonjs/src/scheduler/queueManager.js +1 -1
- package/npm-shrinkwrap.json +3 -2
- package/package.json +4 -3
- package/src/cache.js +3 -2
- package/src/check.js +205 -0
- package/src/cli.js +81 -3
- package/src/dedup.js +256 -0
- package/src/defaults.js +40 -4
- package/src/enqueue.js +185 -114
- package/src/qrlCache.js +1 -1
- package/src/scheduler/jobExecutor.js +5 -0
- package/src/scheduler/queueManager.js +1 -1
- package/src/worker.js +6 -0
package/src/defaults.js
CHANGED
|
@@ -18,11 +18,16 @@ export const defaults = Object.freeze({
|
|
|
18
18
|
fifo: false,
|
|
19
19
|
disableLog: false,
|
|
20
20
|
includeFailed: false,
|
|
21
|
+
includeDead: false,
|
|
22
|
+
externalDedup: false,
|
|
23
|
+
dedupPeriod: 60 * 5,
|
|
24
|
+
dedupStats: false,
|
|
21
25
|
|
|
22
26
|
// Enqueue
|
|
23
27
|
groupId: uuidv1(),
|
|
24
28
|
groupIdPerMessage: false,
|
|
25
29
|
deduplicationId: undefined,
|
|
30
|
+
dedupIdPerMessage: false,
|
|
26
31
|
messageRetentionPeriod: 1209600,
|
|
27
32
|
delay: 0,
|
|
28
33
|
sendRetries: 6,
|
|
@@ -42,7 +47,10 @@ export const defaults = Object.freeze({
|
|
|
42
47
|
// Idle Queues
|
|
43
48
|
idleFor: 60,
|
|
44
49
|
delete: false,
|
|
45
|
-
unpair: false
|
|
50
|
+
unpair: false,
|
|
51
|
+
|
|
52
|
+
// Check
|
|
53
|
+
create: false
|
|
46
54
|
})
|
|
47
55
|
|
|
48
56
|
function validateInteger (opt, name) {
|
|
@@ -51,6 +59,19 @@ function validateInteger (opt, name) {
|
|
|
51
59
|
return parsed
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
export function validateMessageOptions (messageOptions) {
|
|
63
|
+
const validKeys = ['deduplicaitonId', 'groupId']
|
|
64
|
+
if (typeof messageOptions === 'object' &&
|
|
65
|
+
!Array.isArray(messageOptions) &&
|
|
66
|
+
messageOptions !== null) {
|
|
67
|
+
for (const key in messageOptions) {
|
|
68
|
+
if (!validKeys.includes(key)) throw new Error(`Invalid message option ${key}`)
|
|
69
|
+
}
|
|
70
|
+
return messageOptions
|
|
71
|
+
}
|
|
72
|
+
return {}
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
/**
|
|
55
76
|
* This function should be called by each exposed API entry point on the
|
|
56
77
|
* options passed in from the caller. It supports options named in camelCase
|
|
@@ -64,7 +85,7 @@ export function getOptionsWithDefaults (options) {
|
|
|
64
85
|
if (!options) options = {}
|
|
65
86
|
|
|
66
87
|
// Activate DLQ if any option is set
|
|
67
|
-
const dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'])
|
|
88
|
+
const dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'] || options.dlqSuffix || options.dlqAfter || options.dlqName)
|
|
68
89
|
|
|
69
90
|
const opt = {
|
|
70
91
|
// Shared
|
|
@@ -76,6 +97,11 @@ export function getOptionsWithDefaults (options) {
|
|
|
76
97
|
fifo: options.fifo || defaults.fifo,
|
|
77
98
|
sentryDsn: options.sentryDsn || options['sentry-dsn'],
|
|
78
99
|
disableLog: options.disableLog || options['disable-log'] || defaults.disableLog,
|
|
100
|
+
includeFailed: options.includeFailed || options['include-failed'] || defaults.includeFailed,
|
|
101
|
+
includeDead: options.includeDead || options['include-dead'] || defaults.includeDead,
|
|
102
|
+
externalDedup: options.externalDedup || options['external-dedup'] || defaults.externalDedup,
|
|
103
|
+
dedupPeriod: options.dedupPeriod || options['dedup-period'] || defaults.dedupPeriod,
|
|
104
|
+
dedupStats: options.dedupStats || options['dedup-stats'] || defaults.dedupStats,
|
|
79
105
|
|
|
80
106
|
// Cache
|
|
81
107
|
cacheUri: options.cacheUri || options['cache-uri'] || defaults.cacheUri,
|
|
@@ -86,6 +112,7 @@ export function getOptionsWithDefaults (options) {
|
|
|
86
112
|
groupId: options.groupId || options['group-id'] || defaults.groupId,
|
|
87
113
|
groupIdPerMessage: false,
|
|
88
114
|
deduplicationId: options.deduplicationId || options['deduplication-id'] || defaults.deduplicationId,
|
|
115
|
+
dedupIdPerMessage: options.dedupIdPerMessage || options['dedup-id-per-message'] || defaults.dedupIdPerMessage,
|
|
89
116
|
messageRetentionPeriod: options.messageRetentionPeriod || options['message-retention-period'] || defaults.messageRetentionPeriod,
|
|
90
117
|
delay: options.delay || defaults.delay,
|
|
91
118
|
sendRetries: options['send-retries'] || defaults.sendRetries,
|
|
@@ -100,14 +127,17 @@ export function getOptionsWithDefaults (options) {
|
|
|
100
127
|
killAfter: options.killAfter || options['kill-after'] || defaults.killAfter,
|
|
101
128
|
archive: options.archive || defaults.archive,
|
|
102
129
|
activeOnly: options.activeOnly || options['active-only'] || defaults.activeOnly,
|
|
103
|
-
includeFailed: options.includeFailed || options['include-failed'] || defaults.includeFailed,
|
|
104
130
|
maxConcurrentJobs: options.maxConcurrentJobs || defaults.maxConcurrentJobs,
|
|
105
131
|
maxMemoryPercent: options.maxMemoryPercent || defaults.maxMemoryPercent,
|
|
106
132
|
|
|
107
133
|
// Idle Queues
|
|
108
134
|
idleFor: options.idleFor || options['idle-for'] || defaults.idleFor,
|
|
109
135
|
delete: options.delete || defaults.delete,
|
|
110
|
-
unpair: options.delete || defaults.unpair
|
|
136
|
+
unpair: options.delete || defaults.unpair,
|
|
137
|
+
|
|
138
|
+
// Check
|
|
139
|
+
create: options.create || defaults.create,
|
|
140
|
+
overwrite: options.overwrite || defaults.overwrite
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
// Setting this env here means we don't have to in AWS SDK constructors
|
|
@@ -118,6 +148,7 @@ export function getOptionsWithDefaults (options) {
|
|
|
118
148
|
opt.messageRetentionPeriod = validateInteger(opt, 'messageRetentionPeriod')
|
|
119
149
|
opt.delay = validateInteger(opt, 'delay')
|
|
120
150
|
opt.sendRetries = validateInteger(opt, 'sendRetries')
|
|
151
|
+
opt.dedupPeriod = validateInteger(opt, 'dedupPeriod')
|
|
121
152
|
opt.failDelay = validateInteger(opt, 'failDelay')
|
|
122
153
|
opt.dlqAfter = validateInteger(opt, 'dlqAfter')
|
|
123
154
|
opt.waitTime = validateInteger(opt, 'waitTime')
|
|
@@ -126,6 +157,11 @@ export function getOptionsWithDefaults (options) {
|
|
|
126
157
|
opt.maxMemoryPercent = validateInteger(opt, 'maxMemoryPercent')
|
|
127
158
|
opt.idleFor = validateInteger(opt, 'idleFor')
|
|
128
159
|
|
|
160
|
+
// Validate dedup args
|
|
161
|
+
if (opt.externalDedup && !opt.cacheUri) throw new Error('--external-dedup requires the --cache-uri argument')
|
|
162
|
+
if (opt.externalDedup && (!opt.dedupPeriod || opt.dedupPeriod < 1)) throw new Error('--external-dedup of redis requires a --dedup-period > 1 second')
|
|
163
|
+
if (opt.dedupIdPerMessage && opt.deduplicationId) throw new Error('Use either --deduplication-id or --dedup-id-per-message but not both')
|
|
164
|
+
|
|
129
165
|
return opt
|
|
130
166
|
}
|
|
131
167
|
|
package/src/enqueue.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addBreadcrumb } from '@sentry/node'
|
|
1
|
+
import { addBreadcrumb, setExtra } from '@sentry/node'
|
|
2
2
|
import { v1 as uuidV1 } from 'uuid'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
import Debug from 'debug'
|
|
@@ -20,14 +20,31 @@ import {
|
|
|
20
20
|
normalizeDLQName
|
|
21
21
|
} from './qrlCache.js'
|
|
22
22
|
import { getSQSClient } from './sqs.js'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
addDedupParamsToMessage,
|
|
25
|
+
dedupShouldEnqueue,
|
|
26
|
+
dedupShouldEnqueueMulti,
|
|
27
|
+
dedupSuccessfullyProcessed
|
|
28
|
+
} from './dedup.js'
|
|
29
|
+
import { getOptionsWithDefaults, validateMessageOptions } from './defaults.js'
|
|
24
30
|
import { ExponentialBackoff } from './exponentialBackoff.js'
|
|
25
31
|
|
|
26
32
|
const debug = Debug('qdone:enqueue')
|
|
27
33
|
|
|
34
|
+
export function getDLQParams (queue, opt) {
|
|
35
|
+
const dqname = normalizeDLQName(queue, opt)
|
|
36
|
+
const params = {
|
|
37
|
+
Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
|
|
38
|
+
QueueName: dqname
|
|
39
|
+
}
|
|
40
|
+
if (opt.tags) params.tags = opt.tags
|
|
41
|
+
if (opt.fifo) params.Attributes.FifoQueue = 'true'
|
|
42
|
+
return { dqname, params }
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
export async function getOrCreateDLQ (queue, opt) {
|
|
29
46
|
debug('getOrCreateDLQ(', queue, ')')
|
|
30
|
-
const dqname =
|
|
47
|
+
const { dqname, params } = getDLQParams(queue, opt)
|
|
31
48
|
try {
|
|
32
49
|
const dqrl = await qrlCacheGet(dqname)
|
|
33
50
|
return dqrl
|
|
@@ -37,12 +54,6 @@ export async function getOrCreateDLQ (queue, opt) {
|
|
|
37
54
|
|
|
38
55
|
// Create our DLQ
|
|
39
56
|
const client = getSQSClient()
|
|
40
|
-
const params = {
|
|
41
|
-
Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
|
|
42
|
-
QueueName: dqname
|
|
43
|
-
}
|
|
44
|
-
if (opt.tags) params.tags = opt.tags
|
|
45
|
-
if (opt.fifo) params.Attributes.FifoQueue = 'true'
|
|
46
57
|
const cmd = new CreateQueueCommand(params)
|
|
47
58
|
if (opt.verbose) console.error(chalk.blue('Creating dead letter queue ') + dqname)
|
|
48
59
|
const data = await client.send(cmd)
|
|
@@ -53,35 +64,62 @@ export async function getOrCreateDLQ (queue, opt) {
|
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Returns the parameters needed for creating a failed queue. If DLQ options
|
|
69
|
+
* are set, it makes an API call to get this DLQ's ARN.
|
|
70
|
+
*/
|
|
71
|
+
export async function getFailParams (queue, opt) {
|
|
72
|
+
const fqname = normalizeFailQueueName(queue, opt)
|
|
73
|
+
const params = {
|
|
74
|
+
Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
|
|
75
|
+
QueueName: fqname
|
|
76
|
+
}
|
|
77
|
+
// If we have a dlq, we grab it and set a redrive policy
|
|
78
|
+
if (opt.dlq) {
|
|
79
|
+
const dqname = normalizeDLQName(queue, opt)
|
|
80
|
+
const dqrl = await qrlCacheGet(dqname)
|
|
81
|
+
const dqa = await getQueueAttributes(dqrl)
|
|
82
|
+
debug('dqa', dqa)
|
|
83
|
+
params.Attributes.RedrivePolicy = JSON.stringify({
|
|
84
|
+
deadLetterTargetArn: dqa.Attributes.QueueArn,
|
|
85
|
+
maxReceiveCount: opt.dlqAfter
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
if (opt.failDelay) params.Attributes.DelaySeconds = opt.failDelay + ''
|
|
89
|
+
if (opt.tags) params.tags = opt.tags
|
|
90
|
+
if (opt.fifo) params.Attributes.FifoQueue = 'true'
|
|
91
|
+
return params
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the qrl for the failed queue for the given queue. Creates the queue
|
|
96
|
+
* if it does not exist.
|
|
97
|
+
*/
|
|
98
|
+
export async function getOrCreateFailQueue (queue, opt, doesNotExist) {
|
|
57
99
|
debug('getOrCreateFailQueue(', queue, ')')
|
|
58
100
|
const fqname = normalizeFailQueueName(queue, opt)
|
|
59
101
|
try {
|
|
102
|
+
// Bail early if the caller knew we didn't have a queue
|
|
103
|
+
if (doesNotExist) throw new QueueDoesNotExist(fqname)
|
|
60
104
|
const fqrl = await qrlCacheGet(fqname)
|
|
61
105
|
return fqrl
|
|
62
106
|
} catch (err) {
|
|
63
107
|
// Anything other than queue doesn't exist gets re-thrown
|
|
64
108
|
if (!(err instanceof QueueDoesNotExist)) throw err
|
|
65
109
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const dqa = await getQueueAttributes(dqrl)
|
|
76
|
-
debug('dqa', dqa)
|
|
77
|
-
params.Attributes.RedrivePolicy = JSON.stringify({
|
|
78
|
-
deadLetterTargetArn: dqa.Attributes.QueueArn,
|
|
79
|
-
maxReceiveCount: opt.dlqAfter + ''
|
|
80
|
-
})
|
|
110
|
+
// Grab params, creating DLQ if needed
|
|
111
|
+
let params
|
|
112
|
+
try {
|
|
113
|
+
params = await getFailParams(queue, opt)
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// If DLQ doesn't exist, create it
|
|
116
|
+
if (!(opt.dlq && e instanceof QueueDoesNotExist)) throw e
|
|
117
|
+
await getOrCreateDLQ(queue, opt)
|
|
118
|
+
params = await getFailParams(queue, opt)
|
|
81
119
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
|
|
121
|
+
// Create our fail queue
|
|
122
|
+
const client = getSQSClient()
|
|
85
123
|
const cmd = new CreateQueueCommand(params)
|
|
86
124
|
if (opt.verbose) console.error(chalk.blue('Creating fail queue ') + fqname)
|
|
87
125
|
const data = await client.send(cmd)
|
|
@@ -92,6 +130,30 @@ export async function getOrCreateFailQueue (queue, opt) {
|
|
|
92
130
|
}
|
|
93
131
|
}
|
|
94
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Returns the parameters needed for creating a queue. If fail options
|
|
135
|
+
* are set, it makes an API call to get the fail queue's ARN.
|
|
136
|
+
*/
|
|
137
|
+
export async function getQueueParams (queue, opt) {
|
|
138
|
+
const qname = normalizeQueueName(queue, opt)
|
|
139
|
+
const fqname = normalizeFailQueueName(queue, opt)
|
|
140
|
+
const fqrl = await qrlCacheGet(fqname, opt)
|
|
141
|
+
const fqa = await getQueueAttributes(fqrl)
|
|
142
|
+
const params = {
|
|
143
|
+
Attributes: {
|
|
144
|
+
MessageRetentionPeriod: opt.messageRetentionPeriod + '',
|
|
145
|
+
RedrivePolicy: JSON.stringify({
|
|
146
|
+
deadLetterTargetArn: fqa.Attributes.QueueArn,
|
|
147
|
+
maxReceiveCount: 1
|
|
148
|
+
})
|
|
149
|
+
},
|
|
150
|
+
QueueName: qname
|
|
151
|
+
}
|
|
152
|
+
if (opt.tags) params.tags = opt.tags
|
|
153
|
+
if (opt.fifo) params.Attributes.FifoQueue = 'true'
|
|
154
|
+
return params
|
|
155
|
+
}
|
|
156
|
+
|
|
95
157
|
/**
|
|
96
158
|
* Returns a qrl for a queue that either exists or does not
|
|
97
159
|
*/
|
|
@@ -105,29 +167,25 @@ export async function getOrCreateQueue (queue, opt) {
|
|
|
105
167
|
// Anything other than queue doesn't exist gets re-thrown
|
|
106
168
|
if (!(err instanceof QueueDoesNotExist)) throw err
|
|
107
169
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
170
|
+
// Grab params, creating fail queue if needed
|
|
171
|
+
let params
|
|
172
|
+
try {
|
|
173
|
+
params = await getQueueParams(qname, opt)
|
|
174
|
+
} catch (e) {
|
|
175
|
+
// If fail queue doesn't exist, create it
|
|
176
|
+
if (!(e instanceof QueueDoesNotExist)) throw e
|
|
177
|
+
await getOrCreateFailQueue(qname, opt, true)
|
|
178
|
+
params = await getQueueParams(qname, opt)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
debug({ getOrCreateQueue: { qname, params } })
|
|
111
182
|
|
|
112
183
|
// Create our queue
|
|
113
184
|
const client = getSQSClient()
|
|
114
|
-
const params = {
|
|
115
|
-
Attributes: {
|
|
116
|
-
MessageRetentionPeriod: opt.messageRetentionPeriod + '',
|
|
117
|
-
RedrivePolicy: JSON.stringify({
|
|
118
|
-
deadLetterTargetArn: fqa.Attributes.QueueArn,
|
|
119
|
-
maxReceiveCount: '1'
|
|
120
|
-
})
|
|
121
|
-
},
|
|
122
|
-
QueueName: qname
|
|
123
|
-
}
|
|
124
|
-
if (opt.tags) params.tags = opt.tags
|
|
125
|
-
if (opt.fifo) params.Attributes.FifoQueue = 'true'
|
|
126
185
|
const cmd = new CreateQueueCommand(params)
|
|
127
|
-
|
|
128
|
-
if (opt.verbose) console.error(chalk.blue('Creating queue ') + qname)
|
|
186
|
+
if (opt.verbose) console.error(chalk.blue('Creating fail queue ') + qname)
|
|
129
187
|
const data = await client.send(cmd)
|
|
130
|
-
debug('createQueue returned', data)
|
|
188
|
+
debug('AWS createQueue returned', data)
|
|
131
189
|
const qrl = data.QueueUrl
|
|
132
190
|
qrlCacheSet(qname, qrl)
|
|
133
191
|
return qrl
|
|
@@ -145,17 +203,15 @@ export async function getQueueAttributes (qrl) {
|
|
|
145
203
|
return data
|
|
146
204
|
}
|
|
147
205
|
|
|
148
|
-
export function formatMessage (
|
|
149
|
-
const message = {
|
|
150
|
-
/*
|
|
151
|
-
MessageAttributes: {
|
|
152
|
-
City: { DataType: 'String', StringValue: 'Any City' },
|
|
153
|
-
Population: { DataType: 'Number', StringValue: '1250800' }
|
|
154
|
-
},
|
|
155
|
-
*/
|
|
156
|
-
MessageBody: command
|
|
157
|
-
}
|
|
206
|
+
export function formatMessage (body, id, opt, messageOptions) {
|
|
207
|
+
const message = { MessageBody: body }
|
|
158
208
|
if (typeof id !== 'undefined') message.Id = '' + id
|
|
209
|
+
if (opt.fifo) {
|
|
210
|
+
message.MessageGroupId = messageOptions?.groupId || opt?.groupId
|
|
211
|
+
}
|
|
212
|
+
addDedupParamsToMessage(message, opt, messageOptions)
|
|
213
|
+
if (opt.delay) message.DelaySeconds = opt.delay
|
|
214
|
+
if (messageOptions?.delay) message.DelaySeconds = messageOptions.delay
|
|
159
215
|
return message
|
|
160
216
|
}
|
|
161
217
|
|
|
@@ -166,14 +222,19 @@ const retryableExceptions = [
|
|
|
166
222
|
QueueDoesNotExist // Queue could temporarily not exist due to eventual consistency, let it retry
|
|
167
223
|
]
|
|
168
224
|
|
|
169
|
-
export async function sendMessage (qrl, command, opt) {
|
|
225
|
+
export async function sendMessage (qrl, command, opt, messageOptions) {
|
|
170
226
|
debug('sendMessage(', qrl, command, ')')
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
227
|
+
const uuidFunction = opt.uuidFunction || uuidV1
|
|
228
|
+
const params = {
|
|
229
|
+
QueueUrl: qrl,
|
|
230
|
+
...formatMessage(command, null, opt, messageOptions)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// See if we even have to send it
|
|
234
|
+
if (opt.externalDedup) {
|
|
235
|
+
const shouldEnqueue = await dedupShouldEnqueue(params, opt)
|
|
236
|
+
if (!shouldEnqueue) return { MessageId: uuidFunction() }
|
|
175
237
|
}
|
|
176
|
-
if (opt.delay) params.DelaySeconds = opt.delay
|
|
177
238
|
|
|
178
239
|
// Send it
|
|
179
240
|
const client = getSQSClient()
|
|
@@ -187,12 +248,15 @@ export async function sendMessage (qrl, command, opt) {
|
|
|
187
248
|
return data
|
|
188
249
|
}
|
|
189
250
|
const shouldRetry = async (result, error) => {
|
|
251
|
+
if (!error) return false
|
|
190
252
|
for (const exceptionClass of retryableExceptions) {
|
|
191
253
|
if (error instanceof exceptionClass) {
|
|
192
254
|
debug({ sendMessageRetryingBecause: { error, result } })
|
|
193
255
|
return true
|
|
194
256
|
}
|
|
195
257
|
}
|
|
258
|
+
// If we could not send it, we also need to remove our dedup flag
|
|
259
|
+
await dedupSuccessfullyProcessed(params, opt)
|
|
196
260
|
return false
|
|
197
261
|
}
|
|
198
262
|
const result = await backoff.run(send, shouldRetry)
|
|
@@ -203,25 +267,17 @@ export async function sendMessage (qrl, command, opt) {
|
|
|
203
267
|
export async function sendMessageBatch (qrl, messages, opt) {
|
|
204
268
|
debug('sendMessageBatch(', qrl, messages.map(e => Object.assign(Object.assign({}, e), { MessageBody: e.MessageBody.slice(0, 10) + '...' })), ')')
|
|
205
269
|
const params = { Entries: messages, QueueUrl: qrl }
|
|
206
|
-
const uuidFunction = opt.uuidFunction || uuidV1
|
|
207
|
-
// Add in group id if we're using fifo
|
|
208
|
-
if (opt.fifo) {
|
|
209
|
-
params.Entries = params.Entries.map(
|
|
210
|
-
message => Object.assign({
|
|
211
|
-
MessageGroupId: opt.groupIdPerMessage ? uuidFunction() : opt.groupId,
|
|
212
|
-
MessageDeduplicationId: opt.deduplicationId || uuidFunction()
|
|
213
|
-
}, message)
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
if (opt.delay) {
|
|
217
|
-
params.Entries = params.Entries.map(message =>
|
|
218
|
-
Object.assign({ DelaySeconds: opt.delay }, message))
|
|
219
|
-
}
|
|
220
270
|
if (opt.sentryDsn) {
|
|
221
271
|
addBreadcrumb({ category: 'sendMessageBatch', message: JSON.stringify({ params }), level: 'debug' })
|
|
222
272
|
}
|
|
223
273
|
debug({ params })
|
|
224
274
|
|
|
275
|
+
// See which messages we even have to send
|
|
276
|
+
if (opt.externalDedup) {
|
|
277
|
+
params.Entries = await dedupShouldEnqueueMulti(params.Entries, opt)
|
|
278
|
+
if (!params.Entries.length) return
|
|
279
|
+
}
|
|
280
|
+
|
|
225
281
|
// Send them
|
|
226
282
|
const client = getSQSClient()
|
|
227
283
|
const cmd = new SendMessageBatchCommand(params)
|
|
@@ -320,8 +376,8 @@ export async function flushMessages (qrl, opt, sendBuffer) {
|
|
|
320
376
|
// Automaticaly flushes if queue has >= 10 messages.
|
|
321
377
|
// Returns number of messages flushed.
|
|
322
378
|
//
|
|
323
|
-
export async function addMessage (qrl, command, messageIndex, opt, sendBuffer) {
|
|
324
|
-
const message = formatMessage(command, messageIndex)
|
|
379
|
+
export async function addMessage (qrl, command, messageIndex, opt, sendBuffer, messageOptions) {
|
|
380
|
+
const message = formatMessage(command, messageIndex, opt, messageOptions)
|
|
325
381
|
sendBuffer[qrl] = sendBuffer[qrl] || []
|
|
326
382
|
sendBuffer[qrl].push(message)
|
|
327
383
|
debug({ location: 'addMessage', sendBuffer })
|
|
@@ -338,8 +394,16 @@ export async function addMessage (qrl, command, messageIndex, opt, sendBuffer) {
|
|
|
338
394
|
export async function enqueue (queue, command, options) {
|
|
339
395
|
debug('enqueue(', { queue, command }, ')')
|
|
340
396
|
const opt = getOptionsWithDefaults(options)
|
|
341
|
-
|
|
342
|
-
|
|
397
|
+
if (opt.sentryDsn) {
|
|
398
|
+
setExtra({ qdoneOperation: 'enqueue', args: { queue, command, opt } })
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const qrl = await getOrCreateQueue(queue, opt)
|
|
402
|
+
return sendMessage(qrl, command, opt)
|
|
403
|
+
} catch (e) {
|
|
404
|
+
console.log(e)
|
|
405
|
+
throw e
|
|
406
|
+
}
|
|
343
407
|
}
|
|
344
408
|
|
|
345
409
|
//
|
|
@@ -349,43 +413,50 @@ export async function enqueue (queue, command, options) {
|
|
|
349
413
|
export async function enqueueBatch (pairs, options) {
|
|
350
414
|
debug('enqueueBatch(', pairs, ')')
|
|
351
415
|
const opt = getOptionsWithDefaults(options)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// queues are created prior to going through our flush logic
|
|
355
|
-
const normalizedPairs = pairs.map(({ queue, command }) => ({
|
|
356
|
-
qname: normalizeQueueName(queue, opt),
|
|
357
|
-
command
|
|
358
|
-
}))
|
|
359
|
-
const uniqueQnames = new Set(normalizedPairs.map(p => p.qname))
|
|
360
|
-
|
|
361
|
-
// Prefetch qrls / create queues in parallel
|
|
362
|
-
const createPromises = []
|
|
363
|
-
for (const qname of uniqueQnames) {
|
|
364
|
-
createPromises.push(getOrCreateQueue(qname, opt))
|
|
365
|
-
}
|
|
366
|
-
await Promise.all(createPromises)
|
|
367
|
-
|
|
368
|
-
// After we've prefetched, all qrls are in cache
|
|
369
|
-
// so go back through the list of pairs and fire off messages
|
|
370
|
-
requestCount = 0
|
|
371
|
-
const sendBuffer = {}
|
|
372
|
-
let messageIndex = 0
|
|
373
|
-
let initialFlushTotal = 0
|
|
374
|
-
for (const { qname, command } of normalizedPairs) {
|
|
375
|
-
const qrl = await getOrCreateQueue(qname, opt)
|
|
376
|
-
initialFlushTotal += await addMessage(qrl, command, messageIndex++, opt, sendBuffer)
|
|
416
|
+
if (opt.sentryDsn) {
|
|
417
|
+
setExtra({ qdoneOperation: 'enqueueBatch', args: { pairs, opt } })
|
|
377
418
|
}
|
|
419
|
+
try {
|
|
420
|
+
// Find unique queues so we can pre-fetch qrls. We do this so that all
|
|
421
|
+
// queues are created prior to going through our flush logic
|
|
422
|
+
const normalizedPairs = pairs.map(({ queue, command, messageOptions }) => ({
|
|
423
|
+
qname: normalizeQueueName(queue, opt),
|
|
424
|
+
command,
|
|
425
|
+
messageOptions: validateMessageOptions(messageOptions)
|
|
426
|
+
}))
|
|
427
|
+
const uniqueQnames = new Set(normalizedPairs.map(p => p.qname))
|
|
378
428
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
429
|
+
// Prefetch qrls / create queues in parallel
|
|
430
|
+
const createPromises = []
|
|
431
|
+
for (const qname of uniqueQnames) {
|
|
432
|
+
createPromises.push(getOrCreateQueue(qname, opt))
|
|
433
|
+
}
|
|
434
|
+
await Promise.all(createPromises)
|
|
435
|
+
// After we've prefetched, all qrls are in cache
|
|
436
|
+
// so go back through the list of pairs and fire off messages
|
|
437
|
+
requestCount = 0
|
|
438
|
+
const sendBuffer = {}
|
|
439
|
+
let messageIndex = 0
|
|
440
|
+
let initialFlushTotal = 0
|
|
441
|
+
for (const { qname, command, messageOptions } of normalizedPairs) {
|
|
442
|
+
const qrl = await getOrCreateQueue(qname, opt)
|
|
443
|
+
initialFlushTotal += await addMessage(qrl, command, messageIndex++, opt, sendBuffer, messageOptions)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// And flush any remaining messages
|
|
447
|
+
const extraFlushPromises = []
|
|
448
|
+
for (const qrl in sendBuffer) {
|
|
449
|
+
extraFlushPromises.push(flushMessages(qrl, opt, sendBuffer))
|
|
450
|
+
}
|
|
451
|
+
const extraFlushCounts = await Promise.all(extraFlushPromises)
|
|
452
|
+
const extraFlushTotal = extraFlushCounts.reduce((a, b) => a + b, 0)
|
|
453
|
+
const totalFlushed = initialFlushTotal + extraFlushTotal
|
|
454
|
+
debug({ initialFlushTotal, extraFlushTotal, totalFlushed })
|
|
455
|
+
return totalFlushed
|
|
456
|
+
} catch (e) {
|
|
457
|
+
console.log(e)
|
|
458
|
+
throw e
|
|
383
459
|
}
|
|
384
|
-
const extraFlushCounts = await Promise.all(extraFlushPromises)
|
|
385
|
-
const extraFlushTotal = extraFlushCounts.reduce((a, b) => a + b, 0)
|
|
386
|
-
const totalFlushed = initialFlushTotal + extraFlushTotal
|
|
387
|
-
debug({ initialFlushTotal, extraFlushTotal, totalFlushed })
|
|
388
|
-
return totalFlushed
|
|
389
460
|
}
|
|
390
461
|
|
|
391
462
|
debug('loaded')
|
package/src/qrlCache.js
CHANGED
|
@@ -66,7 +66,7 @@ export async function qrlCacheGet (qname) {
|
|
|
66
66
|
// debug({ cmd })
|
|
67
67
|
const result = await client.send(cmd)
|
|
68
68
|
// debug('result', result)
|
|
69
|
-
|
|
69
|
+
if (!result) throw new QueueDoesNotExist(qname)
|
|
70
70
|
const { QueueUrl: qrl } = result
|
|
71
71
|
// debug('getQueueUrl returned', data)
|
|
72
72
|
qcache.set(qname, qrl)
|
|
@@ -8,6 +8,7 @@ import { ChangeMessageVisibilityBatchCommand, DeleteMessageBatchCommand } from '
|
|
|
8
8
|
import chalk from 'chalk'
|
|
9
9
|
import Debug from 'debug'
|
|
10
10
|
|
|
11
|
+
import { dedupSuccessfullyProcessedMulti } from '../dedup.js'
|
|
11
12
|
import { getSQSClient } from '../sqs.js'
|
|
12
13
|
|
|
13
14
|
const debug = Debug('qdone:jobExecutor')
|
|
@@ -221,6 +222,10 @@ export class JobExecutor {
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
debug('DeleteMessageBatch returned', result)
|
|
225
|
+
|
|
226
|
+
// Mark batch as processed for dedup
|
|
227
|
+
await dedupSuccessfullyProcessedMulti(entries.map(e => this.jobsByMessageId[e.Id]), this.opt)
|
|
228
|
+
|
|
224
229
|
// TODO Sentry
|
|
225
230
|
}
|
|
226
231
|
}
|
|
@@ -104,7 +104,7 @@ export class QueueManager {
|
|
|
104
104
|
.filter(({ qname, qrl }) => {
|
|
105
105
|
const isFifo = qname.endsWith('.fifo')
|
|
106
106
|
const isDead = isFifo ? qname.endsWith('_dead.fifo') : qname.endsWith('_dead')
|
|
107
|
-
return !isDead
|
|
107
|
+
return this.opt.includeDead ? true : !isDead
|
|
108
108
|
})
|
|
109
109
|
// then icehouse
|
|
110
110
|
.filter(({ qname, qrl }) => !this.keepInIcehouse(qrl, now))
|
package/src/worker.js
CHANGED
|
@@ -13,6 +13,7 @@ import treeKill from 'tree-kill'
|
|
|
13
13
|
import chalk from 'chalk'
|
|
14
14
|
import Debug from 'debug'
|
|
15
15
|
|
|
16
|
+
import { dedupSuccessfullyProcessed } from './dedup.js'
|
|
16
17
|
import { normalizeQueueName, getQnameUrlPairs } from './qrlCache.js'
|
|
17
18
|
import { getOptionsWithDefaults } from './defaults.js'
|
|
18
19
|
import { cheapIdleCheck } from './idleQueues.js'
|
|
@@ -129,10 +130,15 @@ export async function executeJob (job, qname, qrl, opt) {
|
|
|
129
130
|
QueueUrl: qrl,
|
|
130
131
|
ReceiptHandle: job.ReceiptHandle
|
|
131
132
|
}))
|
|
133
|
+
|
|
132
134
|
if (opt.verbose) {
|
|
133
135
|
console.error(chalk.blue(' done'))
|
|
134
136
|
console.error()
|
|
135
137
|
}
|
|
138
|
+
|
|
139
|
+
// Let dedup system know we processed it
|
|
140
|
+
await dedupSuccessfullyProcessed(job, opt)
|
|
141
|
+
|
|
136
142
|
return { noJobs: 0, jobsSucceeded: 1, jobsFailed: 0 }
|
|
137
143
|
} catch (err) {
|
|
138
144
|
// Fail path for job execution
|