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

docs/coding-guidelines: Add document #2780

Merged
merged 14 commits into from Aug 17, 2022
Merged

Conversation

mxinden
Copy link
Member

@mxinden mxinden commented Jul 28, 2022

Description

Add document outlining a set of coding guidelines followed and to be followed across the rust-libp2p code base.

My goal is not to impose these guidelines onto anyone, nor to be dogmatic about them. Instead I would like to discuss their value, eventually establishing a shared mindset, helping newcomers getting started faster.

For now, this is a draft only. Lots of the wording still need polishing. Suggestions / feedback / disagreement is welcome anytime.

Links to any relevant issues

This might be a helpful resource for discussions like #2622 (comment) in the future.

Open Questions

Change checklist

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • A changelog entry has been made in the appropriate crates

Add document outlining a set of coding guidelines followed and to be
followed across the rust-libp2p code base.
Copy link
Contributor

@thomaseizinger thomaseizinger left a comment

Choose a reason for hiding this comment

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

Excellent document, lots of good knowledge here :)

Left some comments and ignored typos for now, we can deal with those once we settle on the content.

Comment on lines +27 to +28
If you sqint, rust-libp2p is just a big hierarchy of [state
machines](https://en.wikipedia.org/wiki/Finite-state_machine) where parents pass
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I am a fan of using a "one-line per sentence" rule for version-controlled markdown documents.

Git works on a line-by-line basis and sentences in natural language tend to change as a whole too. Using one sentence per line makes it really easy to use GitHub's suggestions to collaborate on documents like this and diffs are more meaningful too.

Just an idea :)

Copy link
Contributor

Choose a reason for hiding this comment

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

But I have to admit I am also in the "use an editor with word wrap enabled"-camp :D

Copy link
Contributor

Choose a reason for hiding this comment

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

@mxinden Any comment on this? We don't have to resolve it here. Should I open a separate discussion?

Copy link
Member Author

Choose a reason for hiding this comment

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

Like this, correct @thomaseizinger? mxinden@ecf86c8

With GitHubs diff highlighting showing what within a line has changed, I don't think there is a great benefit for it. As a downside, I think it adds complexity, e.g. having to explain this to newcomers.

Just an idea :)

I don't feel strongly about it. It seems like you don't feel strongly about it either. Thus I will proceed without the change.

That said, feel free to propose a new pull request with the commit above.


But I have to admit I am also in the "use an editor with word wrap enabled"-camp :D

Same here. I even stopped truncating my emails to 80 characters 🤯

Copy link
Contributor

Choose a reason for hiding this comment

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

If I get to choose, I'd pick one sentence per line.

We don't have to enforce it though (I am yet to find a tool that reliably does).

Are you happy with doing it on a personal preference basis?

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you happy with doing it on a personal preference basis?

For markdown files on rust-libp2p? Yes, that is fine by me.

