Modular Diplonat (with the option to disable useless modules) #8
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Blocks
#7 Make Diplonat more modular (breaking changes)}
Deuxfleurs/diplonat
Reference: Deuxfleurs/diplonat#8
Loading…
Reference in a new issue
No description provided.
Delete branch "adrien/diplonat:feature/modular-config"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #7. I just have one problem, maybe you can help:
I modified the Diplonat struct (in
src/diplonat.rs
) to holdfirewall: Option<FirewallActor>
instead offirewall: FirewallActor
(same forIgdActor
). Now we can't so easily do:I don't understand Tokio (nor Rust) enough to find an easy fix.
Can you give it a look? The problematic function is in
src/diplonat.rs:47
. I added some comments.18122d87c9
to76fe63791b
My issue with
try_join!
is resolved thanks to @LX :)The PR should be good to go!
I want integration tests, though T_T
WIP: modular Diplonat (with the option to disable useless modules)to Modular Diplonat (with the option to disable useless modules)0651a7d185
to195aec2cfe
Do you use some code formatting tool?
It makes harder to review the PR :s
A suggestion:
It could be interesting also to document this code formatting tool in the README and maybe explain how contributors can run it in a pre-commit git hook!
Thanks :)
Hadn't seen the mixed indents in e.g.
src/config/runtime.rs
. Indeed, I need to setup some tooling to ensure good code quality---for myself and the others!Done. See PR#10.
Thanks for your PR, I better understand what you want to do know.
I think we could still improve your code to make it shorter and clearer,
please read the comments for the details. As a general manner:
unwrap
orexpect
in your code if you return aResult
,there is no need to panicate if your function promises to return errors!
ask yourself if you can not rewrite your code with a
match
constructors, this is bad.
try_join!
fails to communicate that we want to launch onlythe initialized modules, we have better options
Overall, I think these remarks are only minors compared to all the code you wrote, so I hope that you will not be discouraged by my requests and that we will be able to merge great code soon :)
@ -46,3 +49,2 @@
let consul = RuntimeConfigConsul::new(opts.consul.clone())?;
let firewall = RuntimeConfigFirewall::new(opts.base.clone())?;
let igd = RuntimeConfigIgd::new(opts.base.clone())?;
let acme = RuntimeConfigAcme::new(opts.acme.clone())?;
Not related to this PR, but do we really need to clone the configuration here, it is strange because I do not think we modify it.
@ -59,0 +64,4 @@
pub(super) fn new(opts: ConfigOptsConsul) -> Result<Self> {
let node_name = opts
.node_name
.expect("'DIPLONAT_CONSUL_NODE_NAME' is required");
cf my following comment on
DIPLONAT_ACME_EMAIL
@ -67,1 +80,3 @@
);
let email = opts
.email
.expect("'DIPLONAT_ACME_EMAIL' is required if ACME is enabled");
I know this is not linked to this specific pull request but when your function returns a Result, there is no reason to panic with expect.
Instead, replace it with:
Please do it for the whole file :)
.context(...)
is not a method ofOption
.There's sense in doing so: when a parameter is invalid (like here), the method should panic or return an Err(..).
I'm still modifying the calls to return errors instead of panicking straight away. Using
match
for better clarity ;)You can convert an Option to an Error with
ok_or
: https://doc.rust-lang.org/std/option/enum.Option.html#method.ok_orBut we need an Error to map to the None. It is a bit challenging as it will be our first error in Diplonat, previously we were relaying only on errors generated by our dependencies.
For now, let's keep it simple, our error library provides us a simple macro to create an error from a string. Just a simple example:
So you can write:
For your information,
context
allows to enrich an existing error with some context. Indeed, here it is not appropriate as we have no error at all. But in the case where your library throw a cryptic error, you can enrich this error with some context in your program and your users will thank you many times later.You made the following point:
First, you're right, when a parameter is invalid, we must handle this as an error. It was what I was suggesting to you in my first comment but I thought the return type of
opts.email
would be aResult
and not anOption
.Second, we are writing Rust and we are in a library: in this situation, it is not acceptable to panic. As there is no clean way to recover from a panic, we terminate the program from a library without giving the program a chance to recover from the error. Even if we are not a "standalone library" and are writing the main program, we might change our mind the future. We should
panic!
only when there is no option. To illustrate my point, we could think that later, we would allow our main program to reload its configuration and retry the initialization if one actor fails.Third, I am not sure we discussed it but
?
is syntaxic sugar for:So in fact, what I suggest is that we return from the function with an Error if this parameter has not be provided.
Here's my new RuntimeConfigAcme implementation in full (in commit
f5ac36e
). Do you find it better?We do return an error instead of panicking. But I may have overdone it with using match, but I didn't find any nice shorthand in
Option
methods (I checkedok_or
). At least the intent is clear!PS: Quite puzzled by the fact that Rust allows putting a
return
inside amatch
arm, which should be an expression returning a String (in this case). Welp, gotta get used to it!Sorry, I was not clear enough ><
About handling error, I want to avoid
expect
andunwrap
because they make our program panic, ie. the program is killed.But
unwrap_or
is totally fine as it does not make the program panic.And when the case is simple enough we can use one of these keywords:
unwrap_or
onResult
orok_or
on Options instead of amatch
, it is totally ok.So, I don't think you should replace your
unwrap_or
with amatch
inf5ac36e
.I have written a small example on how to use
ok_or
to convert anOption
into aResult
and checked that it compiles here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5bc6046485bab0cd4ae694b94d2ba1dbSee the code without leaving gitea
So, you may ask why I was advocating for
match
?Because in
fw_actors.rs
you were using againunwrap()
that can cause a panic in the program and we can't replace it by these simple keywords that areok_or
,unwrap_or
, etc.To sum up, I think we can define the following rules:
unwrap()
orexpect()
(yes, never is too brutal, but it should be an exception)I hope that this time I will be more clear.
I know that when we start coding together, it takes some times to align our minds but once it will be done, it will be easier :)
@ -18,0 +17,4 @@
let config = ConfigOpts::from_env()?;
println!("{:#?}", config);
let consul_actor = ConsulActor::new(config.consul);
Now that our actors are modular, I think that this function fails to convey the real meaning of what we want to do.
IMHO, I think we want to:
With the current code, I see many problems.
((1)) I have a feeling that we are duplicating logic between the "runtime config" and our actor initialization. Currently, the constructor of the actor is only:
To fix this problem, I see 2 options:
((2)) The current
try_join!
fails to convey the information that we run only the initialized actors. I think that, independently of the choice you made for 1, you should create a vector of initialized actors that you will populate and then run. You will replacetry_join!
bytry_join_all
Tip: start by solving problem number 2, it will help you solve problem number 1
@ -42,3 +46,2 @@
self.consul.listen(),
self.igd.listen(),
self.firewall.listen()
async {
This is not very idiomatic, please refer to my previous comment to know how to improve it.
I died. I won't touch that, I don't have a good enough understanding of Rust and this goes deep into ownership, lifetimes and (async) traits.
I think we won't go far on our actor model without writing some minimal actor library, or using Actix. I did try to write a simple generic Actor trait, but did not succeed once.
Send help :O
@ -26,0 +28,4 @@
if config.is_none() {
return Ok(None)
}
let config = config.unwrap();
No. Never use unwrap.
Your function should be written with a match:
As a general rule, in many places in your code, you could better communicate your intent with a
match
@ -28,2 +27,3 @@
rxp: &watch::Receiver<messages::PublicExposedPorts>,
) -> Result<Self> {
) -> Result<Option<Self>> {
if config.is_none() {
Same as my previous comment
Old, unmaintained. History Changed. Bye bye
Pull request closed