Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: classify #23

Merged
merged 7 commits into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ npm-debug.log
node_modules/
coverage/
run/
.vscode
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ sudo: false
language: node_js
node_js:
- '6'
- '7'
- '8'
install:
- npm i npminstall && npminstall
Expand Down
47 changes: 31 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ $ npm i egg-schedule --save

`egg-schedule` is a plugin that has been built-in for egg. It is enabled by default.

```javascript
```js
// {app_root}/config/plugin.js
exports.schedule = {
package: 'egg-schedule',
Expand Down Expand Up @@ -82,14 +82,14 @@ When the scheduled task runs, the scheduled job information will be logged and w

To create a task, it is as simple as write a generator function. For example:

```javascript
```js
// A simple logger example
exports.task = function* (ctx) {
ctx.logger.info('Info about your task');
};
```

```javascript
```js
// A real world example: wipe out your database.
// Use it with caution. :)
exports.task = function* (ctx) {
Expand Down Expand Up @@ -123,7 +123,7 @@ Use [cron-parser](https://github.com/harrisiirak/cron-parser).

Example:

```javascript
```js
// To execute task every 3 hours
exports.schedule = {
type: 'worker',
Expand All @@ -137,7 +137,7 @@ To use `setInterval`, and support [ms](https://www.npmjs.com/package/ms) convers

Example:

```javascript
```js
// To execute task every 3 hours
exports.schedule = {
type: 'worker',
Expand All @@ -154,28 +154,43 @@ exports.schedule = {

**Custom schedule**

To create a custom schedule, simply create a schedule with a type `custom` and its corresponding method. Inside your custom method, you can schedule the task to be executed by one random worker or all workers with the built-in method `sender.one()` or `sender.all()`.
To create a custom schedule, simply extend `agent.ScheduleStrategy` and register it by `agent.schedule.use(type, clz)`.
You can schedule the task to be executed by one random worker or all workers with the built-in method `this.sendOne()` or `this.sendAll()`.

```javascript
```js
// {app_root}/agent.js
const SCHEDULE_HANDLER = Symbol.for('egg#scheduleHandler');

module.exports = agent => {
// sender.one() - will notify one random worker to execute task
// sender.all() - will notify all workers
agent[SCHEDULE_HANDLER].custom = (schedule, sender) =>
setInterval(sender.one, schedule.interval);
module.exports = function(agent) {
class CustomStrategy extends agent.ScheduleStrategy {
start() {
this.interval = setInterval(() => {
this.sendOne();
}, this.schedule.interval);
}
close() {
if (this.interval) {
this.clear(this.interval);
this.interval = undefined;
}
}
}
agent.schedule.use('custsom', CustomStrategy);
};
```

Then you could use defined your job:

```js
// {app_root}/app/schedule/other.js
exports.schedule = {
type: 'custom',
};

exports.task = function* (ctx) {};
```

## Dynamic schedule

```javascript
```js
// {app_root}/app/schedule/sync.js
module.exports = app => {
exports.schedule = {
Expand All @@ -199,7 +214,7 @@ module.exports = app => {

Example:

```javascript
```js
it('test a schedule task', function* () {
// get app instance
yield app.runSchedule('clean_cache');
Expand Down
144 changes: 33 additions & 111 deletions agent.js
Original file line number Diff line number Diff line change
@@ -1,121 +1,43 @@
'use strict';

const loadSchedule = require('./lib/load_schedule');
const parser = require('cron-parser');
const ms = require('humanize-ms');
const safetimers = require('safe-timers');
const SCHEDULE_HANDLER = Symbol.for('egg#scheduleHandler');
const WorkerStrategy = require('./lib/strategy/worker');
const AllStrategy = require('./lib/strategy/all');

module.exports = agent => {
// add handler into `agent[SCHEDULE_HANDLER]` for extend other kind of schedule type.
// worker: will excute in one random worker when schedule excuted.
// all: will excute in all workers when schedule excuted.
const handlers = agent[SCHEDULE_HANDLER] = {
worker: workerHandler,
all: allHander,
};

agent.messenger.once('egg-ready', startSchedule);

function startSchedule() {
agent.disableSchedule = false;
const schedules = loadSchedule(agent);
for (const s in schedules) {
const schedule = schedules[s];
if (schedule.schedule.disable) continue;

const type = schedule.schedule.type;
const handler = handlers[type];
if (!handler) {
const err = new Error(`schedule type [${type}] is not defined`);
err.name = 'EggScheduleError';
throw err;
}
handler(schedule.schedule, {
one() {
sendMessage(agent, 'sendRandom', schedule.key);
},
all() {
sendMessage(agent, 'send', schedule.key);
},
});
}
}

agent.on('close', () => {
agent.disableSchedule = true;
return;
// register built-in strategy
agent.schedule.use('worker', WorkerStrategy);
agent.schedule.use('all', AllStrategy);

// TODO: compatible, will remove at next major
const handlers = {};
Object.defineProperty(agent, SCHEDULE_HANDLER, {
get() {
agent.deprecate('should use `agent.schedule.use()` instead of `agent[Symbol.for(\'egg#scheduleHandler\')]` to register handler.');
return handlers;
},
});
};

function sendMessage(agent, method, key) {
if (agent.disableSchedule) {
agent.coreLogger.info(`[egg-schedule] message ${key} did not sent`);
return;
}
agent.coreLogger.info(`[egg-schedule] send message: ${method} ${key}`);
agent.messenger[method](key);
}

function workerHandler(schedule, sender) {
baseHander(schedule, sender.one);
}

function allHander(schedule, sender) {
baseHander(schedule, sender.all);
}

function baseHander(schedule, send) {
if (!schedule.interval && !schedule.cron) {
throw new Error('[egg-schedule] schedule.interval or schedule.cron must be present');
}

if (schedule.interval) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dead-horse 这里原来就可以设置多个的吗

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

啥?

const interval = ms(schedule.interval);
safeInterval(send, interval);
}

if (schedule.cron) {
let interval;
try {
interval = parser.parseExpression(schedule.cron);
} catch (err) {
err.message = `[egg-schedule] parse cron instruction(${schedule.cron}) error: ${err.message}`;
throw err;
agent.messenger.once('egg-ready', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

加一下注释,需要等插件注册 strategy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

加了,在下一行

const keys = Object.keys(handlers);
for (const type of keys) {
agent.schedule.use(type, handler2Class(type, handlers[type]));
}
startCron(interval, send);
}

if (schedule.immediate) {
setImmediate(send);
}
}

function startCron(interval, listener) {
const now = Date.now();
let nextTick;
do {
nextTick = interval.next().getTime();
} while (now >= nextTick);

safeTimeout(() => {
listener();
startCron(interval, listener);
}, nextTick - now);
}

function safeTimeout(fn, delay, ...args) {
if (delay < safetimers.maxInterval) {
setTimeout(fn, delay, ...args);
} else {
safetimers.setTimeout(fn, delay, ...args);
}
}
// start schedule
agent.schedule.start();
});

function safeInterval(fn, delay, ...args) {
if (delay < safetimers.maxInterval) {
setInterval(fn, delay, ...args);
} else {
safetimers.setInterval(fn, delay, ...args);
function handler2Class(type, fn) {
return class CustomStrategy extends agent.ScheduleStrategy {
start() {
fn(this.schedule, {
one: this.sendOne.bind(this),
all: this.sendAll.bind(this),
});
}
close() {
this.agent.logger.warn(`schedule type [${type}] stop is not implemented yet`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没有 close 就不要实现了。

}
};
}
}
};
22 changes: 11 additions & 11 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ module.exports = app => {

const start = Date.now();
task(ctx)
.then(() => true) // succeed
.catch(err => {
err.message = `[egg-schedule] ${key} excute error. ${err.message}`;
app.logger.error(err);
return false; // failed
})
.then(success => {
const rt = Date.now() - start;
const status = success ? 'succeed' : 'failed';
app.coreLogger.info(`[egg-schedule] ${key} excute ${status}, used ${rt}ms`);
});
.then(() => true) // succeed
.catch(err => {
err.message = `[egg-schedule] ${key} excute error. ${err.message}`;
app.logger.error(err);
return false; // failed
})
.then(success => {
const rt = Date.now() - start;
const status = success ? 'succeed' : 'failed';
app.coreLogger.info(`[egg-schedule] ${key} excute ${status}, used ${rt}ms`);
});
});
}
};
25 changes: 25 additions & 0 deletions app/extend/agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const Strategy = require('../../lib/strategy/base');
const Schedule = require('../../lib/schedule');
const SCHEDULE = Symbol('agent#schedule');

module.exports = {
/**
* @member agent#ScheduleStrategy
*/
ScheduleStrategy: Strategy,

/**
* @member agent#schedule
*/
get schedule() {
if (!this[SCHEDULE]) {
this[SCHEDULE] = new Schedule(this);
this.beforeClose(() => {
return this[SCHEDULE].close();
});
}
return this[SCHEDULE];
},
};
3 changes: 1 addition & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
environment:
matrix:
- nodejs_version: '6'
- nodejs_version: '7'
- nodejs_version: '8'

install:
Expand All @@ -11,6 +10,6 @@ install:
test_script:
- node --version
- npm --version
- npm run ci
- npm run test

build: off
50 changes: 50 additions & 0 deletions lib/schedule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

const STRATEGY = Symbol('strategy');
const HANDLER = Symbol('handler');
const assert = require('assert');
const loadSchedule = require('./load_schedule');

module.exports = class Schedule {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

用 sdk-base?实现 _init?这样不需要手动调用 start 了。

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉继承 sdk-base 好像也没啥用处,就是 nextTick 自动调用 init,因为 handler 是在 ready 里面才注册的,这样的话就要把 Schedule 的实例化也放进来,感觉继承也没啥好处

constructor(agent) {
this.agent = agent;
this[STRATEGY] = new Map();
this[HANDLER] = new Map();
this.closed = false;
}

use(type, clz) {
assert(typeof clz.prototype.start === 'function', `schedule type [${type}] should implement \`start\` method`);
this[STRATEGY].set(type, clz);
}

start() {
this.closed = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是不是不用设了

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果手动调用一次 close,然后再调用 start 这种场景呢?

const scheduleItems = loadSchedule(this.agent);

for (const k of Object.keys(scheduleItems)) {
const { key, schedule } = scheduleItems[k];
const type = schedule.type;
if (schedule.disable) continue;

const Strategy = this[STRATEGY].get(type);
if (!Strategy) {
const err = new Error(`schedule type [${type}] is not defined`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is not supported?

err.name = 'EggScheduleError';
throw err;
}

const handler = new Strategy(schedule, this.agent, key);
this[HANDLER].set(key, handler);
handler.start();
}
}

close() {
this.closed = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

实际化设 false ?

for (const [ key, handler ] of this[HANDLER].entries()) {
this.agent.coreLogger.info(`[egg-schedule] close tasks: ${key}`);
handler.close();
}
}
};
Loading