You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 7-8.RSVP
Socket
Socket
Sign inDemoInstall

bossman

Package Overview
Dependencies
4
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.2 to 1.1.1

.travis.yml

91

lib/__tests__/Bossman.test.js

@@ -10,2 +10,3 @@ /* eslint-env jest */

expect(boss).toBeInstanceOf(Bossman);
boss.quit();
});

@@ -18,2 +19,3 @@

expect(boss.prefix).toEqual('p');
boss.quit();
});

@@ -28,2 +30,3 @@

expect(boss.qas).toEqual([fn1, fn2]);
boss.quit();
});

@@ -36,8 +39,13 @@ });

var Bossman = require('../Bossman')['default'];
var Redis = require('ioredis');
var boss = void 0;
var bossAlternative = void 0;
beforeEach(function () {
boss = new Bossman({
db: 3
connection: { db: 3 }
});
bossAlternative = new Bossman({
connection: { db: 3 }
});
});

@@ -51,3 +59,3 @@

}).then(function () {
return boss.quit();
return Promise.all([boss.quit(), bossAlternative.quit()]);
})

@@ -114,2 +122,81 @@ );

});
it('only performs on one worker, even when given multiple workers', function (done) {
var performed = 0;
// Start 50 of these jobs, which still should only be fired once:
Array(50).fill().forEach(function () {
[boss, bossAlternative].forEach(function (b) {
b.hire('one', {
every: '0.5 seconds',
work: function () {
function work() {
performed += 1;
expect(performed).toEqual(1);
done();
}
return work;
}()
});
});
});
});
it('removes tasks with fire', function (done) {
boss.hire('fired', {
every: '0.5 seconds',
work: function () {
function work() {
done(new Error('Work should not be called'));
}
return work;
}()
});
boss.fire('fired');
setTimeout(done, 1000);
});
it('does not require every to be passed', function (done) {
boss.hire('demanded', {
work: function () {
function work() {
done('Work performed non-scheduled.');
}
return work;
}()
});
boss.demand('demanded');
});
it('ignores non-work expiring messages', function (done) {
var redis = new Redis({ db: 3 });
redis.set('some-other-key', 'val', 'PX', 1);
redis.quit();
setTimeout(done, 100);
});
it('can demand over a scheduled key', function (done) {
var performed = 0;
boss.hire('both', {
every: '0.5 seconds',
work: function () {
function work() {
performed += 1;
if (performed === 2) done();
}
return work;
}()
});
boss.demand('both');
});
});

85

lib/Bossman.js

@@ -27,3 +27,3 @@ Object.defineProperty(exports, "__esModule", {

var JOB_TTL = 2000;
var JOB_PREFIX = 'bossman:job';
var JOB_PREFIX = 'bossman';

@@ -71,3 +71,5 @@ var Bossman = function () {

// just a simple set command, and is okay to run on top of eachother.
_this.scheduleRun(jobName, _this.jobs[jobName].every);
if (_this.jobs[jobName].every) {
_this.scheduleRun(jobName, _this.jobs[jobName].every);
}
}

@@ -81,2 +83,3 @@ });

