-
Notifications
You must be signed in to change notification settings - Fork 0
Conversation
e156811
to
48f550a
Compare
@@ -660,6 +662,7 @@ impl Service { | |||
} | |||
|
|||
/// Write service files from gossip data to disk. | |||
/// For the location, check `svc_file_path()`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't sure how to create a symlink for this. Also, the function is private, so I'm not sure if there's a point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is a note to yourself, because I don't understand what it is about. Not enough context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The svc_file_path
function could be rendered as a link, since ///
comments are Markdown. I just don't know what the target should be, as it seems to be relative to something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed this.
NB: The config is not currently reloaded, so this doesn't work yet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, I had some nits.
components/sup/src/manager/mod.rs
Outdated
fs_cfg: Arc::new(fs_cfg), | ||
organization: cfg.organization, | ||
service_states: HashMap::new(), | ||
sys: Arc::new(sys), | ||
peer_watcher: peer_watcher, | ||
user_config_watcher: UserConfigWatcher::new(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you changed the order of members in Manager
struct, it would be good to reorder the initializations here too.
@@ -660,6 +662,7 @@ impl Service { | |||
} | |||
|
|||
/// Write service files from gossip data to disk. | |||
/// For the location, check `svc_file_path()`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is a note to yourself, because I don't understand what it is about. Not enough context.
|
||
|
||
pub struct UserConfigWatcher { | ||
// TODO use a HashSet? we don't really need the boolean: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment does not seem to be true - reset_events_for
sets the boolean to false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is a note to myself saying that we could change the implementation. True/false values can be represented by presence/absence in a set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried using a HashSet
and turns out it's more complicated than expected (if we put a HashSet
in an Arc
, that HashSet
is immutable, unless I use mutexes), so not worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you would need to do something similar to what some code in butterfly does - Arc<RwLock<HashSet<Whatever>>>
. Anyway, I think that current solution is fine as well.
); | ||
let callbacks = UserConfigCallbacks { have_events: have_events }; | ||
let mut file_watcher = | ||
default_file_watcher(&path, callbacks).expect("creating user-config watcher"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using expect
, I'd use outputln!
macro to report an error and maybe quit the thread. I'm not sure if we should panic in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I've committed a change.
File::create(&file_path).expect("creating file"); | ||
|
||
// Allow the watcher to notice that a file was created. | ||
thread::sleep(Duration::from_millis(100)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why sleeping instead of wait_for_events
?
use hcore::service::ServiceGroup; | ||
use manager::service::Service; | ||
|
||
static LOGKEY: &'static str = "UCM"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding this log key to the documentation here - www/source/docs/reference/log-keys.html.md
.
48f550a
to
0086935
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another two things I noticed.
name: String::from("foo"), | ||
path: TempDir::new("user-config-watcher") | ||
.expect("creating temp dir") | ||
.into_path(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One note about this - when you use the into_path()
function, you also take over the responsibility of removing the created temporary directory. Which you probably don't do, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, fixed.
// This trait exists to ease the testing of functions that receive a Service. Creating Services | ||
// requires a lot of ceremony, so we work around this with this trait. | ||
pub trait Serviceable { | ||
fn name(&self) -> &String; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning references makes the trait less flexible, because you need an existing, non-temporary thing in memory to return its address (like a member of a struct that implements this trait). In other words - you can't compute the return value. Not sure if it is really important, but in general I would just return plain values. Of course the downside of returning values is that you need to clone the stuff, but OTOH this won't likely be a bottleneck ever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My rationale here is that the name is always a reference to its containing struct.
Isn't it enough for users of this trait to be able to call name.clone()
if they want to own the String
?
Can you show an example of where the current implementation creates issues? As I'm not sure I understand your comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no problems with current implementation. It was just a general note about returning references in trait functions. So in this case it's likely ok, but in general I would avoid it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let me know if you feel strongly about changing this, otherwise I'm gonna leave it as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, leave it as is, please. If somebody else points out this thing, then you can change it.
2d091fb
to
4b80116
Compare
5f040da
to
92f71aa
Compare
50409ed
to
9351a44
Compare
@krnowak addressed your comments, but I guess this will just wait until your PR gets merged. I also rebased on yours. I will clean up the commit messages next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had two questions, otherwise looks good.
let (reload, reconfigure) = { | ||
let ctx = self.render_context(census_ring); | ||
let reload = self.compile_hooks(&ctx); | ||
let reload = self.compile_hooks(&ctx) || self.user_config_updated; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this || self.user_config_updated
be moved one line below, to the let reconfigure
line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the needs_reconfiguration
flag is only used to trigger a reconfigure
hook, which may not be defined. The reload
flag is actually going to trigger a service reload, which is what we need here.
Self { states: HashMap::new() } | ||
} | ||
|
||
pub fn add<T: Serviceable>(&mut self, service: &T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an add
function. Do we need a remove
function then?
9351a44
to
eac986e
Compare
The PR is in a weird state right now. It shows commit that are not in the PR's branch. What happened is that the base branch got merged into master, and after that happened, I rebased my branch on master. I then changed the PR to use master as the base branch, but now it shows commits here that shouldn't be in this branch (and aren't there according to Also, please don't be too picky about commit messages (and the commits themselves), as I am still testing this PR out. |
0f04074
to
1a5b686
Compare
The UserConfigWatcher watches each service's user.toml file. It reacts on file creation, deletion and change, triggering a service reload and configuration. If a service has hooks defined, those are executed, otherwise the default actions are performed instead.
Now that there are multiple watcher, it makes sense to make the SpecWatcher field more descriptive
It's used in several places, so a constant makes sense.
The comment was introduced in a9c98dd and was incorrect back then as well. In the meantime, the function has been changed and now doesn't return any value.
The `cfg_updated` variable is used to determine whether to run the following block, in which we rebuild the configuration. We need this to happen if the user-config watcher has detected changes to the user-config file.
After we have performed all the actions required on detecting that the user-config has changed, we revert the field to `false`, so that we don't perform them again unless there are more changes.
The `load_user` function is in charge of loading the user-config file. We need this function to be called every time the user-config watcher has detected change to that file.
`user_config_updated` is set in the manager's `update_running_services_from_user_config_watcher` function, which receives events from the user-config watcher. This ensures that the service is then reloaded whenever the configuration is updated.
By adding another channel, we allow the User Config Watcher to communicate to any of the Workers that they should stop running. This allows the manager to cleanly remove a service.
Using a sync channel with buffer size of 1 allows us to mimic the behaviour of a boolean. We are only interested in the fact that there were events, not how many (since the message on this channel is always `()`).
We are not interested in the messages themselves, only in their presence
Signed-off-by: Krzesimir Nowak <krzesimir@kinvolk.io>
Before this refactor, the "Hooks recompiled" message was being printed twice: The first time, because the `user.toml` had been updated, which in turn meant the service got restarted. That counted as a change in the census ring, which in turn caused the hooks to be recompiled and the message to be printed again. With this refactor, we avoid running compile_hooks when the only thing that's changed is the user config. Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
Before this change, we were calling the `load_user` method every time there was a change, even if it wasn't unrelated to user config. Now, we only do it if the `user_config_updated` flag is set. Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
This function mirrors `default_file_watcher`, but instead of calling create it calls `create_with_no_initial_event`. It is used to start a default file watcher, where the first event triggered by the file already being there isn't emitted. Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
Before this change, if the user.toml file was already present, the service would be restarted immediately after being booted, because the File Watcher would notice the file and emit an event. With this function, the File Watcher doesn't emit the event anymore. Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
3b31707
to
648d99f
Compare
@krnowak could you take a look again? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly fine, but there was one hairy piece of code I'd like to see if it can be "balder".
components/sup/src/manager/mod.rs
Outdated
// The problem we're trying to work around here by adding this block is that `write` | ||
// creates an immutable borrow on `self`, and `self.remove_service` needs `&mut self`. | ||
// The solution is to introduce the block to drop the borrow before the call to | ||
// `self.remove_service`, and use `mem::swap` to copy the services to a variable defined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To move, not copy. After swapping, the vector under self.services
lock will be empty.
let cfg_changed = cfg_updated_from_rumors || self.user_config_updated; | ||
let something_to_do = cfg_changed || census_ring.changed(); | ||
|
||
if something_to_do { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This became a bit hard to follow. Would pseudo-rust below be clearer? (cufr
= cfg_updated_from_rumors
, sucu
= self.user_config_updated
, crc
= census_ring.changed()
)
if sucu {
if let Err(e) = self.cfg.load_user(&self.pkg) {…}
}
let (cfg_changed, reload, reconfigure) = {
let ctx = …;
if cufr || crc {
(cufr || sucu, self.compile_hooks(&ctx) || sucu, self.compile_configuration(&ctx))
} else if sucu {
(true, true, self.compile_configuration(&ctx))
} else {
(false, self.needs_reload, self.needs_reconfiguration)
}
}
self.needs_reload = reload;
self.needs_reconfiguration = reconfigure;
self.user_config_updated = false;
/* return */ cfg_changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The something_to_do
variable would be gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the 3 value tuple even less clear tbh...
(cufr || sucu, self.compile_hooks(&ctx) || sucu, self.compile_configuration(&ctx))
what?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what this "what?" is about, but to be clear - I don't actually want to have variables like cufr
or sucu
. I wrote it like that to avoid typing in the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that even with more descriptive names, it's hard to parse logically. There's just too much happening on one line: returning a tuple, ORing two values, calling functions..
Not sure it's an improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to put everything in one line. You could very well have:
let config_changed = config_updated_from_rumors || self.user_config_updated;
let needs_reload = self.compile_hooks(&ctx) || self.user_config_updated;
let needs_reconfigure = self.compile_configuration(&ctx);
(config_changed, needs_reload, needs_reconfigure)
In general, what I wanted to propose is to simplify the flow of the code, instead of using nested if
s and match
es that are sometimes executed and sometimes not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the above implementation though, we're calling self.compile_hooks
even if we don't need to, which will lead to "Hooks recompiled" being printed over and over (this function gets executed on every tick).
I'm definitely open to simplifying the code but obviously it has to have the same behaviour.
The problem is that some of these functions have side effects so we can't just call them willy-nilly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The self.compile_hooks(&ctx)
should only be executed when either cfg_updated_from_rumors
is true or if census_ring.changed()
returns true. So if this happens everytime, then something is wrong.
stop_running: Sender<()>, | ||
} | ||
|
||
type ServiceName = String; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's nice.
Ok(()) | ||
} | ||
|
||
/// Removes a service from the User Config Watcher, thereby stopping the watcher thread. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add "eventually" - "thereby eventually stopping …"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why eventually? Do you mean that the operation is asynchronous?
If so, I think I can explain it more explicitly.
Ok(()) | ||
} | ||
|
||
/// Checks whether the watcher for the specified service has observed any events, thereby |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thereby is your new favourite word, eh? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a pretty cool word, you gotta admit that
/// consuming them. | ||
pub fn have_events_for<T: Serviceable>(&self, service: &T) -> bool { | ||
if let Some(state) = self.states.get(service.name()) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop this empty line?
0a1d210
to
ca15182
Compare
Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
The thread is currently not restarted. Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
This refactor aims at simplifying the flow of the code and reducing conditionals. Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
eda6fa1
to
a416de0
Compare
Closing in favor of habitat-sh#3898. |
This PR adds a watcher for the
user.toml
file.It's based on @krnowak's still-unmerged work on the file watcher.