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

Monitor config file for changes and reload when needed #79

Merged
merged 2 commits into from
Jun 17, 2022

Conversation

alexsnaps
Copy link
Member

@alexsnaps alexsnaps commented Jun 15, 2022

See issue #74

What has changed

  • bafcb99 Adds the behavior to reload the limit file off disk when it changed and do the changes
  • 4394736 Returns proper errors when handling the limit file off disk

Testing it locally

  1. Build locally
    $ cargo build
  2. Start limitador server, using the example config
    $ LIMITS_FILE=limitador-server/examples/limits.yaml RUST_LOG=info ./target/debug/limitador-server
  3. Hit the HTTP end point to list limits at:
    http://localhost:8080/limits/test_namespace
  4. Change a value in the config file at limitador-server/examples/limits.yaml and save
  5. Reload the URL and the changes should be visible
  6. An invalid config file would trigger an error to be logged and be ignored (i.e. the old config remains active)

@alexsnaps alexsnaps mentioned this pull request Jun 15, 2022
4 tasks
@alexsnaps alexsnaps force-pushed the issue_74_monitor branch 5 times, most recently from 67a2c20 to cd3a161 Compare June 15, 2022 23:04
@alexsnaps alexsnaps marked this pull request as ready for review June 16, 2022 12:54
match parsed_limits {
Ok(limits) => {
match &self {
Self::Blocking(limiter) => limiter.configure_with(limits)?,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! the configure_with that was "reconciling" limits in the external storage, now it is a perfect match for the in-memory limits.

Copy link
Contributor

@eguzki eguzki left a comment

Choose a reason for hiding this comment

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

owesome!

@@ -219,6 +249,40 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
};

let _watcher = if let Ok(limits_file_path) = env::var(LIMITS_FILE_ENV) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nothing to be done with the watcher? maybe register a signal to stop watching?

Copy link
Member Author

@alexsnaps alexsnaps Jun 17, 2022

Choose a reason for hiding this comment

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

Well that's what this does… RAII… i.e. we keep it around, and things get cleaned up upon drop(), e.g. in the case of linux, that'd be here.

.drop() is automatically invoked when the thing gets out of scope and can safely be dropped. Not keeping it around, it be dropped immediately…

Copy link
Member Author

@alexsnaps alexsnaps Jun 17, 2022

Choose a reason for hiding this comment

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

btw, this is very much like deferal as you'd know it from golang… only it's automatic so that you cannot forget to tell it to release the resources!

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, let me verify if I understood.

When main() function run, the execution point is blocked at

run_http_server(&http_api_address, rate_limiter.clone()).await?;

When the process gets killed (with a signal), the execution continues in main returning Ok(()). Then, _watcher which is an Option holding the watcher, gets out of scope and dropped. Notify lib will clean up at that moment.

is that right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes that's exactly it. Or to be real pedantic, while you hit the return, the function doesn't return until .drop() has itself returned on the Option (that if it owns the Watcher eventually delegates to its drop())…

The doc might provide more clarity than my explanation here… otherwise (and I recommend it in all cases) Rust for Rustaceans is excellent, and the free first chapter has the part on Drop and drop order on page 9!

process::exit(1)
}

let limiter = Arc::clone(&rate_limiter);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this is the same as

let limiter = rate_limiter.clone();

Copy link
Member Author

Choose a reason for hiding this comment

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

As per the documentation, it is indeed:

// The two syntaxes below are equivalent.
let a = foo.clone();
let b = Arc::clone(&foo);

Dunno why I used one over the other… which ever you prefer ¯_(ツ)_/¯

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 thanks for the doc ref

@eguzki
Copy link
Contributor

eguzki commented Jun 17, 2022

Two more questions:

  • The watcher is waiting for EventKind::Modify events. What happens when the process starts? The file is already there but not changed. Does the modify even trigger?
  • If I do not get it wrong, the LIMITS_FILE_ENV env var is optional, and if it does not exist, the watcher will not start. Effectively, the limits will be empty forever (forever is the lifetime of the process). I wonder if we need to enforce the existence of the env var and if not, exit the process with an error.

@alexsnaps
Copy link
Member Author

alexsnaps commented Jun 17, 2022

Two more questions:

* The watcher is waiting for `EventKind::Modify` events. What happens when the process starts? The file is already there but not changed. Does the modify even trigger?

No the event triggers only when observed.

* If I do not get it wrong, the `LIMITS_FILE_ENV` env var is optional, and if it does not exist, the watcher will not start. Effectively, the limits will be empty forever (forever is the lifetime of the process). I wonder if we need to enforce the existence of the env var and if not, exit the process with an error.

That's what the Option is for. If you did not provide a LIMITS_FILE env var then yes, no watcher… but most importantly you effectively have a useless limitador instance 🤔 So I think you might be right there!

Do you want to do this as part of this PR? I have one question about this tho: should we have a default? If so which?
And now I really start hating the idea of a mandatory env variable and would much rather provide this as an argument to the process… wdyt?

I'd create another issue & PR for this, with #78 being merged… this is the behavior you'd get running HEAD of main already…

@eguzki
Copy link
Contributor

eguzki commented Jun 17, 2022

Do you want to do this as part of this PR? I have one question about this tho: should we have a default? If so which?
And now I really start hating the idea of a mandatory env variable and would much rather provide this as an argument to the process… wdyt?

I would say a different PR.

For the remaining questions: I agree that and env var should always be optional. The main question is, do we want to have this limits file path as a required input to the process? If yes, then I would go to command line arg. The libraries to manage command line args usually have clear mechanisms to flag some command line arg as required. It could even be a positional param instead of a optional param (e.g. --limits-file).
On the other hand, if limitador defines a default file as the source of limits (for example /opt/limitador/etc/limits.yaml), then I really do not care about being via env var or optional param (e.g. --limits-file) the mechanism to override the default one.

@alexsnaps
Copy link
Member Author

Looks like we have a similar view on this :)
Created #83 to track this… i.e. make a decision and impl. it.

@alexsnaps alexsnaps merged commit 5b7db58 into main Jun 17, 2022
@alexsnaps alexsnaps deleted the issue_74_monitor branch June 17, 2022 13:43
@alexsnaps alexsnaps added this to the Limitador Server v0.6 milestone Jun 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants