Updated Examples and Recipes (markdown)

Howard Hinnant
2017-10-27 21:34:37 -04:00
parent 09ea094a48
commit 850c2f46cb

@@ -32,6 +32,7 @@ This page contains examples and recipes contributed by community members. Feel f
- [`microfortnights`?! Are you serious?](#microfortnights)
- [Find and set UNIX download folder thanks to xdg-user-dir](#set_install_with_xdg_user_dir)
- [Re-using unzoned date/time code for zoned date/time values (and vice versa)](#zoned2unzoned)
- [Thoughts on reloading the IANA tzdb for long running programs](#tzdb_manage)
***
@@ -2150,6 +2151,108 @@ const some_sys_time utcTime{unzonedTime.time_since_epoch()};
Where some_local_time and some_sys_time are template instantiations of local_time and sys_time.
<a name="tzdb_manage"></a>
### Thoughts on reloading the IANA tzdb for long running programs
(by [Howard Hinnant](https://github.com/HowardHinnant))
Let's start with this thought: Most programs don't need to stay up long enough to have to worry about the IANA time zone database changing out from under it. The IANA time zone database gets updated anywhere from every month to every season (3 months, might be 4). There is not a regular schedule for IANA time zone database updates. But it doesn't happen daily, or even weekly, and it does happen several times a year.
Next, if your program is up long enough and needs to worry about the latest IANA tzdb, and if multiple threads access the tzdb, then you have multithreading issues, and this lib doesn't provide an out-of-the-box fool-proof solution for you. The reason for that is that I don't want to impose a performance penalty on the vast majority of tzdb clients that don't need thread safe support for updatable tzdb databases. Most clients are fine with the IANA tzdb that exists when their program starts, and don't need to worry about it updating while their program is running.
Finally, if you are in the majority, and don't want to have to worry about the IANA tzdb updating while your program is running, _all_ you have to do is **never** call `date::reload_tzdb()`. If you _never_ call `date::reload_tzdb()`, then you can _never_ have the thread safety issues this short article addresses. You're fine, stop reading now. Go grab a mojito.
Ok, now you're in the position of: I have several threads accessing the tzdb, and IANA has updated the database. I need to migrate to this new database, without stopping all of my threads. _HELP!!!_
Helping you is what this article addresses.
This library maintains a singly-linked list of `tzdb` as a singleton. The head of this list is what you implicitly access through common functions such as `current_zone()` or `locate_zone("some time zone name")`. If you never call `date::reload_tzdb()`, this list will always be of length 1, and you just don't have to worry about it. But if you call `date::reload_tzdb()`, and if this function succeeds in initializing a new `tzdb`, then it will _atomically_ push_front a new `tzdb` onto this singleton list. `date::reload_tzdb()` does not invalidate references or pointers into any of the previous `tzdb`. They continue to exist and work just fine.
Here begins the strategy for your code...
One way to deal with this is to just let the singleton `tzdb_list` grow forever. That's ok with me if it is ok with you. It only grows by one database a few times a year. This guarantees that all of your threads which might be pointing in to older `tzdb` continue to work, although they might be using outdated data.
Another way to deal with this is to use a-priori knowledge that your threads won't continue to point in to a `tzdb` for longer than `X` amount of time, where `X` might be a minute, an hour, a day, a year, whatever. After whatever time has elapsed, you can just navigate the singleton `tzdb_list` and delete old databases.
Below is code that launches a thread that does nothing but wake up once a day and download a new tzdb if it is available, and set a timer to delete old tzdb if it has been 10 days since they were replaced.
```c++
#include "date/tz.h"
#include <atomic>
#include <iostream>
#include <thread>
// Checks for new tzdb once a day (early am local time).
// Allows old tzdb to hang around for 10 days and then deletes it
// Lock-free
void
tzdb_manager(std::atomic<bool>& run)
{
using namespace std;
using namespace std::chrono;
using namespace date;
// Get the current UTC time for today's local 02:00, approximation is ok
auto tz = current_zone();
auto check_at = make_zoned(tz, floor<days>(make_zoned(tz, system_clock::now())
.get_local_time()) + 2h,
choose::latest).get_sys_time();
// Initialize clean-trigger for several years in the future
auto clean_at = check_at + days{1000};
while (run)
{
// Sleep until the wee hours of the morning
this_thread::sleep_until(check_at);
// If time to stop, stop
if (!run)
break;
// Time for morning maintenance
// First check if we need to pop_back the tzdb_list
if (check_at >= clean_at)
{
auto& list = get_tzdb_list();
auto i0 = list.begin();
auto i1 = next(i0);
// if the list has more than one tzdb
if (i1 != list.end())
{
// pop_back the tzdb_list
for (auto i2 = next(i1); i2 != list.end(); ++i0, ++i1, ++i2)
;
list.erase_after(i0);
// if the new size is 1, clean again in a few years
if (i0 == list.begin())
clean_at = check_at + days{1000};
else // otherwise new size > 1, clean again in 10 days
clean_at = check_at + days{10};
}
else // list has only 1 tzdb, clean again in a few years
clean_at = check_at + days{1000};
}
// If the remote version has been updated
auto rv = remote_version();
if (rv != get_tzdb().version)
{
// download, install and load the new tzdb
if (remote_download(rv))
{
if (remote_install(rv))
{
reload_tzdb();
// Schedule cleanup of the old tzdb for 10 days from now
clean_at = check_at + days{10};
}
}
// if anything failed, we just try again tomorrow morning
}
// Maintenance done, go to sleep for a day
check_at += days{1};
}
}
```
It isn't the most friendly code to have to write. But on the other hand, it gives you complete discretion on your `tzdb` delete policy and doesn't penalize clients that have no need to update their `tzdb` database.
More complex (and expensive) policies could track reference count usage for each tzdb and delete only when that reference count drops to zero. It is completely implementable externally without any privileged hooks into `"tz.h"`. This design is motivated by the "don't pay for what you don't use" philosophy.
***
![CC BY Logo](http://mirrors.creativecommons.org/presskit/buttons/80x15/svg/by.svg) _This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/)._