Skip to content
This repository was archived by the owner on Sep 22, 2020. It is now read-only.

Commit 9b711c8

Browse files
committed
sqs integration
1 parent cdcbf06 commit 9b711c8

File tree

8 files changed

+386
-12
lines changed

8 files changed

+386
-12
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"devDependencies": {
2323
"amqplib": "^0.5.1",
2424
"ava": "^0.18.1",
25+
"aws-sdk": "^2.172.0",
2526
"babel-cli": ">=6.7 <=7.0",
2627
"babel-eslint": "^6.1.1",
2728
"babel-preset-grind": "^0.7.0",

src/Commands/QueueWorkCommand.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class QueueWorkCommand extends Command {
1111

1212
options = [
1313
new InputOption('queue', InputOption.VALUE_OPTIONAL, 'Specify the queue(s) to perform work for.'),
14-
new InputOption('concurrency', InputOption.VALUE_OPTIONAL, 'Number of jobs to process concurrency.', '1'),
14+
new InputOption('concurrency', InputOption.VALUE_OPTIONAL, 'Number of jobs to process concurrently.', '1'),
1515
new InputOption('watch', InputOption.VALUE_OPTIONAL, 'Folders to watch for changes')
1616
]
1717

@@ -36,7 +36,7 @@ export class QueueWorkCommand extends Command {
3636

3737
async run() {
3838
let queues = null
39-
const connection = this.app.queue.get(this.argument('connection'))
39+
const connection = await this.app.queue.get(this.argument('connection'))
4040

4141
if(this.containsOption('queue')) {
4242
queues = this.option('queue').split(/,/).map(job => job.trim()).filter(job => job.length > 0)

src/Drivers/BaseDriver.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Base class all drivers must extend
33
*/
44
export class BaseDriver {
5+
56
app = null
67
state = null
78
retryDelay = 90000
@@ -16,6 +17,15 @@ export class BaseDriver {
1617
}
1718
}
1819

20+
/**
21+
* Performs setup operations when starting the driver
22+
*
23+
* @return Promise
24+
*/
25+
ready() {
26+
return Promise.resolve()
27+
}
28+
1929
/**
2030
* Connects to the backend engine
2131
*

src/Drivers/SQSDriver.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import './BaseDriver'
2+
import '../Support/SQS'
3+
4+
/**
5+
* AWS Simple Queue Service (SQS) backed Queue Driver
6+
*/
7+
export class SQSDriver extends BaseDriver {
8+
client = null
9+
10+
constructor(app, config) {
11+
super(app, config)
12+
13+
this.client = new SQS(config)
14+
}
15+
16+
ready() {
17+
return this.client.createQueue()
18+
}
19+
20+
connect() {
21+
return Promise.resolve()
22+
}
23+
24+
async dispatch(job) {
25+
const payload = this.buildPayload(job)
26+
const queueUrl = this.client.queueUrls[payload.queue]
27+
28+
if(queueUrl.isNil) {
29+
return Promise.reject(`Unable to find SQS url for job queue ${payload.queue}`)
30+
}
31+
32+
const params = {
33+
QueueUrl: queueUrl,
34+
MessageBody: JSON.stringify(payload),
35+
DelaySeconds: payload.delay || 0
36+
}
37+
38+
return this.client.put(params)
39+
}
40+
41+
listen(queues, concurrency, jobHandler, errorHandler) {
42+
// SQS can batch ingest up to 10 jobs in a single call
43+
const concurrentListens = Math.ceil(concurrency / 10)
44+
const remainderJobConcurrency = 10 - ((concurrentListens * 10) - concurrency)
45+
46+
const listeners = [ ]
47+
48+
for(let i = 0; i < concurrentListens; i++) {
49+
for(const queue of queues) {
50+
let concurrentJobs = 10
51+
52+
if(i === (concurrentListens - 1)) {
53+
concurrentJobs = remainderJobConcurrency
54+
}
55+
56+
listeners.push(this._listen(queue, concurrentJobs, jobHandler, errorHandler))
57+
}
58+
}
59+
60+
return Promise.all(listeners)
61+
}
62+
63+
_listen(queue, concurrency, jobHandler, errorHandler) {
64+
return this.client.watch(queue, concurrency, async function callback(jobData, dispatchedAt, pastTries = 1) {
65+
const job = JSON.parse(jobData)
66+
const isExpired = timeout => {
67+
return (timeout !== 0) && ((new Date - dispatchedAt) > timeout)
68+
}
69+
70+
try {
71+
if(isExpired(job.timeout)) {
72+
return
73+
}
74+
75+
await jobHandler(job)
76+
} catch(err) {
77+
try {
78+
const tries = Number.parseInt(job.tries) || 1
79+
80+
if((pastTries >= tries) || isExpired(job.timeout)) {
81+
throw err
82+
}
83+
84+
if(job.retry_delay > 0) {
85+
await new Promise(resolve => {
86+
return setTimeout(() => {
87+
return resolve()
88+
}, job.retry_delay)
89+
})
90+
}
91+
} catch(err) {
92+
return errorHandler(job, err)
93+
}
94+
95+
return callback(jobData, dispatchedAt, pastTries += 1)
96+
}
97+
})
98+
}
99+
100+
destroy() {
101+
this.client.constructor.queueUrls = { }
102+
return super.destroy()
103+
}
104+
105+
}

src/QueueFactory.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import './Queue'
44
import './Drivers/BaseDriver'
55
import './Drivers/BeanstalkDriver'
66
import './Drivers/RabbitDriver'
7+
import './Drivers/SQSDriver'
78

89
export class QueueFactory {
910
app = null
@@ -14,22 +15,25 @@ export class QueueFactory {
1415
beanstalkd: BeanstalkDriver,
1516
rabbit: RabbitDriver,
1617
rabbitmq: RabbitDriver,
17-
amqp: RabbitDriver
18+
amqp: RabbitDriver,
19+
sqs: SQSDriver
1820
}
1921

2022
constructor(app) {
2123
this.app = app
2224
}
2325

24-
dispatch(job, connection = null) {
25-
return this.get(connection).dispatch(job)
26+
async dispatch(job, connection = null) {
27+
connection = await this.get(connection)
28+
return connection.dispatch(job)
2629
}
2730

28-
status(job, connection = null) {
29-
return this.get(connection).status(job)
31+
async status(job, connection = null) {
32+
connection = await this.get(connection)
33+
return connection.status(job)
3034
}
3135

32-
get(connection) {
36+
async get(connection) {
3337
let name = null
3438

3539
if(connection.isNil) {
@@ -58,7 +62,7 @@ export class QueueFactory {
5862
throw new Error(`Unsupported queue driver: ${config.driver}`)
5963
}
6064

61-
connection = this.make(driverClass, config)
65+
connection = await this.make(driverClass, config)
6266

6367
if(!name.isNil) {
6468
this.connections[name] = connection
@@ -67,8 +71,11 @@ export class QueueFactory {
6771
return connection
6872
}
6973

70-
make(driverClass, config) {
71-
return new Queue(this.app, this, new driverClass(this.app, config))
74+
async make(driverClass, config) {
75+
const driver = new driverClass(this.app, config)
76+
await driver.ready()
77+
78+
return new Queue(this.app, this, driver)
7279
}
7380

7481
registerDriver(name, driverClass) {

src/QueueProvider.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import './QueueFactory'
33
import './Commands/MakeJobCommand'
44
import './Commands/QueueWorkCommand'
55

6-
export function QueueProvider(app, classes = { }) {
6+
export async function QueueProvider(app, classes = { }) {
77
const factoryClass = classes.factoryClass || QueueFactory
88
app.queue = new factoryClass(app)
99

10+
for(const connectionName of Object.keys(app.config.get('queue.connections'))) {
11+
// Trigger initial setup of backend engines
12+
await app.queue.get(connectionName)
13+
}
14+
1015
if(app.cli.isNil) {
1116
return
1217
}

0 commit comments

Comments
 (0)