This is an automated email from the ASF dual-hosted git repository.

style95 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwhisk-package-alarms.git


The following commit(s) were added to refs/heads/master by this push:
     new 238d6f9  Make cron triggers fire with random delay with parameter 
`strict` (#198)
238d6f9 is described below

commit 238d6f95e69f76b76a949e527ee86652d462df00
Author: Jeongmin Yu <machen...@gmail.com>
AuthorDate: Tue Mar 3 22:58:44 2020 +0900

    Make cron triggers fire with random delay with parameter `strict` (#198)
    
    * Make cron triggers fire with random delay with parameter `strict`
    
    * Add comment for behavior of hashName
    
    Its implementation referred String.hashCode in Java
    
    * Fix redundant code
    
    * Add description of parameter to README
    
    * Add limit delay configuration
    
    * Fix wrong conversion bug
    
    * Add log for cron conversion
    
    * Fix missing semicolon
---
 README.md                 | 12 +++++++++++-
 action/alarmWebAction.js  |  1 +
 installCatalog.sh         |  2 +-
 provider/lib/cronAlarm.js | 40 +++++++++++++++++++++++++++++++++++++++-
 4 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 5a375b1..7c3fcc6 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ The following is an example of creating a trigger that will 
be fired once on Dec
 The `/whisk.system/alarms/alarm` feed configures the Alarm service to fire a 
Trigger event at a specified frequency. The parameters are as follows:
 
 - `cron` (*required*): A string, based on the UNIX crontab syntax that 
indicates when to fire the Trigger in Coordinated Universal Time (UTC). The 
string is a sequence of five fields that are separated by spaces: `X X X X X`.
-For more information, see: http://crontab.org. The following strings are 
examples that use varying duration's of frequency.
+  For more information, see: http://crontab.org. The following strings are 
examples that use varying duration's of frequency.
 
   - `* * * * *`: The Trigger fires at the top of every minute.
   - `0 * * * *`: The Trigger fires at the top of every hour.
@@ -101,6 +101,7 @@ For more information, see: http://crontab.org. The 
following strings are example
   **Note**: The parameter `cron` supports five or six fields.  Not all 
OpenWhisk vendors may support 6 fields so please check their documentation for 
support.
   For more details about using this custom cron syntax, see: 
https://github.com/ncb000gt/node-cron.
   Here is an example using six fields notation:
+
     - `*/30 * * * * *`: every thirty seconds.
 
 - `trigger_payload` (*optional*): The value of this parameter becomes the 
content of the Trigger every time the Trigger is fired.
@@ -127,6 +128,15 @@ January 1, 2019, 00:00:00 UTC and will stop firing January 
31, 2019, 23:59:00 UT
 
  **Note**: The parameter `maxTriggers` is deprecated and will be removed soon. 
 To stop the Trigger, use the `stopDate` parameter.
 
+- `strict` (*optional*): A boolean value that decides to add a few seconds to 
the Trigger. This work with only five-field cron alarms.
+
+  - If it's true, the Trigger will fire at the top of the hour/minute 
(\**:**:00).
+  - Otherwise, the Trigger will fire after the specific seconds (\**:**:00-59).
+
+The delay is determined by the hash value of the Trigger's name, so it keeps 
the same interval before and after the (re)deployment.
+
+**Note** This option can be helpful to avoid thundering herds when the 
second-unit errors are not critical.
+
 # Building from Source
 
 To build this package from source, execute the command `./gradlew distDocker`
diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js
index 6267549..97826e1 100644
--- a/action/alarmWebAction.js
+++ b/action/alarmWebAction.js
@@ -103,6 +103,7 @@ function main(params) {
                     }
                     newTrigger.cron = params.cron;
                     newTrigger.timezone = params.timezone;
+                    newTrigger.strict = params.strict === 'true';
                 } catch(ex) {
                     var message = ex.message !== 'Invalid timezone.' ? `cron 
pattern '${params.cron}' is not valid` : ex.message;
                     return common.sendError(400, message);
diff --git a/installCatalog.sh b/installCatalog.sh
index 117793e..fb51232 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -84,7 +84,7 @@ zip -r alarmFeed.zip lib package.json alarm.js -q
 
 $WSK_CLI -i --apihost "$EDGEHOST" action update --kind 
"$ACTION_RUNTIME_VERSION" --auth "$AUTH" alarms/alarm 
"$PACKAGE_HOME/action/alarmFeed.zip" \
      -a description 'Fire trigger when alarm occurs' \
-     -a parameters '[ {"name":"cron", "required":true}, {"name":"timezone", 
"required":false}, {"name":"startDate", "required":false}, {"name":"stopDate", 
"required":false} ]' \
+     -a parameters '[ {"name":"cron", "required":true}, {"name":"timezone", 
"required":false}, {"name":"startDate", "required":false}, {"name":"stopDate", 
"required":false}, {"name":"strict", "required":false} ]' \
      -a feed true
 
 $WSK_CLI -i --apihost "$EDGEHOST" action update --kind 
"$ACTION_RUNTIME_VERSION" --auth "$AUTH" alarms/once 
"$PACKAGE_HOME/action/alarmFeed.zip" \
diff --git a/provider/lib/cronAlarm.js b/provider/lib/cronAlarm.js
index 1a00b0e..ef26668 100644
--- a/provider/lib/cronAlarm.js
+++ b/provider/lib/cronAlarm.js
@@ -22,6 +22,7 @@ var constants = require('./constants.js');
 module.exports = function(logger, newTrigger) {
 
     var maxTriggers = newTrigger.maxTriggers || constants.DEFAULT_MAX_TRIGGERS;
+    var delayLimit = validateLimit(parseInt(process.env.ALARM_DELAY_LIMIT)) || 
0;
 
     var cachedTrigger = {
         apikey: newTrigger.apikey,
@@ -42,7 +43,7 @@ module.exports = function(logger, newTrigger) {
         try {
             return new Promise(function(resolve, reject) {
 
-                var cronHandle = new CronJob(newTrigger.cron, callback, 
undefined, false, newTrigger.timezone);
+                var cronHandle = new CronJob(distributeCron(newTrigger), 
callback, undefined, false, newTrigger.timezone);
 
                 if (newTrigger.stopDate) {
                     cachedTrigger.stopDate = newTrigger.stopDate;
@@ -85,4 +86,41 @@ module.exports = function(logger, newTrigger) {
         }
     };
 
+    // Convert string to integer in [0, delayLimit)
+    function hashName(name) {
+        var hash = 0;
+
+        for (var i = 0; i < name.length; i++) {
+            var char = name.charCodeAt(i);
+            hash = ((hash << 5) - hash) + char;
+        }
+        hash %= delayLimit + 1;
+        hash = Math.abs(hash);
+
+        return hash.toString(10);
+    }
+
+    function distributeCron(trigger) {
+        var method = "distributeCronAlarm";
+
+        var cronFields = (trigger.cron + '').trim().split(/\s+/);
+        if (trigger.strict !== 'true' && cronFields.length === 5 && delayLimit 
!== 0) {
+            var newCron = [hashName(trigger.name), ...cronFields].join(' ');
+            logger.info(method, trigger.triggerID, 'is converted to', '"' + 
newCron + '"');
+            return newCron;
+        }
+
+        return trigger.cron;
+    }
+
+    function validateLimit(limit) {
+        if (isNaN(limit)) {
+            return 0;
+        }
+        if (limit < 0 || limit >= 60) {
+            return 0;
+        }
+        return limit;
+    }
+
 };

Reply via email to