passenger 5.1.7 → 5.1.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +13 -2
  3. data/CONTRIBUTING.md +1 -1
  4. data/build/agent.rb +1 -1
  5. data/build/cxx_tests.rb +6 -0
  6. data/build/support/cxx_dependency_map.rb +1286 -391
  7. data/build/support/general.rb +0 -26
  8. data/resources/templates/standalone/rails_asset_pipeline.erb +2 -2
  9. data/src/agent/Core/ApiServer.h +49 -44
  10. data/src/agent/Core/ApplicationPool/Pool.h +1 -1
  11. data/src/agent/Core/ApplicationPool/Process.h +1 -1
  12. data/src/agent/Core/ApplicationPool/Socket.h +1 -1
  13. data/src/agent/Core/Controller.h +16 -8
  14. data/src/agent/Core/Controller/CheckoutSession.cpp +1 -1
  15. data/src/agent/Core/Controller/Config.cpp +68 -0
  16. data/src/agent/Core/Controller/Config.h +70 -34
  17. data/src/agent/Core/Controller/ForwardResponse.cpp +5 -5
  18. data/src/agent/Core/Controller/Hooks.cpp +5 -14
  19. data/src/agent/Core/Controller/Implementation.cpp +1 -1
  20. data/src/agent/Core/Controller/InitRequest.cpp +31 -29
  21. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +4 -4
  22. data/src/agent/Core/Controller/InternalUtils.cpp +3 -3
  23. data/src/agent/Core/Controller/Miscellaneous.cpp +1 -1
  24. data/src/agent/Core/Controller/Request.h +2 -2
  25. data/src/agent/Core/Controller/SendRequest.cpp +5 -5
  26. data/src/agent/Core/Controller/StateInspection.cpp +1 -1
  27. data/src/agent/Core/Controller/TurboCaching.h +2 -2
  28. data/src/agent/Core/CoreMain.cpp +2 -2
  29. data/src/agent/Core/ResponseCache.h +3 -3
  30. data/src/agent/Core/SpawningKit/BackgroundIOCapturer.h +3 -3
  31. data/src/agent/Core/SpawningKit/DirectSpawner.h +2 -2
  32. data/src/agent/Core/SpawningKit/PipeWatcher.h +3 -3
  33. data/src/agent/Core/SpawningKit/SmartSpawner.h +2 -2
  34. data/src/agent/Core/SpawningKit/Spawner.h +1 -1
  35. data/src/agent/Core/UnionStation/Connection.h +1 -1
  36. data/src/agent/Core/UnionStation/Context.h +1 -1
  37. data/src/agent/Core/UnionStation/Transaction.h +1 -1
  38. data/src/agent/Shared/ApiServerUtils.h +73 -27
  39. data/src/agent/Shared/Base.cpp +61 -73
  40. data/src/agent/UstRouter/ApiServer.h +34 -45
  41. data/src/agent/UstRouter/Controller.h +86 -60
  42. data/src/agent/UstRouter/RemoteSender.h +1 -1
  43. data/src/agent/UstRouter/RemoteSink.h +1 -1
  44. data/src/agent/Watchdog/ApiServer.h +42 -50
  45. data/src/agent/Watchdog/WatchdogMain.cpp +1 -1
  46. data/src/apache2_module/Configuration.hpp +1 -1
  47. data/src/apache2_module/Hooks.cpp +27 -13
  48. data/src/cxx_supportlib/AppTypes.h +1 -1
  49. data/src/cxx_supportlib/BackgroundEventLoop.cpp +1 -1
  50. data/src/cxx_supportlib/ConfigKit/AsyncUtils.h +86 -0
  51. data/src/cxx_supportlib/ConfigKit/Common.h +6 -3
  52. data/src/cxx_supportlib/ConfigKit/IN_PRACTICE.md +1039 -0
  53. data/src/cxx_supportlib/ConfigKit/README.md +112 -497
  54. data/src/cxx_supportlib/ConfigKit/Schema.h +78 -15
  55. data/src/cxx_supportlib/ConfigKit/Store.h +272 -53
  56. data/src/cxx_supportlib/ConfigKit/SubComponentUtils.h +59 -0
  57. data/src/cxx_supportlib/ConfigKit/Utils.h +26 -65
  58. data/src/cxx_supportlib/ConfigKit/ValidationUtils.h +69 -0
  59. data/src/cxx_supportlib/ConfigKit/VariantMapUtils.h +7 -4
  60. data/src/cxx_supportlib/Constants.h +4 -1
  61. data/src/cxx_supportlib/Crypto.cpp +1 -1
  62. data/src/cxx_supportlib/DataStructures/StringKeyTable.h +26 -7
  63. data/src/cxx_supportlib/FileDescriptor.h +1 -1
  64. data/src/cxx_supportlib/Hooks.h +1 -1
  65. data/src/cxx_supportlib/LoggingKit/Assert.h +130 -0
  66. data/src/cxx_supportlib/LoggingKit/Config.h +97 -0
  67. data/src/cxx_supportlib/LoggingKit/Context.h +94 -0
  68. data/src/cxx_supportlib/LoggingKit/Forward.h +95 -0
  69. data/src/cxx_supportlib/LoggingKit/Implementation.cpp +695 -0
  70. data/src/cxx_supportlib/LoggingKit/Logging.h +204 -0
  71. data/src/cxx_supportlib/LoggingKit/LoggingKit.h +33 -0
  72. data/src/cxx_supportlib/LveLoggingDecorator.h +1 -1
  73. data/src/cxx_supportlib/MemoryKit/mbuf.cpp +1 -1
  74. data/src/cxx_supportlib/RandomGenerator.h +1 -1
  75. data/src/cxx_supportlib/SafeLibev.h +1 -1
  76. data/src/cxx_supportlib/ServerKit/AcceptLoadBalancer.h +1 -1
  77. data/src/cxx_supportlib/ServerKit/Channel.h +1 -1
  78. data/src/cxx_supportlib/ServerKit/FileBufferedChannel.h +1 -1
  79. data/src/cxx_supportlib/ServerKit/FileBufferedFdSinkChannel.h +1 -1
  80. data/src/cxx_supportlib/ServerKit/HttpChunkedBodyParser.h +1 -1
  81. data/src/cxx_supportlib/ServerKit/HttpHeaderParser.h +1 -1
  82. data/src/cxx_supportlib/ServerKit/HttpServer.h +48 -15
  83. data/src/cxx_supportlib/ServerKit/Server.h +79 -52
  84. data/src/cxx_supportlib/StaticString.h +12 -0
  85. data/src/cxx_supportlib/Utils/Curl.h +16 -0
  86. data/src/cxx_supportlib/Utils/FastStringStream.h +6 -1
  87. data/src/cxx_supportlib/Utils/ScopeGuard.h +1 -1
  88. data/src/cxx_supportlib/Utils/StrIntUtils.cpp +2 -19
  89. data/src/cxx_supportlib/WatchdogLauncher.h +3 -2
  90. data/src/ruby_supportlib/phusion_passenger.rb +3 -3
  91. data/src/ruby_supportlib/phusion_passenger/common_library.rb +12 -12
  92. data/src/ruby_supportlib/phusion_passenger/constants.rb +6 -3
  93. data/src/ruby_supportlib/phusion_passenger/standalone/start_command.rb +1 -0
  94. data/src/ruby_supportlib/phusion_passenger/standalone/stop_command.rb +1 -0
  95. metadata +14 -4
  96. data/src/cxx_supportlib/Logging.cpp +0 -295
  97. data/src/cxx_supportlib/Logging.h +0 -385
