Skip to main content

Pool

Struct Pool 

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

A blueprint for per-thread pools: build it once, clone it onto each compio thread you spawn, and call local there.

Clone + Send + Sync. Cloning shares nothing that is touched on the hot path — only the manager, the Builder settings, the shutdown flag and the metrics registry. Every connection, idle list and slot counter belongs to one thread’s LocalPool.

Implementations§

Source§

impl<M: ManageConnection> Pool<M>

Source

pub fn builder() -> Builder<M>

Start configuring a pool.

Examples found in repository?
examples/udp_echo.rs (line 72)
61fn main() -> io::Result<()> {
62    let addr: SocketAddr = std::env::args()
63        .nth(1)
64        .unwrap_or_else(|| "0.0.0.0:7000".into())
65        .parse()
66        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
67    let capacity: u32 = std::env::args()
68        .nth(2)
69        .and_then(|s| s.parse().ok())
70        .unwrap_or(1024);
71
72    let pool = Pool::builder().max_size(capacity).build(Buffers);
73
74    let cores = cpu::cores();
75    if cores.is_empty() {
76        return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
77    }
78    println!(
79        "udp_echo on {addr}: {} workers, capacity {capacity}/worker",
80        cores.len()
81    );
82
83    let mut handles = Vec::new();
84    for (index, core) in cores.into_iter().enumerate() {
85        let pool = pool.clone();
86        let handle = thread::Builder::new()
87            .name(format!("worker/{index}"))
88            .spawn(move || worker(core, addr, pool))?;
89        handles.push(handle);
90    }
91    for handle in handles {
92        let _ = handle.join();
93    }
94    Ok(())
95}
More examples
Hide additional examples
examples/file_server.rs (line 73)
62fn main() -> io::Result<()> {
63    let addr: SocketAddr = std::env::args()
64        .nth(1)
65        .unwrap_or_else(|| "0.0.0.0:7000".into())
66        .parse()
67        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
68    let capacity: u32 = std::env::args()
69        .nth(2)
70        .and_then(|s| s.parse().ok())
71        .unwrap_or(1024);
72
73    let pool = Pool::builder().max_size(capacity).build(Buffers);
74
75    let cores = cpu::cores();
76    if cores.is_empty() {
77        return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
78    }
79    println!(
80        "file_server on {addr} serving {}: {} workers, capacity {capacity}/worker",
81        BASE_DIR.display(),
82        cores.len()
83    );
84
85    let mut handles = Vec::new();
86    for (index, core) in cores.into_iter().enumerate() {
87        let pool = pool.clone();
88        let handle = thread::Builder::new()
89            .name(format!("worker/{index}"))
90            .spawn(move || worker(core, addr, pool))?;
91        handles.push(handle);
92    }
93    for handle in handles {
94        let _ = handle.join();
95    }
96    Ok(())
97}
examples/echo.rs (line 62)
50fn main() -> io::Result<()> {
51    let addr: SocketAddr = std::env::args()
52        .nth(1)
53        .unwrap_or_else(|| "0.0.0.0:7000".into())
54        .parse()
55        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
56    let capacity: u32 = std::env::args()
57        .nth(2)
58        .and_then(|s| s.parse().ok())
59        .unwrap_or(1024);
60
61    // Build the pool once. `Pool` is `Send + Clone`; every worker gets a clone.
62    let pool = Pool::builder().max_size(capacity).build(Buffers);
63
64    let cores = cpu::cores();
65    if cores.is_empty() {
66        return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
67    }
68    println!(
69        "echo on {addr}: {} workers, capacity {capacity}/worker",
70        cores.len()
71    );
72
73    let mut handles = Vec::new();
74    for (index, core) in cores.into_iter().enumerate() {
75        let pool = pool.clone();
76        let handle = thread::Builder::new()
77            .name(format!("worker/{index}"))
78            .spawn(move || worker(core, addr, pool))?;
79        handles.push(handle);
80    }
81    for handle in handles {
82        let _ = handle.join();
83    }
84    Ok(())
85}
examples/tcp_pool.rs (line 100)
88fn main() -> io::Result<()> {
89    let addr: SocketAddr = std::env::args()
90        .nth(1)
91        .unwrap_or_else(|| "0.0.0.0:7000".into())
92        .parse()
93        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
94    let capacity: u32 = std::env::args()
95        .nth(2)
96        .and_then(|s| s.parse().ok())
97        .unwrap_or(64);
98
99    // Build the pool once; every worker gets a clone of the `Send` handle.
100    let pool = Pool::builder().max_size(capacity).build(Backend);
101
102    let cores = cpu::cores();
103    if cores.is_empty() {
104        return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
105    }
106    println!(
107        "tcp_pool on {addr} -> upstream {}: {} workers, {capacity} upstreams/worker",
108        *UPSTREAM,
109        cores.len()
110    );
111
112    let mut handles = Vec::new();
113    for (index, core) in cores.into_iter().enumerate() {
114        let pool = pool.clone();
115        let handle = thread::Builder::new()
116            .name(format!("worker/{index}"))
117            .spawn(move || worker(core, addr, pool))?;
118        handles.push(handle);
119    }
120    for handle in handles {
121        let _ = handle.join();
122    }
123    Ok(())
124}
examples/wal.rs (line 107)
95fn main() -> io::Result<()> {
96    let addr: SocketAddr = std::env::args()
97        .nth(1)
98        .unwrap_or_else(|| "0.0.0.0:7000".into())
99        .parse()
100        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
101    let capacity: u32 = std::env::args()
102        .nth(2)
103        .and_then(|s| s.parse().ok())
104        .unwrap_or(1024);
105
106    std::fs::create_dir_all(&*WAL_DIR)?;
107    let pool = Pool::builder().max_size(capacity).build(WalManager);
108
109    let cores = cpu::cores();
110    if cores.is_empty() {
111        return Err(io::Error::new(io::ErrorKind::Unsupported, "no cores to run on"));
112    }
113    println!(
114        "wal on {addr} -> {} (fdatasync: {}): {} workers, capacity {capacity}/worker",
115        WAL_DIR.display(),
116        if *SYNC { "on" } else { "off" },
117        cores.len()
118    );
119
120    let mut handles = Vec::new();
121    for (index, core) in cores.into_iter().enumerate() {
122        let pool = pool.clone();
123        let handle = thread::Builder::new()
124            .name(format!("worker/{index}"))
125            .spawn(move || worker(core, addr, pool))?;
126        handles.push(handle);
127    }
128    for handle in handles {
129        let _ = handle.join();
130    }
131    Ok(())
132}
examples/task_echo.rs (line 67)
56fn main() -> io::Result<()> {
57    let addr: SocketAddr = std::env::args()
58        .nth(1)
59        .unwrap_or_else(|| "127.0.0.1:7000".into())
60        .parse()
61        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
62    let capacity: u32 = std::env::args()
63        .nth(2)
64        .and_then(|s| s.parse().ok())
65        .unwrap_or(1024);
66
67    let pool = Pool::builder().max_size(capacity).build(Buffers);
68
69    // One runtime, one ring. Everything below runs on this single thread.
70    let runtime = Runtime::builder().build().expect("build compio runtime");
71    runtime.block_on(async move {
72        // This runtime's pool. `!Send`, so it and its buffers stay on this thread.
73        let local = pool.local();
74
75        let listener = TcpListener::bind(addr).await?;
76        println!("task_echo on {addr}: 1 runtime, capacity {capacity}");
77
78        // Accept forever; hand each connection to its own task on this same ring.
79        loop {
80            let Ok((stream, _peer)) = listener.accept().await else {
81                continue;
82            };
83            let _ = stream.set_nodelay(true);
84            compio::runtime::spawn(serve(local.clone(), stream)).detach();
85        }
86    })
87}
Source

