qdone 2.0.56-alpha → 2.1.1

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  ISC License
2
2
 
3
- Copyright (c) 2017, Ryan Witt
3
+ Copyright (c) 2017-2026, SureDone, Inc.
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any
6
6
  purpose with or without fee is hereby granted, provided that the above
@@ -12,4 +12,4 @@ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
12
  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
13
  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
14
  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,9 +1,6 @@
1
1
  [![NPM Package](https://img.shields.io/npm/v/qdone.svg)](https://www.npmjs.com/package/qdone)
2
- [![Build Status](https://travis-ci.org/suredone/qdone.svg?branch=master)](https://travis-ci.org/suredone/qdone)
3
- [![Coverage Status](https://coveralls.io/repos/github/suredone/qdone/badge.svg)](https://coveralls.io/github/suredone/qdone)
4
- [![Dependencies](https://img.shields.io/david/suredone/qdone.svg)](https://david-dm.org/suredone/qdone)
2
+ [![Build](https://github.com/suredone/qdone/actions/workflows/build.yaml/badge.svg)](https://github.com/suredone/qdone/actions/workflows/build.yaml)
5
3
  [![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
6
- [![Greenkeeper badge](https://badges.greenkeeper.io/suredone/qdone.svg)](https://greenkeeper.io/)
7
4
 
8
5
  # qdone
9
6
 
@@ -223,7 +223,7 @@ const retryableExceptions = [
223
223
  client_sqs_1.KmsThrottled,
224
224
  client_sqs_1.QueueDoesNotExist // Queue could temporarily not exist due to eventual consistency, let it retry
225
225
  ];
226
- async function sendMessage(qrl, command, opt, messageOptions) {
226
+ async function sendMessage(qrl, queue, command, opt, messageOptions) {
227
227
  debug('sendMessage(', qrl, command, ')');
228
228
  const uuidFunction = opt.uuidFunction || uuid_1.v1;
229
229
  const params = {
@@ -238,7 +238,7 @@ async function sendMessage(qrl, command, opt, messageOptions) {
238
238
  }
239
239
  // Send it
240
240
  const client = (0, sqs_js_1.getSQSClient)();
241
- const cmd = new client_sqs_1.SendMessageCommand(params);
241
+ let cmd = new client_sqs_1.SendMessageCommand(params);
242
242
  debug({ cmd });
243
243
  const backoff = new exponentialBackoff_js_1.ExponentialBackoff(opt.sendRetries);
244
244
  const send = async (attemptNumber) => {
@@ -250,6 +250,14 @@ async function sendMessage(qrl, command, opt, messageOptions) {
250
250
  const shouldRetry = async (result, error) => {
251
251
  if (!error)
252
252
  return false;
253
+ if (error instanceof client_sqs_1.QueueDoesNotExist) {
254
+ const qname = (0, qrlCache_js_1.normalizeQueueName)(queue, opt);
255
+ (0, qrlCache_js_1.qrlCacheInvalidate)(qname);
256
+ // clear cache in case cache does not reflect reality, then try recreating the queue before sending message again
257
+ const newQrl = await getOrCreateQueue(queue, opt);
258
+ params.QueueUrl = newQrl;
259
+ cmd = new client_sqs_1.SendMessageCommand(params);
260
+ }
253
261
  for (const exceptionClass of retryableExceptions) {
254
262
  if (error instanceof exceptionClass) {
255
263
  debug({ sendMessageRetryingBecause: { error, result } });
@@ -265,7 +273,7 @@ async function sendMessage(qrl, command, opt, messageOptions) {
265
273
  return result;
266
274
  }
267
275
  exports.sendMessage = sendMessage;
268
- async function sendMessageBatch(qrl, messages, opt) {
276
+ async function sendMessageBatch(qrl, queue, messages, opt) {
269
277
  debug('sendMessageBatch(', qrl, messages.map(e => Object.assign(Object.assign({}, e), { MessageBody: e.MessageBody.slice(0, 10) + '...' })), ')');
270
278
  const params = { Entries: messages, QueueUrl: qrl };
271
279
  if (opt.sentryDsn) {
@@ -291,7 +299,7 @@ async function sendMessageBatch(qrl, messages, opt) {
291
299
  }
292
300
  // Send them
293
301
  const client = (0, sqs_js_1.getSQSClient)();
294
- const cmd = new client_sqs_1.SendMessageBatchCommand(params);
302
+ let cmd = new client_sqs_1.SendMessageBatchCommand(params);
295
303
  debug({ cmd });
296
304
  const backoff = new exponentialBackoff_js_1.ExponentialBackoff(opt.sendRetries);
297
305
  const send = async (attemptNumber) => {
@@ -299,7 +307,7 @@ async function sendMessageBatch(qrl, messages, opt) {
299
307
  const data = await client.send(cmd);
300
308
  return data;
301
309
  };
302
- const shouldRetry = (result, error) => {
310
+ const shouldRetry = async (result, error) => {
303
311
  debug({ shouldRetry: { error, result } });
304
312
  if (result) {
305
313
  // Handle failed result of one or more messages in the batch
@@ -323,6 +331,14 @@ async function sendMessageBatch(qrl, messages, opt) {
323
331
  if (opt.sentryDsn) {
324
332
  (0, node_1.addBreadcrumb)({ category: 'sendMessageBatch', message: JSON.stringify({ error }), level: 'error' });
325
333
  }
334
+ if (error instanceof client_sqs_1.QueueDoesNotExist) {
335
+ const qname = (0, qrlCache_js_1.normalizeQueueName)(queue, opt);
336
+ (0, qrlCache_js_1.qrlCacheInvalidate)(qname);
337
+ // Clear stale cache entry and recreate queue before retrying
338
+ const newQrl = await getOrCreateQueue(queue, opt);
339
+ params.QueueUrl = newQrl;
340
+ cmd = new client_sqs_1.SendMessageBatchCommand(params);
341
+ }
326
342
  for (const exceptionClass of retryableExceptions) {
327
343
  debug({ exceptionClass, retryableExceptions });
328
344
  if (error instanceof exceptionClass) {
@@ -341,8 +357,8 @@ let requestCount = 0;
341
357
  // If the message is too large, batch is retried with half the messages.
342
358
  // Returns number of messages flushed.
343
359
  //
344
- async function flushMessages(qrl, opt, sendBuffer) {
345
- debug('flushMessages', { qrl, sendBuffer });
360
+ async function flushMessages(qrl, queue, opt, sendBuffer) {
361
+ debug('flushMessages', { qrl, queue, sendBuffer });
346
362
  // Track our outgoing messages to map with Failed / Successful returns
347
363
  const messagesById = new Map();
348
364
  const resultsById = new Map();
@@ -376,7 +392,7 @@ async function flushMessages(qrl, opt, sendBuffer) {
376
392
  nextSize = 0;
377
393
  }
378
394
  // Send batch
379
- const data = await sendMessageBatch(qrl, batch, opt);
395
+ const data = await sendMessageBatch(qrl, queue, batch, opt);
380
396
  // Fail if there are any individual message failures
381
397
  if (data?.Failed && data?.Failed.length) {
382
398
  const err = new Error('One or more message failures: ' + JSON.stringify(data.Failed));
@@ -412,13 +428,13 @@ exports.flushMessages = flushMessages;
412
428
  // Returns number of messages flushed.
413
429
  //
414
430
  const debugAddMessage = (0, debug_1.default)('qdone:enqueue:addMessage');
415
- async function addMessage(qrl, command, messageIndex, opt, sendBuffer, messageOptions) {
431
+ async function addMessage(qrl, queue, command, messageIndex, opt, sendBuffer, messageOptions) {
416
432
  const message = formatMessage(command, messageIndex, opt, messageOptions);
417
433
  sendBuffer[qrl] = sendBuffer[qrl] || [];
418
434
  sendBuffer[qrl].push(message);
419
435
  debugAddMessage({ location: 'addMessage', messageIndex, sendBuffer });
420
436
  if (sendBuffer[qrl].length >= 10) {
421
- return flushMessages(qrl, opt, sendBuffer);
437
+ return flushMessages(qrl, queue, opt, sendBuffer);
422
438
  }
423
439
  return { numFlushed: 0, results: [] };
424
440
  }
@@ -435,7 +451,7 @@ async function enqueue(queue, command, options) {
435
451
  }
436
452
  try {
437
453
  const qrl = await getOrCreateQueue(queue, opt);
438
- return sendMessage(qrl, command, opt);
454
+ return sendMessage(qrl, queue, command, opt);
439
455
  }
440
456
  catch (e) {
441
457
  console.log(e);
@@ -463,10 +479,11 @@ async function enqueueBatch(pairs, options) {
463
479
  messageOptions: (0, defaults_js_1.validateMessageOptions)(messageOptions)
464
480
  }));
465
481
  const uniqueQnames = new Set(normalizedPairs.map(p => p.qname));
466
- // Prefetch qrls / create queues in parallel
482
+ // Prefetch qrls / create queues in parallel, building a reverse map for cache invalidation
467
483
  const createPromises = [];
484
+ const qrlToQname = new Map();
468
485
  for (const qname of uniqueQnames) {
469
- createPromises.push(getOrCreateQueue(qname, opt));
486
+ createPromises.push(getOrCreateQueue(qname, opt).then(qrl => qrlToQname.set(qrl, qname)));
470
487
  }
471
488
  await Promise.all(createPromises);
472
489
  // After we've prefetched, all qrls are in cache
@@ -477,14 +494,14 @@ async function enqueueBatch(pairs, options) {
477
494
  let initialFlushTotal = 0;
478
495
  for (const { qname, command, messageOptions } of normalizedPairs) {
479
496
  const qrl = await getOrCreateQueue(qname, opt);
480
- const { numFlushed, results } = await addMessage(qrl, command, messageIndex++, opt, sendBuffer, messageOptions);
497
+ const { numFlushed, results } = await addMessage(qrl, qname, command, messageIndex++, opt, sendBuffer, messageOptions);
481
498
  initialFlushTotal += numFlushed;
482
499
  allResults.push(...results);
483
500
  }
484
501
  // And flush any remaining messages
485
502
  const extraFlushPromises = [];
486
503
  for (const qrl in sendBuffer) {
487
- extraFlushPromises.push(flushMessages(qrl, opt, sendBuffer));
504
+ extraFlushPromises.push(flushMessages(qrl, qrlToQname.get(qrl), opt, sendBuffer));
488
505
  }
489
506
  let extraFlushTotal = 0;
490
507
  for (const { numFlushed, results } of await Promise.all(extraFlushPromises)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdone",
3
- "version": "2.0.56-alpha",
3
+ "version": "2.1.1",
4
4
  "description": "A distributed scheduler for SQS",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -57,8 +57,12 @@
57
57
  "publish-latest": "npm publish --tag latest",
58
58
  "publish-next": "npm publish --tag next"
59
59
  },
60
- "author": "Ryan Witt",
61
- "license": "MIT",
60
+ "author": "SureDone, Inc.",
61
+ "contributors": [
62
+ "Ryan Witt <onecreativenerd@gmail.com>",
63
+ "Shane O'Hanlon <shane.c.ohanlon@gmail.com>"
64
+ ],
65
+ "license": "ISC",
62
66
  "repository": {
63
67
  "type": "git",
64
68
  "url": "git+https://github.com/suredone/qdone.git"
package/src/check.js CHANGED
@@ -103,7 +103,7 @@ export async function checkFailQueue (queue, qrl, opt, indent = '') {
103
103
  try {
104
104
  // Get fail queue params, creating fail queue if it doesn't exist and create flag is set
105
105
  if (opt.verbose) console.error(chalk.blue(indent + 'checking ') + fqname)
106
- const { params: { Attributes: desired } } = await getFailParams(queue, opt)
106
+ const { Attributes: desired } = await getFailParams(queue, opt)
107
107
  const { Attributes: current } = await getQueueAttributes(fqrl)
108
108
  if (attributesMatch(current, desired, opt, indent + ' ')) {
109
109
  if (opt.verbose) console.error(chalk.green(indent + ' all good'))
@@ -128,7 +128,7 @@ export async function checkQueue (queue, qrl, opt, indent = '') {
128
128
  if (opt.verbose) console.error(chalk.blue(indent + 'checking ') + qname)
129
129
  await checkFailQueue(queue, qrl, opt, indent + ' ')
130
130
  try {
131
- const { params: { Attributes: desired } } = await getQueueParams(queue, opt)
131
+ const { Attributes: desired } = await getQueueParams(queue, opt)
132
132
  const { Attributes: current, $metadata } = await getQueueAttributes(qrl)
133
133
  debug({ current, $metadata })
134
134
  if (attributesMatch(current, desired, opt, indent + ' ')) {
package/src/cli.js CHANGED
@@ -520,7 +520,8 @@ export async function root (originalArgv, testHook) {
520
520
  { name: 'enqueue-batch', summary: 'Enqueue multiple commands from stdin or a file' },
521
521
  { name: 'worker', summary: 'Execute work on one or more queues' },
522
522
  { name: 'idle-queues', summary: 'Write a list of idle queues to stdout' },
523
- { name: 'monitor', summary: 'Monitor multiple queues at once' }
523
+ { name: 'monitor', summary: 'Monitor multiple queues at once' },
524
+ { name: 'check', summary: 'Check or update queue configurations' }
524
525
  ]
525
526
  },
526
527
  { content: 'Global Options', raw: true },
package/src/enqueue.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  import {
16
16
  qrlCacheGet,
17
17
  qrlCacheSet,
18
+ qrlCacheInvalidate,
18
19
  normalizeQueueName,
19
20
  normalizeFailQueueName,
20
21
  normalizeDLQName
@@ -221,7 +222,7 @@ const retryableExceptions = [
221
222
  QueueDoesNotExist // Queue could temporarily not exist due to eventual consistency, let it retry
222
223
  ]
223
224
 
224
- export async function sendMessage (qrl, command, opt, messageOptions) {
225
+ export async function sendMessage (qrl, queue, command, opt, messageOptions) {
225
226
  debug('sendMessage(', qrl, command, ')')
226
227
  const uuidFunction = opt.uuidFunction || uuidV1
227
228
  const params = {
@@ -237,7 +238,7 @@ export async function sendMessage (qrl, command, opt, messageOptions) {
237
238
 
238
239
  // Send it
239
240
  const client = getSQSClient()
240
- const cmd = new SendMessageCommand(params)
241
+ let cmd = new SendMessageCommand(params)
241
242
  debug({ cmd })
242
243
  const backoff = new ExponentialBackoff(opt.sendRetries)
243
244
  const send = async (attemptNumber) => {
@@ -248,6 +249,16 @@ export async function sendMessage (qrl, command, opt, messageOptions) {
248
249
  }
249
250
  const shouldRetry = async (result, error) => {
250
251
  if (!error) return false
252
+
253
+ if (error instanceof QueueDoesNotExist) {
254
+ const qname = normalizeQueueName(queue, opt)
255
+ qrlCacheInvalidate(qname)
256
+ // clear cache in case cache does not reflect reality, then try recreating the queue before sending message again
257
+ const newQrl = await getOrCreateQueue(queue, opt)
258
+ params.QueueUrl = newQrl
259
+ cmd = new SendMessageCommand(params)
260
+ }
261
+
251
262
  for (const exceptionClass of retryableExceptions) {
252
263
  if (error instanceof exceptionClass) {
253
264
  debug({ sendMessageRetryingBecause: { error, result } })
@@ -263,7 +274,7 @@ export async function sendMessage (qrl, command, opt, messageOptions) {
263
274
  return result
264
275
  }
265
276
 
266
- export async function sendMessageBatch (qrl, messages, opt) {
277
+ export async function sendMessageBatch (qrl, queue, messages, opt) {
267
278
  debug('sendMessageBatch(', qrl, messages.map(e => Object.assign(Object.assign({}, e), { MessageBody: e.MessageBody.slice(0, 10) + '...' })), ')')
268
279
  const params = { Entries: messages, QueueUrl: qrl }
269
280
  if (opt.sentryDsn) {
@@ -292,7 +303,7 @@ export async function sendMessageBatch (qrl, messages, opt) {
292
303
 
293
304
  // Send them
294
305
  const client = getSQSClient()
295
- const cmd = new SendMessageBatchCommand(params)
306
+ let cmd = new SendMessageBatchCommand(params)
296
307
  debug({ cmd })
297
308
  const backoff = new ExponentialBackoff(opt.sendRetries)
298
309
  const send = async (attemptNumber) => {
@@ -300,7 +311,7 @@ export async function sendMessageBatch (qrl, messages, opt) {
300
311
  const data = await client.send(cmd)
301
312
  return data
302
313
  }
303
- const shouldRetry = (result, error) => {
314
+ const shouldRetry = async (result, error) => {
304
315
  debug({ shouldRetry: { error, result } })
305
316
  if (result) {
306
317
  // Handle failed result of one or more messages in the batch
@@ -323,6 +334,16 @@ export async function sendMessageBatch (qrl, messages, opt) {
323
334
  if (opt.sentryDsn) {
324
335
  addBreadcrumb({ category: 'sendMessageBatch', message: JSON.stringify({ error }), level: 'error' })
325
336
  }
337
+
338
+ if (error instanceof QueueDoesNotExist) {
339
+ const qname = normalizeQueueName(queue, opt)
340
+ qrlCacheInvalidate(qname)
341
+ // Clear stale cache entry and recreate queue before retrying
342
+ const newQrl = await getOrCreateQueue(queue, opt)
343
+ params.QueueUrl = newQrl
344
+ cmd = new SendMessageBatchCommand(params)
345
+ }
346
+
326
347
  for (const exceptionClass of retryableExceptions) {
327
348
  debug({ exceptionClass, retryableExceptions })
328
349
  if (error instanceof exceptionClass) {
@@ -342,8 +363,8 @@ let requestCount = 0
342
363
  // If the message is too large, batch is retried with half the messages.
343
364
  // Returns number of messages flushed.
344
365
  //
345
- export async function flushMessages (qrl, opt, sendBuffer) {
346
- debug('flushMessages', { qrl, sendBuffer })
366
+ export async function flushMessages (qrl, queue, opt, sendBuffer) {
367
+ debug('flushMessages', { qrl, queue, sendBuffer })
347
368
  // Track our outgoing messages to map with Failed / Successful returns
348
369
  const messagesById = new Map()
349
370
  const resultsById = new Map()
@@ -376,7 +397,7 @@ export async function flushMessages (qrl, opt, sendBuffer) {
376
397
  }
377
398
 
378
399
  // Send batch
379
- const data = await sendMessageBatch(qrl, batch, opt)
400
+ const data = await sendMessageBatch(qrl, queue, batch, opt)
380
401
 
381
402
  // Fail if there are any individual message failures
382
403
  if (data?.Failed && data?.Failed.length) {
@@ -413,13 +434,13 @@ export async function flushMessages (qrl, opt, sendBuffer) {
413
434
  // Returns number of messages flushed.
414
435
  //
415
436
  const debugAddMessage = Debug('qdone:enqueue:addMessage')
416
- export async function addMessage (qrl, command, messageIndex, opt, sendBuffer, messageOptions) {
437
+ export async function addMessage (qrl, queue, command, messageIndex, opt, sendBuffer, messageOptions) {
417
438
  const message = formatMessage(command, messageIndex, opt, messageOptions)
418
439
  sendBuffer[qrl] = sendBuffer[qrl] || []
419
440
  sendBuffer[qrl].push(message)
420
441
  debugAddMessage({ location: 'addMessage', messageIndex, sendBuffer })
421
442
  if (sendBuffer[qrl].length >= 10) {
422
- return flushMessages(qrl, opt, sendBuffer)
443
+ return flushMessages(qrl, queue, opt, sendBuffer)
423
444
  }
424
445
  return { numFlushed: 0, results: [] }
425
446
  }
@@ -436,7 +457,7 @@ export async function enqueue (queue, command, options) {
436
457
  }
437
458
  try {
438
459
  const qrl = await getOrCreateQueue(queue, opt)
439
- return sendMessage(qrl, command, opt)
460
+ return sendMessage(qrl, queue, command, opt)
440
461
  } catch (e) {
441
462
  console.log(e)
442
463
  throw e
@@ -464,10 +485,13 @@ export async function enqueueBatch (pairs, options) {
464
485
  }))
465
486
  const uniqueQnames = new Set(normalizedPairs.map(p => p.qname))
466
487
 
467
- // Prefetch qrls / create queues in parallel
488
+ // Prefetch qrls / create queues in parallel, building a reverse map for cache invalidation
468
489
  const createPromises = []
490
+ const qrlToQname = new Map()
469
491
  for (const qname of uniqueQnames) {
470
- createPromises.push(getOrCreateQueue(qname, opt))
492
+ createPromises.push(
493
+ getOrCreateQueue(qname, opt).then(qrl => qrlToQname.set(qrl, qname))
494
+ )
471
495
  }
472
496
  await Promise.all(createPromises)
473
497
  // After we've prefetched, all qrls are in cache
@@ -478,7 +502,7 @@ export async function enqueueBatch (pairs, options) {
478
502
  let initialFlushTotal = 0
479
503
  for (const { qname, command, messageOptions } of normalizedPairs) {
480
504
  const qrl = await getOrCreateQueue(qname, opt)
481
- const { numFlushed, results } = await addMessage(qrl, command, messageIndex++, opt, sendBuffer, messageOptions)
505
+ const { numFlushed, results } = await addMessage(qrl, qname, command, messageIndex++, opt, sendBuffer, messageOptions)
482
506
  initialFlushTotal += numFlushed
483
507
  allResults.push(...results)
484
508
  }
@@ -486,7 +510,7 @@ export async function enqueueBatch (pairs, options) {
486
510
  // And flush any remaining messages
487
511
  const extraFlushPromises = []
488
512
  for (const qrl in sendBuffer) {
489
- extraFlushPromises.push(flushMessages(qrl, opt, sendBuffer))
513
+ extraFlushPromises.push(flushMessages(qrl, qrlToQname.get(qrl), opt, sendBuffer))
490
514
  }
491
515
  let extraFlushTotal = 0
492
516
  for (const { numFlushed, results } of await Promise.all(extraFlushPromises)) {