qdone 2.0.47-alpha → 2.0.49-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdone",
3
- "version": "2.0.47-alpha",
3
+ "version": "2.0.49-alpha",
4
4
  "description": "A distributed scheduler for SQS",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/src/defaults.js CHANGED
@@ -133,7 +133,7 @@ export function getOptionsWithDefaults (options) {
133
133
  // Idle Queues
134
134
  idleFor: options.idleFor || options['idle-for'] || defaults.idleFor,
135
135
  delete: options.delete || defaults.delete,
136
- unpair: options.delete || defaults.unpair,
136
+ unpair: options.unpair || defaults.unpair,
137
137
 
138
138
  // Check
139
139
  create: options.create || defaults.create,
package/src/enqueue.js CHANGED
@@ -276,7 +276,18 @@ export async function sendMessageBatch (qrl, messages, opt) {
276
276
  const promises = params.Entries.map(async m => ({ m, shouldEnqueue: await dedupShouldEnqueue(m, opt) }))
277
277
  const results = await Promise.all(promises)
278
278
  params.Entries = results.filter(({ shouldEnqueue }) => shouldEnqueue).map(({ m }) => m)
279
- if (!params.Entries.length) return { Failed: [], Successful: [] }
279
+ if (!params.Entries.length) {
280
+ const result = {
281
+ Failed: [],
282
+ Successful: results.map(
283
+ ({ m: { Id: id, MessageAttributes: ma } }) => ({
284
+ Id: id,
285
+ MessageId: 'duplicate',
286
+ QdoneDeduplicationId: ma?.QdoneDeduplicationId?.StringValue
287
+ }))
288
+ }
289
+ return result
290
+ }
280
291
  }
281
292
 
282
293
  // Send them
package/src/idleQueues.js CHANGED
@@ -7,7 +7,7 @@ import { getCloudWatchClient } from './cloudWatch.js'
7
7
  import { getOptionsWithDefaults } from './defaults.js'
8
8
  import { GetQueueAttributesCommand, DeleteQueueCommand, QueueDoesNotExist } from '@aws-sdk/client-sqs'
9
9
  import { GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch'
10
- import { normalizeFailQueueName, getQnameUrlPairs, fifoSuffix } from './qrlCache.js'
10
+ import { normalizeFailQueueName, normalizeDLQName, getQnameUrlPairs, fifoSuffix } from './qrlCache.js'
11
11
  import { getCache, setCache } from './cache.js'
12
12
  // const AWS = require('aws-sdk')
13
13
 
@@ -37,6 +37,7 @@ const metricNames = [
37
37
  * Actual SQS call, used in conjunction with cache.
38
38
  */
39
39
  export async function _cheapIdleCheck (qname, qrl, opt) {
40
+ debug('_cheapIdleCheck', qname, qrl)
40
41
  try {
41
42
  const client = getSQSClient()
42
43
  const cmd = new GetQueueAttributesCommand({ AttributeNames: attributeNames, QueueUrl: qrl })
@@ -46,11 +47,13 @@ export async function _cheapIdleCheck (qname, qrl, opt) {
46
47
  result.queue = qname.slice(opt.prefix.length)
47
48
  // We are idle if all the messages attributes are zero
48
49
  result.idle = attributeNames.filter(k => result[k] === '0').length === attributeNames.length
50
+ result.exists = true
51
+ debug({ result, SQS: 1 })
49
52
  return { result, SQS: 1 }
50
53
  } catch (e) {
54
+ debug({ _cheapIdleCheck: e })
51
55
  if (e instanceof QueueDoesNotExist) {
52
- // Count deleted queues as idle
53
- return { result: { idle: true }, SQS: 1 }
56
+ return { result: { idle: undefined, exists: false }, SQS: 1 }
54
57
  } else {
55
58
  throw e
56
59
  }
@@ -62,6 +65,7 @@ export async function _cheapIdleCheck (qname, qrl, opt) {
62
65
  * at this immediate moment.
63
66
  */
64
67
  export async function cheapIdleCheck (qname, qrl, opt) {
68
+ debug('cheapIdleCheck', qname, qrl)
65
69
  // Just call the API if we don't have a cache
66
70
  if (!opt.cacheUri) return _cheapIdleCheck(qname, qrl, opt)
67
71
 
@@ -125,19 +129,20 @@ export async function checkIdle (qname, qrl, opt) {
125
129
  const { result: cheapResult, SQS } = await cheapIdleCheck(qname, qrl, opt)
126
130
  debug('cheapResult', cheapResult)
127
131
 
128
- // Short circuit further calls if cheap result shows data
129
- if (cheapResult.idle === false) {
132
+ // Short circuit further calls if cheap result is conclusive
133
+ if (cheapResult.idle === false || cheapResult.exists === false) {
130
134
  return {
131
135
  queue: qname.slice(opt.prefix.length),
132
136
  cheap: cheapResult,
133
- idle: false,
137
+ idle: cheapResult.idle,
138
+ exists: cheapResult.exists,
134
139
  apiCalls: { SQS, CloudWatch: 0 }
135
140
  }
136
141
  }
137
142
 
138
143
  // If we get here, there's nothing in the queue at the moment,
139
144
  // so we have to check metrics one at a time
140
- const apiCalls = { SQS: 1, CloudWatch: 0 }
145
+ const apiCalls = { SQS, CloudWatch: 0 }
141
146
  const results = []
142
147
  let idle = true
143
148
  for (const metricName of metricNames) {
@@ -158,7 +163,8 @@ export async function checkIdle (qname, qrl, opt) {
158
163
  queue: qname.slice(opt.prefix.length),
159
164
  cheap: cheapResult,
160
165
  apiCalls,
161
- idle
166
+ idle,
167
+ exists: true
162
168
  },
163
169
  ...results // merge in results from CloudWatch
164
170
  )
@@ -210,92 +216,96 @@ export async function processQueue (qname, qrl, opt) {
210
216
  }
211
217
 
212
218
  /**
213
- * Processes a queue and its fail queue, treating them as a unit.
219
+ * Processes a queue and its fail and delete queue, treating them as a unit.
214
220
  */
215
- export async function processQueuePair (qname, qrl, opt) {
221
+ export async function processQueueSet (qname, qrl, opt) {
216
222
  const isFifo = qname.endsWith('.fifo')
217
223
  const normalizeOptions = Object.assign({}, opt, { fifo: isFifo })
224
+
225
+ // Generate DLQ name/url
226
+ const dqname = normalizeDLQName(qname, normalizeOptions)
227
+ const dqrl = normalizeDLQName(dqname, normalizeOptions)
228
+
218
229
  // Generate fail queue name/url
219
230
  const fqname = normalizeFailQueueName(qname, normalizeOptions)
220
231
  const fqrl = normalizeFailQueueName(fqname, normalizeOptions)
221
232
 
222
- // Idle check
223
- const result = await checkIdle(qname, qrl, opt)
224
- debug('result', result)
225
-
226
- // Queue is active
227
- const active = !result.idle
228
- if (active) {
229
- if (opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + opt.idleFor + chalk.blue(' minutes.'))
230
- return result
231
- }
233
+ debug({ qname, qrl, dqname, dqrl, fqname, fqrl })
232
234
 
233
- // Queue is idle
234
- if (opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
235
+ // Idle check
236
+ const qresult = await checkIdle(qname, qrl, opt)
237
+ const fqresult = await checkIdle(fqname, fqrl, opt)
238
+ const dqresult = await checkIdle(dqname, dqrl, opt)
239
+ debug({ qresult, fqresult, dqresult })
235
240
 
236
- // Check fail queue
237
- try {
238
- const fresult = await checkIdle(fqname, fqrl, opt)
239
- debug('fresult', fresult)
240
- const idleCheckResult = Object.assign(
241
- result,
242
- { idle: result.idle && fresult.idle, failq: fresult },
243
- {
244
- apiCalls: {
245
- SQS: result.apiCalls.SQS + fresult.apiCalls.SQS,
246
- CloudWatch: result.apiCalls.CloudWatch + fresult.apiCalls.CloudWatch
247
- }
241
+ // Start building return value
242
+ const result = Object.assign(
243
+ {
244
+ queue: qname,
245
+ idle: (
246
+ qresult.idle &&
247
+ (!fqresult.exists || fqresult.idle) &&
248
+ (!fqresult.exists || dqresult.idle)
249
+ ),
250
+ apiCalls: {
251
+ SQS: qresult.apiCalls.SQS + fqresult.apiCalls.SQS + dqresult.apiCalls.SQS,
252
+ CloudWatch: qresult.apiCalls.CloudWatch + fqresult.apiCalls.CloudWatch + dqresult.apiCalls.CloudWatch
248
253
  }
249
- )
250
-
251
- // Queue is active
252
- const factive = !fresult.idle
253
- if (factive) {
254
- if (opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + opt.idleFor + chalk.blue(' minutes.'))
255
- return idleCheckResult
256
254
  }
255
+ )
257
256
 
258
- // Queue is idle
259
- if (opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
257
+ // Queue is idle
258
+ if (qresult.idle && opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
259
+ if (fqresult.idle && opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
260
+ if (dqresult.idle && opt.verbose) console.error(chalk.blue('Queue ') + dqname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
260
261
 
261
- // Trigger a delete if the user wants it
262
- if (!opt.delete) return idleCheckResult
263
- const [dresult, dfresult] = await Promise.all([
264
- deleteQueue(qname, qrl, opt),
265
- deleteQueue(fqname, fqrl, opt)
266
- ])
267
- return Object.assign(idleCheckResult, {
268
- apiCalls: {
269
- // Sum the SQS calls across all four
270
- SQS: [result, fresult, dresult, dfresult]
271
- .map(r => r.apiCalls.SQS)
272
- .reduce((a, b) => a + b, 0),
273
- // Sum the CloudWatch calls across all four
274
- CloudWatch: [result, fresult, dresult, dfresult]
275
- .map(r => r.apiCalls.CloudWatch)
276
- .reduce((a, b) => a + b, 0)
262
+ // Delete if all are idle
263
+ const canDelete = (
264
+ (qresult.idle || qresult.exists === false) &&
265
+ (fqresult.idle || fqresult.exists === false) &&
266
+ (dqresult.idle || dqresult.exists === false)
267
+ )
268
+ debug({ canDelete })
269
+
270
+ if (opt.delete && canDelete) {
271
+ // Normal
272
+ const qdresult = await (async () => {
273
+ debug({ qresult })
274
+ try {
275
+ if (qresult.idle) return deleteQueue(qname, qrl, opt)
276
+ } catch (e) {
277
+ if (!(e instanceof QueueDoesNotExist)) throw e
277
278
  }
278
- })
279
- } catch (e) {
280
- // Handle the case where the fail queue has been deleted or was never
281
- // created for some reason
282
- if (!(e instanceof QueueDoesNotExist)) throw e
279
+ })()
280
+ if (qdresult) { result.apiCalls.SQS += qdresult.apiCalls.SQS }
281
+ debug({ qdresult })
283
282
 
284
- // Fail queue doesn't exist if we get here
285
- if (opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' does not exist.'))
283
+ // Fail
284
+ const fqdresult = await (async () => {
285
+ debug({ fqresult })
286
+ try {
287
+ if (fqresult.idle) return deleteQueue(fqname, fqrl, opt)
288
+ } catch (e) {
289
+ if (!(e instanceof QueueDoesNotExist)) throw e
290
+ }
291
+ })()
292
+ if (fqdresult) { result.apiCalls.SQS += fqdresult.apiCalls.SQS }
293
+ debug({ fqdresult })
286
294
 
287
- // Handle delete
288
- if (!opt.delete) return result
289
- const deleteResult = await deleteQueue(qname, qrl, opt)
290
- const resultIncludingDelete = Object.assign(result, {
291
- deleted: deleteResult.deleted,
292
- apiCalls: {
293
- SQS: result.apiCalls.SQS + deleteResult.apiCalls.SQS,
294
- CloudWatch: result.apiCalls.CloudWatch + deleteResult.apiCalls.CloudWatch
295
+ // Dead
296
+ const dqdresult = await (async () => {
297
+ debug({ dqresult })
298
+ try {
299
+ if (dqresult.idle) return deleteQueue(dqname, dqrl, opt)
300
+ } catch (e) {
301
+ if (!(e instanceof QueueDoesNotExist)) throw e
295
302
  }
296
- })
297
- return resultIncludingDelete
303
+ })()
304
+ if (dqdresult) { result.apiCalls.SQS += dqdresult.apiCalls.SQS }
305
+ debug({ dqdresult })
298
306
  }
307
+
308
+ return result
299
309
  }
300
310
 
301
311
  //
@@ -318,7 +328,11 @@ export async function idleQueues (queues, options) {
318
328
  const sufFifo = opt.failSuffix + fifoSuffix
319
329
  const isFail = entry.qname.endsWith(suf)
320
330
  const isFifoFail = entry.qname.endsWith(sufFifo)
321
- return opt.includeFailed ? true : (!isFail && !isFifoFail)
331
+ const sufDead = opt.dlqSuffix
332
+ const sufFifoDead = opt.dlqSuffix + fifoSuffix
333
+ const isDead = entry.qname.endsWith(sufDead)
334
+ const isFifoDead = entry.qname.endsWith(sufFifoDead)
335
+ return opt.includeFailed ? true : (!isFail && !isFifoFail && !isDead && !isFifoDead)
322
336
  })
323
337
 
324
338
  // But only if we have queues to remove
@@ -332,7 +346,7 @@ export async function idleQueues (queues, options) {
332
346
  }
333
347
  // Check each queue in parallel
334
348
  if (opt.unpair) return Promise.all(filteredEntries.map(e => processQueue(e.qname, e.qrl, opt)))
335
- return Promise.all(filteredEntries.map(e => processQueuePair(e.qname, e.qrl, opt)))
349
+ return Promise.all(filteredEntries.map(e => processQueueSet(e.qname, e.qrl, opt)))
336
350
  }
337
351
 
338
352
  // Otherwise, let caller know