Skip to main content

LocalPool

Struct LocalPool 

Source
pub struct LocalPool<M: ManageConnection> { /* private fields */ }
Expand description

One thread’s pool, created by Pool::local.

Holds the idle connections and the slot count for this thread alone. Clone shares the same per-thread pool between tasks on the thread; it is never Send.

Implementations§

Source§

impl<M: ManageConnection> LocalPool<M>

Source

pub fn max_size(&self) -> u32

The per-thread ceiling on open connections.

Source

pub fn state(&self) -> State

A snapshot of this thread’s open, idle and waiting counts. Lock-free. For the process-wide sum use Pool::state.

Examples found in repository?
examples/warmup.rs (line 82)
74fn main() -> io::Result<()> {
75    // Keep at most 8 per thread; warm 4 of them ahead of demand.
76    let pool = Pool::builder().max_size(8).min_idle(4).build(Manager);
77
78    // One runtime, one thread — the pool's natural unit.
79    let runtime = Runtime::builder().build().expect("build compio runtime");
80    runtime.block_on(async move {
81        let local = pool.local();
82        println!("fresh pool:     {:?}  opened={}", local.state(), opened());
83
84        // Warm opens min_idle connections up front and parks them on the idle list.
85        let made = local.warm().await.expect("warm");
86        println!(
87            "after warm:     {:?}  opened={}  (warm made {made})",
88            local.state(),
89            opened()
90        );
91
92        // Checking three out pulls them off the warm idle list — no new opens.
93        let a = local.get().await.expect("get a");
94        let b = local.get().await.expect("get b");
95        let c = local.get().await.expect("get c");
96        println!("3 checked out:  {:?}  opened={}", local.state(), opened());
97
98        // Dropping the leases returns the resources to the idle list.
99        drop((a, b, c));
100        println!("3 returned:     {:?}  opened={}", local.state(), opened());
101
102        // A burst reuses the 4 idle resources first, then opens more — but only up
103        // to max_size=8, which caps the pool. A 9th get here would wait for a free
104        // slot rather than open a ninth.
105        let mut held = Vec::new();
106        for _ in 0..8 {
107            held.push(local.get().await.expect("burst"));
108        }
109        println!("8 checked out:  {:?}  opened={}", local.state(), opened());
110
111        drop(held);
112        println!("8 returned:     {:?}  opened={}", local.state(), opened());
113    });
114
115    Ok(())
116}
More examples
Hide additional examples
examples/fanout_requests.rs (line 142)
82fn main() -> io::Result<()> {
83    let requests: usize = arg(1).unwrap_or(50_000);
84    let capacity: u32 = arg(2).unwrap_or(8);
85    let clients: usize = arg(3).unwrap_or(128);
86    let size: usize = arg(4).unwrap_or(256);
87
88    let runtime = Runtime::builder().build().expect("build compio runtime");
89    runtime.block_on(async move {
90        // 1. An in-process echo backend on an ephemeral port, run as a task.
91        let listen: SocketAddr = "127.0.0.1:0".parse().unwrap();
92        let backend = TcpListener::bind(listen).await?;
93        let backend_addr = backend.local_addr()?;
94        compio::runtime::spawn(backend_loop(backend)).detach();
95
96        // 2. One pool of upstreams to the backend, and this thread's LocalPool.
97        let pool = Pool::builder()
98            .max_size(capacity)
99            .build(Upstreams { addr: backend_addr });
100        let local = pool.local();
101
102        println!(
103            "fanout_requests -> {backend_addr}: {clients} client tasks over {capacity} pooled \
104             upstreams, {requests} requests x {size} B"
105        );
106
107        // Shared, single-thread state: plain Rc<Cell>, no atomics needed.
108        let next = Rc::new(Cell::new(0usize)); // next request index to claim
109        let bytes = Rc::new(Cell::new(0u64)); // total bytes echoed back
110        let inflight = Rc::new(Cell::new(0u32)); // upstreams in use right now
111        let peak = Rc::new(Cell::new(0u32)); // high-water mark of `inflight`
112
113        // 3. Fan out: spawn the client tasks and await them all.
114        let started = Instant::now();
115        let mut tasks = Vec::with_capacity(clients);
116        for _ in 0..clients {
117            tasks.push(compio::runtime::spawn(client(
118                local.clone(),
119                next.clone(),
120                requests,
121                size,
122                bytes.clone(),
123                inflight.clone(),
124                peak.clone(),
125            )));
126        }
127        for task in tasks {
128            let _ = task.await;
129        }
130        let elapsed = started.elapsed();
131
132        println!(
133            "done: {requests} requests in {:.3}s ({:.0} req/s), {} B echoed",
134            elapsed.as_secs_f64(),
135            requests as f64 / elapsed.as_secs_f64(),
136            bytes.get(),
137        );
138        println!(
139            "pool: opened {} upstreams total, peak {} in use at once (cap {capacity}); final {:?}",
140            OPENS.load(Ordering::Relaxed),
141            peak.get(),
142            local.state(),
143        );
144        Ok(())
145    })
146}
Source

