Skip to main content

mysql_async/conn/pool/
mod.rs

1// Copyright (c) 2016 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9use futures_util::FutureExt;
10use tokio::sync::mpsc;
11
12use crate::{
13    conn::{pool::futures::*, Conn},
14    error::*,
15    opts::{Opts, PoolOpts},
16    queryable::transaction::{Transaction, TxOpts},
17};
18use std::sync::atomic::Ordering;
19use std::{
20    future::Future,
21    str::FromStr,
22    sync::{atomic, Arc, Mutex},
23    task::{Context, Poll},
24    time::{Duration, Instant},
25};
26
27use in_pool_connections::InPoolConnections;
28pub use metrics::Metrics;
29use waitlist::{QueueId, Waitlist};
30
31mod recycler;
32// this is a really unfortunate name for a module
33pub mod futures;
34mod in_pool_connections;
35mod metrics;
36mod ttl_check_inerval;
37mod waitlist;
38
39/// Connection that is idling in the pool.
40#[derive(Debug)]
41struct IdlingConn {
42    /// The connection is idling since this `Instant`.
43    since: Instant,
44    /// Idling connection.
45    conn: Conn,
46}
47
48impl IdlingConn {
49    /// Returns true when this connection has a TTL and it elapsed.
50    fn expired(&self) -> bool {
51        self.conn
52            .inner
53            .ttl_deadline
54            .map(|t| Instant::now() > t)
55            .unwrap_or_default()
56    }
57
58    /// Returns duration elapsed since this connection is idling.
59    fn elapsed(&self) -> Duration {
60        self.since.elapsed()
61    }
62}
63
64impl From<Conn> for IdlingConn {
65    fn from(conn: Conn) -> Self {
66        Self {
67            since: Instant::now(),
68            conn,
69        }
70    }
71}
72
73/// The exchange is where we track all connections as they come and go.
74///
75/// It is held under a single, non-asynchronous lock.
76/// This is fine as long as we never do expensive work while holding the lock!
77#[derive(Debug)]
78struct Exchange {
79    waiting: Waitlist,
80    available: in_pool_connections::InPoolConnections,
81    exist: usize,
82    // only used to spawn the recycler the first time we're in async context
83    recycler: Option<(mpsc::UnboundedReceiver<Option<Conn>>, PoolOpts)>,
84}
85
86impl Exchange {
87    /// This function will spawn the recycler for this pool
88    /// as well as the ttl check interval if `inactive_connection_ttl` isn't `0`.
89    fn spawn_futures_if_needed(&mut self, inner: &Arc<Inner>) {
90        use recycler::Recycler;
91        use ttl_check_inerval::TtlCheckInterval;
92        if let Some((dropped, pool_opts)) = self.recycler.take() {
93            // Spawn the Recycler.
94            tokio::spawn(Recycler::new(pool_opts.clone(), inner.clone(), dropped));
95
96            // Spawn the ttl check interval if `inactive_connection_ttl` isn't `0` or
97            // connections have an absolute TTL.
98            if pool_opts.inactive_connection_ttl() > Duration::ZERO
99                || pool_opts.abs_conn_ttl().is_some()
100            {
101                tokio::spawn(TtlCheckInterval::new(pool_opts, inner.clone()));
102            }
103        }
104    }
105}
106
107/// Connection pool data.
108#[derive(Debug)]
109pub struct Inner {
110    metrics: Arc<Metrics>,
111    close: atomic::AtomicBool,
112    closed: atomic::AtomicBool,
113    exchange: Mutex<Exchange>,
114}
115
116/// Asynchronous pool of MySql connections.
117///
118/// Actually `Pool` is a shared reference, i.e. every clone will lead to the same instance
119/// created with [`Pool::new`]. Also `Pool` satisfies `Send` and `Sync`, so you don't have to wrap
120/// it into an `Arc` or `Mutex`.
121///
122/// Note that you will probably want to await [`Pool::disconnect`] before dropping the runtime, as
123/// otherwise you may end up with a number of connections that are not cleanly terminated.
124///
125/// ## Multi-runtime environments
126///
127/// `Pool` must not be shared across independent tokio runtimes. Each `tokio::net::TcpStream`
128/// inside a pooled connection is bound to the I/O driver of the runtime that established it;
129/// connections become invalid when that runtime shuts down. This is most likely to surface
130/// during graceful shutdown, when some runtimes exit before others while pooled connections
131/// are still in circulation. The pool's background tasks ([`Recycler`], TTL interval) are
132/// also spawned on the first runtime to call [`Pool::get_conn`] and do not survive that
133/// runtime's shutdown.
134///
135/// If your framework runs each worker on its own `current_thread` runtime (e.g. actix-server),
136/// create a separate `Pool` per worker rather than sharing one across all workers.
137#[derive(Debug, Clone)]
138pub struct Pool {
139    opts: Opts,
140    inner: Arc<Inner>,
141    drop: mpsc::UnboundedSender<Option<Conn>>,
142}
143
144impl Pool {
145    /// Creates a new pool of connections.
146    ///
147    /// # Panic
148    ///
149    /// It'll panic if `Opts::try_from(opts)` returns error.
150    pub fn new<O>(opts: O) -> Pool
151    where
152        Opts: TryFrom<O>,
153        <Opts as TryFrom<O>>::Error: std::error::Error,
154    {
155        let opts = Opts::try_from(opts).unwrap();
156        let pool_opts = opts.pool_opts().clone();
157        let metrics = Arc::new(Metrics::default());
158        let (tx, rx) = mpsc::unbounded_channel();
159        Pool {
160            opts,
161            inner: Arc::new(Inner {
162                close: false.into(),
163                closed: false.into(),
164                metrics: metrics.clone(),
165                exchange: Mutex::new(Exchange {
166                    available: InPoolConnections::new(
167                        pool_opts.constraints().max(),
168                        metrics.clone(),
169                    ),
170                    waiting: Waitlist::new(metrics),
171                    exist: 0,
172                    recycler: Some((rx, pool_opts)),
173                }),
174            }),
175            drop: tx,
176        }
177    }
178
179    /// Returns metrics for the connection pool.
180    pub fn metrics(&self) -> Arc<Metrics> {
181        self.inner.metrics.clone()
182    }
183
184    /// Creates a new pool of connections.
185    pub fn from_url<T: AsRef<str>>(url: T) -> Result<Pool> {
186        let opts = Opts::from_str(url.as_ref())?;
187        Ok(Pool::new(opts))
188    }
189
190    /// Async function that resolves to `Conn`.
191    pub fn get_conn(&self) -> impl Future<Output = Result<Conn>> {
192        get_conn(self.clone())
193    }
194
195    /// Starts a new transaction.
196    pub async fn start_transaction(&self, options: TxOpts) -> Result<Transaction<'static>> {
197        let conn = self.get_conn().await?;
198        Transaction::new(conn, options).await
199    }
200
201    /// Async function that disconnects this pool from the server and resolves to `()`.
202    ///
203    /// **Note:** This Future won't resolve until all active connections, taken from it,
204    /// are dropped or disonnected. Also all pending and new `get_conn()`'s will resolve to error.
205    pub async fn disconnect(self) -> Result<()> {
206        disconnect_pool(self).await
207    }
208
209    /// A way to return connection taken from a pool.
210    fn return_conn(&mut self, conn: Conn) {
211        // NOTE: we're not in async context here, so we can't block or return NotReady
212        // any and all cleanup work _has_ to be done in the spawned recycler
213        self.inner
214            .metrics
215            .connections_in_use
216            .fetch_sub(1, Ordering::Relaxed);
217        self.send_to_recycler(conn);
218    }
219
220    fn send_to_recycler(&self, conn: Conn) {
221        if let Err(conn) = self.drop.send(Some(conn)) {
222            let conn = conn.0.unwrap();
223
224            // This _probably_ means that the Runtime is shutting down, and that the Recycler was
225            // dropped rather than allowed to exit cleanly.
226            if !self.inner.closed.load(atomic::Ordering::SeqCst) {
227                // Yup, Recycler was forcibly dropped!
228                // All we can do here is try the non-pool drop path for Conn.
229                assert!(conn.inner.pool.is_none());
230                drop(conn);
231            } else {
232                unreachable!("Recycler exited while connections still exist");
233            }
234        }
235    }
236
237    /// Indicate that a connection failed to be created and release it.
238    ///
239    /// Decreases the exist counter since a broken or dropped connection should not count towards
240    /// the total.
241    pub(super) fn cancel_connection(&self) {
242        let mut exchange = self.inner.exchange.lock().unwrap();
243        exchange.exist -= 1;
244        self.inner
245            .metrics
246            .create_failed
247            .fetch_add(1, atomic::Ordering::Relaxed);
248        self.inner
249            .metrics
250            .connection_count
251            .store(exchange.exist, atomic::Ordering::Relaxed);
252        // we just enabled the creation of a new connection!
253        exchange.waiting.wake();
254    }
255
256    /// Poll the pool for an available connection.
257    fn poll_new_conn(
258        &mut self,
259        cx: &mut Context<'_>,
260        queue_id: QueueId,
261    ) -> Poll<Result<GetConnInner>> {
262        let mut exchange = self.inner.exchange.lock().unwrap();
263
264        // NOTE: this load must happen while we hold the lock,
265        // otherwise the recycler may choose to exit, see that .exist == 0, and then exit,
266        // and then we decide to create a new connection, which would then never be torn down.
267        if self.inner.close.load(atomic::Ordering::Acquire) {
268            return Err(Error::Driver(DriverError::PoolDisconnected)).into();
269        }
270
271        exchange.spawn_futures_if_needed(&self.inner);
272
273        // Check if we are higher priority than anything current
274        let highest = if let Some(cur) = exchange.waiting.peek_id() {
275            queue_id > cur
276        } else {
277            true
278        };
279
280        // If we are not, just queue
281        if !highest {
282            exchange.waiting.push(cx.waker().clone(), queue_id);
283            return Poll::Pending;
284        }
285
286        #[allow(unused_variables)] // `since` is only used when `hdrhistogram` is enabled
287        while let Some(IdlingConn { mut conn, since }) = exchange.available.pop_back() {
288            if !conn.expired() {
289                #[cfg(feature = "hdrhistogram")]
290                self.inner
291                    .metrics
292                    .connection_idle_duration
293                    .lock()
294                    .unwrap()
295                    .record(since.elapsed().as_micros() as u64)
296                    .ok();
297                #[cfg(feature = "hdrhistogram")]
298                let metrics = self.metrics();
299                conn.inner.active_since = Instant::now();
300                return Poll::Ready(Ok(GetConnInner::Checking(
301                    async move {
302                        conn.stream_mut()?.check().await?;
303                        #[cfg(feature = "hdrhistogram")]
304                        metrics
305                            .check_duration
306                            .lock()
307                            .unwrap()
308                            .record(conn.inner.active_since.elapsed().as_micros() as u64)
309                            .ok();
310                        Ok(conn)
311                    }
312                    .boxed(),
313                )));
314            } else {
315                self.send_to_recycler(conn);
316            }
317        }
318
319        // we didn't _immediately_ get one -- try to make one
320        // we first try to just do a load so we don't do an unnecessary add then sub
321        if exchange.exist < self.opts.pool_opts().constraints().max() {
322            // we are allowed to make a new connection, so we will!
323            exchange.exist += 1;
324
325            self.inner
326                .metrics
327                .connection_count
328                .store(exchange.exist, atomic::Ordering::Relaxed);
329
330            let opts = self.opts.clone();
331            #[cfg(feature = "hdrhistogram")]
332            let metrics = self.metrics();
333
334            return Poll::Ready(Ok(GetConnInner::Connecting(
335                async move {
336                    let conn = Conn::new(opts).await;
337                    #[cfg(feature = "hdrhistogram")]
338                    if let Ok(conn) = &conn {
339                        metrics
340                            .connect_duration
341                            .lock()
342                            .unwrap()
343                            .record(conn.inner.active_since.elapsed().as_micros() as u64)
344                            .ok();
345                    }
346                    conn
347                }
348                .boxed(),
349            )));
350        }
351
352        // Polled, but no conn available? Back into the queue.
353        exchange.waiting.push(cx.waker().clone(), queue_id);
354        Poll::Pending
355    }
356
357    fn unqueue(&self, queue_id: QueueId) {
358        let mut exchange = self.inner.exchange.lock().unwrap();
359        exchange.waiting.remove(queue_id);
360    }
361}
362
363impl Drop for Conn {
364    fn drop(&mut self) {
365        self.inner.infile_handler = None;
366
367        if std::thread::panicking() {
368            // Try to decrease the number of existing connections.
369            if let Some(pool) = self.inner.pool.take() {
370                pool.inner
371                    .metrics
372                    .connections_in_use
373                    .fetch_sub(1, Ordering::Relaxed);
374                pool.cancel_connection();
375            }
376
377            return;
378        }
379
380        if let Some(mut pool) = self.inner.pool.take() {
381            pool.return_conn(self.take());
382        } else if self.inner.stream.is_some() && !self.inner.disconnected {
383            crate::conn::disconnect(self.take());
384        }
385    }
386}
387
388#[cfg(test)]
389mod test {
390    use futures_util::{
391        future::{join_all, select, select_all, try_join_all, Either},
392        poll, try_join, FutureExt,
393    };
394    use tokio::time::{sleep, timeout};
395    use waker_fn::waker_fn;
396
397    use std::{
398        future::Future,
399        pin::pin,
400        sync::{Arc, OnceLock},
401        task::{Context, Poll},
402        time::Duration,
403    };
404
405    use crate::{
406        conn::pool::Pool, opts::PoolOpts, prelude::*, test_misc::get_opts, PoolConstraints, Row,
407        TxOpts, Value,
408    };
409
410    macro_rules! conn_ex_field {
411        ($conn:expr, $field:tt) => {
412            ex_field!($conn.inner.pool.as_ref().unwrap(), $field)
413        };
414    }
415
416    macro_rules! ex_field {
417        ($pool:expr, $field:tt) => {
418            $pool.inner.exchange.lock().unwrap().$field
419        };
420    }
421
422    fn pool_with_one_connection() -> Pool {
423        let pool_opts = PoolOpts::new().with_constraints(PoolConstraints::new(1, 1).unwrap());
424        let opts = get_opts().pool_opts(pool_opts.clone());
425        Pool::new(opts)
426    }
427
428    #[tokio::test]
429    async fn should_opt_out_of_connection_reset() -> super::Result<()> {
430        let pool_opts = PoolOpts::new().with_constraints(PoolConstraints::new(1, 1).unwrap());
431        let opts = get_opts().pool_opts(pool_opts.clone());
432
433        let pool = Pool::new(opts.clone());
434
435        let mut conn = pool.get_conn().await.unwrap();
436        assert_eq!(
437            conn.query_first::<Value, _>("SELECT @foo").await?.unwrap(),
438            Value::NULL
439        );
440        conn.query_drop("SET @foo = 'foo'").await?;
441        assert_eq!(
442            conn.query_first::<String, _>("SELECT @foo").await?.unwrap(),
443            "foo",
444        );
445        drop(conn);
446
447        conn = pool.get_conn().await.unwrap();
448        assert_eq!(
449            conn.query_first::<Value, _>("SELECT @foo").await?.unwrap(),
450            Value::NULL
451        );
452        conn.query_drop("SET @foo = 'foo'").await?;
453        conn.reset_connection(false);
454        drop(conn);
455
456        conn = pool.get_conn().await.unwrap();
457        assert_eq!(
458            conn.query_first::<String, _>("SELECT @foo").await?.unwrap(),
459            "foo",
460        );
461        drop(conn);
462        pool.disconnect().await.unwrap();
463
464        let pool = Pool::new(opts.pool_opts(pool_opts.with_reset_connection(false)));
465        conn = pool.get_conn().await.unwrap();
466        conn.query_drop("SET @foo = 'foo'").await?;
467        drop(conn);
468        conn = pool.get_conn().await.unwrap();
469        assert_eq!(
470            conn.query_first::<String, _>("SELECT @foo").await?.unwrap(),
471            "foo",
472        );
473        drop(conn);
474        pool.disconnect().await
475    }
476
477    #[test]
478    fn should_not_hang() -> super::Result<()> {
479        pub struct Database {
480            pool: Pool,
481        }
482
483        impl Database {
484            pub async fn disconnect(self) -> super::Result<()> {
485                self.pool.disconnect().await?;
486                Ok(())
487            }
488        }
489
490        let runtime = tokio::runtime::Runtime::new().unwrap();
491        let database = Database {
492            pool: Pool::new(get_opts()),
493        };
494        runtime.block_on(database.disconnect())
495    }
496
497    #[tokio::test]
498    async fn should_track_conn_if_disconnected_outside_of_a_pool() -> super::Result<()> {
499        let pool = Pool::new(get_opts());
500        let conn = pool.get_conn().await?;
501        conn.disconnect().await?;
502        pool.disconnect().await?;
503        Ok(())
504    }
505
506    #[tokio::test]
507    async fn should_connect() -> super::Result<()> {
508        let pool = Pool::new(crate::Opts::from(get_opts()));
509        pool.get_conn().await?.ping().await?;
510        pool.disconnect().await?;
511        Ok(())
512    }
513
514    #[tokio::test]
515    async fn should_reconnect() -> super::Result<()> {
516        let mut master = crate::Conn::new(get_opts()).await?;
517
518        async fn test(master: &mut crate::Conn, opts: crate::OptsBuilder) -> super::Result<()> {
519            const NUM_CONNS: usize = 5;
520            let pool = Pool::new(opts);
521
522            // create some conns..
523            let connections = (0..NUM_CONNS).map(|_| {
524                async {
525                    let mut conn = pool.get_conn().await?;
526                    conn.ping().await?;
527                    crate::Result::Ok(conn)
528                }
529                .boxed()
530            });
531
532            // collect ids..
533            let ids = try_join_all(connections)
534                .await?
535                .into_iter()
536                .map(|conn| conn.id())
537                .collect::<Vec<_>>();
538
539            // give some time to reset connections
540            sleep(Duration::from_millis(1000)).await;
541
542            // get_conn should work if connection is available and alive
543            pool.get_conn().await?;
544
545            // now we'll kill connections..
546            for id in ids {
547                master.query_drop(format!("KILL {}", id)).await?;
548            }
549
550            // now check, that they're still in the pool..
551            assert_eq!(ex_field!(pool, available).len(), NUM_CONNS);
552
553            sleep(Duration::from_millis(500)).await;
554
555            // now get new connection..
556            let _conn = pool.get_conn().await?;
557
558            // now check, that broken connections are dropped
559            assert_eq!(ex_field!(pool, available).len(), 0);
560
561            drop(_conn);
562            pool.disconnect().await
563        }
564
565        println!("Check socket/pipe..");
566        test(&mut master, get_opts()).await?;
567
568        println!("Check tcp..");
569        test(&mut master, get_opts().prefer_socket(false)).await?;
570
571        master.disconnect().await
572    }
573
574    #[tokio::test]
575    async fn should_reuse_connections() -> super::Result<()> {
576        let pool = pool_with_one_connection();
577        let mut conn = pool.get_conn().await?;
578
579        let server_version = conn.server_version();
580        let connection_id = conn.id();
581
582        for _ in 0..16 {
583            drop(conn);
584            conn = pool.get_conn().await?;
585            println!("CONN connection_id={}", conn.id());
586            assert!(conn.id() == connection_id || server_version < (5, 7, 2));
587        }
588
589        Ok(())
590    }
591
592    #[tokio::test]
593    #[ignore]
594    async fn can_handle_the_pressure() {
595        let pool = Pool::new(get_opts());
596        for _ in 0..10i32 {
597            let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
598            for i in 0..10_000 {
599                let pool = pool.clone();
600                let tx = tx.clone();
601                tokio::spawn(async move {
602                    let _ = pool.get_conn().await.unwrap();
603                    tx.send(i).unwrap();
604                });
605            }
606            drop(tx);
607            // see that all the tx's eventually complete
608            while (rx.recv().await).is_some() {}
609        }
610        drop(pool);
611    }
612
613    #[tokio::test]
614    async fn should_start_transaction() -> super::Result<()> {
615        let pool = pool_with_one_connection();
616
617        "CREATE TABLE IF NOT EXISTS mysql.tmp(id int)"
618            .ignore(&pool)
619            .await?;
620        "DELETE FROM mysql.tmp".ignore(&pool).await?;
621
622        let mut tx = pool.start_transaction(TxOpts::default()).await?;
623        tx.exec_batch(
624            "INSERT INTO mysql.tmp (id) VALUES (?)",
625            vec![(1_u8,), (2_u8,)],
626        )
627        .await?;
628        tx.exec_drop("SELECT * FROM mysql.tmp", ()).await?;
629        drop(tx);
630        let row_opt = pool
631            .get_conn()
632            .await?
633            .query_first("SELECT COUNT(*) FROM mysql.tmp")
634            .await?;
635        assert_eq!(row_opt, Some((0u8,)));
636        pool.get_conn()
637            .await?
638            .query_drop("DROP TABLE mysql.tmp")
639            .await?;
640        pool.disconnect().await?;
641        Ok(())
642    }
643
644    #[tokio::test]
645    async fn should_check_inactive_connection_ttl() -> super::Result<()> {
646        const POOL_MIN: usize = 5;
647        const POOL_MAX: usize = 10;
648
649        const INACTIVE_CONNECTION_TTL: Duration = Duration::from_millis(500);
650        const TTL_CHECK_INTERVAL: Duration = Duration::from_secs(1);
651
652        let constraints = PoolConstraints::new(POOL_MIN, POOL_MAX).unwrap();
653        let pool_opts = PoolOpts::default()
654            .with_constraints(constraints)
655            .with_inactive_connection_ttl(INACTIVE_CONNECTION_TTL)
656            .with_ttl_check_interval(TTL_CHECK_INTERVAL);
657
658        let pool = Pool::new(get_opts().pool_opts(pool_opts));
659        let pool_clone = pool.clone();
660        let conns = (0..POOL_MAX).map(|_| pool.get_conn()).collect::<Vec<_>>();
661
662        let conns = try_join_all(conns).await?;
663
664        assert_eq!(ex_field!(pool_clone, exist), POOL_MAX);
665        drop(conns);
666
667        // wait for a bit to let the connections be reclaimed
668        sleep(Duration::from_millis(100)).await;
669
670        // check that connections are still in the pool because of inactive_connection_ttl
671        assert_eq!(ex_field!(pool_clone, available).len(), POOL_MAX);
672
673        // then, wait for ttl_check_interval
674        sleep(TTL_CHECK_INTERVAL).await;
675
676        // wait a bit more to let the connections be reclaimed by the ttl check
677        sleep(Duration::from_millis(500)).await;
678
679        // check that we have the expected number of connections
680        assert_eq!(ex_field!(pool_clone, available).len(), POOL_MIN);
681
682        Ok(())
683    }
684
685    #[tokio::test]
686    async fn aa_should_hold_bounds2() -> super::Result<()> {
687        use std::cmp::min;
688
689        const POOL_MIN: usize = 5;
690        const POOL_MAX: usize = 10;
691
692        let constraints = PoolConstraints::new(POOL_MIN, POOL_MAX).unwrap();
693        let pool_opts = PoolOpts::default().with_constraints(constraints);
694
695        let pool = Pool::new(get_opts().pool_opts(pool_opts));
696        let pool_clone = pool.clone();
697        let conns = (0..POOL_MAX).map(|_| pool.get_conn()).collect::<Vec<_>>();
698
699        let mut conns = try_join_all(conns).await?;
700
701        // we want to continuously drop connections
702        // and check that they are _actually_ dropped until we reach POOL_MIN
703        assert_eq!(ex_field!(pool_clone, exist), POOL_MAX);
704
705        while !conns.is_empty() {
706            // first, drop a connection
707            let _ = conns.pop();
708
709            // then, wait for a bit to let the connection be reclaimed
710            sleep(Duration::from_millis(500)).await;
711
712            // now check that we have the expected # of connections
713            // this may look a little funky, but think of it this way:
714            //
715            //  - if we hold all 10 connections, we expect 10
716            //  - if we drop one,  we still expect 10, because POOL_MIN limits
717            //    the number of _idle_ connections (of which there is only 1)
718            //  - once we've dropped 5, there are now 5 idle connections. thus,
719            //    if we drop one more, we _now_ expect there to be only 9
720            //    connections total (no more connections should be pushed to
721            //    idle).
722            let dropped = POOL_MAX - conns.len();
723            let idle = min(dropped, POOL_MIN);
724            let expected = conns.len() + idle;
725
726            // check that we have the expected number of connections
727            let have = ex_field!(pool_clone, exist);
728            assert_eq!(have, expected);
729        }
730
731        pool.disconnect().await?;
732        Ok(())
733    }
734
735    #[tokio::test]
736    async fn should_hold_bounds1() -> super::Result<()> {
737        let constraints = PoolConstraints::new(1, 2).unwrap();
738        let opts = get_opts().pool_opts(PoolOpts::default().with_constraints(constraints));
739        let pool = Pool::new(opts);
740        let pool_clone = pool.clone();
741
742        let (conn1, conn2) = try_join!(pool.get_conn(), pool.get_conn()).unwrap();
743
744        assert_eq!(conn_ex_field!(conn1, exist), 2);
745        assert_eq!(conn_ex_field!(conn1, available).len(), 0);
746
747        drop(conn1);
748        drop(conn2);
749        // only one of conn1 and conn2 should have gone to idle,
750        // and should have immediately been picked up by new_conn (now conn1)
751        let conn1 = pool_clone.get_conn().await?;
752        assert_eq!(conn_ex_field!(conn1, available).len(), 0);
753
754        drop(conn1);
755
756        // the connection should be returned to idle
757        // (but may not have been returned _yet_)
758        assert!(ex_field!(pool, available).len() <= 1);
759        pool.disconnect().await?;
760        Ok(())
761    }
762
763    // Test that connections which err do not count towards the connection count in the pool.
764    #[tokio::test]
765    async fn should_hold_bounds_on_error() -> super::Result<()> {
766        // Should not be possible to connect to broadcast address.
767        let pool = Pool::new("mysql://255.255.255.255");
768
769        assert!(try_join!(pool.get_conn(), pool.get_conn()).is_err());
770        assert_eq!(ex_field!(pool, exist), 0);
771        Ok(())
772    }
773
774    #[tokio::test]
775    async fn zz_should_check_wait_timeout_on_get_conn() -> super::Result<()> {
776        let pool = Pool::new(get_opts());
777
778        let mut conn = pool.get_conn().await?;
779        let wait_timeout_orig: Option<usize> = conn.query_first("SELECT @@wait_timeout").await?;
780        conn.query_drop("SET GLOBAL wait_timeout = 3").await?;
781        conn.disconnect().await?;
782
783        let mut conn = pool.get_conn().await?;
784        let wait_timeout: Option<usize> = conn.query_first("SELECT @@wait_timeout").await?;
785        let id1: Option<usize> = conn.query_first("SELECT CONNECTION_ID()").await?;
786        drop(conn);
787
788        assert_eq!(wait_timeout, Some(3));
789        assert_eq!(ex_field!(pool, exist), 1);
790
791        sleep(Duration::from_secs(6)).await;
792
793        let mut conn = pool.get_conn().await?;
794        let id2: Option<usize> = conn.query_first("SELECT CONNECTION_ID()").await?;
795        assert_eq!(ex_field!(pool, exist), 1);
796        assert_ne!(id1, id2);
797
798        conn.exec_drop("SET GLOBAL wait_timeout = ?", (wait_timeout_orig,))
799            .await?;
800        drop(conn);
801
802        pool.disconnect().await?;
803
804        Ok(())
805    }
806
807    #[tokio::test]
808    async fn droptest() -> super::Result<()> {
809        let pool = Pool::new(get_opts());
810        let conns = try_join_all((0..10).map(|_| pool.get_conn()))
811            .await
812            .unwrap();
813        drop(conns);
814        drop(pool);
815
816        let pool = Pool::new(get_opts());
817        let conns = try_join_all((0..10).map(|_| pool.get_conn()))
818            .await
819            .unwrap();
820        drop(pool);
821        drop(conns);
822        Ok(())
823    }
824
825    #[test]
826    fn drop_impl_for_conn_should_not_panic_within_unwind() {
827        use tokio::runtime;
828
829        const PANIC_MESSAGE: &str = "ORIGINAL_PANIC";
830
831        let result = std::panic::catch_unwind(|| {
832            runtime::Builder::new_current_thread()
833                .enable_all()
834                .build()
835                .unwrap()
836                .block_on(async {
837                    let pool = Pool::new(get_opts());
838                    let _conn = pool.get_conn().await.unwrap();
839                    std::panic::panic_any(PANIC_MESSAGE);
840                });
841        });
842
843        assert_eq!(
844            *result.unwrap_err().downcast::<&str>().unwrap(),
845            "ORIGINAL_PANIC",
846        );
847    }
848
849    #[test]
850    fn should_not_panic_on_unclean_shutdown() {
851        // run more than once to trigger different drop orders
852        for _ in 0..10 {
853            let rt = tokio::runtime::Runtime::new().unwrap();
854            let (tx, rx) = tokio::sync::oneshot::channel();
855            rt.block_on(async move {
856                let pool = Pool::new(get_opts());
857                let mut c = pool.get_conn().await.unwrap();
858                tokio::spawn(async move {
859                    let _ = rx.await;
860                    let _ = c.query_drop("SELECT 1").await;
861                });
862            });
863            drop(rt);
864            // c is still active here, so if anything it's been forcibly dropped
865            let _ = tx.send(());
866        }
867    }
868
869    #[test]
870    fn should_perform_clean_shutdown() {
871        // run more than once to trigger different drop orders
872        for _ in 0..10 {
873            let rt = tokio::runtime::Runtime::new().unwrap();
874            let (tx, rx) = tokio::sync::oneshot::channel();
875            let jh = rt.spawn(async move {
876                let pool = Pool::new(get_opts());
877                let mut c = pool.get_conn().await.unwrap();
878                tokio::spawn(async move {
879                    let _ = rx.await;
880                    let _ = c.query_drop("SELECT 1").await;
881                });
882                let _ = pool.disconnect().await;
883            });
884            let _ = tx.send(());
885            rt.block_on(jh).unwrap();
886        }
887    }
888
889    #[tokio::test]
890    async fn issue_126_should_cleanup_errors_in_multiresult_sets() -> super::Result<()> {
891        let pool_constraints = PoolConstraints::new(0, 1).unwrap();
892        let pool_opts = PoolOpts::default().with_constraints(pool_constraints);
893
894        let pool = Pool::new(get_opts().pool_opts(pool_opts));
895
896        for _ in 0u8..100 {
897            pool.get_conn()
898                .await?
899                .query_iter("DO '42'; BLABLA;")
900                .await?;
901        }
902
903        Ok(())
904    }
905
906    #[tokio::test]
907    async fn should_ignore_non_fatal_errors_while_returning_to_a_pool() -> super::Result<()> {
908        let pool = pool_with_one_connection();
909        let id = pool.get_conn().await?.id();
910
911        // non-fatal errors are ignored
912        for _ in 0u8..10 {
913            let mut conn = pool.get_conn().await?;
914            conn.query_iter("DO '42'; BLABLA;").await?;
915            assert_eq!(id, conn.id());
916        }
917
918        Ok(())
919    }
920
921    #[tokio::test]
922    async fn should_remove_waker_of_cancelled_task() {
923        let pool = pool_with_one_connection();
924        let only_conn = pool.get_conn().await.unwrap();
925
926        let join_handle = tokio::spawn(timeout(Duration::from_secs(1), pool.get_conn()));
927
928        sleep(Duration::from_secs(2)).await;
929
930        match join_handle.await.unwrap() {
931            Err(_elapsed) => (),
932            _ => panic!("unexpected Ok()"),
933        }
934        drop(only_conn);
935
936        assert_eq!(0, pool.inner.exchange.lock().unwrap().waiting.len());
937        // metrics should catch up with waiting queue (see #335)
938        assert_eq!(
939            0,
940            pool.metrics()
941                .active_wait_requests
942                .load(std::sync::atomic::Ordering::Relaxed)
943        );
944    }
945
946    #[tokio::test]
947    async fn should_work_if_pooled_connection_operation_is_cancelled() -> super::Result<()> {
948        let pool = Pool::new(get_opts());
949
950        // warm up
951        join_all((0..10).map(|_| pool.get_conn())).await;
952
953        /// some operation
954        async fn op(pool: &Pool) {
955            let _: Option<Row> = pool
956                .get_conn()
957                .await
958                .unwrap()
959                .exec_first("SELECT ?, ?", (42, "foo"))
960                .await
961                .unwrap();
962        }
963
964        // Measure the delay
965        let mut max_delay = 0_u128;
966        for _ in 0..10_usize {
967            let start = std::time::Instant::now();
968            op(&pool).await;
969            max_delay = std::cmp::max(max_delay, start.elapsed().as_micros());
970        }
971
972        for _ in 0_usize..128 {
973            let fut = select_all((0_usize..5).map(|_| op(&pool).boxed()));
974
975            // we need to cancel the op in the middle
976            // this should not lead to the `packet out of order` error.
977            let delay_micros = rand::random::<u128>() % max_delay;
978            select(
979                sleep(Duration::from_micros(delay_micros as u64)).boxed(),
980                fut,
981            )
982            .await;
983
984            // give some time for connections to return to the pool
985            sleep(Duration::from_millis(100)).await;
986        }
987        Ok(())
988    }
989
990    #[tokio::test]
991    async fn check_absolute_connection_ttl() -> super::Result<()> {
992        let constraints = PoolConstraints::new(1, 3).unwrap();
993        let pool_opts = PoolOpts::default()
994            .with_constraints(constraints)
995            .with_inactive_connection_ttl(Duration::from_secs(99))
996            .with_ttl_check_interval(Duration::from_secs(1))
997            .with_abs_conn_ttl(Some(Duration::from_secs(2)));
998
999        let pool = Pool::new(get_opts().pool_opts(pool_opts));
1000
1001        let conn_ttl0 = pool.get_conn().await?;
1002        sleep(Duration::from_millis(1000)).await;
1003        let conn_ttl1 = pool.get_conn().await?;
1004        sleep(Duration::from_millis(1000)).await;
1005        let conn_ttl2 = pool.get_conn().await?;
1006
1007        drop(conn_ttl0);
1008        drop(conn_ttl1);
1009        drop(conn_ttl2);
1010        assert_eq!(ex_field!(pool, exist), 3);
1011
1012        sleep(Duration::from_millis(1500)).await;
1013        assert_eq!(ex_field!(pool, exist), 2);
1014
1015        sleep(Duration::from_millis(1000)).await;
1016        assert_eq!(ex_field!(pool, exist), 1);
1017
1018        // Go even below min pool size.
1019        sleep(Duration::from_millis(1000)).await;
1020        assert_eq!(ex_field!(pool, exist), 0);
1021
1022        Ok(())
1023    }
1024
1025    #[tokio::test]
1026    async fn save_last_waker() {
1027        // Test that if passed multiple wakers, we call the last one.
1028
1029        let pool = pool_with_one_connection();
1030
1031        // Get a connection, so we know the next future will be
1032        // queued.
1033        let conn = pool.get_conn().await.unwrap();
1034        let mut pending_fut = pin!(pool.get_conn());
1035
1036        let build_waker = || {
1037            let called = Arc::new(OnceLock::new());
1038            let called2 = called.clone();
1039            let waker = waker_fn(move || called2.set(()).unwrap());
1040            (called, waker)
1041        };
1042
1043        let mut assert_pending = |waker| {
1044            let mut context = Context::from_waker(&waker);
1045            let p = pending_fut.as_mut().poll(&mut context);
1046            assert!(matches!(p, Poll::Pending));
1047        };
1048
1049        let (first_called, waker) = build_waker();
1050        assert_pending(waker);
1051
1052        let (second_called, waker) = build_waker();
1053        assert_pending(waker);
1054
1055        drop(conn);
1056
1057        while second_called.get().is_none() {
1058            assert!(first_called.get().is_none());
1059            tokio::time::sleep(Duration::from_millis(100)).await;
1060        }
1061
1062        assert!(first_called.get().is_none());
1063    }
1064
1065    #[tokio::test]
1066    async fn check_priorities() -> super::Result<()> {
1067        let pool = pool_with_one_connection();
1068
1069        let queue_len = || {
1070            let exchange = pool.inner.exchange.lock().unwrap();
1071            exchange.waiting.len()
1072        };
1073
1074        // Get a connection, so we know the next futures will be
1075        // queued.
1076        let conn = pool.get_conn().await.unwrap();
1077
1078        #[allow(clippy::async_yields_async)]
1079        let get_pending = || async {
1080            let fut = async {
1081                pool.get_conn().await.unwrap();
1082            }
1083            .shared();
1084            let p = poll!(fut.clone());
1085            assert!(matches!(p, Poll::Pending));
1086            fut
1087        };
1088
1089        let fut1 = get_pending().await;
1090        let fut2 = get_pending().await;
1091
1092        // Both futures are queued
1093        assert_eq!(queue_len(), 2);
1094
1095        drop(conn); // This will pop fut1 from the queue, making it [2]
1096        while queue_len() != 1 {
1097            tokio::time::sleep(Duration::from_millis(100)).await;
1098        }
1099
1100        // We called wake on fut1, and even with the select fut1 will
1101        // resolve first
1102        let Either::Right((_, fut2)) = select(fut2, fut1).await else {
1103            panic!("wrong future");
1104        };
1105
1106        // We dropped the connection of fut1, but very likely hasn't
1107        // made it through the recycler yet.
1108        assert_eq!(queue_len(), 1);
1109
1110        let p = poll!(fut2.clone());
1111        assert!(matches!(p, Poll::Pending));
1112        assert_eq!(queue_len(), 1); // The queue still has fut2
1113
1114        // The connection will pass by the recycler and unblock fut2
1115        // and pop it from the queue.
1116        fut2.await;
1117        assert_eq!(queue_len(), 0);
1118
1119        // The recycler is probably not done, so a new future will be
1120        // pending.
1121        let fut3 = get_pending().await;
1122        assert_eq!(queue_len(), 1);
1123
1124        // It is OK to await it.
1125        fut3.await;
1126
1127        Ok(())
1128    }
1129
1130    #[cfg(feature = "hdrhistogram")]
1131    #[tokio::test]
1132    async fn metrics() -> super::Result<()> {
1133        let pool = pool_with_one_connection();
1134
1135        let metrics = pool.metrics();
1136        let conn = pool.get_conn().await.unwrap();
1137        tokio::time::sleep(Duration::from_millis(100)).await;
1138        drop(conn);
1139        pool.get_conn().await.unwrap();
1140
1141        let max = metrics.connection_active_duration.lock().unwrap().max();
1142        // We slept for 100 miliseconds holding a conneciton.
1143        assert!(max > 100_000);
1144
1145        Ok(())
1146    }
1147
1148    #[tokio::test]
1149    async fn connection_in_use_metric() {
1150        let pool = pool_with_one_connection();
1151        let metrics = pool.metrics();
1152
1153        let conn = pool.get_conn().await.unwrap();
1154        assert_eq!(
1155            metrics
1156                .connections_in_use
1157                .load(std::sync::atomic::Ordering::Relaxed),
1158            1
1159        );
1160
1161        drop(conn);
1162        assert_eq!(
1163            metrics
1164                .connections_in_use
1165                .load(std::sync::atomic::Ordering::Relaxed),
1166            0
1167        );
1168        loop {
1169            if metrics
1170                .connections_in_pool
1171                .load(std::sync::atomic::Ordering::Relaxed)
1172                == 1
1173            {
1174                break;
1175            }
1176            tokio::time::sleep(Duration::from_millis(100)).await;
1177        }
1178
1179        // Ensure that we increase the connections in use metric when we get a connection from the pool
1180        let conn = pool.get_conn().await.unwrap();
1181        assert_eq!(
1182            metrics
1183                .connections_in_use
1184                .load(std::sync::atomic::Ordering::Relaxed),
1185            1
1186        );
1187        assert_eq!(
1188            metrics
1189                .connections_in_pool
1190                .load(std::sync::atomic::Ordering::Relaxed),
1191            0
1192        );
1193
1194        drop(conn);
1195        assert_eq!(
1196            metrics
1197                .connections_in_use
1198                .load(std::sync::atomic::Ordering::Relaxed),
1199            0
1200        );
1201        loop {
1202            if metrics
1203                .connections_in_pool
1204                .load(std::sync::atomic::Ordering::Relaxed)
1205                == 1
1206            {
1207                break;
1208            }
1209            tokio::time::sleep(Duration::from_millis(100)).await;
1210        }
1211    }
1212
1213    #[cfg(feature = "nightly")]
1214    mod bench {
1215        use futures_util::future::{FutureExt, TryFutureExt};
1216        use tokio::runtime::Runtime;
1217
1218        use crate::{prelude::Queryable, test_misc::get_opts, Pool, PoolConstraints, PoolOpts};
1219        use std::time::Duration;
1220
1221        #[bench]
1222        fn get_conn(bencher: &mut test::Bencher) {
1223            let mut runtime = Runtime::new().unwrap();
1224            let pool = Pool::new(get_opts());
1225
1226            bencher.iter(|| {
1227                let fut = pool
1228                    .get_conn()
1229                    .and_then(|mut conn| async { conn.ping().await.map(|_| conn) });
1230                runtime.block_on(fut).unwrap();
1231            });
1232
1233            runtime.block_on(pool.disconnect()).unwrap();
1234        }
1235
1236        #[bench]
1237        fn new_conn_on_pool_soft_boundary(bencher: &mut test::Bencher) {
1238            let mut runtime = Runtime::new().unwrap();
1239
1240            let pool_constraints = PoolConstraints::new(0, 1).unwrap();
1241            let pool_opts = PoolOpts::default()
1242                .with_constraints(pool_constraints)
1243                .with_inactive_connection_ttl(Duration::from_secs(1));
1244
1245            let pool = Pool::new(get_opts().pool_opts(pool_opts));
1246
1247            bencher.iter(|| {
1248                let fut = pool.get_conn().map(drop);
1249                runtime.block_on(fut);
1250            });
1251
1252            runtime.block_on(pool.disconnect()).unwrap();
1253        }
1254    }
1255}