I am just getting started with libp2p, rust-libp2p specifically, and I am trying to figure out how to use the Kademlia distributed hash table for arbitrary key-value store, not for peer discovery. I am very closely following the example here and it is working very nicely for me. I understand how
Transports are built and how custom
Behaviours are implemented. I was also able to create a
Swarm just like in the example, and I understand that. However, there is one aspect of all of this that I am slightly confused about…
Starting on line 153 is where I start to get confused. My main point of confusion is: Why does this code need to be in a future, and what is the point of the second
Why does this code need to be run in a future?
So, I tried removing the future entirely and just making a loop that handles each line from stdin, but that didn’t work at all. It appears that the
handle_input_line wasn’t working at all, and no data was stored or retrieved from the swarm’s Kad DHT. Why didn’t that work? Isn’t it the same thing as reading a line from stdin and then handling that line? I don’t get why the same code magically works when it’s being run inside a future. Also,
I don’t see the point of the second
It doesn’t look like the second loop does anything besides just print out the listening address… am I correct? Does this second
loop block actually do anything crucial?
I’ve done a lot of experimenting, like removing the second loop and changing around how data is read from stdin with a hope of trying to understand the last part of this (otherwise really helpful) example code. But I still don’t get why the code works in a future.
So, are there any resources that you can point me to to help me understand this problem of mine?
TL;DR: Why does reading from stdin need to be in a future (example code in link above)
I think having a read over the async book: https://rust-lang.github.io/async-book/ and understanding rust futures may help out a bit here.
Let me start with “why does these need to be in a future?”
In rust, futures need to be poll’d. This means run on an executor or something that can drive the future to completion. You can make a future out of sub-future and build like a tree of futures, but the root future eventually needs to be driven by an executor. This is the same for
What is happening in the example, is that you are making a big future, composed of two smaller futures (reading from the stdin and driving the swarm).
stdin is a future. You can poll it via the
try_poll_next_unpin() which will return
Poll::Pending when there is no input. The loop reads all lines that are ready to be read and when there are none left and the future has nothing to return, the loop is exited.
Note again that we need an executor to poll this future. This is why it is run inside the
future::poll_fn() is creating a big future of the smaller two futures (
swarm) which will only ever complete when the swarm has finished (see line 146).
As for your second question, 2. - This is driving the entire libp2p stack. The swarm implements
Stream and by polling it, you drive it to do work until it outputs events.
Notice that on line 145, it will print an event that gets emitted. Polling this stream will drive all the protocols and connections and when ready output swarm events.
When there is nothing to process, the task will idle and swarm will output
Poll::Pending. In this instance, we take the opportunity to print out any listening addresses if we have not already done so, before returning
Poll::Pending to the big future which puts the task “to sleep” until it is awoken again and needs to be processed.
swarm creates a big future. It doesn’t do anything until you drive it with an executor.