1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Watch generation: the background reload thread and the hooks that observe
//! every reload it performs.
use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
use crate::args::Args;
/// Generates `start_watch()`.
pub(super) fn expand_watch(name: &Ident, args: &Args) -> TokenStream {
let debounce = args.debounce;
let mode = match args.poll_interval {
Some(interval) => quote! {
::dynamic_config::watch::WatchMode::Poll {
interval: ::core::time::Duration::from_millis(#interval),
}
},
None => quote!(::dynamic_config::watch::WatchMode::Native),
};
quote! {
/// Starts a background thread that reloads the snapshot when one of the
/// configured files changes.
///
/// **Dropping the returned handle stops the watcher.** Bind it for as
/// long as the configuration should stay live, or call `.detach()` to
/// watch for the rest of the process:
///
/// ```ignore
/// Config::start_watch()?.detach(); // a server
/// let _watch = Config::start_watch()?; // a test, a subcommand
/// ```
///
/// Calling this while a watcher for this type is already running —
/// including one that was [`detach`]ed — is an error
/// (`ErrorKind::AlreadyExists`): a second handle could only mislead,
/// and the first watcher keeps running. Generic configurations are
/// watched per instantiation: `Db<Postgres>` and `Db<Mysql>` each get
/// their own watcher.
///
/// [`detach`]: ::dynamic_config::watch::WatchHandle::detach
///
/// A reload that fails is reported and the previous snapshot is kept,
/// so an invalid edit degrades to "no change" rather than a crash.
///
/// # Errors
///
/// If a watcher for this type is already running, if the notification
/// backend cannot start, if no directory holding a configured file can
/// be watched, or if the thread cannot be spawned.
pub fn start_watch() -> ::std::io::Result<::dynamic_config::watch::WatchHandle>
where
// Watchers are registered by `TypeId`, the identity that tells
// `Db<Postgres>` apart from `Db<Mysql>` — a *name* keyed both as
// "Db", and the second silently watched nothing. `TypeId` needs
// `'static`, which a configuration type in a `static` already is;
// the bound is spelled out so a violation reads as itself.
Self: 'static,
{
::dynamic_config::__spawn_watch!(
::core::any::TypeId::of::<Self>(),
::core::stringify!(#name),
Self::dynamic_config_spec(),
::core::time::Duration::from_millis(#debounce),
#mode,
Self::dynamic_config_apply,
)
}
}
}
/// `on_reload` and `on_reload_scoped`: the callbacks a reload fires.
pub(super) fn hook_methods() -> TokenStream {
quote! {
/// Registers a callback for every later reload, for the life of
/// the process.
///
/// The callback receives the outgoing and incoming snapshots, in
/// that order, and runs on whichever thread performed the reload —
/// the watcher thread, usually. Keep it short, and do not call
/// [`replace`](Self::replace) from inside one.
///
/// It cannot be removed — use
/// [`on_reload_scoped`](Self::on_reload_scoped) for anything whose
/// life is shorter than the process. A callback that panics is
/// caught and reported; the remaining callbacks and the watcher
/// survive.
///
/// Installing the first snapshot is not a reload, so `init()` does
/// not fire callbacks.
pub fn on_reload<__DynamicConfigHook>(hook: __DynamicConfigHook)
where
__DynamicConfigHook: ::core::ops::Fn(
&::std::sync::Arc<Self>,
&::std::sync::Arc<Self>,
) + ::core::marker::Send
+ ::core::marker::Sync
+ 'static,
{
Self::dynamic_config_cell().on_reload(hook);
}
/// [`on_reload`](Self::on_reload), scoped: dropping the returned
/// guard unregisters the callback.
///
/// For tests, plugins, and anything else that is torn down while
/// the process keeps running — the permanent variant would keep a
/// dead subsystem's callback firing forever.
#[must_use = "dropping the guard unregisters the hook; bind it for \
as long as the hook should fire"]
pub fn on_reload_scoped<__DynamicConfigHook>(
hook: __DynamicConfigHook,
) -> ::dynamic_config::HookGuard<Self>
where
__DynamicConfigHook: ::core::ops::Fn(
&::std::sync::Arc<Self>,
&::std::sync::Arc<Self>,
) + ::core::marker::Send
+ ::core::marker::Sync
+ 'static,
{
Self::dynamic_config_cell().on_reload_scoped(hook)
}
}
}