pub fn statistics(&self) -> Statistics

This thread’s cumulative counters. Lock-free. For the process-wide sum use Pool::statistics.

Source

pub async fn warm(&self) -> Result<u32, RunError<M::Error>>

Open connections up to min_idle and park them on the idle list. Returns how many were created. Tops up to the floor each time it is called, so it doubles as the warm-up and the min-idle maintenance step; maintain pairs it with a reap. A no-op (returns Ok(0)) once the pool is closed. Stops early and returns RunError::User if the manager fails to connect.

Examples found in repository?
examples/warmup.rs (line 85)
74fn main() -> io::Result<()> {
75    // Keep at most 8 per thread; warm 4 of them ahead of demand.
76    let pool = Pool::builder().max_size(8).min_idle(4).build(Manager);
77
78    // One runtime, one thread — the pool's natural unit.
79    let runtime = Runtime::builder().build().expect("build compio runtime");
80    runtime.block_on(async move {
81        let local = pool.local();
82        println!("fresh pool:     {:?}  opened={}", local.state(), opened());
83
84        // Warm opens min_idle connections up front and parks them on the idle list.
85        let made = local.warm().await.expect("warm");
86        println!(
87            "after warm:     {:?}  opened={}  (warm made {made})",
88            local.state(),
89            opened()
90        );
91
92        // Checking three out pulls them off the warm idle list — no new opens.
93        let a = local.get().await.expect("get a");
94        let b = local.get().await.expect("get b");
95        let c = local.get().await.expect("get c");
96        println!("3 checked out:  {:?}  opened={}", local.state(), opened());
97
98        // Dropping the leases returns the resources to the idle list.
99        drop((a, b, c));
100        println!("3 returned:     {:?}  opened={}", local.state(), opened());
101
102        // A burst reuses the 4 idle resources first, then opens more — but only up
103        // to max_size=8, which caps the pool. A 9th get here would wait for a free
104        // slot rather than open a ninth.
105        let mut held = Vec::new();
106        for _ in 0..8 {
107            held.push(local.get().await.expect("burst"));
108        }
109        println!("8 checked out:  {:?}  opened={}", local.state(), opened());
110
111        drop(held);
112        println!("8 returned:     {:?}  opened={}", local.state(), opened());
113    });
114
115    Ok(())
116}
Source

pub fn reap(&self) -> u32

Drop idle connections that have passed idle_timeout or max_lifetime, freeing their slots. Returns how many were reaped.

Expiry is otherwise lazy — a connection is only checked as it is handed out or returned — so a pool that goes quiet would hold dead backend connections open indefinitely. Call this on a timer (see maintain) to retire them promptly. Only idle connections are considered; one that is checked out is left to its holder.

Source

pub async fn maintain(&self) -> Result<u32, RunError<M::Error>>

The periodic maintenance step: reap expired idle connections, then warm back up to min_idle. Returns how many fresh connections were opened. Call it from a timer on each worker thread — thread-per-core has no shared reaper thread, so maintenance is yours to drive:

use std::time::Duration;
compio::runtime::spawn(async move {
    loop {
        compio::time::sleep(Duration::from_secs(30)).await;
        let _ = local.maintain().await;
    }
})
.detach();
Source

pub fn clear(&self) -> u32

Drop every idle connection now, freeing their slots, and return how many were dropped. Connections currently checked out are untouched and return as usual. Use it to drain after a backend failover, or as part of a shutdown once Pool::close has been called. Unlike reap it ignores the timeouts and clears unconditionally.

Source

pub async fn get(&self) -> Result<PooledConnection<M>, RunError<M::Error>>

Check a connection out of this thread’s pool, waiting up to connection_timeout for one.

Reuses an idle connection when there is one (validated first if test_on_check_out is set), otherwise opens one while under max_size, otherwise waits for a slot. The returned PooledConnection derefs to the connection and returns it to this pool when dropped. Fails with RunError::Closed if the pool has been closed.

