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

Refactor the crate to use stable futures #1328

Merged
merged 87 commits into from
Jan 6, 2020
Merged

Refactor the crate to use stable futures #1328

merged 87 commits into from
Jan 6, 2020

Conversation

tomaka
Copy link
Member

@tomaka tomaka commented Nov 29, 2019

Note: This PR should be merged using the "Merge" action of GitHub, in order to not lose history.

Rewrites lots of parts of the library to use stable futures rather than futures 0.1.
This unlocks the possibility to use async/await, and opens the door for further API refactorings later on.

There is one test failure right now, the "network simultaneous dial" test, which seems to be caused by the V1Lazy upgrade system. This needs to be investigated.

When it comes to reviewing, I don't know what everyone's opinion is. While many individual changes to the stable-futures branch have already been reviewed, unfortunately most of the changes made early on (notably in core and swarm haven't). I think most of the reviewing should be straight-forward, as most of the code is a 1:1 port of the old code.

A few things have changed:

  • ProtocolsHandler::poll doesn't return an Error anymore. Instead, ProtocolsHandlerEvent (the return value of poll) has a new Close variant, which corresponds to what an error represented before.
  • We're using async-std for networking, rather than tokio. This is for two reasons: this unties us from tokio (async-std futures can be polled with anything, while tokio networking futures can only be polled with tokio), and async-std was ready before tokio was.
  • All the traits that have a poll function (i.e. NetworkBehaviour, ProtocolsHandler, NodeHandler) now have a &mut Context parameter to reflect the changes in the Future trait.
  • The core::upgrade module was refactored. It now contains a few async methods that allow reading or writing a length-delimited message from a socket.

tomaka and others added 30 commits September 16, 2019 11:08
* Switch to stable futures

* Remove from_fn

* Fix secio

* Fix core --lib tests
* Upgrade libp2p-kad to stable futures

* Fix comment
* protocols/noise: Fix obvious future errors

* protocol/noise: Make Handshake methods independent functions

* protocols/noise: Abstract T and C for handshake

* protocols/noise: Replace FutureResult with Result

* protocols/noise: Introduce recv_identity stub

* protocols/noise: Implement recv_identity stub

* protocols/noise: Change NoiseConfig::Future from Handshake to Result

* protocols/noise: Adjust to new Poll syntax

* protocols/noise: Return early on state creation failure

* protocols/noise: Add bounds Async{Write,Read} to initiator / respoder

* protocols/noise: Add Protocol trait bound for C in rt functions

* protocols/noise: Do io operations on state.io instead of state

* protocols/noise: Have upgrade_xxx return a pinned future

* protocols/noise: Have NoiseOutput::poll_read self be mutable

* protocols/noise: Make recv_identity buffers mutable

* protocols/noise: Fix warnings

* protocols/noise: Replace NoiseOutput io::Read impl with AsyncRead

* protocols/noise: Replace NoiseOutput io::Write impl with AsyncWrite

* protocols/noise: Adjust tests to new futures

* protocols/noise: Don't use {AsyncRead,AsyncWrite,TryStream}*Ext* bound

* protocols/noise: Don't use async_closure feature

* protocols/noise: use futures::ready! macro

* protocols/noise: Make NoiseOutput AsyncRead return unsafe NopInitializer

The previous implementation of AsyncRead for NoiseOutput would operate
on uninitialized buffers, given that it properly returned the number of
bytest that were written to the buffer. With this patch the current
implementation operates on uninitialized buffers as well by returning an
Initializer::nop() in AsyncRead::initializer.

* protocols/noise: Remove resolved TODO questions

* protocols/noise: Remove 'this = self' comment

Given that `let mut this = &mut *self` is not specific to a pinned self,
but follows the dereference coercion [1] happening at compile time when
trying to mutably borrow two distinct struct fields, this patch removes
the code comment.

[1]
```rust
let x = &mut self.deref_mut().x;
let y = &mut self.deref_mut().y; // error

// ---

let mut this = self.deref_mut();
let x = &mut this.x;
let y = &mut this.y; // ok
```

* Remove redundant nested futures.

* protocols/noise/Cargo: Update to futures preview 0.3.0-alpha.18

* protocols/noise: Improve formatting

* protocols/noise: Return pinned future on authenticated noise upgrade

* protocols/noise: Specify Output of Future embedded in Handshake directly

* *: Ensure Noise handshake futures are Send

* Revert "*: Ensure Noise handshake futures are Send"

This reverts commit 555c2df.

* protocols/noise: Ensure NoiseConfig Future is Send

* protocols/noise: Use relative import path for {In,Out}boundUpgrade
Upgrade websocket transport to soketto 0.3.0.
* Configurable multistream-select protocol. Add V1Lazy variant. (#1245)

Make the multistream-select protocol (version) configurable
on transport upgrades as well as for individual substreams.

Add a "lazy" variant of multistream-select 1.0 that delays
sending of negotiation protocol frames as much as possible
but is only safe to use under additional assumptions that
go beyond what is required by the multistream-select v1
specification.

* Improve the code readability of the chat example (#1253)

* Add bridged chats (#1252)

* Try fix CI (#1261)

* Print Rust version on CI

* Don't print where not appropriate

* Change caching strategy

* Remove win32 build

* Remove win32 from list

* Update libsecp256k1 dep to 0.3.0 (#1258)

* Update libsecp256k1 dep to 0.3.0

* Sign now cannot fail

* Upgrade url and percent-encoding deps to 2.1.0 (#1267)

* Upgrade percent-encoding dep to 2.1.0

* Upgrade url dep to 2.1.0

* Fix more conflicts

* Revert CIPHERS set to null (#1273)
* Configurable multistream-select protocol. Add V1Lazy variant. (#1245)

Make the multistream-select protocol (version) configurable
on transport upgrades as well as for individual substreams.

Add a "lazy" variant of multistream-select 1.0 that delays
sending of negotiation protocol frames as much as possible
but is only safe to use under additional assumptions that
go beyond what is required by the multistream-select v1
specification.

* Improve the code readability of the chat example (#1253)

* Add bridged chats (#1252)

* Try fix CI (#1261)

* Print Rust version on CI

* Don't print where not appropriate

* Change caching strategy

* Remove win32 build

* Remove win32 from list

* Update libsecp256k1 dep to 0.3.0 (#1258)

* Update libsecp256k1 dep to 0.3.0

* Sign now cannot fail

* Upgrade url and percent-encoding deps to 2.1.0 (#1267)

* Upgrade percent-encoding dep to 2.1.0

* Upgrade url dep to 2.1.0

* Revert CIPHERS set to null (#1273)

* Update dependency versions (#1265)

* Update versions of many dependencies

* Bump version of rand

* Updates for changed APIs in rand, ring, and webpki

* Replace references to `snow::Session`

`Session` no longer exists in `snow` but the replacement is two structs `HandshakeState` and `TransportState`
Something will have to be done to harmonize `NoiseOutput.session`

* Add precise type for UnparsedPublicKey

* Update data structures/functions to match new snow's API

* Delete diff.diff

Remove accidentally committed diff file

* Remove commented lines in identity/rsa.rs

* Bump libsecp256k1 to 0.3.1

* Implement /plaintext/2.0.0 (#1236)

* WIP

* plaintext/2.0.0

* Refactor protobuf related issues to compatible with the spec

* Rename: new PlainTextConfig -> PlainText2Config

* Keep plaintext/1.0.0 as PlainText1Config

* Config contains pubkey

* Rename: proposition -> exchange

* Add PeerId to Exchange

* Check the validity of the remote's `Exchange`

* Tweak

* Delete unused import

* Add debug log

* Delete unused field: public_key_encoded

* Delete unused field: local

* Delete unused field: exchange_bytes

* The inner instance should not be public

* identity::Publickey::Rsa is not available on wasm

* Delete PeerId from Config as it should be generated from the pubkey

* Catch up for #1240

* Tweak

* Update protocols/plaintext/src/error.rs

Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>

* Update protocols/plaintext/src/handshake.rs

Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>

* Update protocols/plaintext/src/error.rs

Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>

* Update protocols/plaintext/src/error.rs

Co-Authored-By: Roman Borschel <romanb@users.noreply.github.com>

* Update protocols/plaintext/src/error.rs

Co-Authored-By: Roman Borschel <romanb@users.noreply.github.com>

* Rename: pubkey -> local_public_key

* Delete unused error

* Rename: PeerIdValidationFailed -> InvalidPeerId

* Fix: HandShake -> Handshake

* Use bytes insteadof Publickey to avoid code duplication

* Replace with ProtobufError

* Merge HandshakeContext<()> into HandshakeContext<Local>

* Improve the peer ID validation to simplify the handshake

* Propagate Remote to allow extracting the PeerId from the Remote

* Collapse the same kind of errors into the variant

* [noise]: `sodiumoxide 0.2.5` (#1276)

Fixes rustsec/advisory-db#192

* examples/ipfs-kad.rs: Remove outdated reference to `without_init` (#1280)

* CircleCI Test Fix (#1282)

* Disabling "Docker Layer Caching" because it breaks one of the circleci checks

* Bump to trigger CircleCI build

* unbump

* zeroize: Upgrade to v1.0 (#1284)

v1.0 final release is out. Release notes:

iqlusioninc/crates#279

* *: Consolidate protobuf scripts and update to rust-protobuf 2.8.1 (#1275)

* *: Consolidate protobuf generation scripts

* *: Update to rust-protobuf 2.8.1

* *: Mark protobuf generated modules with '_proto'

* examples: Add distributed key value store (#1281)

* examples: Add distributed key value store

This commit adds a basic distributed key value store supporting GET and
PUT commands using Kademlia and mDNS.

* examples/distributed-key-value-store: Fix typo

* Simple Warning Cleanup (#1278)

* Cleaning up warnings - removing unused `use`

* Cleaning up warnings - unused tuple value

* Cleaning up warnings - removing dead code

* Cleaning up warnings - fixing deprecated name

* Cleaning up warnings - removing dead code

* Revert "Cleaning up warnings - removing dead code"

This reverts commit f18a765.

* Enable the std feature of ring (#1289)
* *: Remove usage of custom buffer initialization usage

With version `0.3.0-alpha.19` the futures-preview crate makes the
`AsyncRead::initializer` API unstable.

In order to improve interoperability with e.g. both a library depending
on alpha.18 as well as a library depending on alpha.19 and in order for
rust-libp2p to become stable again, this commit removes all usages of
the unstable `initializer` API.

* protocols/noise: Remove NoiseOutput Asyncread initializer

* transports/tcp: Remove TcpTransStream AsyncRead initializer

* *: Remove version pinning of futures-preview to 0.3.0-alpha.18

With version 0.3.0-alpha.19 the futures-preview crate makes the
AsyncRead::initializer API unstable. Given that the previous commits
removed usage of the initializer API, the version pinning is not needed
any longer.
* Implement Debug for (ed25519|secp256k1)::(Keypair|SecretKey) (#1285)

* Fix possible arithmetic overflow in libp2p-kad. (#1291)

When the number of active queries exceeds the (internal)
JOBS_MAX_QUERIES limit, which is only supposed to bound
the number of concurrent queries relating to background
jobs, an arithmetic overflow occurs. This is fixed by
using saturating subtraction.

* protocols/plaintext: Add example on how to upgrade with PlainTextConfig1 (#1286)

* [mdns] - Support for long mDNS names (Bug #1232) (#1287)

* Dead code -- commenting out with a note referencing future implementation

* Adding "std" feature so that cargo can build in other directories (notably `misc/mdns`, so that I could run these tests)

* Permitting `PeerID` to be built from an `Identity` multihash

* The length limit for DNS labels is 63 characters, as per RFC1035

* Allocates the vector with capacity for the service name plus additional QNAME encoding bytes

* Added support for encoding/decoding peer IDs with an encoded length greater than 63 characters

* Removing "std" from ring features

Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>

* Retaining MAX_INLINE_KEY_LENGTH with comment about future usage

* `segment_peer_id` consumes `peer_id` ... plus an early return for IDs that don't need to be segmented

* Fixing logic

* Bump most dependencies (#1268)

* Bump most dependencies

This actually builds 😊.

* Bump all dependencies

Includes the excellent work of @rschulman in #1265.

* Remove use of ed25519-dalek fork

* Monomorphize more dependencies

* Add compatibility hack for rand

Cargo allows a crate to depend on multiple versions of another, but
`cargo-web` panics in that situation.  Use a wrapper crate to work
around the panic.

* Use @tomaka’s idea for using a newer `rand`

instead of my own ugly hack.

* Switch to Parity master

as its dependency-bumping PR has been merged.

* Update some depenendencies again

* Remove unwraps and `#[allow(deprecated)]`.

* Remove spurious changes to dependencies

Bumping minor or patch versions is not needed, and increases likelyhood
of merge conflicts.

* Remove some redundant Cargo.toml changes

* Replace a retry loop with an expect

`ed25519::SecretKey::from_bytes` will never fail for 32-byte inputs.

* Revert changes that don’t belong in this PR

* Remove using void to bypass ICE (#1295)

* Publish 0.13.0 (#1294)
* Update `rw-stream-sink` to futures-0.3.

* Update core, tcp, secio and mplex to futures-0.3.

On top of #1301
Update yamux to development version.

For the boxed `futures::stream::Stream` we have to decide if we require
a `Send` bound or not. Since some upgrades may produce outputs which are
`!Send` we offer both upgrade versions.
* Update libp2p-uds to futures 0.3

* Some clean-up
…1306)

* protocols/plaintext: Move to stable futures and use unsigned varints

The plaintext 2.0 specification requires to use unsigned varints for
frame length delimiting instead of fixed 4 byte integer frame length
delimiting. This commit aligns the implementation with the
specification.

* protocols/secio: Fix doc comment BytesMut -> Vec<u8>

* protocols/plaintext: Add quick check smoke test

* protocols/plaintext: Rework imports and log levels

* protocols/plaintext: Use BytesMut instead of Vec<u8>

* protocols/plaintext: Use BoxFuture
* misc/mdns/service: Use async std with stack pinned futures

* misc/mdns: Define mdns broadcast address as lazy static

* misc/mdns: Drop future before borrowing their arguments again

* misc/mdns: Send queries on query socket, not socket

* misc/mdns: Use poll_next_unpin on query interval stream

* misc/mdns: Ensure underlying task is woken up on next interval tick

* misc/mdns: Wrap match expression in scope to drop future early

* misc/mdns: Adjust 'discovery_ourselves' test

* misc/mdns: Make query interval fire instantly on first tick

This is an optimization only important for short lived use cases, e.g.
unit tests. Instead of waiting for 20 seconds at first, the query
interval fires right away and thereby the service makes progress
instantly.

* misc/mdns: Adjust MdnsService documentation tests

* misc/mdns: Do not drop UDP socket send and reicv futures

Libp2p-mdns uses the async-std crate for network io. This crate only
offers async send and receive functions. In order to use this in non
async/await functions one needs to keep the future returned by the crate
functions around across `poll` invocations.

The future returned by the crate functions references the io resource.
Thus one has to keep both the io resource as well as the future
referencing it. This results in a self-referencing struct which is not
possible to create with safe Rust.

Instead, by having `MdnsService::next` (former `MdnsService::poll`) take
ownership of `self`, the Rust async magic takes care of the above (See
code comments for more details).

As a (negative) side effect, given that `MdnsService::next` takes
ownership of `self`, there is nothing to bind the lifetime of the
returned `MdnsPacket` to. With no better solution in mind, this patch
makes `MdnsPacket` static, not referencing the `MdnsService` receive
buffer.

* misc/mdns: Fix code comments and remove *if Free* TODO

* misc/mdns: Minor refactorings

* misc/mdns: Build responses in behaviour.rs directly

* misc/mdns: Move response ttl duration to constant

* misc/mdns: Remove optimization todo comment

* misc/mdns: Add query interval test

* misc/mdns: Move packet parsing into MdnPacket impl

* misc/mdns: Don't have receiving packets starve the query interval

When we 'await' on receiving a packet on the udp socket without
receiving a single packet we starve the remaining logic of the mdns
service, in this case the logic triggered on the receive interval.

* misc/mdns: Add debug_assert to MaybeBusyMdnsService check

* misc/mdns: Implement Debug for MaybeBusyMdnsService

* misc/mdns: Make ownership note a normal comment, not a doc comment

* misc/mdns: Have discovered_peers return an iterator
Mostly mechanical. Creating a `CommonTransport` yields an
`io::Result<_>` now since creating the `DnsConfig` may fail with an
`io::Error` when creating the `ThreadPool`.

The `DnsConfig` `Transport` impl had to be changed slightly:

(a) PR [[1311]] requires some `Send` bounds.
(b) The async block had to be changed to work around lifetime inference
issues which resulted in an "one type is more general than the other"
error.

[1311]: #1311
@twittner
Copy link
Contributor

twittner commented Jan 2, 2020

Another one to do:

test identify::tests::periodic_id_works ... test identify::tests::periodic_id_works has been running for over 60 seconds

So the reason for this is that when the dialling swarm tries to connect to the address before the listener has bound to it, the connection refused error is eventually ignored. It will bubble up through the layers until finally reaching the swarm as a NetworkEvent::UnknownPeerDialError (which in itself is a misclassification as network.rs assumes in handle_reach_error that all other_reach_attempts are inbound). Then the behaviour callback will be invoked (i.e. inject_addr_reach_failure) which by default just ignores the error. Presumably, the NetworkBehaviour impl of Identify could map such errors to its own output event?

@tomaka
Copy link
Member Author

tomaka commented Jan 2, 2020

Presumably, the NetworkBehaviour impl of Identify could map such errors to its own output event?

Identify itself never opens any connection and passively operates on existing connections, so I don't think it makes sense to propagate a dialing error here.

@twittner
Copy link
Contributor

twittner commented Jan 2, 2020

Identify itself never opens any connection and passively operates on existing connections, so I don't think it makes sense to propagate a dialing error here.

Makes sense, but I am curious where such errors should be handled?

(On a related note, the Stream::Item of ExtendedSwarm is currently a Result<TBehaviour::OutEvent, io::Error>, but no io::Error is ever produced.)

swarm/src/protocols_handler/mod.rs Outdated Show resolved Hide resolved
swarm/src/protocols_handler/mod.rs Outdated Show resolved Hide resolved
tomaka and others added 2 commits January 3, 2020 11:22
Merge master in stable-futures
Co-Authored-By: Max Inden <mail@max-inden.de>
Copy link
Contributor

@Demi-Marie Demi-Marie left a comment

Choose a reason for hiding this comment

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

Looks good! I have a few comments, but that is all.

}
}
},
MaybeBusyMdnsService::Poisoned => panic!("Mdns poisoned"),
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 a very useful design pattern for Rust futures. Thanks!

MDNS_RESPONSE_TTL,
);
service.enqueue_response(resp.unwrap());
} else { debug_assert!(false); }
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens in release mode? It seems wrong to continue in an invalid state.

Copy link
Member

Choose a reason for hiding this comment

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

We could panic in release mode as well. But given that MDns is probably only a auxiliary component, I think panicing would be a bit exaggerated. In case you have suggestions how we could enforce MaybeBusyMdnsService to be Free at compile time, I am happy to do a follow up pull request.

MDNS_RESPONSE_TTL,
);
service.enqueue_response(resp);
} else { debug_assert!(false); }
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

misc/mdns/src/service.rs Outdated Show resolved Hide resolved
misc/mdns/src/service.rs Outdated Show resolved Hide resolved
core/Cargo.toml Outdated
parking_lot = "0.9.0"
protobuf = "2.8"
pin-project = "0.4.6"
protobuf = "= 2.8.1"
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we would do protobuf generation in build.rs. This is okay so long as we are not missing out on upstream security fixes.

sender: mpsc::Sender<Channel<Bytes>>,
channel_to_send: Option<Channel<Bytes>>,
channel_to_return: Option<Channel<Bytes>>,
sender: mpsc::Sender<Channel<Vec<u8>>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because of changes in the Sink trait

fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
// It is debatable whether we should poll the inner future first or the timer first.
// For example, if you start dialing with a timeout of 10 seconds, then after 15 seconds
// the dialing succeeds on the wire, then after 20 seconds you poll, then depending on
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 we should poll the inner future first.


// We start by importing the remote's public key into the WebCrypto world.
let public_key = {
// Note: contrary to what one might think, we shouldn't add the "deriveBits" usage.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's how the WebCrypto API is defined. Also, this is old code not relevant to this PR.


DnsConfig {
trace!("Created a DNS thread pool");
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we switch to trust-dns? Not now, obviously.

Co-Authored-By: Demi Obenour <48690212+DemiMarie-parity@users.noreply.github.com>
Copy link
Contributor

@romanb romanb left a comment

Choose a reason for hiding this comment

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

I skimmed most of it. Kudos to everyone involved in this PR, this really seems to have been a lot of work (and a lot of it chore).

@@ -58,6 +54,9 @@ where
}
}

impl<TInEvent, TOutEvent, THandler, TReachErr, THandlerErr, TUserData, TConnInfo, TPeerId> Unpin for
CollectionStream<TInEvent, TOutEvent, THandler, TReachErr, THandlerErr, TUserData, TConnInfo, TPeerId> { }
Copy link
Contributor

Choose a reason for hiding this comment

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

How come this explicit impl is necessary?

Copy link
Member Author

@tomaka tomaka Jan 6, 2020

Choose a reason for hiding this comment

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

Otherwise Unpin is implemented automatically only if all the fields are Unpin as well.
It is never a bad idea to implement Unpin for a type (provided you don't use unsafe). The reason why we do it for this type is because it implements the Stream trait, but we could theoretically implement Unpin for all the types we define.

/// Start sending an event to the node.
pub fn start_send_event(&mut self, event: TInEvent) -> StartSend<TInEvent, ()> {
/// Sends an event to the handler of the node.
pub fn send_event<'s: 'a>(&'s mut self, event: TInEvent) -> impl Future<Output = ()> + 's + 'a {
Copy link
Contributor

Choose a reason for hiding this comment

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

What does the use of both 's and 'a with 's: 'a accomplish?

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 think that this is a bug with Rust in general. I will try to make it compile by removing a bound, but the chances are high that it's not possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

This:

pub fn send_event<'s>(&'s mut self, event: TInEvent) -> impl Future<Output = ()> + 's {

Results in:

error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
    --> core/src/nodes/network.rs:1647:61
     |
1647 |     pub fn send_event<'s>(&'s mut self, event: TInEvent) -> impl Future<Output = ()> + 's {

And this:

pub fn send_event(&mut self, event: TInEvent) -> impl Future<Output = ()> + 'a {

Leads to:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
    --> core/src/nodes/network.rs:1649:34

Copy link
Contributor

Choose a reason for hiding this comment

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

But why not

    pub fn send_event(&'a mut self, event: TInEvent) -> impl Future<Output = ()> + 'a {

which compiles and expresses what I would expect (that the returned future cannot outlive self which contains references with lifetime 'a).

/// If no tokio executor is available, we move tasks to this list, and futures are polled on
/// the current thread instead.
local_spawns: Vec<Box<dyn Future<Item = (), Error = ()> + Send>>,
/// If no executor is available, we move tasks to this list, and futures are polled on the
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
/// If no executor is available, we move tasks to this list, and futures are polled on the
/// If no executor is available, we move tasks to this set, and futures are polled on the

task.pending = Some(AsyncSink::NotReady(msg))
match task.sender.start_send(msg) {
Ok(()) => {},
Err(ref err) if err.is_full() => {}, // TODO: somehow report to user?
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe at least a log statement meanwhile?

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'll add the logging, but note that this isn't supposed to happen in practice. The user is supposed to call poll_ready_broadcast beforehand, to make sure that room is available. And since both these functions require &mut self, they're not racy.

Copy link
Contributor

Choose a reason for hiding this comment

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

And since both these functions require &mut self, they're not racy.

I do not think this implication is valid. Given a type T with two operations a and b, each of which requires &mut self, we can wrap T in a Mutex, e.g. Arc<Mutex<T>> and have:

// thread 1 (t1: Arc<Mutex<T>>):
t1.lock().a();
t1.lock().b();

// thread 2 (t2: Arc<Mutex<T>>):
t2.lock().a();
t2.lock().b();

where the MutexGuard is dropped after each call to a or b, resulting in sequences such as a a b b for example.

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 is true for any Rust struct that exists.
The point is that you can use them in a non-racy way. We can't prevent stupidity.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can't prevent stupidity.

The current API in master apparently can.

match self.inner.get_mut().sender.start_send(msg) {
Ok(()) => {},
Err(ref err) if err.is_full() => {}, // TODO: somehow report to user?
Err(_) => {},
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe at least log something?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above. poll_ready_broadcast would return an error if the channel is closed.
This time it's possible for the function to be racy (as the background task can close the channel after poll_ready_broadcast but before start_broadcast), but the API is racy anyway as the background task can be in the process of closing the channel while start_broadcast is being called.

Copy link
Member Author

@tomaka tomaka Jan 6, 2020

Choose a reason for hiding this comment

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

I don't think we should add any logging, as this is actually a normal path of execution. Network connections can close at any point, and this is what happens if a connection has been closed.

@@ -79,19 +82,24 @@ pub struct AndThenStream<TListener, TMap> {
fun: TMap
}

impl<TListener, TMap> Unpin for AndThenStream<TListener, TMap> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the other case, I'm wondering why such impls are necessary. Just curious.

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.

7 participants