@@ -77,7 +77,7 @@ PassengerAppType pp_get_app_type2(const char *name, unsigned int len);
77
77
  #include <cstdlib>
78
78
  #include <limits.h>
79
79
  #include <string>
80
- #include <Logging.h>
80
+ #include <LoggingKit/LoggingKit.h>
81
81
  #include <StaticString.h>
82
82
  #include <Utils.h>
83
83
  #include <Utils/StrIntUtils.h>
@@ -36,7 +36,7 @@
36
36
  #include <ev.h>
37
37
  #include <uv.h>
38
38
  #include <BackgroundEventLoop.h>
39
- #include <Logging.h>
39
+ #include <LoggingKit/LoggingKit.h>
40
40
  #include <Exceptions.h>
41
41
  #include <SafeLibev.h>
42
42
 
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Phusion Passenger - https://www.phusionpassenger.com/
3
+ * Copyright (c) 2017 Phusion Holding B.V.
4
+ *
5
+ * "Passenger", "Phusion Passenger" and "Union Station" are registered
6
+ * trademarks of Phusion Holding B.V.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+ #ifndef _PASSENGER_CONFIG_KIT_ASYNC_UTILS_H_
27
+ #define _PASSENGER_CONFIG_KIT_ASYNC_UTILS_H_
28
+
29
+ #include <vector>
30
+ #include <boost/function.hpp>
31
+ #include <ConfigKit/Common.h>
32
+
33
+ namespace Passenger {
34
+ namespace ConfigKit {
35
+
36
+ using namespace std;
37
+
38
+
39
+ template<typename Component>
40
+ struct CallbackTypes {
41
+ typedef
42
+ boost::function<void (const vector<Error> &errors, typename Component::ConfigChangeRequest &req)>
43
+ PrepareConfigChange;
44
+ typedef
45
+ boost::function<void (typename Component::ConfigChangeRequest &req)>
46
+ CommitConfigChange;
47
+ typedef
48
+ boost::function<void (const Json::Value &config)>
49
+ InspectConfig;
50
+ };
51
+
52
+
53
+ template<typename Component>
54
+ inline void
55
+ callPrepareConfigChangeAndCallback(Component *component, Json::Value updates,
56
+ typename Component::ConfigChangeRequest *req,
57
+ const typename CallbackTypes<Component>::PrepareConfigChange &callback)
58
+ {
59
+ vector<Error> errors;
60
+ component->prepareConfigChange(updates, errors, *req);
61
+ callback(errors, *req);
62
+ }
63
+
64
+ template<typename Component>
65
+ inline void
66
+ callCommitConfigChangeAndCallback(Component *component,
67
+ typename Component::ConfigChangeRequest *req,
68
+ const typename CallbackTypes<Component>::CommitConfigChange &callback)
69
+ {
70
+ component->commitConfigChange(*req);
71
+ callback(*req);
72
+ }
73
+
74
+ template<typename Component>
75
+ inline void
76
+ callInspectConfigAndCallback(Component *component,
77
+ const typename CallbackTypes<Component>::InspectConfig &callback)
78
+ {
79
+ callback(component->inspectConfig());
80
+ }
81
+
82
+
83
+ } // namespace ConfigKit
84
+ } // namespace Passenger
85
+
86
+ #endif /* _PASSENGER_CONFIG_KIT_ASYNC_UTILS_H_ */
@@ -44,7 +44,6 @@ class Store;
44
44
 
45
45
  enum Type {
46
46
  STRING_TYPE,
47
- PASSWORD_TYPE, // Like STRING_TYPE, but inspect() won't show its value
48
47
  INT_TYPE,
49
48
  UINT_TYPE,
50
49
  FLOAT_TYPE,
@@ -53,6 +52,10 @@ enum Type {
53
52
  ARRAY_TYPE,
54
53
  STRING_ARRAY_TYPE,
55
54
 
55
+ OBJECT_TYPE,
56
+
57
+ ANY_TYPE,
58
+
56
59
  UNKNOWN_TYPE
57
60
  };
58
61
 
@@ -61,6 +64,7 @@ enum Flags {
61
64
  REQUIRED = 1 << 0,
62
65
  CACHE_DEFAULT_VALUE = 1 << 1,
63
66
  READ_ONLY = 1 << 2,
67
+ SECRET = 1 << 3,
64
68
 
65
69
  _DYNAMIC_DEFAULT_VALUE = 1 << 30,
66
70
  _FROM_SUBSCHEMA = 1 << 31
@@ -120,8 +124,7 @@ public:
120
124
  };
121
125
 
122
126
  typedef boost::function<Json::Value (const Store &store)> ValueGetter;
123
- typedef boost::function<void (const Json::Value &config, const vector<Error> &errors)> ConfigCallback;
124
- typedef boost::function<void (const Json::Value &config)> InspectCallback;
127
+ typedef boost::function<Json::Value (const Json::Value &value)> ValueFilter;
125
128
 
126
129
 
127
130
  } // namespace ConfigKit
