{"id":480,"date":"2016-03-24T22:28:47","date_gmt":"2016-03-24T22:28:47","guid":{"rendered":"http:\/\/www.crazygaze.com\/blog\/?p=480"},"modified":"2023-10-11T23:34:03","modified_gmt":"2023-10-11T23:34:03","slug":"portable-c-timer-queue","status":"publish","type":"post","link":"https:\/\/www.crazygaze.com\/blog\/2016\/03\/24\/portable-c-timer-queue\/","title":{"rendered":"Portable C++ Timer Queue"},"content":{"rendered":"<h4>Introduction<\/h4>\n<p>If you looked at my previous posts, you noticed I explored some of the nice things Boost Asio gives you, and how I implemented my own version.<\/p>\n<p>I covered what I called <a href=\"http:\/\/www.crazygaze.com\/blog\/2016\/03\/11\/callstack-markers-boostasiodetailcall_stack\/\">callstack markers<\/a>, which has more uses than it looks on the surface. Also covered <a href=\"http:\/\/www.crazygaze.com\/blog\/2016\/03\/17\/how-strands-work-and-why-you-should-use-them\/\">strands<\/a>, and why they are such a nice concept.<\/p>\n<p>This time around, I&#8217;m going to focus on the equivalent of <a href=\"http:\/\/www.boost.org\/doc\/libs\/1_60_0\/doc\/html\/boost_asio\/reference\/deadline_timer.html\">boost::timer::deadline_timer<\/a>. The implementation shown here is custom made, portable C++11 and self contained.<\/p>\n<p>If I had to roughly describe what <em>deadline_timer<\/em> is for while at the same time hinting at the implementation, I would say &#8220;<em>deadline_timer allows you group together a bunch of handlers to be called when a timer expires<\/em>&#8220;.<br \/>\nI think the &#8220;deadline&#8221; in <em>deadline_timer<\/em> is a bit misleading. With the definition of &#8220;deadline&#8221; being &#8220;<em>the latest time or date by which something should be completed<\/em>&#8220;, the first thing that crosses my mind is that it is used to cancel some operation that did not complete by the specified time\/date. Such as to cancel a connect attempt. Certainly it can, but can be used for a lot more.<br \/>\nFor simplicity, I&#8217;ll be calling it &#8220;timer(s)&#8221;, and not &#8220;deadline timer(s)&#8221;<\/p>\n<h4>A bit of brainstorming<\/h4>\n<p>So, how do we go about implementing timers?<br \/>\nAt its core, you need a queue for the timers, and a worker thread that dequeues and executes timers according to the specified expiry times\/dates. Lets call it &#8220;Timer Queue&#8221;. Win32 API uses the same name for such a thing: <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms686796.aspx\">Timer Queues<\/a><br \/>\nWhat I explore in this post is a Timer Queue implementation. It allows one-shot timers. Higher level things such as grouping handlers in one timer and timers that can be reused (like boost Asio&#8217;s own implementation), can be built on top.<\/p>\n<p>The trickiest thing to solve is how to correctly notify the worker thread that something happened that might change the current wait time.<\/p>\n<p>For example:<\/p>\n<ul>\n<li>Worker thread is waiting for timers to process<\/li>\n<li>Main thread adds a timer A with expiry time of 10 seconds.<\/li>\n<li>Worker thread detects there is a timer in the queue, and starts a wait based on that timer expiry time.<\/li>\n<li>Main thread adds a timer B with expiry time 1 second<\/li>\n<li>Worker thread needs to rethink the waiting time, since timer B should be executed before A.<\/li>\n<\/ul>\n<p>Now, if you throw in a couple more threads adding or cancelling timers, things might get confusing. Personally, I think it&#8217;s a bad sign if to verify correctness of your multithreaded code you have to think too hard. Sure, performance is good, but first get it right. If performance is a problem, profile it, then think as hard as you need to get it right and fast.<\/p>\n<p>My first pass at this was convoluted to say the least. There was a queue for the timers, then communication with the worker thread was done with another queue where I would pass commands such as &#8220;recalculate wait time&#8221;, or &#8220;execute this timer right now&#8221;, or &#8220;shutdown&#8221;.<br \/>\nTo add insult to injury, those two queues were locked independently.<br \/>\nSure enough, as I started to write this post to share the code, I&#8217;ve spotted another bug. Ever had that feeling of &#8220;<em>Surely there must be a cleaner way to do this<\/em>&#8221; ?<\/p>\n<p>To be honest, in hindsight that first pass was quite bad. Maybe it just got that bad in small increments as I was focusing on the wrong problems.<\/p>\n<p>What was sabotaging my efforts was that I was in the wrong mindset. I kept thinking off the thread-to-thread communication as &#8220;<em>work to be done<\/em>&#8220;, and thus the obvious choice there was to have a command queue.<\/p>\n<p>After a moment of clarity, I changed my mindset from &#8220;<em>work to be done<\/em>&#8221; to &#8220;<em>something has changed<\/em>&#8220;, and a cleaner solution formed:<\/p>\n<p><img loading=\"lazy\" width=\"306\" height=\"313\" alt=\"\" class=\"alignnone size-full wp-image-484 \" src=\"https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca.png\" srcset=\"https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca.png 306w, https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca-293x300.png 293w, https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca-100x102.png 100w, https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca-150x153.png 150w, https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca-200x205.png 200w, https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46caa712ca-300x307.png 300w\" sizes=\"(max-width: 306px) 100vw, 306px\" \/><\/p>\n<p>The thread wakes up when something has changed (e,g: Timer added\/cancelled, or expired), and checks for work (e.g: execute handlers for expired timers). Extraneous notifications to wake up the thread have no side effects, since the thread wakes up, checks for work, recalculates the new wait time and goes back to waiting.<\/p>\n<h4>Implementation<\/h4>\n<p>The only helper class needed is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Semaphore_(programming)\">semaphore<\/a>, which can be implemented in portable C++11 with a <a href=\"http:\/\/en.cppreference.com\/w\/cpp\/thread\/mutex\">mutex<\/a>, a <a href=\"http:\/\/en.cppreference.com\/w\/cpp\/thread\/condition_variable\">condition_variable<\/a> and a counter.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &amp;lt;mutex&amp;gt;\n#include &amp;lt;condition_variable&amp;gt;\n\nclass Semaphore {\npublic:\nSemaphore(unsigned int count = 0) : m_count(count) {}\n\nvoid notify() {\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lock(m_mtx);\nm_count++;\nm_cv.notify_one();\n}\n\nvoid wait() {\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lock(m_mtx);\nm_cv.wait(lock, [this]() { return m_count &amp;gt; 0; });\nm_count--;\n}\n\ntemplate &amp;lt;class Clock, class Duration&amp;gt;\nbool waitUntil(const std::chrono::time_point&amp;lt;Clock, Duration&amp;gt;&amp;amp; point) {\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lock(m_mtx);\nif (!m_cv.wait_until(lock, point, [this]() { return m_count &amp;gt; 0; }))\nreturn false;\nm_count--;\nreturn true;\n}\n\nprivate:\nstd::mutex m_mtx;\nstd::condition_variable m_cv;\nunsigned int m_count;\n};\n<\/pre>\n<p>A few more useful methods could be added to <em>Semaphore<\/em>, but were omitted to keep it short.<\/p>\n<ul>\n<li><em>bool waitFor(duration)<\/em> : To wait for specified duration before timeout<\/li>\n<li><em>bool tryWait()<\/em> : To check and decrement the semaphore without waiting.<\/li>\n<\/ul>\n<p>And the <em>TimerQueue<\/em> implementation, heavily documented to further explain the implementation&#8230;<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;Semaphore.h&quot;\n#include &amp;lt;thread&amp;gt;\n#include &amp;lt;queue&amp;gt;\n#include &amp;lt;chrono&amp;gt;\n#include &amp;lt;assert.h&amp;gt;\n\n\/\/ Timer Queue\n\/\/\n\/\/ Allows execution of handlers at a specified time in the future\n\/\/ Guarantees:\n\/\/\t- All handlers are executed ONCE, even if canceled (aborted parameter will\n\/\/be set to true)\n\/\/\t\t- If TimerQueue is destroyed, it will cancel all handlers.\n\/\/\t- Handlers are ALWAYS executed in the Timer Queue worker thread.\n\/\/\t- Handlers execution order is NOT guaranteed\n\/\/\nclass TimerQueue {\npublic:\nTimerQueue() {\nm_th = std::thread([this] { run(); });\n}\n\n~TimerQueue() {\ncancelAll();\n\/\/ Abusing the timer queue to trigger the shutdown.\nadd(0, [this](bool) { m_finish = true; });\nm_th.join();\n}\n\n\/\/! Adds a new timer\n\/\/ return\n\/\/\tReturns the ID of the new timer. You can use this ID to cancel the\n\/\/ timer\nuint64_t add(int64_t milliseconds, std::function&amp;lt;void(bool)&amp;gt; handler) {\nWorkItem item;\nitem.end = Clock::now() + std::chrono::milliseconds(milliseconds);\nitem.handler = std::move(handler);\n\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lk(m_mtx);\nuint64_t id = ++m_idcounter;\nitem.id = id;\nm_items.push(std::move(item));\nlk.unlock();\n\n\/\/ Something changed, so wake up timer thread\nm_checkWork.notify();\nreturn id;\n}\n\n\/\/! Cancels the specified timer\n\/\/ return\n\/\/\t1 if the timer was cancelled.\n\/\/\t0 if you were too late to cancel (or the timer ID was never valid to\n\/\/ start with)\nsize_t cancel(uint64_t id) {\n\/\/ Instead of removing the item from the container (thus breaking the\n\/\/ heap integrity), we set the item as having no handler, and put\n\/\/ that handler on a new item at the top for immediate execution\n\/\/ The timer thread will then ignore the original item, since it has no\n\/\/ handler.\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lk(m_mtx);\nfor (auto&amp;amp;&amp;amp; item : m_items.getContainer()) {\nif (item.id == id &amp;amp;&amp;amp; item.handler) {\nWorkItem newItem;\n\/\/ Zero time, so it stays at the top for immediate execution\nnewItem.end = Clock::time_point();\nnewItem.id = 0;  \/\/ Means it is a canceled item\n\/\/ Move the handler from item to newitem.\n\/\/ Also, we need to manually set the handler to nullptr, since\n\/\/ the standard does not guarantee moving an std::function will\n\/\/ empty it. Some STL implementation will empty it, others will\n\/\/ not.\nnewItem.handler = std::move(item.handler);\nitem.handler = nullptr;\nm_items.push(std::move(newItem));\n\nlk.unlock();\n\/\/ Something changed, so wake up timer thread\nm_checkWork.notify();\nreturn 1;\n}\n}\nreturn 0;\n}\n\n\/\/! Cancels all timers\n\/\/ return\n\/\/\tThe number of timers cancelled\nsize_t cancelAll() {\n\/\/ Setting all &quot;end&quot; to 0 (for immediate execution) is ok,\n\/\/ since it maintains the heap integrity\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lk(m_mtx);\nfor (auto&amp;amp;&amp;amp; item : m_items.getContainer()) {\nif (item.id) {\nitem.end = Clock::time_point();\nitem.id = 0;\n}\n}\nauto ret = m_items.size();\n\nlk.unlock();\nm_checkWork.notify();\nreturn ret;\n}\n\nprivate:\nusing Clock = std::chrono::steady_clock;\nTimerQueue(const TimerQueue&amp;amp;) = delete;\nTimerQueue&amp;amp; operator=(const TimerQueue&amp;amp;) = delete;\n\nvoid run() {\nwhile (!m_finish) {\nauto end = calcWaitTime();\nif (end.first) {\n\/\/ Timers found, so wait until it expires (or something else\n\/\/ changes)\nm_checkWork.waitUntil(end.second);\n} else {\n\/\/ No timers exist, so wait forever until something changes\nm_checkWork.wait();\n}\n\n\/\/ Check and execute as much work as possible, such as, all expired\n\/\/ timers\ncheckWork();\n}\n\n\/\/ If we are shutting down, we should not have any items left,\n\/\/ since the shutdown cancels all items\nassert(m_items.size() == 0);\n}\n\nstd::pair&amp;lt;bool, Clock::time_point&amp;gt; calcWaitTime() {\nstd::lock_guard&amp;lt;std::mutex&amp;gt; lk(m_mtx);\nwhile (m_items.size()) {\nif (m_items.top().handler) {\n\/\/ Item present, so return the new wait time\nreturn std::make_pair(true, m_items.top().end);\n} else {\n\/\/ Discard empty handlers (they were cancelled)\nm_items.pop();\n}\n}\n\n\/\/ No items found, so return no wait time (causes the thread to wait\n\/\/ indefinitely)\nreturn std::make_pair(false, Clock::time_point());\n}\n\nvoid checkWork() {\nstd::unique_lock&amp;lt;std::mutex&amp;gt; lk(m_mtx);\nwhile (m_items.size() &amp;amp;&amp;amp; m_items.top().end &amp;lt;= Clock::now()) {\nWorkItem item(std::move(m_items.top()));\nm_items.pop();\n\nlk.unlock();\nif (item.handler)\nitem.handler(item.id == 0);\nlk.lock();\n}\n}\n\nSemaphore m_checkWork;\nstd::thread m_th;\nbool m_finish = false;\nuint64_t m_idcounter = 0;\n\nstruct WorkItem {\nClock::time_point end;\nuint64_t id;  \/\/ id==0 means it was cancelled\nstd::function&amp;lt;void(bool)&amp;gt; handler;\nbool operator&amp;gt;(const WorkItem&amp;amp; other) const {\nreturn end &amp;gt; other.end;\n}\n};\n\nstd::mutex m_mtx;\n\/\/ Inheriting from priority_queue, so we can access the internal container\nclass Queue : public std::priority_queue&amp;lt;WorkItem, std::vector&amp;lt;WorkItem&amp;gt;,\nstd::greater&amp;lt;WorkItem&amp;gt;&amp;gt; {\npublic:\nstd::vector&amp;lt;WorkItem&amp;gt;&amp;amp; getContainer() {\nreturn this-&amp;gt;c;\n}\n} m_items;\n};\n<\/pre>\n<p>And a small example just adding and cancelling timers&#8230;<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &quot;TimerQueue.h&quot;\n#include &amp;lt;future&amp;gt;\n\nnamespace Timing {\n\nusing Clock = std::chrono::high_resolution_clock;\nstatic thread_local Clock::time_point ms_previous;\ndouble now() {\nstatic auto start = Clock::now();\nreturn std::chrono::duration&amp;lt;double, std::milli&amp;gt;(Clock::now() - start)\n.count();\n}\n\nvoid sleep(unsigned ms) {\nstd::this_thread::sleep_for(std::chrono::milliseconds(ms));\n}\n\n}  \/\/ namespace Timing\n\nint main() {\nTimerQueue q;\n\n\/\/ Create timer with ID 1\nq.add(10000, [start = Timing::now()](bool aborted) mutable {\nprintf(&quot;ID 1: aborted=%s, Elapsed %4.2fmsn&quot;,\naborted ? &quot;true &quot; : &quot;false&quot;, Timing::now() - start);\n});\n\n\/\/ Create Timer with ID 2\nq.add(10001, [start = Timing::now()](bool aborted) mutable {\nprintf(&quot;ID 2: aborted=%s, Elapsed %4.2fmsn&quot;,\naborted ? &quot;true &quot; : &quot;false&quot;, Timing::now() - start);\n});\n\n\/\/ Should cancel timers with ID 1 and 2\nq.cancelAll();\n\n\/\/ Create timer with ID 3\nq.add(1000, [start = Timing::now()](bool aborted) mutable {\nprintf(&quot;ID 3: aborted=%s, Elapsed %4.2fmsn&quot;,\naborted ? &quot;true &quot; : &quot;false&quot;, Timing::now() - start);\n});\n\n\/\/ Create timer with ID 4\nauto id = q.add(2000, [start = Timing::now()](bool aborted) mutable {\nprintf(&quot;ID 4: aborted=%s, Elapsed %4.2fmsn&quot;,\naborted ? &quot;true &quot; : &quot;false&quot;, Timing::now() - start);\n});\n\n\/\/ Cancel timer with ID 4\nauto ret = q.cancel(id);\nassert(ret == 1);\n\n\/\/ Give just enough time to execute timer with ID 3 before destroying the\n\/\/ TimerQueue\nTiming::sleep(1500);\n\n\/\/ At this point, when destroying TimerQueue, the timer with ID 4 is still\n\/\/ pending and will be cancelled implicitly by the destructor\nreturn 0;\n}\n<\/pre>\n<p>The output will be this (with varying *Elapsed&#8221; values)&#8230;<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nID 1: aborted=true , Elapsed 0.01ms\nID 2: aborted=true , Elapsed 0.03ms\nID 4: aborted=true , Elapsed 0.03ms\nID 3: aborted=false, Elapsed 1000.92ms\n<\/pre>\n<h5>Things to improve<\/h5>\n<p>Main objective with this implementation was to have a small, self-contained and portable implementation. Therefore a few things can be improved if performance is a problem:<\/p>\n<ul>\n<li>Use some kind of lockless queue<\/li>\n<li>Cancelling a timer requires iterating through all the timers (the worst case scenario)<\/li>\n<li>Add checks in TimerQueue::add to detect if we are shutting down<\/li>\n<li>Try to get rid of unnecessary wake ups.\n<ul>\n<li>For example, for simplicity, adding or cancelling a timer makes no effort to detect if it&#8217;s really necessary to wake up the timer thread.<\/li>\n<\/ul>\n<\/li>\n<li>Using a <em>uint64_t<\/em> to identify timers works fine, but it&#8217;s error prone.<\/li>\n<li>A fresh pair of eyes looking at the code, since this was a quick implementation with some basic tests.<\/li>\n<\/ul>\n<h4>Conclusion<\/h4>\n<p>Although <em>TimerQueue<\/em> is useful as-is, if you are familiar with Boost&#8217;s Asio <em>deadline_timer<\/em>, you will spot a couple of differences:<\/p>\n<ul>\n<li>TimerQueue is not in any way associated with something akin to Boost Asio&#8217;s <a href=\"http:\/\/www.boost.org\/doc\/libs\/1_60_0\/doc\/html\/boost_asio\/reference\/io_service.html\">io_service<\/a>\n<ul>\n<li>Handlers are executed in the TimerQueue&#8217;s own thread.<\/li>\n<\/ul>\n<\/li>\n<li>Timers are one-shot\n<ul>\n<li>Boost Asio <em>deadline_timer<\/em> is reusable.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>A close match to Boost Asio <em>deadline_timer<\/em> can be implemented on top of <em>TimerQueue<\/em>  by having a <em>Timer<\/em> class that aggregates handlers, and puts a one-off timer on <em>TimerQueue<\/em> whenever we set an expiry time. That one-off timer will run any handlers the <em>Timer<\/em> instance owns. Even more, instead of executing the handlers in the <em>TimerQueue<\/em> thread, <em>Timer<\/em> can forward execution of the handlers to another object, thus giving the application control when\/where to execute the handlers.<\/p>\n<p>While writing this, I took a quick look at how Boost&#8217;s Asio&#8217;s implements deadline timers (on Windows), to see if it was significantly different. Particularly, I was curious how <em>deadline_timer<\/em> is tied to <em>io_service<\/em>. If there is another thread involved for triggering timer related things, or if somehow everything was controlled by Windows Completion Ports.<br \/>\nFrom my short study of the beast&#8217;s guts, it seems it uses a somewhat similar method:<\/p>\n<ul>\n<li>There is a thread for timer related things (<em>struct win_iocp_io_service::timer_thread_function<\/em> )\n<ul>\n<li>This thread just loops until shutdown, waiting on a Win32 <a href=\"https:\/\/msdn.microsoft.com\/en-gb\/library\/windows\/desktop\/ms682492.aspx\">waitable timer object<\/a> for something to happen.<\/li>\n<li>When this waitable timer expires, it uses <em>PostQueuedCompletionStatus<\/em> on the associated Completion Port, to trigger any handler execution in the appropriate threads (where the user is calling <em>io_service::run<\/em>)<\/li>\n<\/ul>\n<\/li>\n<li>Changes to <em>deadline_timer<\/em> instances set that win32 waitable timer<\/li>\n<\/ul>\n<p>I said somewhat similar, since my implementation executes handlers in the timer queue thread itself when a timer expires. In Boost Asio, when a timer expires, the timer thread sends a signal to the respective <em>io_service<\/em>, and the handlers are executed there, as explained above.<\/p>\n<p>Feel free to comment with corrections, suggestions, or ask me to cover other areas.<\/p>\n<h3>License<\/h3>\n<p>The source code in this article is licensed under the <a href=\"https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/\">CC0 license<\/a>, so feel free to copy, modify, share, do whatever you want with it.<br \/>\nNo attribution is required, but I&#8217;ll be happy if you do.<\/p>\n<h2>Updates<\/h2>\n<ul>\n<li><strong>2016-08-25<\/strong> : Fixed a bug in <em>cancel<\/em> (Thanks to Daniel&#8217;s comment)\n<ul>\n<li>According to the standard, moving an std::function does not guarantee it will empty it. Visual Studio&#8217;s empties it, and so I missed that bug.<\/li>\n<\/ul>\n<\/li>\n<li><strong>2016-09-01<\/strong> : Fixed a bug in <em>cancelAll<\/em> (Thanks to Patrick&#8217;s comment)\n<ul>\n<li>Because calls to <em>cancel<\/em> don&#8217;t remove the original item, but set the <em>handler<\/em> to nullptr so that the worker thread then ignores those, <em>cancelAll<\/em> was then ignoring those items and not changing the expiry time (for immediate execution). This meant destroying a TimerQueue would assert since the container would not be empty by the time the worker thread finished.<\/li>\n<li>The behaviour was still correct since the items left in the container at the time of destruction were already cancelled items (no handler to execute).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Introduction If you looked at my previous posts, you noticed I explored some of the nice things Boost Asio gives you, and how I implemented my own version. I covered what I called callstack markers, which has more uses than it looks on the surface. Also covered strands, and why they are such a nice [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":481,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_mi_skip_tracking":false,"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[50,3],"tags":[12,52,15],"jetpack_featured_media_url":"https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56f46a0bce929.png","jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p7jpe0-7K","_links":{"self":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts\/480"}],"collection":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/comments?post=480"}],"version-history":[{"count":9,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts\/480\/revisions"}],"predecessor-version":[{"id":926,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts\/480\/revisions\/926"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/media\/481"}],"wp:attachment":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/media?parent=480"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/categories?post=480"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/tags?post=480"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}