Examples found in repository?
examples/scatter_gather.rs (line 222)
221async fn query_shard(pool: LocalPool<Shard>, query: String) -> Option<String> {
222    let mut conn = match pool.get().await {
223        Ok(conn) => conn,
224        Err(_) => return None,
225    };
226    if conn.write_line(&query).await.is_err() {
227        conn.healthy = false;
228        return None;
229    }
230    match conn.read_line().await {
231        Ok(Some(reply)) => Some(reply),
232        _ => {
233            conn.healthy = false;
234            None
235        }
236    }
237}
More examples
Hide additional examples
examples/echo.rs (line 113)
112async fn serve(local: LocalPool<Buffers>, mut stream: TcpStream) {
113    let mut lease = match local.get().await {
114        Ok(lease) => lease,
115        Err(_) => return,
116    };
117    loop {
118        let mut buf = std::mem::take(&mut *lease);
119        buf.clear();
120        let BufResult(read, buf) = stream.read(buf).await;
121        match read {
122            Ok(0) | Err(_) => {
123                *lease = buf;
124                return;
125            }
126            Ok(_) => {}
127        }
128        let BufResult(written, buf) = stream.write_all(buf).await;
129        *lease = buf;
130        if written.is_err() {
131            return;
132        }
133    }
134}
examples/task_echo.rs (line 92)
91async fn serve(local: LocalPool<Buffers>, mut stream: TcpStream) {
92    let mut lease = match local.get().await {
93        Ok(lease) => lease,
94        Err(_) => return,
95    };
96    loop {
97        let mut buf = std::mem::take(&mut *lease);
98        buf.clear();
99        let BufResult(read, buf) = stream.read(buf).await;
100        match read {
101            Ok(0) | Err(_) => {
102                *lease = buf;
103                return;
104            }
105            Ok(_) => {}
106        }
107        let BufResult(written, buf) = stream.write_all(buf).await;
108        *lease = buf;
109        if written.is_err() {
110            return;
111        }
112    }
113}
examples/unix_echo.rs (line 90)
89async fn serve(local: LocalPool<Buffers>, mut stream: UnixStream) {
90    let mut lease = match local.get().await {
91        Ok(lease) => lease,
92        Err(_) => return,
93    };
94    loop {
95        let mut buf = std::mem::take(&mut *lease);
96        buf.clear();
97        let BufResult(read, buf) = stream.read(buf).await;
98        match read {
99            Ok(0) | Err(_) => {
100                *lease = buf;
101                return;
102            }
103            Ok(_) => {}
104        }
105        let BufResult(written, buf) = stream.write_all(buf).await;
106        *lease = buf;
107        if written.is_err() {
108            return;
109        }
110    }
111}
examples/wal.rs (line 156)
155async fn serve(local: LocalPool<WalManager>, mut stream: TcpStream) {
156    let mut lease = match local.get().await {
157        Ok(lease) => lease,
158        Err(_) => return,
159    };
160    let mut buf = Vec::with_capacity(64 * 1024);
161    loop {
162        buf.clear();
163        let BufResult(read, b) = stream.read(buf).await;
164        buf = b;
165        let n = match read {
166            Ok(0) | Err(_) => return,
167            Ok(n) => n,
168        };
169
170        // Append the record at the segment tail. Read `offset` into a local so the
171        // expression does not borrow `lease` twice.
172        let offset = lease.offset;
173        let BufResult(w, b) = lease.file.write_all_at(buf, offset).await;
174        buf = b;
175        if w.is_err() {
176            return;
177        }
178        lease.offset += n as u64;
179
180        if *SYNC && lease.file.sync_data().await.is_err() {
181            return;
182        }
183
184        let BufResult(w, _) = stream.write_all(b"OK\n".to_vec()).await;
185        if w.is_err() {
186            return;
187        }
188    }
189}
examples/tcp_pool.rs (line 161)
149async fn serve(local: LocalPool<Backend>, mut client: TcpStream) {
150    let mut buf = Vec::with_capacity(16 * 1024);
151    loop {
152        buf.clear();
153        let BufResult(read, b) = client.read(buf).await;
154        buf = b;
155        let n = match read {
156            Ok(0) | Err(_) => return,
157            Ok(n) => n,
158        };
159
160        // Lease one upstream for just this round trip.
161        let mut up = match local.get().await {
162            Ok(up) => up,
163            Err(_) => return, // pool exhausted past the timeout, or connect failed
164        };
165
166        // Request out to the upstream.
167        let BufResult(written, b) = up.stream.write_all(buf).await;
168        buf = b;
169        if written.is_err() {
170            up.healthy = false;
171            return;
172        }
173
174        // Echo backend replies with exactly what it got, so read n back.
175        let BufResult(got, reply) = up.stream.read_exact(Vec::with_capacity(n)).await;
176        if got.is_err() {
177            up.healthy = false;
178            return;
179        }
180        drop(up); // return the upstream to the pool before touching the client
181
182        let BufResult(written, _) = client.write_all(reply).await;
183        if written.is_err() {
184            return;
185        }
186    }
187}

Trait Implementations§

Source§

impl<M: ManageConnection> Clone for LocalPool<M>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<M: ManageConnection> Debug for LocalPool<M>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<M> !RefUnwindSafe for LocalPool<M>

§

impl<M> !Send for LocalPool<M>

§

impl<M> !Sync for LocalPool<M>

§

impl<M> !UnwindSafe for LocalPool<M>

§

impl<M> Freeze for LocalPool<M>
where Rc<Inner<M>>: Freeze,

§

impl<M> Unpin for LocalPool<M>
where Rc<Inner<M>>: Unpin,

§

impl<M> UnsafeUnpin for LocalPool<M>
where Rc<Inner<M>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more