docs/coding-guidelines.md Outdated Show resolved Hide resolved
docs/coding-guidelines.md Show resolved Hide resolved
Comment on lines 108 to 110
match self.child_2.poll(cx) {
// As above.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is important to mention that using continue in here will go back to child1 which could make progress again.

There is a subtle difference between looping over the entire thing and exhausting each child until it returns Pending. We want the former, not the latter.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, though not sure how to put it into words.

There is this line already:

        // or `continue`, thus polling `child_1` again. `child_1` can potentially make more progress:
        continue

Could you make a suggestion @thomaseizinger ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest to just fill in the match here too and make a comment next to the continue of the Poll::Pending branch.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd suggest to just fill in the match here too

As in the same as the above? What would be the benefit of that? Isn't that just duplicating the same comments once more?

Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't duplicate the entire explanation but perhaps have a complete example of the control flow and make one comment at the bottom that highlights that we always continue to go back to the top. Even though it should be clear from the written text, I find that seeing the entire control flow is better.

Maybe also build in the case where the event from child2 is dispatched to child1?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added ed4d633. Let me know what you think.

docs/coding-guidelines.md Outdated Show resolved Hide resolved
Comment on lines +172 to +173
When using channels (e.g. `futures::channel::mpsc` or `std::sync::mpsc`)
always use the bounded variant, never use the unbounded variant. When using a
Copy link
Contributor

Choose a reason for hiding this comment

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

We should use clippy's mechanism of banning these functions!

https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea. Though I am failing to have clippy pick up my Clippy.toml when I save it in the root of the repository.

disallowed-methods = [
  { path = "futures::sync::mpsc::channel", reason = "does not enforce backpressure" },
]

@thomaseizinger does this work for you?

Copy link
Contributor

Choose a reason for hiding this comment

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

It reliably works for me after a cargo clean.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will be introduced with #2823.

@thomaseizinger thomaseizinger mentioned this pull request Jul 29, 2022
4 tasks
Copy link
Contributor

@melekes melekes left a comment

Choose a reason for hiding this comment

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

👍

Using hierarchical state machines is a deliberate choice throughout the
rust-libp2p code base. It makes reasoning about control and data flow easy and
works well with Rust's `Future` model. The archetecture pattern of
hierarchical state machines should be used wherever possible.
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there downsides to this design? If so, should we list them?

Copy link
Contributor

Choose a reason for hiding this comment

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

In my experience, the main downsides are:

  • It feels more verbose but I think it is not actually in reality when you would compare two solutions side by side.
  • The mix of control flow (loop, return, break, continue) in poll functions together with the asynchronous and thus decoupled communication via events can be very hard to understand.

Both are a form of complexity that we are trading for correctness and performance which seems typical in Rust land.

Copy link
Member Author

Choose a reason for hiding this comment

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

I documented the above in 0ef322a. @thomaseizinger I really liked your wording which I mostly adopted. Hope you don't mind.

@melekes in case you can think of other downsides, please comment and let's get them included in the discussion and the document.


// The child did not make progress. It has registered the waker for a
// later wake up. Proceed with the other childs.
Poll::Ready(_) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Poll::Ready(_) => {
Poll::Pending => {

?

Copy link
Member Author

Choose a reason for hiding this comment

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

🙏 2b40afa

@thomaseizinger
Copy link
Contributor

This blogpost might also go onto a further reading list: https://sans-io.readthedocs.io/how-to-sans-io.html

@mxinden
Copy link
Member Author

mxinden commented Aug 4, 2022

This blogpost might also go onto a further reading list: https://sans-io.readthedocs.io/how-to-sans-io.html

I am a great fan of this coding style. It makes testing so easy.

That said, I don't think we apply this coding style anywhere within the rust-libp2p code-base. E.g. a Transport like libp2p-dns wrapps an I/O source, multistream-select wraps an I/O source, a ConnectionHandler has direct access to an I/O source. Thus I am having difficulties relating it to these coding guidelines.

@thomaseizinger
Copy link
Contributor

This blogpost might also go onto a further reading list: sans-io.readthedocs.io/how-to-sans-io.html

I am a great fan of this coding style. It makes testing so easy.

That said, I don't think we apply this coding style anywhere within the rust-libp2p code-base. E.g. a Transport like libp2p-dns wrapps an I/O source, multistream-select wraps an I/O source, a ConnectionHandler has direct access to an I/O source. Thus I am having difficulties relating it to these coding guidelines.

Well, it does apply to some degree. NetworkBehaviour is basically a sans-IO state machine right? Swarm is passing events between the network and the behaviour with the minor exception that Substream has a boxed muxer inside that directly connects to the IO source. But given that we don't follow it fully, it is maybe not the best blog post to reference.

@mxinden mxinden marked this pull request as ready for review August 13, 2022 01:23
# Coding Guidelines

<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**
Copy link
Contributor

Choose a reason for hiding this comment

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

GitHub Markdown has a built-in ToC:

image

It is a bit hidden so I don't know we want to rely on people using it if they want a ToC.

Copy link
Member Author

Choose a reason for hiding this comment

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

Neat. Though as you say, quite hidden. I suggest we keep the additional ToC, though that is not a strong opinion.

machines](https://en.wikipedia.org/wiki/Finite-state_machine) where parents pass
events down to their children and children pass events up to their parents.

![Architecture](architecture.svg)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not familiar with planttext. Is it a service we can rely on? What if they prune their database?

Copy link
Contributor

Choose a reason for hiding this comment

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

I've been using it for years without issues.

There is no database, I am pretty sure the diagram source code is embedded in the URL in some form of encoding.

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes sense. I would argue though that there is benefit in being able to access the diagram and thus read the document without internet access.

docs/coding-guidelines.md Outdated Show resolved Hide resolved
Comment on lines +27 to +28
If you sqint, rust-libp2p is just a big hierarchy of [state
machines](https://en.wikipedia.org/wiki/Finite-state_machine) where parents pass
Copy link
Contributor

Choose a reason for hiding this comment

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

@mxinden Any comment on this? We don't have to resolve it here. Should I open a separate discussion?

Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
mxinden added a commit to mxinden/rust-libp2p that referenced this pull request Aug 17, 2022
When using channels (e.g. `futures::channel::mpsc` or `std::sync::mpsc`)
always use the bounded variant, never use the unbounded variant. When
using a bounded channel, a slow consumer eventually slows down a fast
producer once the channel bound is reached, ideally granting the slow
consumer more system resources e.g. CPU time, keeping queues small and
thus latencies low.  When using an unbounded channel a fast producer
continues being a fast producer, growing the channel buffer
indefinitely, increasing latency until the illusion of unboundedness
breaks and the system runs out of memory.

One may use an unbounded channel if one enforces backpressure through an
out-of-band mechanism, e.g. the consumer granting the producer
send-tokens through a side-channel.

See also
libp2p#2780 (comment)
mxinden added a commit to mxinden/rust-libp2p that referenced this pull request Aug 17, 2022
When using channels (e.g. `futures::channel::mpsc` or `std::sync::mpsc`)
always use the bounded variant, never use the unbounded variant. When
using a bounded channel, a slow consumer eventually slows down a fast
producer once the channel bound is reached, ideally granting the slow
consumer more system resources e.g. CPU time, keeping queues small and
thus latencies low.  When using an unbounded channel a fast producer
continues being a fast producer, growing the channel buffer
indefinitely, increasing latency until the illusion of unboundedness
breaks and the system runs out of memory.

One may use an unbounded channel if one enforces backpressure through an
out-of-band mechanism, e.g. the consumer granting the producer
send-tokens through a side-channel.

See also
libp2p#2780 (comment)
@mxinden mxinden merged commit 475289c into libp2p:master Aug 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.

None yet

3 participants