pub fn max_size(&self) -> u32

The per-thread ceiling on open connections.

Source

pub fn local(&self) -> LocalPool<M>

Make this thread’s pool.

Each call creates an independent LocalPool bound to the calling thread. Call it once per compio thread and keep the handle; it is Clone (a cheap Rc bump) if several tasks on that thread need it. Because the returned pool is !Send, the compiler guarantees it — and the ring-bound connections it holds — never move to another thread.

Examples found in repository?
examples/tcp_pool.rs (line 133)
128fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<Backend>) {
129    cpu::pin_current(core);
130
131    let runtime = Runtime::builder().build().expect("build compio runtime");
132    runtime.block_on(async move {
133        let local = pool.local();
134
135        let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
136        let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
137
138        loop {
139            let Ok((stream, _peer)) = listener.accept().await else {
140                continue;
141            };
142            compio::runtime::spawn(serve(local.clone(), stream)).detach();
143        }
144    });
145}
More examples
Hide additional examples
examples/wal.rs (line 139)
134fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<WalManager>) {
135    cpu::pin_current(core);
136
137    let runtime = Runtime::builder().build().expect("build compio runtime");
138    runtime.block_on(async move {
139        let local = pool.local();
140
141        let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
142        let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
143
144        loop {
145            let Ok((stream, _peer)) = listener.accept().await else {
146                continue;
147            };
148            compio::runtime::spawn(serve(local.clone(), stream)).detach();
149        }
150    });
151}
examples/echo.rs (line 96)
89fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<Buffers>) {
90    cpu::pin_current(core);
91
92    let runtime = Runtime::builder().build().expect("build compio runtime");
93    runtime.block_on(async move {
94        // This thread's pool. `!Send`, so it — and its ring-bound buffers —
95        // cannot escape the thread.
96        let local = pool.local();
97
98        let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
99        let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
100
101        loop {
102            let Ok((stream, _peer)) = listener.accept().await else {
103                continue;
104            };
105            compio::runtime::spawn(serve(local.clone(), stream)).detach();
106        }
107    });
108}
examples/file_server.rs (line 104)
99fn worker(core: cpu::CoreId, addr: SocketAddr, pool: Pool<Buffers>) {
100    cpu::pin_current(core);
101
102    let runtime = Runtime::builder().build().expect("build compio runtime");
103    runtime.block_on(async move {
104        let local = pool.local();
105
106        let std_listener = bind_reuseport(addr, 1024, None).expect("bind SO_REUSEPORT");
107        let listener = TcpListener::from_std(std_listener).expect("wrap listener in ring");
108
109        loop {
110            let Ok((stream, _peer)) = listener.accept().await else {
111                continue;
112            };
113            // The reply is a size header followed by the body — two writes. Without
114            // TCP_NODELAY, Nagle holds the body waiting for the header's ACK, which
115            // the peer delays ~40 ms, so disable it on every accepted connection.
116            let _ = stream.set_nodelay(true);
117            compio::runtime::spawn(serve(local.clone(), stream)).detach();
118        }
119    });
120}
examples/task_echo.rs (line 73)
56fn main() -> io::Result<()> {
57    let addr: SocketAddr = std::env::args()
58        .nth(1)
59        .unwrap_or_else(|| "127.0.0.1:7000".into())
60        .parse()
61        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad address: {e}")))?;
62    let capacity: u32 = std::env::args()
63        .nth(2)
64        .and_then(|s| s.parse().ok())
65        .unwrap_or(1024);
66
67    let pool = Pool::builder().max_size(capacity).build(Buffers);
68
69    // One runtime, one ring. Everything below runs on this single thread.
70    let runtime = Runtime::builder().build().expect("build compio runtime");
71    runtime.block_on(async move {
72        // This runtime's pool. `!Send`, so it and its buffers stay on this thread.
73        let local = pool.local();
74
75        let listener = TcpListener::bind(addr).await?;
76        println!("task_echo on {addr}: 1 runtime, capacity {capacity}");
77
78        // Accept forever; hand each connection to its own task on this same ring.
79        loop {
80            let Ok((stream, _peer)) = listener.accept().await else {
81                continue;
82            };
83            let _ = stream.set_nodelay(true);
84            compio::runtime::spawn(serve(local.clone(), stream)).detach();
85        }
86    })
87}
examples/unix_echo.rs (line 76)
51fn main() -> io::Result<()> {
52    let path = std::env::args()
53        .nth(1)
54        .unwrap_or_else(|| "/tmp/compio-pool-echo.sock".into());
55    let capacity: u32 = std::env::args()
56        .nth(2)
57        .and_then(|s| s.parse().ok())
58        .unwrap_or(1024);
59
60    // A stale socket file from a previous run would make `bind` fail with
61    // EADDRINUSE, so clear it first.
62    let _ = std::fs::remove_file(&path);
63
64    let pool = Pool::builder().max_size(capacity).build(Buffers);
65
66    // One listener, so one worker. Pin it to the first permitted core so the ring
67    // and the socket share a CPU, the same discipline the fan-out examples apply
68    // per thread.
69    if let Some(core) = cpu::cores().into_iter().next() {
70        cpu::pin_current(core);
71    }
72    println!("unix_echo on {path}: 1 worker, capacity {capacity}");
73
74    let runtime = Runtime::builder().build().expect("build compio runtime");
75    runtime.block_on(async move {
76        let local = pool.local();
77        let listener = UnixListener::bind(&path).await?;
78
79        loop {
80            let Ok((stream, _peer)) = listener.accept().await else {
81                continue;
82            };
83            compio::runtime::spawn(serve(local.clone(), stream)).detach();
84        }
85    })
86}
Source

