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

PacketOutOfOrder error on connect with too many connections #96

Closed
jonhoo opened this issue Feb 14, 2020 · 13 comments · Fixed by #140
Closed

PacketOutOfOrder error on connect with too many connections #96

jonhoo opened this issue Feb 14, 2020 · 13 comments · Fixed by #140

Comments

@jonhoo
Copy link
Contributor

jonhoo commented Feb 14, 2020

Conn::new returns a PacketOutOfOrder error if too many connections are established at the same time to a MySQL database. This doesn't seem like the right error to return? You can try this with something like:

#[tokio::test(threaded_scheduler)]
async fn ooo_error() {
    use futures_util::stream::StreamExt;

    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<()>();
    for _ in 0..1000 {
        let tx = tx.clone();
        tokio::spawn(async move {
            let c = Conn::new(get_opts()).await.unwrap();
            let c = c.drop_query("SELECT 1").await.unwrap();
            c.disconnect().await.unwrap();
            drop(tx);
        });
    }

    // wait until all connections have been dropped
    drop(tx);
    assert_eq!(rx.next().await, None);
}

Which will panic at Conn::new(get_opts()).await.unwrap() (expected) with a PacketOutOfOrder error (unexpected).

@jonhoo
Copy link
Contributor Author

jonhoo commented Feb 14, 2020

Note that the test above will appear to pass. You'll need to run it with -- --nocapture to see the panics.

@jonhoo
Copy link
Contributor Author

jonhoo commented Feb 14, 2020

In my experience, the test above fails if the number of connections established is >= half of MySQL's max_connections. The reason for this is that when a Plain connection is upgraded to a Socket connection, there is a period of time during which both versions of the connection are present, and so the connection counts double against the connection limit.

@jonhoo
Copy link
Contributor Author

jonhoo commented Feb 14, 2020

I have a weak suspicion that this might be related to #94. @blackbeam can you reproduce this issue? It should be relatively self-contained and easy enough to debug given that you know the protocol :) Especially because it doesn't require much concurrency at all.

@jonhoo
Copy link
Contributor Author

jonhoo commented Jul 9, 2020

I just ran into this issue again today sadly, so doesn't appear to have been fixed by the resolution to #94.

@blackbeam
Copy link
Owner

Oh, this is quite sad. I'll look into it again.
Could you please, if possible, share your server and OS version, and also a database configuration?

@blackbeam
Copy link
Owner

Please note that the biggest problem for me here was to reproduce this issue (I simply couldn't).

@jonhoo
Copy link
Contributor Author

jonhoo commented Jul 9, 2020

This was on an EC2 Ubuntu machine, running latest LTS, with the latest MariaDB version available there.
I was using the stock database configuration (though note the relationship to max-connections in the first post).

@blackbeam
Copy link
Owner

Btw, which version of mysql_common is in your Cargo.lock?

@jonhoo
Copy link
Contributor Author

jonhoo commented Jul 12, 2020

This was with mysql_async = "0.23.1", and the latest mysql_common that brings in, which I think is mysql_common = "0.22.1".

@blackbeam
Copy link
Owner

I've tried to reproduce it on t2.micro instance with bionic and stock MariaDB 10.1.44.
I got "to may connections", "connection reset" and plantly of other errors but no PacketOutOfOrder.
I tried with and without --release.

Maybe I'm missing something? Here is the code:

use mysql_async::{Conn, prelude::*};

use std::future::Future;

#[tokio::main(core_threads=8)]
async fn main() {
    use futures::stream::StreamExt;

    for _ in 0..100_000 {
        let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<()>();
        for i in 0..160 {
            let tx = tx.clone();
            tokio::spawn(async move {
                let c = Conn::new("mysql://user:password@127.0.0.1/mysql").await.unwrap();
                let c = c.drop_query("SELECT 1").await.unwrap();
                c.disconnect().await.unwrap();
                drop(tx);
            });
        }
        drop(tx);
        assert_eq!(rx.next().await, None);
    }
}

@jonhoo
Copy link
Contributor Author

jonhoo commented Jul 16, 2020

That's very weird — I don't have a good answer for you unfortunately. It could be that it only happens when there's significant concurrency, which might require more cores than a t2.micro. I'm running on a 16-core instance when I see the issue.

@blackbeam
Copy link
Owner

blackbeam commented Aug 15, 2020

Hi, @jonhoo. Could you please test the following revision in your environment?

[dependencies]
mysql_async = { git = "https://github.com/blackbeam/mysql_async.git", rev = "4602469d303321ca4a307c7377b8502e8b5a77cf" }

@jonhoo
Copy link
Contributor Author

jonhoo commented Aug 15, 2020

Ooh, interesting, #4602469d303321ca4a307c7377b8502e8b5a77cf does look like a promising fix! Unfortunately I'm pretty swamped in the coming month(s) as I'm also moving cross-country, so may not be able to test this for a while. I'll add it to my backlog though!

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 a pull request may close this issue.

2 participants