| /* |
| * Copyright 2015 Nest Labs, Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include <functional> |
| #include <map> |
| #include <chrono> |
| #include <algorithm> |
| |
| namespace folly { namespace detail { |
| |
| /* A replacement for folly::HHWheelTimer (folly/io/async library) that |
| * provides O(logN) insertion. This is not a drop-in replacement for |
| * HHWHeelTimer, but is something that |
| * folly::detail::ThreadWheelTimekeeper can use for its |
| * implementation. Used to break the dependency on |
| * folly/io/async. Their hashed and heirarchical timing wheel is |
| * probably superior... and we should probably write our own. See: |
| * http://www.cs.columbia.edu/~nahum/w6998/papers/sosp87-timing-wheels.pdf |
| */ |
| class TimerMap |
| { |
| public: |
| using clock_type = std::chrono::steady_clock; |
| using time_point_type = clock_type::time_point; |
| using duration_type = clock_type::duration; |
| using callback_type = std::function<void()>; |
| using multimap_type = std::multimap<time_point_type, callback_type>; |
| using data_type = std::pair<time_point_type, callback_type>; |
| |
| TimerMap(duration_type tick); |
| |
| /* Non-copyable */ |
| TimerMap(const TimerMap& o) = delete; |
| TimerMap& operator=(const TimerMap&o) = delete; |
| TimerMap(TimerMap&& o) = delete; |
| TimerMap& operator=(TimerMap&& o) = delete; |
| |
| /** |
| * Get the "tick" size for the map. |
| * |
| * @return when calling nextTimeout(), the return values will be |
| * at least this much time apart. |
| */ |
| duration_type getTick() const { |
| return tick_; |
| } |
| |
| /** |
| * Returns true if there are no events stored in database |
| */ |
| bool empty() const { |
| return db_.empty(); |
| } |
| |
| void scheduleTimeout(duration_type timeout, callback_type cb); |
| void scheduleAlarm(time_point_type at, callback_type cb); |
| time_point_type nextTimeout() const; |
| void invokeTimers(); |
| template <typename Iterator> |
| static void invokeTimers(Iterator beg, Iterator end); |
| template <typename InsertIterator> |
| void popExpiredTimers(InsertIterator it); |
| template <typename InsertIterator> |
| void popExpiredTimers(InsertIterator it, time_point_type t); |
| |
| private: |
| duration_type tick_; //< Minimum time between next_tick_ values |
| time_point_type next_tick_; //< The next time invokeTimers() should be called |
| multimap_type db_; //< Internal database of timepoints and callbacks |
| }; |
| |
| /** |
| * Invoke all timers between iterators beg and end. |
| * |
| * THIS IS A STATIC FUNCTION |
| * |
| * Given an iterator pair, invoke all the callbacks/closures between |
| * them. The closures will be moved into the function and thus deleted |
| * after each execution. |
| * |
| * The Iterator (it) type must support the following expressions: |
| * |
| * 1. ++it |
| * 2. it != end |
| * 3. std::move(it->second) // extract the closure |
| * |
| * The expectation is that the iterators are pointing to a |
| * pair<Unspecified, callback_type>. |
| * |
| * @param beg iterator pointing to the first closure to execute. |
| * |
| * @param end iterator pointing to one-past-the-last closure to |
| * execute. |
| */ |
| template <typename Iterator> |
| void TimerMap::invokeTimers(Iterator beg, Iterator end) |
| { |
| Iterator cur; |
| for (cur = beg ; cur != end ; ++cur) { |
| /* destroy each closure after executing callback */ |
| callback_type tmp = std::move(cur->second); |
| tmp(); |
| } |
| } |
| |
| /** |
| * Remove expired iterators from TimerMap, putting them in the provided iterator. |
| * |
| * This is a convenience function for popExpiredTimers(it, clock_type::now()). |
| * See documentation for that function. |
| * |
| * @param it insertion iterator to place events. |
| */ |
| template <typename InsertIterator> |
| void TimerMap::popExpiredTimers(InsertIterator it) { |
| auto t = clock_type::now(); |
| popExpiredTimers(it, t); |
| } |
| |
| /** |
| * Remove expired iterators from TimerMap, putting them in the provided iterator. |
| * |
| * Since it's not possible to query how many elements will be inserted |
| * <em>a priori</em>, it is expected that you will provide an |
| * <em>insertion iterator</em>, like. |
| * std::insert_iterator<std::pair<time_point_type, |
| * callback_type>>. The iterator (it) must support the following |
| * expressions: |
| * |
| * 1. ++it |
| * 2. (*it) = std::pair<time_point_type, callback_type>(...); |
| * |
| * @param it iterator pointing to the first place where the values |
| * should be written. |
| * |
| * @param t extract all timers/callbacks/closures that expired before |
| * or at this absolute time. |
| */ |
| |
| template <typename ForwardInputIterator> |
| void TimerMap::popExpiredTimers(ForwardInputIterator it, time_point_type t) { |
| multimap_type::iterator beg = db_.begin(); |
| multimap_type::iterator end = db_.upper_bound(t); |
| |
| // std::move() doesn't work because multimap_type::value_type |
| // uses 'const Key' in the pair. |
| for (auto cur = beg ; cur != end ; ++cur, ++it) { |
| (*it) = std::make_pair(cur->first, std::move(cur->second)); |
| } |
| |
| db_.erase(beg, end); |
| next_tick_ = t + tick_; |
| } |
| |
| } // namespace detail |
| } // namespace folly |