Skip to content

Conversation

@notoriaga
Copy link
Collaborator

@notoriaga notoriaga commented Nov 23, 2021

Had a really hard time debugging the various settings issues. After spending so much time looking at the c code, I realized it wasn't actually that much to re-implement everything natively

Pros:

  • No unsafe/c code
  • All the methods support timeouts/manual cancellation
  • Somehow less code than the wrapper

Cons:

  • reinventing the wheel a bit

Overall seems to behave much better, I was able to bump the workers back up to 10 like the python version and haven't seen any problems yet

Using it here - swift-nav/swift-toolbox#249

@notoriaga notoriaga requested review from a team and silverjam November 23, 2021 22:25
@notoriaga notoriaga changed the title [WIP] riir riir 🦀 🦀 🦀 Nov 23, 2021
src/client.rs Outdated
Comment on lines 198 to 200
if fields.len() < 2 || fields[0] != group || fields[1] != name {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move this to a function it appears to be used similarly in write_setting_inner. Is this an anticipated mode? If so what settings is this behavior accounting for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah it is, unfortunately there is no sequence/session associated with messages like fileio. So if two write requests are made at the same time, we register two MsgSettingsWriteResp callbacks. So you might have callback A triggered when the response intended for B comes in. So the check is to make sure that it's the response for the proper message.

But its not 100% foolproof, like if you made two write requests for the same setting, you'd have no way to figure out which one was intended for which, at least as far as I can tell. This is why all the pub functions take &mut self even though we could get away with &self, so we can make sure only one action is performed at a time

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

And r.e. the refactor, was able to factor out the check, looks a little cleaner

Copy link
Contributor

@john-michaelburke john-michaelburke left a comment

Choose a reason for hiding this comment

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

This is great work. It is a shame you did not show the change diff in your hackathon presentation seems worthy of recognition.

I wonder if this would be worth setting up with the headless runner for a day or two.

@notoriaga
Copy link
Collaborator Author

I wonder if this would be worth setting up with the headless runner for a day or two.

Yeah I think that's a good idea. One thing is afaik the headless running won't actually call any of this api except for the first read_all() call, and then another one each time the connection drops/reconnects. I wonder if it's a good idea to have the runner randomly write a setting, randomly refresh, etc

@john-michaelburke
Copy link
Contributor

I wonder if this would be worth setting up with the headless runner for a day or two.

Yeah I think that's a good idea. One thing is afaik the headless running won't actually call any of this api except for the first read_all() call, and then another one each time the connection drops/reconnects. I wonder if it's a good idea to have the runner randomly write a setting, randomly refresh, etc

Yeah that would make sense to me. I could see an integration test suite of sorts that could run various test portfolios like this. Outside of manually validating I think running the headless runner may be sufficient for now until we do implement something like this.

Copy link
Contributor

@samvrlewis samvrlewis left a comment

Choose a reason for hiding this comment

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

This is awesome - I learnt some cool Rust techniques.

I wasn't super familiar with this library before this change, so apologies if some of my comments/questions aren't strictly related to this change.

fn try_from(msg: MsgSettingsReadResp) -> Result<Self, Self::Error> {
let fields = split_multipart(&msg.setting);
match fields.as_slice() {
[group, name] => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a valid response? The SBP spec says it should always be group/name/value?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fn try_from(msg: MsgSettingsReadByIndexResp) -> Result<Self, Self::Error> {
let fields = split_multipart(&msg.setting);
match fields.as_slice() {
[group, name, value, fmt_type] => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just checking - according to the spec fmt_type is optional, but with the way this is implemented I guess this means optional in that it won't necessarily be included in the response but the delimiting null characters will still be there?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah as far as I can tell it's still there, we just get an empty string

});
let (settings, errors) = (Mutex::new(Vec::new()), Mutex::new(Vec::new()));
let idx = AtomicU16::new(0);
thread::scope(|scope| {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is nice! 🦀

sbp::to_writer(&mut wtr, &msg).map_err(Into::into)
})
.unwrap();
assert!(now.elapsed().as_millis() >= 100);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a sleep here or something to make sure that 100ms has actually elapsed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

assert!(now.elapsed().as_millis() >= 100); should be checking that. It's not super obvious but scope(...) will block in place until all the threads spawned inside the closure have joined. So, client.read_setting_ctx(group, name, ctx); should block for 100ms and then scope will end, and the assert is checked

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh right, sorry, that makes total sense - I totally misunderstood the intention of this test.

let ctx = ctx.clone();
scope.spawn(move |_| loop {
let idx = idx.fetch_add(1, Ordering::SeqCst);
match this.read_by_index(idx, done_rx, &ctx) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Am I understanding correctly here that this means that the workers will likely read past the last index? I guess there's not a nice way around it with this approach given that there doesn't appear to be a way to query for the length of settings up front. I'm assuming that all the settings registry implementations should be able to handle this? 🙈

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah this is how the python version works as well, a way to get the length upfront would be very nice indeed

@@ -1,52 +0,0 @@
#ifndef LIBSETTINGS_WRAPPER_H
Copy link
Contributor

Choose a reason for hiding this comment

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

Think you should also be able to delete the submodule now? 🎉

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We are using the settings.yaml in the root of the repo unfortunately. We could ditch the idea of a default settings.yaml, and move it into the swift-toolbox repo, could be nice

src/client.rs Outdated
WriteSettingError::Timeout => write!(f, "request wasn't replied in time"),
WriteSettingError::Unknown => {
write!(f, "unknown settings write response")
[group, name, value] | [group, name, value, ..] => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would there be more than group, name, value out of interest?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point I don't that should happen, I'll try out removing it

@silverjam silverjam changed the title riir 🦀 🦀 🦀 riir 🦀 🦀 🦀 [CPP-405] Dec 1, 2021
@silverjam silverjam changed the title riir 🦀 🦀 🦀 [CPP-405] riir 🦀🦀🦀 [CPP-405] Dec 1, 2021
@notoriaga notoriaga merged commit acf5628 into main Dec 1, 2021
@notoriaga notoriaga deleted the steve/riir branch December 1, 2021 17:15
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.

4 participants