diff --git a/README.md b/README.md index e57cd68..db0894e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,29 @@ To start processing your jobs, somewhere in your project add: SyncedCron.start(); ``` +If you want a job to run only once or a limited number of times, you can either remove it inside the job function or create a limited schedule: + +```javascript +SyncedCron.add({ + name: 'myJob', + schedule: function(parser) { + // EITHER set a limited schedule + var date = new Date; + date.setHours(date.getHours() + 12); + + // (this Later.js object only contains a single event) + return parser.recur().on(date).fullDate(); + }, + job: function() { + // (do something) + + // ... OR remove the job inside function + SyncedCron.remove('myJob'); + } +}); +``` + + ### Advanced SyncedCron uses a collection called `cronHistory` to syncronize between processes. This also serves as a useful log of when jobs ran along with their output or error. A sample item looks like: diff --git a/synced-cron-server.js b/synced-cron-server.js index 54cbab5..ad01aa2 100644 --- a/synced-cron-server.js +++ b/synced-cron-server.js @@ -101,7 +101,7 @@ Meteor.startup(function() { var scheduleEntry = function(entry) { var schedule = entry.schedule(Later.parse); entry._timer = - SyncedCron._laterSetInterval(SyncedCron._entryWrapper(entry), schedule); + SyncedCron._laterSetInterval(SyncedCron._entryWrapper(entry), schedule, entry.name); log.info('Scheduled "' + entry.name + '" next run @' + Later.schedule(schedule).next(1)); @@ -238,9 +238,9 @@ SyncedCron._reset = function() { // between multiple, potentially laggy and unsynced machines // From: https://github.com/bunkat/later/blob/master/src/core/setinterval.js -SyncedCron._laterSetInterval = function(fn, sched) { +SyncedCron._laterSetInterval = function(fn, sched, name) { - var t = SyncedCron._laterSetTimeout(scheduleTimeout, sched), + var t = SyncedCron._laterSetTimeout(scheduleTimeout, sched, name), done = false; /** @@ -248,10 +248,12 @@ SyncedCron._laterSetInterval = function(fn, sched) { * interval. */ function scheduleTimeout(intendedAt) { - if(!done) { + if (!done) fn(intendedAt); - t = SyncedCron._laterSetTimeout(scheduleTimeout, sched); - } + + // Check for done again, in case entry is removed inside job function body + if (!done) + t = SyncedCron._laterSetTimeout(scheduleTimeout, sched, name); } return { @@ -269,7 +271,7 @@ SyncedCron._laterSetInterval = function(fn, sched) { }; // From: https://github.com/bunkat/later/blob/master/src/core/settimeout.js -SyncedCron._laterSetTimeout = function(fn, sched) { +SyncedCron._laterSetTimeout = function(fn, sched, name) { var s = Later.schedule(sched), t; scheduleTimeout(); @@ -281,21 +283,29 @@ SyncedCron._laterSetTimeout = function(fn, sched) { */ function scheduleTimeout() { var now = Date.now(), - next = s.next(2, now), - diff = next[0].getTime() - now, - intendedAt = next[0]; - - // minimum time to fire is one second, use next occurrence instead - if(diff < 1000) { - diff = next[1].getTime() - now; - intendedAt = next[1]; - } + next = s.next(2, now); - if(diff < 2147483647) { - t = Meteor.setTimeout(function() { fn(intendedAt); }, diff); + if (next) { + var diff = next[0].getTime() - now, + intendedAt = next[0]; + + // minimum time to fire is one second, use next occurrence instead + if(next[1] && diff < 1000) { + diff = next[1].getTime() - now; + intendedAt = next[1]; + } + + if(diff < 2147483647) { + t = Meteor.setTimeout(function() { fn(intendedAt); }, diff); + } + else { + t = Meteor.setTimeout(scheduleTimeout, 2147483647); + } } else { - t = Meteor.setTimeout(scheduleTimeout, 2147483647); + log.info('SyncedCron: No more future events for "' + name + '".'); + + SyncedCron.remove(name); } } diff --git a/synced-cron-tests.js b/synced-cron-tests.js index 8a59ea0..cea16b6 100644 --- a/synced-cron-tests.js +++ b/synced-cron-tests.js @@ -183,3 +183,45 @@ Tinytest.addAsync('SyncedCron should pass correct arguments to logger', function SyncedCron.options.logger = null; }); + + +/* + Unfinished tests +*/ + +Tinytest.add('Removing current entry inside job prevents the next event from running', function(test) { + //! Preparation + + SyncedCron.add({ + name: "testRemove", + schedule: function (parser) { + return parser.recur().every(1).second(); + }, + job: function () { + console.log("TEST"); + SyncedCron.remove("testRemove"); + } + }); + + //! Should test that "TEST" is only output to log once +}); + +Tinytest.add('Automatically remove entry after last event in schedule has been triggered', function(test) { + //! Preparation + + SyncedCron.add({ + name: "testRemove", + schedule: function (parser) { + var date = new Date; + date.setSeconds(date.getSeconds() + 2); + + return parser.recur().on(date).fullDate(); + }, + job: function () { + console.log("TEST"); + } + }); + + //! Should test that "TEST" is only output to log once + // and that "testRemove" is removed after 2 seconds +});