Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

configfile: allow selecting slow watch behavior via env var #58

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

## 1.N.NN - 20YY-MM-DD

- configfile: allow selecting slow watch behavior via env var

## 1.0.19 - 2019-MM-DD

- configfile: convert to es6 class
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,8 @@ in a plugins register() function are read *before* Haraka drops privileges.
Be sure that Haraka's user/group has permission to read these files else
Haraka will be unable to update them after changes.

### Network filesystems

Auto-reload is not supported on an FS that doesn't implement proper file changes notify mechanism.

Imagine you've got several Haraka nodes that keep their config dir on a network FS. You have updated a configuration file on the network server and would like to let all Haraka nodes know your file has been updated. For that, you have to login to all your Haraka nodes and `touch` the relevant config file manually. Obviously, this really resembles plain old `scp` copy, except that you only touch files instead of copying data. It almost makes no point of using a network FS then. So, for those people who desire all this drama to be irrelevant, we've made a special option available that allows to poll files for changes periodically instead of relying on notify mechanism. It is very slow and triggers traffic on network FS during the poll, but it's the only way around. Set `HARAKA_CONFIG_SLOWWATCH` env var to desired poll interval (> 10 recommended) and you'll have your config files reloaded (in > 15 secs) after the file update.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very verbose way to say, "I didn't like my manual implementation of rdist. Here's a slow alternative to fs.watch() for platforms and/or filesystems where fs.watch is not supported."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, it just details the reasoning.

2 changes: 1 addition & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class Config {
}

module_config (defaults_path, overrides_path) {
const cfg = new Config(path.join(defaults_path, 'config'), true);
const cfg = new Config(process.env.HARAKA_CONFIG_CUSTOM_DIR || path.join(defaults_path, 'config'), true);
if (overrides_path) {
cfg.overrides_path = path.join(overrides_path, 'config');
}
Expand Down
46 changes: 44 additions & 2 deletions configfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@ class cfreader {
is_truth: /^(?:true|yes|ok|enabled|on|1)$/i,
is_array: /(.+)\[\]$/,
}

let watch_interval = process.env.HARAKA_CONFIG_SLOWWATCH
if (watch_interval) console.error(`configfile.js: Requested to use SLOW config watchers with POLL interval ${watch_interval} secs`)
}

get_path_to_config_dir () {
if (process.env.HARAKA_CONFIG_CUSTOM_DIR) {
this.config_path = process.env.HARAKA_CONFIG_CUSTOM_DIR
return
}

if (process.env.HARAKA) {
// console.log(`process.env.HARAKA: ${process.env.HARAKA}`);
this.config_path = path.join(process.env.HARAKA, 'config');
Expand Down Expand Up @@ -103,6 +111,25 @@ class cfreader {
}
}

on_slowwatch_event (name, type, options, cb) {
return (fstat_cur, fstat_prev) => {
// skipping initial missing file watch reload
if (fstat_prev.mtimeMs === 0 && fstat_cur.mtimeMs === 0) return

console.log(`WATCH EVENT: ${fstat_prev.mtime} -> ${fstat_cur.mtime}, will trigger reload in 5secs`)

if (this._sedation_timers[name]) {
clearTimeout(this._sedation_timers[name]);
}
this._sedation_timers[name] = setTimeout(() => {
console.log(`Reloading file: ${name}`);
this.load_config(name, type, options);
delete this._sedation_timers[name];
if (typeof cb === 'function') cb();
}, 5 * 1000);
}
}

watch_dir () {
// NOTE: Has OS platform limitations:
// https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener
Expand Down Expand Up @@ -155,6 +182,14 @@ class cfreader {
}
}

slowwatch_file (name, type, cb, options) {
if (this._watchers[name] || (options && options.no_watch)) return;

this._watchers[name] = fs.watchFile(name,
{ persistent: false, interval: process.env.HARAKA_CONFIG_SLOWWATCH * 1000 },
this.on_slowwatch_event(name, type, options, cb))
}

get_cache_key (name, options) {

// Ignore options etc. if this is an overriden value
Expand Down Expand Up @@ -198,6 +233,12 @@ class cfreader {
const result = this.load_config(name, type, options);
if (!this.watch_files) return result;

if (process.env.HARAKA_CONFIG_SLOWWATCH) {
this.slowwatch_file(name, type, cb, options)

return result
}

// We can watch the directory on these platforms which
// allows us to notice when files are newly created.
switch (process.platform) {
Expand Down Expand Up @@ -284,10 +325,12 @@ class cfreader {
}

let cfrType = this.get_filetype_reader(type);
const cache_key = this.get_cache_key(name, options);

if (!fs.existsSync(name)) {
if (!/\.h?json$/.test(name)) {
return cfrType.empty(options, type);
// console.error(`ENOENT ${name}`)
return this._config_cache[cache_key] = cfrType.empty(options, type)
}

const yaml_name = name.replace(/\.h?json$/, '.yaml');
Expand All @@ -299,7 +342,6 @@ class cfreader {
cfrType = this.get_filetype_reader(type);
}

const cache_key = this.get_cache_key(name, options);
try {
switch (type) {
case 'ini':
Expand Down