Add deferrable callbacks

Deferrable callbacks delay callback invocation until a
pre configured length of time has elapsed.  One example
scenario where deferrable callbacks help is to avoid
oscillation when testing a condition and making callbacks
frequently.

Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: I180c99b57ec1c9bde4da76d947a026f809341c8a
diff --git a/src/callback.hpp b/src/callback.hpp
index f7886cb..06df910 100644
--- a/src/callback.hpp
+++ b/src/callback.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <chrono>
 #include "data_types.hpp"
 
 namespace phosphor
@@ -146,14 +147,14 @@
         ConditionalCallback(ConditionalCallback&&) = default;
         ConditionalCallback& operator=(const ConditionalCallback&) = delete;
         ConditionalCallback& operator=(ConditionalCallback&&) = default;
-        ~ConditionalCallback() = default;
+        virtual ~ConditionalCallback() = default;
         ConditionalCallback(
             const std::vector<size_t>& graphEntry,
             Conditional& cond)
             : graph(graphEntry), condition(cond) {}
 
         /** @brief Run the callback if the condition is satisfied. */
-        void operator()() override
+        virtual void operator()() override
         {
             if (condition())
             {
@@ -161,7 +162,7 @@
             }
         }
 
-    private:
+    protected:
         /** @brief The index of the callback to conditionally invoke. */
         const std::vector<size_t>& graph;
 
@@ -169,6 +170,77 @@
         Conditional& condition;
 };
 
+/** @class DeferrableCallback
+ *
+ *  Deferrable callbacks wait a configurable period before
+ *  invoking their associated callback.
+ *
+ *  When the callback condition is initally met, start a timer.  If the
+ *  condition is tested again before the timer expires and it is not
+ *  met cancel the timer.  If the timer expires invoke the associated
+ *  callback.
+ *
+ *  @tparam CallbackAccess - Provide access to callback group instances.
+ *  @tparam TimerType - Delegated timer access methods.
+ */
+template <typename CallbackAccess, typename TimerType>
+class DeferrableCallback : public ConditionalCallback<CallbackAccess>
+{
+    public:
+        DeferrableCallback() = delete;
+        DeferrableCallback(const DeferrableCallback&) = delete;
+        DeferrableCallback(DeferrableCallback&&) = default;
+        DeferrableCallback& operator=(const DeferrableCallback&) = delete;
+        DeferrableCallback& operator=(DeferrableCallback&&) = default;
+        ~DeferrableCallback() = default;
+
+        DeferrableCallback(
+            const std::vector<size_t>& graphEntry,
+            Conditional& cond,
+            const std::chrono::microseconds& delay)
+            : ConditionalCallback<CallbackAccess>(graphEntry, cond),
+              delayInterval(delay),
+              timer(nullptr) {}
+
+        void operator()() override
+        {
+            if (!timer)
+            {
+                timer = std::make_unique<TimerType>(
+// **INDENT-OFF**
+                    [this](auto & source)
+                    {
+                        this->ConditionalCallback<CallbackAccess>::operator()();
+                    });
+// **INDENT-ON**
+                timer->disable();
+            }
+
+            if (this->condition())
+            {
+                if (!timer->enabled())
+                {
+                    // This is the first time the condition evaluated.
+                    // Start the countdown.
+                    timer->update(timer->now() + delayInterval);
+                    timer->enable();
+                }
+            }
+            else
+            {
+                // The condition did not evaluate.  Stop the countdown.
+                timer->disable();
+            }
+        }
+
+    private:
+        /** @brief The length to wait for the condition to stop evaluating. */
+        std::chrono::microseconds delayInterval;
+
+        /** @brief Delegated timer functions. */
+        std::unique_ptr<TimerType> timer;
+};
+
 } // namespace monitoring
 } // namespace dbus
 } // namespace phosphor
diff --git a/src/example/example.yaml b/src/example/example.yaml
index d76e3ad..899c4c8 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -103,14 +103,13 @@
     For example for a given event, one may wish to trace multiple
     messages to the systemd journal.  The journal callback does not
     support tracing multiple messages.  To do that, define a callback
-    group composed of multiple journal callbacks.
+    group composed of multiple journal callbacks.'
 
-    This example callback group only has one member.  To add more, add
-    additional callbacks to the members element.'
   class: callback
   callback: group
   members:
     - example journal callback
+    - example deferred condition
 
 - name: example count condition
   description: >
@@ -138,3 +137,25 @@
   countbound: 3
   op: '>='
   bound: 115
+
+- name: example deferred condition
+  description: >
+    'Deferred conditions operate in the same fashion as conditional callbacks
+    with the added behavior that when the condition is tested and is met,
+    invocation of the callback is deferred by the interval specified.
+
+    When the configured time has elapsed, if the condition has not been reevaluated
+    the callback is invoked.
+
+    Any condition type can be deferred in this way by setting the defer attribute.'
+
+  class: condition
+  condition: count
+  paths: example path group
+  properties: example property group
+  defer: 1000us
+  callback: example callback group
+  countop: '>='
+  countbound: 3
+  op: '>='
+  bound: 115
diff --git a/src/pdmgen.py b/src/pdmgen.py
index 5d4bf29..5e88e2c 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -532,6 +532,7 @@
     def __init__(self, *a, **kw):
         self.condition = kw.pop('condition')
         self.instance = kw.pop('instance')
+        self.defer = kw.pop('defer', None)
         super(ConditionCallback, self).__init__(**kw)
 
     def factory(self, objs):
@@ -580,6 +581,7 @@
 
     def __init__(self, *a, **kw):
         self.callback = kw.pop('callback')
+        self.defer = kw.pop('defer', None)
         super(Condition, self).__init__(**kw)
 
     def factory(self, objs):
@@ -592,6 +594,7 @@
             'callback': 'conditional',
             'instance': self.callback,
             'name': self.name,
+            'defer': self.defer
         }
 
         callback = ConditionCallback(**args)
diff --git a/src/templates/conditional.mako.cpp b/src/templates/conditional.mako.cpp
index 3cc9564..229b3d6 100644
--- a/src/templates/conditional.mako.cpp
+++ b/src/templates/conditional.mako.cpp
@@ -1,3 +1,10 @@
+% if c.defer:
+std::make_unique<DeferrableCallback<ConfigPropertyCallbacks, SDEventTimer>>(
+${indent(1)}ConfigPropertyCallbackGroups::get()[${c.graph}],
+${indent(1)}*ConfigConditions::get()[${c.condition}],
+${indent(1)}${c.defer})\
+% else:
 std::make_unique<ConditionalCallback<ConfigPropertyCallbacks>>(
 ${indent(1)}ConfigPropertyCallbackGroups::get()[${c.graph}],
 ${indent(1)}*ConfigConditions::get()[${c.condition}])\
+% endif\
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 914b065..51462b1 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <array>
+#include <chrono>
 #include <string>
 #include "count.hpp"
 #include "data_types.hpp"
@@ -13,6 +14,7 @@
 #include "sdevent.hpp"
 
 using namespace std::string_literals;
+using namespace std::chrono_literals;
 
 namespace phosphor
 {