function quit() {
this.jobs = {};
return Promise.all([this.subscriber.quit(), this.client.quit()]);

@@ -88,2 +91,44 @@ }

}, {
key: 'hire',
value: function () {
function hire(name, definition) {
this.jobs[name] = definition;
if (definition.every) {
this.scheduleRun(name, definition.every);
}
}
return hire;
}()
}, {
key: 'fire',
value: function () {
function fire(name) {
return this.client.del(this.getJobKey(name));
}
return fire;
}()
}, {
key: 'qa',
value: function () {
function qa(fn) {
this.qas.push(fn);
}
return qa;
}()
}, {
key: 'demand',
value: function () {
function demand(name) {
this.scheduleRun(name);
}
return demand;
}()
// Semi-privates:
}, {
key: 'getJobKey',

@@ -98,19 +143,18 @@ value: function () {

}, {
key: 'getLockKey',
key: 'getDemandKey',
value: function () {
function getLockKey(name) {
return String(this.prefix) + ':lock:' + String(name);
function getDemandKey(name) {
return String(this.prefix) + ':work:demand:' + String(name);
}
return getLockKey;
return getDemandKey;
}()
}, {
key: 'hire',
key: 'getLockKey',
value: function () {
function hire(name, definition) {
this.jobs[name] = definition;
this.scheduleRun(name, definition.every);
function getLockKey(name) {
return String(this.prefix) + ':lock:' + String(name);
}
return hire;
return getLockKey;
}()

@@ -125,4 +169,6 @@ }, {

var fn = (0, _throwback.compose)(_this2.qas);
var response = fn(name, _this2.jobs[name], function () {
return _this2.jobs[name].work();
// Call the QA functions, then finally the job function. We use a copy of
// the job definition to prevent pollution between scheduled runs.
var response = fn(name, Object.assign({}, _this2.jobs[name]), function (_, definition) {
return definition.work();
});

@@ -151,14 +197,9 @@

}, {
key: 'qa',
value: function () {
function qa(fn) {
this.qas.push(fn);
}
return qa;
}()
}, {
key: 'scheduleRun',
value: function () {
function scheduleRun(name, interval) {
// If there's no interval, it's a demand, let's schedule as tight as we can:
if (!interval) {
return this.client.set(this.getDemandKey(name), name, 'PX', 1, 'NX');
}
var timeout = (0, _humanInterval2['default'])(interval);

@@ -165,0 +206,0 @@ return this.client.set(this.getJobKey(name), name, 'PX', timeout, 'NX');

{
"name": "bossman",
"version": "1.0.2",
"version": "1.1.1",
"description": "Distributed job scheduling in node, based on redis.",

@@ -11,3 +11,3 @@ "main": "lib/index.js",

"lint": "eslint .",
"tests-only": "jest"
"tests-only": "jest --coverage"
},

@@ -19,3 +19,3 @@ "repository": {

"author": "Jordan Gensler <jordangens@gmail.com>",
"license": "ISC",
"license": "MIT",
"bugs": {

@@ -29,2 +29,3 @@ "url": "https://github.com/kesne/bossman/issues"

"babel-preset-airbnb": "^2.1.1",
"coveralls": "^2.11.15",
"eslint": "^3.12.2",

@@ -31,0 +32,0 @@ "eslint-config-airbnb-base": "^11.0.0",

# bossman
_Stupid simple distributed job scheduling in node, backed by redis._
Distributed job scheduling in node, based on redis.
[![npm Version](https://img.shields.io/npm/v/bossman.svg)](https://www.npmjs.com/package/bossman)
[![License](https://img.shields.io/npm/l/bossman.svg)](https://www.npmjs.com/package/bossman)
[![Build Status](https://travis-ci.org/kesne/bossman.svg)](https://travis-ci.org/kesne/bossman)
[![Coverage Status](https://coveralls.io/repos/github/kesne/bossman/badge.svg?branch=master)](https://coveralls.io/github/kesne/bossman?branch=master)
Bossman combines schedulers and workers into a single concept, which aligns better with most node applications.
All of the jobs run with a [redis lock](https://redis.io/topics/distlock), preventing more than once instance from performing a given job at a time.
## Usage
Bossman is published on `npm`, and can be installed simply:
```shell

@@ -11,2 +20,10 @@ npm install bossman --save

Or if you use Yarn in your project:
```shell
yarn add bossman
```
You can then import the module and use it normally. For more details, see the [API documentation](#API).
```js

@@ -26,3 +43,3 @@ import Bossman from 'bossman';

// Hire for a job.
fred.hire('engineer', {
fred.hire('engineers', {
every: '10 minutes',

@@ -34,14 +51,80 @@ work: () => {

// Hire something as soon as possible:
fred.demand('engineers');
// You can also "qa" work:
fred.qa((jobName, jobDefinition, next) => {
return newrelic.createBackgroundTransaction(`job:${jobName}`, () => {
const response = next();
return next();
});
const end = () => {
newrelic.endTransaction();
};
// Fire a job, this will cancel any pending jobs:
fred.fire('engineers');
return response.then(end, end);
})();
// Shut down our instance.
fred.quit();
```
## API
#### `new Bossman(options: Object)`
Creates a new bossman instance. All arguments are optional.
- `options.connection`: Used to configure the connection to your redis. This accepts the same arguments that [`ioredis`](https://github.com/luin/ioredis/blob/master/API.md#new_Redis_new) does.
- `options.prefix`: A string that all redis keys will be prefixed with. Defaults to `bossman`.
- `options.ttl`: The number of milliseconds before a job times out. Setting it will change the maximum duration that jobs can hold a lock. By default, job locks will timeout if a job does not complete in 2000ms.
#### `bossman.hire(jobName: String, jobDefinition: Object)`
Schedules recurring work to be done.
- `jobName`: A unique name for the job.
- `jobDefinition`: The job definition can contain two properties: `work`, and `every`. The `work` function is invoked when the job is completed. `every` is a human-friendly string which describes the interval the job will be run.
It's possible to omit the `every` property if you don't wish to schedule recurring work, and just want to register a job.
#### `bossman.demand(jobName: String)`
Runs a job as soon as possible, outside of the scheduled jobs.
This does **not** prevent any scheduled jobs from running, unless the demand is running at the same time as a scheduled job and all instances fail to acquire a lock on the job.
#### `bossman.qa(qaFunction: Function)`
QA is used to registers functions that will invoked any time a job is run. This function can be called multiple times to register multiple QA functions.
The passed `qaFunction` function will be called with `jobName`, and `jobDefinition` from the `hire` function, as well as a `next` function, which should be called when the QA function is complete.
The `next` function returns a promise that can be used to get
For example, here is what a time logging QA function might look like.
```js
bossman.qa((jobName, jobDefinition, next) => {
const startTime = Date.now();
return next().then(() => {
const endTime = Date.now();
logToServer(`${jobName} completed in ${endTime - startTime}ms`);
})
});
```
#### `bossman.fire(jobName: String)`
Cancels any _scheduled_ jobs with name `jobName`. This does **not** stop any demanded jobs from running.
#### `bossman.quit()`
Shuts down a bossman instance, closing all redis connections.
This does **not** cancel any scheduled work, and does not stop it from running in any other bossman instances.
## How it works
Constructing a new bossman instance sets up an expired key listener on your redis database.
When you `hire` for a new job, Bossman sets a key in Redis that expire when the first run should occur.
When the key expires, the expired key listener is called and Bossman does the following:
1. Attempt to get a lock to perform the work. Only one instance of bossman will acquire the lock.
1. If the lock is acquired, then perform the work and move on.
2. If the lock is not acquired, then move on.
2. Schedule the next run of the job by setting a key that expires when the next run should occur.
It's worth noting that every instance of Bossman attempts to schedule the next run of the job. This is okay because Redis will only schedule the first one that it receives, thanks to the `NX` option in `SET`.
Calling `demand` performs the same operation as `hire`, except with a special key for demands.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc