Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Inotify.cpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2013-2019 Swirly Cloud Limited
3// Copyright (C) 2024 Reactive Markets Limited
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17#include <toolbox/io.hpp>
18#include <toolbox/net.hpp>
19#include <toolbox/sys.hpp>
20#include <toolbox/util.hpp>
21
22using namespace toolbox;
23
24namespace {
25
26using Path = FileWatcher::Path;
27
29struct Config {
30 Path path;
31};
32
33using ConfigPtr = std::unique_ptr<Config>;
34using ConfigFuture = std::future<ConfigPtr>;
35
36class ConfigLoader {
37 using Task = std::packaged_task<ConfigPtr()>;
38
39 public:
40 explicit ConfigLoader(const Path& path)
41 : path_{path}
42 {
43 }
44 ~ConfigLoader() = default;
45
46 // Copy.
47 ConfigLoader(const ConfigLoader& rhs) = delete;
48 ConfigLoader& operator=(const ConfigLoader& rhs) = delete;
49
50 // Move.
51 ConfigLoader(ConfigLoader&&) = delete;
52 ConfigLoader& operator=(ConfigLoader&&) = delete;
53
54 const Path& path() const noexcept { return path_; }
55 bool run()
56 {
57 return tq_.run([](Task&& t) { t(); });
58 }
59 void stop() { tq_.stop(); }
60 void clear()
61 {
62 // This will unblock waiters by throwing a "broken promise" exception.
63 return tq_.clear();
64 }
65 ConfigFuture load()
66 {
67 Task task{[this]() -> ConfigPtr {
68 // TODO: load config from filesystem.
69 return ConfigPtr{new Config{.path = path_}};
70 }};
71 auto future{task.get_future()};
72 tq_.push(std::move(task));
73 return future;
74 }
75
76 private:
77 const Path path_;
79};
80
81class App {
82 public:
84 : config_loader_{config_loader}
85 , file_watcher_{reactor, inotify}
86 // Immediate and then at 5s intervals.
87 , tmr_{reactor.timer(now.mono_time(), 5s, Priority::Low, bind<&App::on_timer>(this))}
88 {
89 // Bind on_config_update slot to the foo.conf configuration file.
90 file_watcher_.watch(config_loader.path(), bind<&App::on_config_update>(this),
92 // Trigger initial config load.
93 config_future_ = config_loader_.load();
94 }
95 ~App() = default;
96
97 private:
98 void on_config_update(const Path& path, int /*wd*/, std::uint32_t event_mask)
99 {
100 if (path != config_loader_.path()) {
101 return;
102 }
104 TOOLBOX_INFO << "config " << path << " updated (IN_CLOSE_WRITE)";
105 }
107 TOOLBOX_INFO << "config " << path << " updated (IN_DELETE_SELF)";
108
109 // On Kubernetes, inotify only receives the IN_DELETE_SELF event on config maps.
110 // This deletion event breaks the inotify watch and so code needs to handle
111 // re-establishing the watch every time the file is updated.
112
113 file_watcher_.watch(path, bind<&App::on_config_update>(this),
115 }
116 config_future_ = config_loader_.load();
117 }
118 void on_timer(CyclTime /*now*/, Timer& /*tmr*/)
119 {
120 if (config_future_.valid()) {
121 if (!is_ready(config_future_)) {
122 TOOLBOX_INFO << "config pending";
123 return;
124 }
125 try {
126 config_ = config_future_.get();
127 } catch (const std::exception& e) {
128 TOOLBOX_ERROR << "could not load config: " << e.what();
129 config_future_ = config_loader_.load();
130 return;
131 }
132 TOOLBOX_INFO << "config loaded: " << config_->path;
133 }
134 }
135
136 ConfigLoader& config_loader_;
137 FileWatcher file_watcher_;
138 Timer tmr_;
139 ConfigFuture config_future_;
140 ConfigPtr config_;
141};
142} // namespace
143
144int main()
145{
146 using namespace std::literals::string_literals;
147 int ret = 1;
148 try {
149
150 const auto start_time{CyclTime::now()};
151 Reactor reactor{1024};
153 ConfigLoader config_loader{"/tmp/inotify_test/files/foo.txt"};
155
156 // Start service threads.
158 ReactorRunner reactor_runner{reactor, 100, "reactor"s};
160
161 // Wait for termination.
163 for (;;) {
164 switch (const auto sig = sig_wait()) {
165 case SIGHUP:
166 TOOLBOX_INFO << "received SIGHUP";
167 continue;
168 case SIGINT:
169 TOOLBOX_INFO << "received SIGINT";
170 break;
171 case SIGTERM:
172 TOOLBOX_INFO << "received SIGTERM";
173 break;
174 default:
175 TOOLBOX_INFO << "received signal: " << sig;
176 continue;
177 }
178 break;
179 }
180 ret = 0;
181
182 } catch (const std::exception& e) {
183 TOOLBOX_ERROR << "exception on main thread: " << e.what();
184 }
185 return ret;
186}
#define TOOLBOX_INFO
Definition Log.hpp:97
#define TOOLBOX_ERROR
Definition Log.hpp:93
FileWatcher watches for changes to files.
Definition Inotify.hpp:137
Inotify provides a simplified interface to an inotify instance.
Definition Inotify.hpp:102
Simple config reader with environment variable substitution.
Definition Config.hpp:67
A vector-based task queue for use in multi-threaded, producer-consumer components.
Definition TaskQueue.hpp:28
int main()
Definition Inotify.cpp:144
bool is_ready(std::future< ResultT > &future)
Returns true if future is ready.
Definition Resolver.hpp:78
void clear(timeval &tv) noexcept
Definition Time.hpp:294
constexpr auto bind() noexcept
Definition Slot.hpp:92