pub fn close(&self)

Shut the pool down for every thread.

After this, each thread’s LocalPool::get fails immediately with RunError::Closed, a returned PooledConnection is dropped instead of recycled, and warm / maintain become no-ops. Connections already checked out keep working until their holders drop them.

The flag is shared, but the wakers that unpark a task waiting in get are not — they live on each worker’s own thread. A task already parked waiting for a slot therefore unblocks on its next local wake (another connection returning, or LocalPool::clear) or when its connection_timeout elapses, not the instant close is called from another thread. For a prompt drain, stop each worker’s accept loop and call LocalPool::clear on its thread.

Source

pub fn is_closed(&self) -> bool

Whether close has been called.

Source

pub fn state(&self) -> State

A process-wide snapshot: the live connection, idle and waiter counts summed across every thread’s LocalPool. Takes the registry lock, so call it off the hot path (a metrics scrape, a health endpoint). For one thread’s numbers use LocalPool::state.

Source

pub fn statistics(&self) -> Statistics

Process-wide cumulative counters summed across every thread’s LocalPool. Like state it takes the registry lock and is meant for a scrape, not the hot path. For one thread’s totals use LocalPool::statistics.

Trait Implementations§

Source§

impl<M: ManageConnection> Clone for Pool<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 Pool<M>

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<M> Freeze for Pool<M>
where Arc<Shared<M>>: Freeze,

§

impl<M> RefUnwindSafe for Pool<M>
where Arc<Shared<M>>: RefUnwindSafe,

§

impl<M> Send for Pool<M>
where Arc<Shared<M>>: Send,

§

impl<M> Sync for Pool<M>
where Arc<Shared<M>>: Sync,

§

impl<M> Unpin for Pool<M>
where Arc<Shared<M>>: Unpin,

§

impl<M> UnsafeUnpin for Pool<M>
where Arc<Shared<M>>: UnsafeUnpin,

§

impl<M> UnwindSafe for Pool<M>
where Arc<Shared<M>>: UnwindSafe,

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