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>
impl<M: ManageConnection> Pool<M>
Sourcepub fn builder() -> Builder<M>
pub fn builder() -> Builder<M>
Start configuring a pool.
Examples found in repository?
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
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}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}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}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}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}Sourcepub fn local(&self) -> LocalPool<M>
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?
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
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}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}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}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}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}Sourcepub fn close(&self)
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.
Sourcepub fn state(&self) -> State
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.
Sourcepub fn statistics(&self) -> Statistics
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>
impl<M: ManageConnection> Clone for Pool<M>
Auto Trait Implementations§
impl<M> Freeze for Pool<M>
impl<M> RefUnwindSafe for Pool<M>where
Arc<Shared<M>>: RefUnwindSafe,
impl<M> Send for Pool<M>
impl<M> Sync for Pool<M>
impl<M> Unpin for Pool<M>
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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