@@ -0,0 +1,1039 @@
1
+ # ConfigKit in practice & design patterns
2
+
3
+ [The ConfigKit README](#README.md) taught you how to use ConfigKit by itself. But how does ConfigKit fit in the bigger picture? This section describes *good practices* and *design patterns* can that can be used throughout the overall Passenger C++ codebase.
4
+
5
+ At the time of writing (25 Feb 2017), ConfigKit was just introduced, so these practices and patterns aren't yet used everywhere, but the long-term plan is to adopt these practices/patterns throughout the entire codebase.
6
+
7
+ <!-- MarkdownTOC depth=3 autolink="true" bracket="round" -->
8
+
9
+ - [The "component" pattern](#the-component-pattern)
10
+ - [Components are composable](#components-are-composable)
11
+ - [Configuration mechanism](#configuration-mechanism)
12
+ - [Typical invocation](#typical-invocation)
13
+ - [Design rationale](#design-rationale)
14
+ - [Inspecting a component's configuration](#inspecting-a-components-configuration)
15
+ - [Asynchronous components](#asynchronous-components)
16
+ - [Synchronous examples](#synchronous-examples)
17
+ - [SecurityChecker example: a configurable, low-level component](#securitychecker-example-a-configurable-low-level-component)
18
+ - [DnsQuerier example: a low-level component with post-configuration application operations](#dnsquerier-example-a-low-level-component-with-post-configuration-application-operations)
19
+ - [HappyDnsQuerier example: subclassing components](#happydnsquerier-example-subclassing-components)
20
+ - [Downloader example: a high-level component that composes subcomponents](#downloader-example-a-high-level-component-that-composes-subcomponents)
21
+ - [The special problem of conflicting overlapping configuration names and translation](#the-special-problem-of-conflicting-overlapping-configuration-names-and-translation)
22
+ - [Code example](#code-example)
23
+ - [Main function example](#main-function-example)
24
+ - [Asynchronous examples](#asynchronous-examples)
25
+ - [Implementation considerations](#implementation-considerations)
26
+ - [Error handling in prepareChangeRequest and commitChangeRequest](#error-handling-in-preparechangerequest-and-commitchangerequest)
27
+ - [Concurrency](#concurrency)
28
+
29
+ <!-- /MarkdownTOC -->
30
+
31
+ ## The "component" pattern
32
+
33
+ _This section only provides an introduction to the component pattern concept. The pattern will be described in more detail in the various examples, as well as in the [Implementation considerations](#implementation-considerations) section._
34
+
35
+ A component is an entity (usually a class) that is configurable. A component has a schema, and contains its own configuration: it embeds a ConfigKit::Store. You cannot access or modify this store directly: all access is encapsulated by the component.
36
+
37
+ ### Components are composable
38
+
39
+ A component is *composable*. Parent components contain child components. The configurations of such child components are encapsulated: they cannot be directly modified by the outside world, and the parent component is solely responsible for updating child components' configuration. Parent components can even choose to completely hide the existance of child components.
40
+
41
+ Thus, a parent component's schema is *usually* a superset of the union of all its child components' schemas: it usually allows access to all of its child components' config options, and may even introduce some config options of its own.
42
+
43
+ But sometimes there are child config options that make no sense outside the parent components. Parent components can choose to hide those options from the outside world.
44
+
45
+ There may also be child config options that should be exposed to the outside world under a different name or different form. Parent components are free to expose any schema they want to the outside world.
46
+
47
+ ### Configuration mechanism
48
+
49
+ For the purpose of changing configuration, a component should:
50
+
51
+ * Expose a `ConfigChangeRequest` type.
52
+ * Expose two methods: `prepareConfigChange()` and `commitConfigChange()`.
53
+
54
+ This is what they are supposed to do:
55
+
56
+ * `bool prepareConfigChange(updates, &errors, &req)` accepts proposed configuration updates, similar to `ConfigKit::Store::update()`. It performs validation, outputs any errors in `errors` and returns whether preparation succeeded (i.e. whether there are no errors).
57
+
58
+ If validation passes, then instead of changing the configuration directly, it merely performs *preparation work* necessary to commit the configuration change later. The entire state of this preparation work is to be stored in the `req` object (which is of type `ConfigChangeRequest`).
59
+
60
+ As long as the preparation work is not commited, the existance of such preparation work must not change the component's behavior or configuration.
61
+
62
+ * `void commitConfigChange(&req) noexcept` accepts a ConfigChangeRequest object -- which was processed by prepareConfigChange -- and commits the preparation work.
63
+
64
+ **Big caveat:** this method is not allowed to throw any exceptions! If anything can go wrong, it should have been detected during prepareConfigChange!
65
+
66
+ If any cleanup work -- which may fail/throw -- needs to be done, then that work must be done outside this method. You can do that for example by making the ConfigChangeRequest destructor responsible for cleaning things up.
67
+
68
+ You can learn more about all this in the examples provided in this document, as well as in the section [Implementation considerations](#implementation-considerations).
69
+
70
+ #### Typical invocation
71
+
72
+ Here is how a typical invocation looks like, given a `Component component` object:
73
+
74
+ ~~~c++
75
+ Json::Value updates;
76
+ updates["url"] = "http://www.google.com";
77
+ updates["debug"] = true;
78
+
79
+ Component::ConfigChangeRequest req;
80
+ vector<ConfigKit::Error> errors;
81
+ if (component.prepareConfigChange(updates, errors, req)) {
82
+ component.commitConfigChange(req);
83
+ } else {
84
+ // Log the errors
85
+ }
86
+ ~~~
87
+
88
+ #### Design rationale
89
+
90
+ Why two methods? Why not just a single `configure(updates, errors)` method similar to `ConfigKit::Store::update()`? The answer: to allow composability and atomicity.
91
+
92
+ If a parent component configures multiple child components, and one of them fails, then the parent is left in an inconsistent in which only some child components have been configured. We want a configuration change to be atomic across all child components.
93
+
94
+ Atomicity *could* be implemented with a rollback system: if the parent detects that a child component failed, then it rolls back the configuration of already-configured child components. But rollback can be difficult to implement.
95
+
96
+ Instead, we think that a preparation+commit system is easier and cleaner. A parent configures child components with the following pseudocode:
97
+
98
+ ~~~c++
99
+ Child1::ConfigChangeRequest req1;
100
+ Child2::ConfigChangeRequest req2;
101
+ Child3::ConfigChangeRequest req3;
102
+
103
+ child1.prepareConfigChange(updates, errors, req1);
104
+ if (errors.empty()) {
105
+ child2.prepareConfigChange(updates, errors, req2);
106
+ }
107
+ if (errors.empty()) {
108
+ child3.prepareConfigChange(updates, errors, req3);
109
+ }
110
+
111
+ // No config has *actually* been changed so far.
112
+
113
+ if (errors.empty()) {
114
+ // Atomically apply all config changes. None of these will fail.
115
+ child1.commitConfigChange(req1);
116
+ child2.commitConfigChange(req2);
117
+ child2.commitConfigChange(req3);
118
+ }
119
+ ~~~
120
+
121
+ ### Inspecting a component's configuration
122
+
123
+ A component should expose a `Json::Value inspectConfig() const` method which returns a JSON object in the same format as `ConfigKit::Store::inspect()`.
124
+
125
+ ## Asynchronous components
126
+
127
+ We've assumed so far that components are synchronous. What about components that deal with asynchronous I/O? They usually run inside an event loop, and all access to their internal data should be performed from the event loop.
128
+
129
+ Our recommendation is that you implement synchronous as well as asynchronous versions of the recommended component methods. The synchronous versions must be called from the event loop. The asynchronous versions...
130
+
131
+ - accept a callback which is to be called with the result of the related synchronous method.
132
+ - schedule an operation via the event loop the perform the equivalent synchronous operation and call the callback.
133
+ - are to be thread-safe.
134
+
135
+ This approach will be explained further under [Asynchronous examples](#asynchronous-examples).
136
+
137
+ ## Synchronous examples
138
+
139
+ Let's demonstrate the component concept through a number of annotated example classes:
140
+
141
+ - `SecurityChecker` checks whether the given URL is secure to connect to, by checking a database of vulnerable sites.
142
+ This example demonstrates basic usage of ConfigKit in a low-level component.
143
+
144
+ - `DnsQuerier` looks up DNS information for a given URL, from multiple DNS servers. Because the algorithm that DnsQuerier uses is a performance-critical hot path (or so we claim for the sake of the example), it should cache certain configuration values in variables instead of looking up the config store over and over, because the latter involve unnecessary hash table lookups and memory allocations.
145
+
146
+ This example demonstrates caching of configuration values. More generally, it demonstrates how to perform arbitrary operations necessary for applying a configuration change.
147
+
148
+ - `HappyDnsQuerier` subclasses `DnsQuerier` to add additional behavior. When the `query()` method is called, it prints a configurable message.
149
+
150
+ This example demonstrates subclassing of components.
151
+
152
+ - `Downloader` is a high-level class (parent component) for downloading a specific URL. Under the hood it utilizes `SecurityChecker` and `HappyDnsQuerier` (subcomponents).
153
+
154
+ This example demonstrates how to combine its own configuration with the configuration of multiple lower-level classes.
155
+
156
+ ### SecurityChecker example: a configurable, low-level component
157
+
158
+ SecurityChecker checks whether the given URL is secure to connect to, by checking a database of vulnerable sites. This example demonstrates basic usage of ConfigKit in a low-level component.
159
+
160
+ ~~~c++
161
+ #include <boost/config.hpp>
162
+ #include <boost/scoped_ptr.hpp>
163
+ #include <string>
164
+ #include <vector>
165
+ #include <ConfigKit/ConfigKit.h>
166
+
167
+ using namespace std;
168
+ using namespace Passenger;
169
+
170
+ class SecurityChecker {
171
+ public:
172
+ // A component class should start with a public section that defines 2-3 things:
173
+ // - A schema.
174
+ // - A ConfigRealization type (optional; will be covered in DnsQuerier example).
175
+ // - A ConfigChangeRequest type.
176
+
177
+ // This defines the SecurityChecker's configuration schema.
178
+ // This is also the recommended naming scheme:
179
+ // <YourComponentName>::Schema.
180
+ //
181
+ // Defining the schema should happen in an `initialize()` method,
182
+ // not directly in the constructor. There should also be two
183
+ // constructors: a default one that calls `finalize()`,
184
+ // and one that takes a boolean and does not call `finalize()`.
185
+ // This is to allow subclassing, which the HappyDnsQuerier example
186
+ // will cover.
187
+ class Schema: public ConfigKit::Schema {
188
+ private:
189
+ void initialize() {
190
+ using namespace ConfigKit;
191
+ add("db_path", STRING_TYPE, REQUIRED);
192
+ add("url", STRING_TYPE, REQUIRED);
193
+ add("timeout", INT_TYPE, OPTIONAL, 60);
194
+ }
195
+
196
+ public:
197
+ Schema() {
198
+ initialize();
199
+ finalize();
200
+ }
201
+
202
+ Schema(bool _subclassing) {
203
+ initialize();
204
+ }
205
+ };
206
+
207
+ // This defines the SecurityChecker's configuration change request
208
+ // type. This is also the recommended naming scheme:
209
+ // <YourComponentName>::ConfigChangeRequest
210
+ struct ConfigChangeRequest {
211
+ // In simple low-level components this usually only
212
+ // contains a smart pointer to a ConfigKit::Store.
213
+ boost::scoped_ptr<ConfigKit::Store> config;
214
+ };
215
+
216
+ protected:
217
+ // This is the internal configuration store. This is also the
218
+ // recommended naming scheme: `config`.
219
+ //
220
+ // We recommend `protected` visibility in order to allow
221
+ // subclasses to access this.
222
+ ConfigKit::Store config;
223
+
224
+ public:
225
+ // The constructor takes a Schema so that we can initialize the
226
+ // configuration store. It also immediately accepts some
227
+ // initial configuration.
228
+ //
229
+ // You might wonder, how about instead of taking a Schema as a parameter,
230
+ // we define the Schema as a global variable? Well, that would interfere
231
+ // with subclassing; see the HappyDnsQuerier example.
232
+ SecurityChecker(const Schema &schema, const Json::Value &initialConfig)
233
+ : config(schema, initialConfig)
234
+ { }
235
+
236
+ // A component should also provide a constructor accepts a translator
237
+ // object. The translator is then simply passed to the ConfigKit::Store
238
+ // constructor.
239
+ //
240
+ // This variant of the component constructor is necessary to allow parent
241
+ // components to compose child components. You will see the usage
242
+ // of this constructor in the Downloader example.
243
+ template<typename Translator>
244
+ SecurityChecker(const Schema &schema, const Json::Value &initialConfig,
245
+ const Translator &translator)
246
+ : config(schema, initialConfig, translator)
247
+ { }
248
+
249
+ // This is the configuration preparation change method, as described
250
+ // in "Configuring a component".
251
+ bool prepareConfigChange(const Json::Value &updates,
252
+ vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
253
+ {
254
+ // In simple low-level components, it simply merges the current
255
+ // configuration and the given updates, into a new config
256
+ // store that is placed inside `req`. Or if validation fails,
257
+ // it outputs errors.
258
+ req.config.reset(new ConfigKit::Store(config, updates, errors));
259
+ return errors.empty();
260
+ }
261
+
262
+ // This is the configuration preparation commit method, as described
263
+ // in "Configuring a component".
264
+ //
265
+ // Note that we use the `BOOST_NOEXCEPT_OR_NOTHROW` macro so that it
266
+ // is compatible with C++03.
267
+ void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
268
+ // In simple low-level components, we simply swap the internal
269
+ // config store with the config store in the req object.
270
+ //
271
+ // Recall that commitConfigChange() is required to be infallible:
272
+ // it may not throw. So we encourage you to only perform a bunch of
273
+ // swap() operations and other simple non-fallible operations here.
274
+ // ConfigKit::Store::swap() and most other swap() methods never
275
+ // throw.
276
+ config.swap(*req.config);
277
+
278
+ // A less obvious but important thing: swap() places the old
279
+ // configuration store into req. The caller is responsible for
280
+ // destroying req (and thus the old configuration store). So if
281
+ // anything goes wrong during destroying the old configuration
282
+ // store, the exception is thrown outside commitConfigChange.
283
+ // This further satisfies the requirement that commitConfigChange()
284
+ // may not fail.
285
+ }
286
+
287
+ // This method inspects the component's configuration.
288
+ Json::Value inspectConfig() const {
289
+ // In simple low-level components, it's as simple as forwarding
290
+ // the call to the configuration store.
291
+ return config.inspect();
292
+ }
293
+
294
+ bool check() const {
295
+ // Fictional code here for performing the lookup.
296
+ openDatabase(config["db_path"].asString());
297
+ Entry entry = lookupEntry(config["url"].asString(),
298
+ config["timeout"].asInt());
299
+ closeDatabase();
300
+ return !entry.isNull();
301
+ }
302
+ };
303
+ ~~~
304
+
305
+ Use SecurityChecker like this:
306
+
307
+ ~~~c++
308
+ SecurityChecker::Schema schema;
309
+ Json::Value config;
310
+ config["db_path"] = "/db";
311
+ config["url"] = "http://www.google.com";
312
+
313
+ // Initiate SecurityChecker with initial configuration
314
+ SecurityChecker securityChecker(schema, config);
315
+ securityChecker.check();
316
+
317
+ // Change configuration
318
+ vector<ConfigKit::Error> errors;
319
+ SecurityChecker::ConfigChangeRequest req;
320
+ config["url"] = "http://www.example.com";
321
+
322
+ if (securityChecker.prepareConfigChange(config, errors, req)) {
323
+ securityChecker.commitConfigChange(req);
324
+ } else {
325
+ cout << "Configuration change failed: " << ConfigKit::toString(errors) << endl;
326
+ }
327
+
328
+ // Inspect configuration
329
+ cout << "Final configuration:" << endl;
330
+ cout << securityChecker.inspectConfig().toStyledString() << endl;
331
+ ~~~
332
+
333
+ ### DnsQuerier example: a low-level component with post-configuration application operations
334
+
335
+ DnsQuerier looks up DNS information for a given URL, from multiple DNS servers.
336
+
337
+ Because the algorithm that DnsQuerier uses is a performance-critical hot path (or so we claim for the sake of the example), it should cache certain configuration values in a variables instead of looking up the config store over and over, because the latter involve unnecessary hash table lookups.
338
+
339
+ DnsQuerier also logs its progress to a log file. This log file is opened during construction and at the time of configuration change.
340
+
341
+ This example demonstrates caching of configuration values, and it demonstrates how to perform arbitrary operations necessary for applying a configuration change (in this case, opening a new log file and closing the previous one).
342
+
343
+ ~~~c++
344
+ // for std::swap()
345
+ #if __cplusplus >= 201103L
346
+ #include <utility>
347
+ #else
348
+ #include <algorithm>
349
+ #endif
350
+ #include <cstdio>
351
+ #include <cstddef>
352
+ #include <string>
353
+ #include <vector>
354
+ #include <boost/config.hpp>
355
+ #include <boost/scoped_ptr.hpp>
356
+ #include <ConfigKit/ConfigKit.h>
357
+ #include <Exceptions.h>
358
+
359
+ using namespace std;
360
+ using namespace Passenger;
361
+
362
+ class DnsQuerier {
363
+ public:
364
+ // The schema, same as with SecurityChecker.
365
+ class Schema: public ConfigKit::Schema {
366
+ private:
367
+ void initialize() {
368
+ using namespace ConfigKit;
369
+ add("url", STRING_TYPE, REQUIRED);
370
+ add("timeout", INT_TYPE, OPTIONAL, 60);
371
+ add("log_file", STRING_TYPE, REQUIRED);
372
+ }
373
+
374
+ public:
375
+ Schema() {
376
+ initialize();
377
+ finalize();
378
+ }
379
+
380
+ Schema(bool _subclassing) {
381
+ initialize();
382
+ }
383
+ };
384
+
385
+ // When a component is initially created, and every time configuration
386
+ // changes, we create a new "config realization" object. Inside
387
+ // this object we store caches of important config options.
388
+ // We also store any other state closely related to the config values:
389
+ // in this case, an open handle to the log file.
390
+ struct ConfigRealization {
391
+ string url;
392
+ FILE *logStream;
393
+
394
+ // Config realization objects are always created from
395
+ // a config store.
396
+ ConfigRealization(const ConfigKit::Store &config)
397
+ : url(config["url"].asString()),
398
+ logStream(fopen(config["log_file"].asCString(), "a"))
399
+ {
400
+ if (logStream == NULL) {
401
+ throw RuntimeException("Cannot open log file "
402
+ + config["log_file"].asString());
403
+ }
404
+ }
405
+
406
+ // Config realization objects own all their fields so it's responsible
407
+ // for them cleaning up.
408
+ ~ConfigRealization() {
409
+ fclose(logStream);
410
+ }
411
+
412
+ // Config realization objects must have a working swap()
413
+ // method that does not throw, because commitConfigChange()
414
+ // relies on it.
415
+ void swap(ConfigRealization &other) BOOST_NOEXCEPT_OR_NOTHROW {
416
+ url.swap(other.url);
417
+ std::swap(logStream, other.logStream);
418
+ }
419
+ };
420
+
421
+ // Just like with SecurityChecker we define a ConfigChangeRequest.
422
+ struct ConfigChangeRequest {
423
+ // Same as with SecurityChecker.
424
+ boost::scoped_ptr<ConfigKit::Store> config;
425
+
426
+ // We extend it with one more field: a smart
427
+ // pointer to a config realization object. This is
428
+ // because during a configuration change we
429
+ // want to create a new one.
430
+ boost::scoped_ptr<ConfigRealization> configRlz;
431
+ };
432
+
433
+ protected:
434
+ // This is the internal configuration store, same as
435
+ // with SecurityChecker.
436
+ ConfigKit::Store config;
437
+
438
+ private:
439
+ // This is the internal config realization object. This
440
+ // is also the recommended naming scheme: `configRlz`.
441
+ //
442
+ // Unlike with `config`, there is no reason why subclasses
443
+ // should be able to access this, so it should have the
444
+ // `private` visibility.
445
+ ConfigRealization configRlz;
446
+
447
+ public:
448
+ // Same as with SecurityChecker, but also initializes
449
+ // the config realization object.
450
+ DnsQuerier(const Schema &schema, const Json::Value &initialConfig)
451
+ : config(schema, initialConfig),
452
+ configRlz(config)
453
+ { }
454
+
455
+ // Same as with SecurityChecker, but also initializes
456
+ // the config realization object.
457
+ template<typename Translator>
458
+ DnsQuerier(const Schema &schema, const Json::Value &initialConfig,
459
+ const Translator &translator)
460
+ : config(schema, initialConfig, translator),
461
+ configRlz(config)
462
+ { }
463
+
464
+ // Same as with SecurityChecker, but also creates a new
465
+ // config realization object from the new config.
466
+ // Note that we only do that if there are no validation errors.
467
+ bool prepareConfigChange(const Json::Value &updates,
468
+ vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
469
+ {
470
+ req.config.reset(new ConfigKit::Store(config, updates, errors));
471
+ if (errors.empty()) {
472
+ req.configRlz.reset(new ConfigRealization(*req.config));
473
+ }
474
+ return errors.empty();
475
+ }
476
+
477
+ // Same as with SecurityChecker, but also swaps the newly-created
478
+ // config realization object with the internal one.
479
+ void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
480
+ config.swap(*req.config);
481
+ configRlz.swap(*req.configRlz);
482
+ }
483
+
484
+ // Same as with SecurityChecker.
485
+ Json::Value inspectConfig() const {
486
+ return config.inspect();
487
+ }
488
+
489
+ string query() {
490
+ // Fictional code here for performing the lookup.
491
+ setSocketTimeout(config["timeout"].asInt());
492
+ for (unsigned i = 0; i < 1000; i++) {
493
+ // Fictional hot code path that requires the url and logStream config
494
+ // options.
495
+ fprintf(configRlz.logStream, "Querying %s (loop %u)\n",
496
+ configRlz.url.c_str(), i);
497
+ queryNextDnsServer(configRlz.url);
498
+ receiveResponse();
499
+ }
500
+ return result;
501
+ }
502
+ };
503
+ ~~~
504
+
505
+ ### HappyDnsQuerier example: subclassing components
506
+
507
+ Components can be subclassed. A subclass may add additional configuration options and may perform additional config realization.
508
+
509
+ However, a subclass may not rename the parent's config options, or remove any of them. If you want to do any of that then you should use composition instead. See the Downloader example.
510
+
511
+ The following example demonstrates HappyDnsQuerier: a DnsQuerier subclass that prints a configurable message whenever `query()` is called.
512
+
513
+ ~~~c++
514
+ // for std::swap()
515
+ #if __cplusplus >= 201103L
516
+ #include <utility>
517
+ #else
518
+ #include <algorithm>
519
+ #endif
520
+ #include <iostream>
521
+ #include <boost/config.hpp>
522
+ #include <boost/scoped_ptr.hpp>
523
+ #include <ConfigKit/ConfigKit.h>
524
+
525
+ class HappyDnsQuerier: public DnsQuerier {
526
+ public:
527
+ // Defines the HappyDnsQuerier's configuration schema. As explained, this schema
528
+ // includes not only options directly pertaining DnsQuerier itself, but also
529
+ // its parent's options. This is done by subclassing the parent's schema.
530
+ class Schema: public DnsQuerier::Schema {
531
+ private:
532
+ void initialize() {
533
+ using namespace ConfigKit;
534
+ add("happy_message", STRING_TYPE, REQUIRED);
535
+ }
536
+
537
+ public:
538
+ Schema()
539
+ // We call the parent's constructor that takes a boolean,
540
+ // in order to prevent it from calling `finalize()`.
541
+ // We will do that ourselves after having added our own
542
+ // fields.
543
+ : DnsQuerier::Schema(true)
544
+ {
545
+ initialize();
546
+ finalize();
547
+ }
548
+
549
+ Schema(bool _subclassing)
550
+ : DnsQuerier::Schema(true)
551
+ {
552
+ initialize();
553
+ }
554
+ };
555
+
556
+ // HappyDnsQuerier has its own config realization object. Its
557
+ // structure is similar to how it was implemented in DnsQuerier.
558
+ //
559
+ // This has got nothing to do with the parent's config realization
560
+ // object. Subclasses do not care at all whether the parent
561
+ // uses config realization or not.
562
+ struct ConfigRealization {
563
+ public:
564
+ string happyMessage;
565
+
566
+ ConfigRealization(const ConfigKit::Store &config) {
567
+ happyMessage = config["happy_message"].asString();
568
+ }
569
+
570
+ void swap(ConfigRealization &other) BOOST_NOEXCEPT_OR_NOTHROW {
571
+ happyMessage.swap(other.happyMessage);
572
+ }
573
+ };
574
+
575
+ struct ConfigChangeRequest {
576
+ // A subclass encapsulates the configuration of its
577
+ // parent component, so its ConfigChangeRequest must
578
+ // have a field for storing the parent's ConfigChangeRequest.
579
+ DnsQuerier::ConfigChangeRequest forParent;
580
+
581
+ // This is the subclass's own new config realization
582
+ // object. It has a similar function to the
583
+ // one in DnsQuerier::ConfigChangeRequest.
584
+ boost::scoped_ptr<ConfigRealization> configRlz;
585
+ };
586
+
587
+ private:
588
+ // The subclass has its own config realization object.
589
+ ConfigRealization configRlz;
590
+
591
+ public:
592
+ // Same as with DnsQuerier, but also initializes our own
593
+ // private config realization object.
594
+ HappyDnsQuerier(const Schema &schema, const Json::Value &initialConfig)
595
+ : DnsQuerier(schema, initialConfig),
596
+ configRlz(config)
597
+ { }
598
+
599
+ // Same as with DnsQuerier, but also initializes our own
600
+ // private config realization object.
601
+ template<typename Translator>
602
+ HappyDnsQuerier(const Schema &schema, const Json::Value &initialConfig,
603
+ const Translator &translator)
604
+ : DnsQuerier(schema, initialConfig, translator),
605
+ configRlz(config)
606
+ { }
607
+
608
+ // Same as with DnsQuerier, but also creates our own
609
+ // config realization object (but only if validation passes).
610
+ bool prepareConfigChange(const Json::Value &updates,
611
+ vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
612
+ {
613
+ if (DnsQuerier::prepareConfigChange(updates, errors, req.forParent)) {
614
+ req.configRlz.reset(new ConfigRealization(*req.forParent.config));
615
+ }
616
+ return errors.empty();
617
+ }
618
+
619
+ // Same as with DnsQuerier, but also swaps our own private
620
+ // config realization object.
621
+ void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
622
+ DnsQuerier::commitConfigChange(req.forParent);
623
+ configRlz.swap(*req.configRlz);
624
+ }
625
+
626
+ // No need to override inspectConfig().
627
+
628
+ void query() {
629
+ std::cout << "Hurray! " << configRlz.happyMessage << endl;
630
+ DnsQuerier::query();
631
+ }
632
+ };
633
+ ~~~
634
+
635
+ ### Downloader example: a high-level component that composes subcomponents
636
+
637
+ `Downloader` is a high-level component for downloading a specific URL. Under the hood it utilizes `SecurityChecker` and `DnsQuerier`. This example demonstrates how to combine its own configuration with the configuration of multiple subcomponents.
638
+
639
+ Downloader completely *encapsulates* its subcomponents. Users of Downloader don't have to know that the subcomponents exist. Downloader also completely encapsulates subcomponents' configuration: subcomponents can only be configured (and their configuration only inspected) through Downloader.
640
+
641
+ Because of this, downloader's configuration schema includes not only options directly pertaining Downloader itself, but also includes options pertaining the subcomponents it uses.
642
+
643
+ Similarly, the Downloader's internal configuration store stores not only the configuration values directly pertaining to Downloader itself, but also (a copy of) the configuration values pertaining to the subcomponents.
644
+
645
+ Whenever Downloader is configured, it configures subcomponents too. But whenever Downloader's configuration is inspected, it only returns the data from its own configuration store. Because only the Downloader is allowed to configure its subcomponents, we know that the Downloader's internal configuration store contains the most up-to-date values.
646
+
647
+ #### The special problem of conflicting overlapping configuration names and translation
648
+
649
+ There is one special problem that deserves attention: a high-level component may not necessarily want to expose their subcomponents' configuration options using the same names, or at all.
650
+
651
+ For example, both `SecurityChecker` and `DnsQuerier` expose a `timeout` option, but they are *different* timeouts, and are even distinct from the Downloader's own download timeout. To solve this special problem of **conflicting overlapping configuration names**, we utilize a **translation system**. We define how the Downloader's configuration keys are to be mapped a specific subcomponent's configuration keys. We obviously can't define the entire mapping, because that would return us to the original problem of having to manually write so much repeated code. There are several ways to deal with this, such as:
652
+
653
+ - Assuming that most options don't have to be renamed, and only define exceptions to this rule. This is the approach that is demonstrated in this example. The `ConfigKit::TableTranslator` class implements this translation strategy.
654
+ - Prefixing the subcomponents' options. The `ConfigKit::PrefixTranslator` class implements this translation strategy.
655
+
656
+ In this Downloader example, we will demonstrate TableTranslator only.
657
+
658
+ #### Code example
659
+
660
+ ~~~c++
661
+ #include <ConfigKit/ConfigKit.h>
662
+ #include <ConfigKit/SubComponentUtils.h>
663
+
664
+ class Downloader {
665
+ public:
666
+ // Defines the Downloader's configuration schema. As explained, this schema
667
+ // includes not only options directly pertaining Downloader itself, but also
668
+ // options pertaining the subcomponents.
669
+ class Schema: public ConfigKit::Schema {
670
+ private:
671
+ void initialize() {
672
+ using namespace ConfigKit;
673
+
674
+ // Here we define how to map Downloader configuration keys to
675
+ // SecurityChecker configuration keys. We add the SecurityChecker's
676
+ // configuration schema to our own, taking into account the
677
+ // translations.
678
+ //
679
+ // Note that everything not defined in these translation mapping
680
+ // definitions are simply not translated (i.e. left as-is). So
681
+ // the Downloader's "url" option maps directly to SecurityChecker's
682
+ // "url" option.
683
+ securityChecker.translator.add("security_checker_db_path", "db_path");
684
+ securityChecker.translator.add("security_checker_timeout", "timeout");
685
+ securityChecker.translator.finalize();
686
+ addSubSchema(securityChecker.schema, securityChecker.translator);
687
+
688
+ // Ditto for HappyDnsQuerier.
689
+ happyDnsQuerier.translator.add("dns_timeout", "timeout");
690
+ happyDnsQuerier.translator.add("dns_query_log_file", "log_file");
691
+ happyDnsQuerier.translator.finalize();
692
+ addSubSchema(happyDnsQuerier.schema, happyDnsQuerier.translator);
693
+
694
+ // Here we define Downloader's own configuration keys.
695
+ add("download_timeout", INT_TYPE, OPTIONAL, 60);
696
+ }
697
+
698
+ public:
699
+ // For each subcomponent that Downloader uses, we define a struct member
700
+ // that contains that subcomponent's schema, as well as a translation table
701
+ // for mapping Downloader's config keys to the subcomponent's config keys.
702
+ struct {
703
+ SecurityChecker::Schema schema;
704
+ ConfigKit::TableTranslator translator;
705
+ } securityChecker;
706
+ struct {
707
+ HappyDnsQuerier::Schema schema;
708
+ ConfigKit::TableTranslator translator;
709
+ } happyDnsQuerier;
710
+
711
+ Schema() {
712
+ initialize();
713
+ finalize();
714
+ }
715
+
716
+ Schema(bool _subclassing) {
717
+ initialize();
718
+ }
719
+ };
720
+
721
+ // Downloader manages the configurations of its subcomponents, so
722
+ // Downloader::ConfigChangeRequest also contains
723
+ // ConfigChangeRequest objects pertaining to its subcomponents.
724
+ struct ConfigChangeRequest {
725
+ SecurityChecker::ConfigChangeRequest forSecurityChecker;
726
+ HappyDnsQuerier::ConfigChangeRequest forHappyDnsQuerier;
727
+ boost::scoped_ptr<ConfigKit::Store> config;
728
+ };
729
+
730
+
731
+ protected:
732
+ // The internal configuration store. As explained, this also contains (a
733
+ // copy of) the configuration options pertaining SecurityChecker and the
734
+ // DnsQuerier.
735
+ //
736
+ // This MUST be declared before the subcomponents because
737
+ // the construction of subcomponents depends on this object.
738
+ ConfigKit::Store config;
739
+
740
+ private:
741
+ // The subcomponents used by Downloader.
742
+ SecurityChecker securityChecker;
743
+ HappyDnsQuerier happyDnsQuerier;
744
+
745
+ public:
746
+ // The constructor creates subcomponents, passing to them the effective
747
+ // configuration values from our internal configuration store,
748
+ // as well as corresponding translators.
749
+ //
750
+ // Why do we pass effective values instead of `initialConfig()`?
751
+ // It is because the parent component may define different default
752
+ // values than subcomponents. By passing effective values,
753
+ // such default value overrides are respected.
754
+ Downloader(const Schema &schema, const Json::Value &initialConfig)
755
+ : config(schema, initialConfig),
756
+ securityChecker(schema.securityChecker.schema,
757
+ config.inspectEffectiveValues(),
758
+ schema.securityChecker.translator),
759
+ happyDnsQuerier(schema.happyDnsQuerier.schema,
760
+ config.inspectEffectiveValues(),
761
+ schema.happyDnsQuerier.translator)
762
+ { }
763
+
764
+ template<typename Translator>
765
+ Downloader(const Schema &schema, const Json::Value &initialConfig,
766
+ const Translator &translator)
767
+ : config(schema, initialConfig, translator),
768
+ securityChecker(schema.securityChecker.schema,
769
+ config.inspectEffectiveValues(),
770
+ schema.securityChecker.translator),
771
+ happyDnsQuerier(schema.happyDnsQuerier.schema,
772
+ config.inspectEffectiveValues(),
773
+ schema.happyDnsQuerier.translator)
774
+ { }
775
+
776
+ bool prepareConfigChange(const Json::Value &updates,
777
+ vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
778
+ {
779
+ const Schema &schema = static_cast<const Schema &>(config.getSchema());
780
+
781
+ // We first merge updates into Downloader's own new config store.
782
+ req.config.reset(new ConfigKit::Store(config, updates, errors));
783
+
784
+ // Next, we call prepareConfigChange() on our subcomponents,
785
+ // passing it the effective values from Downloader's config store.
786
+ // ConfigKit provides a utility method for doing that.
787
+ //
788
+ // This utility method takes a translator and takes care of
789
+ // translating config options and errors, as defined in the
790
+ // schema. This utility method assumes that the subcomponent
791
+ // implements the prepareConfigChange() method and defines
792
+ // the ConfigChangeRequest type.
793
+ ConfigKit::prepareConfigChangeForSubComponent(
794
+ securityChecker, schema.securityChecker.translator,
795
+ req.config->inspectEffectiveValues(),
796
+ errors, req.forSecurityChecker);
797
+ ConfigKit::prepareConfigChangeForSubComponent(
798
+ happyDnsQuerier, schema.happyDnsQuerier.translator,
799
+ req.config->inspectEffectiveValues(),
800
+ errors, req.forHappyDnsQuerier);
801
+
802
+ // Because Downloader's schema also contains subcomponents'
803
+ // schemas, some validations may be performed multiple times,
804
+ // resulting in duplicate error messages. Always call
805
+ // ConfigKit::deduplicateErrors() at the end to get rid of
806
+ // duplicates.
807
+ errors = ConfigKit::deduplicateErrors(errors);
808
+
809
+ return errors.empty();
810
+ }
811
+
812
+ void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
813
+ // This call here is similar to SecurityChecker and (Happy)DnsQuerier.
814
+ config.swap(*req.config);
815
+
816
+ // We also need to call commitConfigChange() on our subcomponents.
817
+ securityChecker.commitConfigChange(req.forSecurityChecker);
818
+ happyDnsQuerier.commitConfigChange(req.forHappyDnsQuerier);
819
+ }
820
+
821
+ // As explained, simply returning our internal configuration store
822
+ // is enough to expose all the subcomponents' configuration values too.
823
+ Json::Value inspectConfig() const {
824
+ return config.inspect();
825
+ }
826
+
827
+ void download() {
828
+ // Fictional code here for performing the download
829
+ cout << "Downloading " << config["url"].asString() << endl;
830
+ securityChecker.check();
831
+ happyDnsQuerier.query();
832
+
833
+ openSocket(config["url"].asString());
834
+ sendRequest();
835
+ receiveData(config["download_timeout"].asInt());
836
+ closeSocket();
837
+ }
838
+ };
839
+ ~~~
840
+
841
+ ### Main function example
842
+
843
+ To close this section, here is an example of how the main function would look like to utilize the aforementioned components:
844
+
845
+ ~~~c++
846
+ int
847
+ main() {
848
+ // Set configuration
849
+ Json::Value config;
850
+ config["url"] = "http://www.google.com";
851
+ config["security_checker_db_path"] = "/db";
852
+ config["dns_query_log_file"] = "dns.log";
853
+ config["dns_timeout"] = 30;
854
+ config["download_timeout"] = 20;
855
+
856
+ // Instantiate and print schema
857
+ Downloader::Schema schema;
858
+ cout << "Configuration schema:" << endl;
859
+ cout << schema.inspect().toStyledString() << endl;
860
+
861
+ // Instantiate a Downloader and perform a download
862
+ Downloader downloader(schema, config);
863
+ downloader.download();
864
+
865
+ // Change configuration, perform another download
866
+ cout << "Changing configuration" << endl;
867
+ Downloader::ConfigChangeRequest req;
868
+ vector<ConfigKit::Error> errors;
869
+ config["url"] = "http://www.slashdot.org";
870
+
871
+ if (downloader.prepareConfigChange(config, errors, req)) {
872
+ downloader.commitConfigChange(req);
873
+ } else {
874
+ cout << "Configure failed! " << Passenger::toString(errors) << endl;
875
+ }
876
+ downloader.download();
877
+
878
+ // Print final configuration
879
+ cout << "Final configuration:" << endl;
880
+ cout << downloader.inspectConfig().toStyledString() << endl;
881
+
882
+ return 0;
883
+ }
884
+ ~~~
885
+
886
+ ## Asynchronous examples
887
+
888
+ The above examples demonstrate how things would work with synchronous components. What about components that deal with asynchronous I/O? They usually run inside an event loop, and all access to their internal data should be performed from the event loop.
889
+
890
+ Our recommendation is that you implement synchronous as well as asynchronous versions of the `prepareConfigChange()`, `commitConfigChange()`, and `inspectConfig()` methods. The synchronous versions must be called from the event loop. The asynchronous versions...
891
+
892
+ - accept a callback which is to be called with the result.
893
+ - schedule an operation via the event loop the perform the equivalent synchronous operation and call the callback.
894
+ - are to be thread-safe.
895
+
896
+ The following example demonstrates how SecurityChecker would look like if introduces asynchronous methods as described above. Other classes, like Downloader, should also be modified in a similar manner.
897
+
898
+ ~~~c++
899
+ #include <ConfigKit/AsyncUtils.h>
900
+
901
+ class SecurityChecker {
902
+ public:
903
+ // No change from synchronous version.
904
+ class Schema: public ConfigKit::Schema {
905
+ private:
906
+ void initialize() {
907
+ using namespace ConfigKit;
908
+ add("db_path", STRING_TYPE, REQUIRED);
909
+ add("url", STRING_TYPE, REQUIRED);
910
+ add("timeout", INTEGER_TYPE, OPTIONAL, 60);
911
+ }
912
+
913
+ public:
914
+ Schema() {
915
+ initialize();
916
+ finalize();
917
+ }
918
+
919
+ Schema(bool _subclassing) {
920
+ initialize();
921
+ }
922
+ };
923
+
924
+ // No change from synchronous version.
925
+ struct ConfigChangeRequest {
926
+ boost::scoped_ptr<ConfigKit::Store> config;
927
+ };
928
+
929
+ private:
930
+ EventLoop &eventLoop;
931
+
932
+ // No change from synchronous version.
933
+ ConfigKit::Store config;
934
+
935
+ public:
936
+ // Only change from synchronous version is that we now accept
937
+ // an event loop object.
938
+ SecurityChecker(const Schema &schema, const Json::Value &initialConfig,
939
+ const EventLoopType &_eventLoop)
940
+ : eventLoop(eventLoop),
941
+ config(schema, initialConfig)
942
+ { }
943
+
944
+ template<typename Translator>
945
+ SecurityChecker(const Schema &schema, const Json::Value &initialConfig,
946
+ const EventLoopType &_eventLoop, const Translator &translator)
947
+ : eventLoop(eventLoop),
948
+ config(schema, initialConfig, translator)
949
+ { }
950
+
951
+ // No change from synchronous version.
952
+ bool prepareConfigChange(const Json::Value &updates,
953
+ vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
954
+ {
955
+ req.config.reset(new ConfigKit::Store(config, updates, errors));
956
+ return errors.empty();
957
+ }
958
+
959
+ // No change from synchronous version.
960
+ void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
961
+ config.swap(*req.config);
962
+ }
963
+
964
+ // No change from synchronous version.
965
+ Json::Value inspectConfig() const {
966
+ return config.inspect();
967
+ }
968
+
969
+
970
+ /****** Introduction of asynchronous methods below ******/
971
+
972
+ // Performs the same thing as the synchronous version, but
973
+ // over the event loop.
974
+ void asyncPrepareConfigChange(const Json::Value &updates,
975
+ ConfigChangeRequest &req,
976
+ const ConfigKit::CallbackTypes<SecurityChecker>::PrepareConfigChange &callback)
977
+ {
978
+ // The exact API depends on which event loop implementation
979
+ // you use. When using libev, use SafeLibev::runLater().
980
+ // When using Boost Asio, use io_service.post().
981
+ //
982
+ // We use the utility function ConfigKit::callPrepareConfigChangeAndCallback.
983
+ // This function assumes that SecurityChecker supports prepareConfigChange().
984
+ eventLoop.threadSafeRunInNextTick(boost::bind(
985
+ ConfigKit::callPrepareConfigChangeAndCallback<SecurityChecker>,
986
+ this, updates, &req, callback));
987
+ }
988
+
989
+ // Performs the same thing as the synchronous version, but
990
+ // over the event loop.
991
+ void asyncCommitConfigChange(ConfigChangeRequest &req,
992
+ const ConfigKit::CallbackTypes<SecurityChecker>::CommitConfigChange &callback)
993
+ BOOST_NOEXCEPT_OR_NOTHROW
994
+ {
995
+ // We use the utility function ConfigKit::callCommitConfigChangeAndCallback.
996
+ // This function assumes that SecurityChecker supports commitConfigChange().
997
+ eventLoop.threadSafeRunInNextTick(boost::bind(
998
+ ConfigKit::callCommitConfigChangeAndCallback<SecurityChecker>,
999
+ this, &req, callback));
1000
+ }
1001
+
1002
+ // Performs the same thing as the synchronous version, but
1003
+ // over the event loop.
1004
+ void asyncInspectConfig(const ConfigKit::CallbackTypes<SecurityChecker>::InspectConfig &callback) const {
1005
+ // We use the utility function ConfigKit::callInspectConfigAndCallback.
1006
+ // This function assumes that SecurityChecker supports inspectConfig().
1007
+ eventLoop.threadSafeRunInNextTick(boost::bind(
1008
+ ConfigKit::callInspectConfigAndCallback<SecurityChecker>,
1009
+ this, callback));
1010
+ }
1011
+
1012
+ // ...further fictional code here for performing the lookup...
1013
+ };
1014
+ ~~~
1015
+
1016
+ ## Implementation considerations
1017
+
1018
+ ### Error handling in prepareChangeRequest and commitChangeRequest
1019
+
1020
+ If anything *can* go wrong, then the error *should* have been detected by `prepareConfigChange()`. If anything goes wrong within `commitChangeRequest()` then you only have two choices: log and ignore the error, or abort the entire program.
1021
+
1022
+ This means that memory allocations, opening files, etc. should be done as much as possible by `prepareConfigChange()`, not `commitConfigChange()`. Ideally `commitConfigChange()` does not allocate any memory or perform any system calls at all.
1023
+
1024
+ In some cases `commitConfigChange()` needs to perform cleanup work (such as freeing old data structures or closing the previous log file handle), which may fail. We recommend that you structure your code in such a way that such cleanup is performed by the ConfigChangeRequest destructor. The design patterns outlined in this document guarantee that the ConfigChangeRequest destructor is called outside commitConfigChange.
1025
+
1026
+ The examples that we have shown make use of `swap()`, which never fails and does not allocate memory, and also ensures that cleanup work is performed by the ConfigChangeRequest destructor.
1027
+
1028
+ ### Concurrency
1029
+
1030
+ Since preparing and committing a config change are two separate operations, ABA problems can occur. Consider the following timeline:
1031
+
1032
+ 1. Thread A: c.prepareConfigChange(..., req1)
1033
+ 2. Thread B: c.prepareConfigChange(..., req2)
1034
+ 3. Thread A: c.commitConfigChange(req1)
1035
+ 4. Thread B: c.commitConfigChange(req2)
1036
+
1037
+ Depending on how the data structure for `req1` looks like and how `commitConfigChange()` is implemented, line 4 can end up overwriting the config changes prepared on line 2.
1038
+
1039
+ In concurrent environments, one should ensure a serialization of such calls. One should prevent a next prepareConfigChange from running, until a previous preparation has been committed. One way to implement this is by serializing operations with a queue.