Skip to content

Commit

Permalink
feat(scripts): use redlock to prevent >1 instance of paypal-processor
Browse files Browse the repository at this point in the history
Because:
 - we could easily end up running two instances of the paypal-processor
   during a deploy

This commit:
 - use a redis based distributed lock to ensure only one
   paypal-processor can run per env
 - add script options to control the lock name and duration, as well as
   completely bypassing the lock
  • Loading branch information
chenba committed Apr 19, 2022
1 parent 39a94ee commit 56ff4a5
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 6 deletions.
4 changes: 3 additions & 1 deletion packages/fxa-auth-server/lib/payments/paypal/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ export class PaypalProcessor {
return;
}

public async processInvoices() {
public async *processInvoices() {
// Generate a time `invoiceAge` hours prior.
const invoiceAgeInSeconds = hoursBeforeInSeconds(this.invoiceAge);

Expand All @@ -369,6 +369,8 @@ export class PaypalProcessor {
});
reportSentryError(err);
}

yield;
}
}
}
1 change: 1 addition & 0 deletions packages/fxa-auth-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"poolee": "^1.0.1",
"punycode.js": "2.1.0",
"qrcode": "^1.5.0",
"redlock": "^5.0.0-beta.2",
"request": "^2.88.2",
"safe-regex": "^2.1.1",
"safe-url-assembler": "1.3.5",
Expand Down
68 changes: 65 additions & 3 deletions packages/fxa-auth-server/scripts/paypal-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import program from 'commander';
import { StatsD } from 'hot-shots';
import Redis from 'ioredis';
import Redlock, { Lock } from 'redlock';
import Container from 'typedi';
import { promisify } from 'util';

Expand All @@ -13,6 +15,21 @@ import { setupProcessingTaskObjects } from '../lib/payments/processing-tasks-set

const pckg = require('../package.json');
const config = require('../config').getProperties();
const PAYPAL_PROCESSOR_LOCK = 'fxa-paypal-processor-lock';
const DEFAULT_LOCK_DURATION_MS = 300000;
let lock: Lock;

const initTimer = () => {
let start = Date.now();

const reset = () => (start = Date.now());
const elapsed = () => Date.now() - start;

return {
reset,
elapsed,
};
};

export async function init() {
// Load program options
Expand All @@ -29,8 +46,28 @@ export async function init() {
'How old in hours the invoice must be to get processed. Defaults to 6.',
'6'
)
.option(
'-l, --use-lock [bool]',
'Whether to require a distributed lock to run. Use "false" to disable. Defaults to true.',
true
)
.option(
'-n, --lock-name [name]',
`The name of the resource for which to acquire a distributed lock. Defaults to ${PAYPAL_PROCESSOR_LOCK}.`,
PAYPAL_PROCESSOR_LOCK
)
.option(
'-d, --lock-duration [milliseconds]',
`The max duration in milliseconds to hold the lock. The lock will be extended as needed. Defaults to ${DEFAULT_LOCK_DURATION_MS}.`,
DEFAULT_LOCK_DURATION_MS
)
.parse(process.argv);

// every arg is a string
const useLock = program.useLock !== 'false';
const lockDuration =
parseInt(`${program.lockDuration}`) || DEFAULT_LOCK_DURATION_MS;

const { log, database, senders } = await setupProcessingTaskObjects(
'paypal-processor'
);
Expand All @@ -53,17 +90,42 @@ export async function init() {
);
const statsd = Container.get(StatsD);
statsd.increment('paypal-processor.startup');
await processor.processInvoices();

const timer = initTimer();

if (useLock) {
try {
const redis = new Redis(config.redis);
const redlock = new Redlock([redis], { retryCount: 1 });
lock = await redlock.acquire([program.lockName], lockDuration);
} catch (err) {
throw new Error(`Cannot acquire lock to run: ${err.message}`);
}
}

for await (const _ of processor.processInvoices()) {
if (useLock && timer.elapsed() > Math.floor(lockDuration / 2)) {
await lock.extend(timer.elapsed());
timer.reset();
}
}

lock?.release();
statsd.increment('paypal-processor.shutdown');
await promisify(statsd.close).bind(statsd)();
return 0;
}

if (require.main === module) {
let exitStatus = 1;
init()
.then((result) => {
exitStatus = result;
})
.catch((err) => {
console.error(err);
process.exit(1);
})
.then((result) => process.exit(result));
.finally(() => {
process.exit(exitStatus);
});
}
12 changes: 10 additions & 2 deletions packages/fxa-auth-server/test/local/payments/paypal-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,11 @@ describe('PaypalProcessor', () => {
yield invoice;
},
});
await processor.processInvoices();
// eslint-disable-next-line
for await (const _ of processor.processInvoices()) {
// No value yield'd; yielding control for potential distributed lock
// extension in actual use case
}
sinon.assert.calledOnceWithExactly(
mockLog.info,
'processInvoice.processing',
Expand All @@ -628,7 +632,11 @@ describe('PaypalProcessor', () => {
},
});
try {
await processor.processInvoices();
// eslint-disable-next-line
for await (const _ of processor.processInvoices()) {
// No value yield'd; yielding control for potential distributed lock
// extension in actual use case
}
assert.fail('Process invoicce should fail');
} catch (err) {
sinon.assert.calledOnceWithExactly(
Expand Down
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -22274,6 +22274,7 @@ fsevents@~2.1.1:
punycode.js: 2.1.0
qrcode: ^1.5.0
read: 1.0.7
redlock: ^5.0.0-beta.2
request: ^2.88.2
rimraf: ^3.0.2
safe-regex: ^2.1.1
Expand Down Expand Up @@ -32251,6 +32252,13 @@ fsevents@~2.1.1:
languageName: node
linkType: hard

"node-abort-controller@npm:^3.0.1":
version: 3.0.1
resolution: "node-abort-controller@npm:3.0.1"
checksum: 2b3d75c65249fea99e8ba22da3a8bc553f034f44dd12f5f4b38b520f718b01c88718c978f0c24c2a460fc01de9a80b567028f547b94440cb47adeacfeb82c2ee
languageName: node
linkType: hard

"node-addon-api@npm:^4.3.0":
version: 4.3.0
resolution: "node-addon-api@npm:4.3.0"
Expand Down Expand Up @@ -37452,6 +37460,15 @@ fsevents@~2.1.1:
languageName: node
linkType: hard

"redlock@npm:^5.0.0-beta.2":
version: 5.0.0-beta2
resolution: "redlock@npm:5.0.0-beta2"
dependencies:
node-abort-controller: ^3.0.1
checksum: d8a0d6d472922d146077e3c12946b942108e3041439fdadced79c3dc285ec3d509a48cee33f7da125e71b3868c65ba248b612fb65825aacfa5e67c118e1ba543
languageName: node
linkType: hard

"reduce-css-calc@npm:^2.1.6, reduce-css-calc@npm:^2.1.8":
version: 2.1.8
resolution: "reduce-css-calc@npm:2.1.8"
Expand Down

0 comments on commit 56ff4a5

Please sign in to comment.