* [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
@ 2026-02-12 8:17 ` Shivam Kalra via B4 Relay
2026-02-12 10:08 ` Danilo Krummrich
2026-02-12 8:17 ` [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
2 siblings, 1 reply; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
Implement shrink_to method specifically for `KVVec` (i.e., `Vec<T, KVmalloc>`).
`shrink_to` reduces the vector's capacity to a specified minimum, but
only performs shrinking when it would free at least one page of memory.
This prevents unnecessary allocations while allowing reclamation of
unused memory.
The implementation uses explicit alloc+copy+free because `vrealloc`
does not yet support in-place shrinking. A TODO note marks this for
future replacement with a generic shrink_to for all allocators that
uses `A::realloc()` once the underlying allocators properly support
shrinking via realloc.
Suggested-by: Alice Ryhl <aliceryhl@google.com>
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
rust/kernel/alloc/kvec.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index ac8d6f763ae81..8524f9f3dff0a 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -9,7 +9,7 @@
};
use crate::{
fmt,
- page::AsPageIter, //
+ page::{AsPageIter, PAGE_SIZE},
};
use core::{
borrow::{Borrow, BorrowMut},
@@ -734,6 +734,82 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
self.truncate(num_kept);
}
}
+// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
+// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
+// underlying allocators properly support shrinking via realloc.
+impl<T> Vec<T, KVmalloc> {
+ /// Shrinks the capacity of the vector with a lower bound.
+ ///
+ /// The capacity will remain at least as large as both the length and the supplied value.
+ /// If the current capacity is less than the lower limit, this is a no-op.
+ ///
+ /// Shrinking only occurs if the operation would free at least one page of memory.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// // Allocate enough capacity to span multiple pages.
+ /// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
+ /// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
+ /// v.push(1, GFP_KERNEL)?;
+ /// v.push(2, GFP_KERNEL)?;
+ ///
+ /// v.shrink_to(0, GFP_KERNEL)?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
+ let target_cap = core::cmp::max(self.len(), min_capacity);
+
+ if self.capacity() <= target_cap {
+ return Ok(());
+ }
+
+ if Self::is_zst() {
+ return Ok(());
+ }
+
+ // Only shrink if we would free at least one page.
+ let current_size = self.capacity() * core::mem::size_of::<T>();
+ let target_size = target_cap * core::mem::size_of::<T>();
+ let current_pages = current_size.div_ceil(PAGE_SIZE);
+ let target_pages = target_size.div_ceil(PAGE_SIZE);
+
+ if current_pages <= target_pages {
+ return Ok(());
+ }
+
+ if target_cap == 0 {
+ if !self.layout.is_empty() {
+ // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
+ unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
+ }
+ self.ptr = NonNull::dangling();
+ self.layout = ArrayLayout::empty();
+ return Ok(());
+ }
+
+ // SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
+ let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
+
+ // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
+ // explicit alloc+copy+free can potentially be replaced with realloc.
+ let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
+
+ // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
+ unsafe {
+ ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
+ }
+
+ // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
+ unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
+
+ // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
+ self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
+ self.layout = new_layout;
+
+ Ok(())
+ }
+}
impl<T: Clone, A: Allocator> Vec<T, A> {
/// Extend the vector by `n` clones of `value`.
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
@ 2026-02-12 10:08 ` Danilo Krummrich
2026-02-13 13:00 ` Shivam Kalra
0 siblings, 1 reply; 8+ messages in thread
From: Danilo Krummrich @ 2026-02-12 10:08 UTC (permalink / raw)
To: Shivam Kalra via B4 Relay
Cc: shivamkalra98, Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On Thu Feb 12, 2026 at 9:17 AM CET, Shivam Kalra via B4 Relay wrote:
> diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
> index ac8d6f763ae81..8524f9f3dff0a 100644
> --- a/rust/kernel/alloc/kvec.rs
> +++ b/rust/kernel/alloc/kvec.rs
> @@ -9,7 +9,7 @@
> };
> use crate::{
> fmt,
> - page::AsPageIter, //
> + page::{AsPageIter, PAGE_SIZE},
Please keep the kernel import style [1].
[1] https://docs.kernel.org/rust/coding-guidelines.html#imports
> };
> use core::{
> borrow::{Borrow, BorrowMut},
> @@ -734,6 +734,82 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
> self.truncate(num_kept);
> }
> }
> +// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
> +// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
> +// underlying allocators properly support shrinking via realloc.
> +impl<T> Vec<T, KVmalloc> {
> + /// Shrinks the capacity of the vector with a lower bound.
> + ///
> + /// The capacity will remain at least as large as both the length and the supplied value.
> + /// If the current capacity is less than the lower limit, this is a no-op.
> + ///
> + /// Shrinking only occurs if the operation would free at least one page of memory.
Since this is now specific to Vec<T, KVmalloc>, the correct statement would be
that for kmalloc() allocations realloc() takes the decision and for vmalloc()
allocations it will only be shrunk if at least one page can be freed. Please
also add that in the vmalloc() case, we are doing a deep copy.
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// // Allocate enough capacity to span multiple pages.
> + /// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
> + /// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
> + /// v.push(1, GFP_KERNEL)?;
> + /// v.push(2, GFP_KERNEL)?;
> + ///
> + /// v.shrink_to(0, GFP_KERNEL)?;
> + /// # Ok::<(), Error>(())
> + /// ```
> + pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
> + let target_cap = core::cmp::max(self.len(), min_capacity);
> +
> + if self.capacity() <= target_cap {
> + return Ok(());
> + }
> +
> + if Self::is_zst() {
> + return Ok(());
> + }
We should check for is_vmalloc_addr() here and just call realloc() if it is
false.
The reason is that this will ensure that the semantics of this function will be
as close as possible to the final implementation, which will fully rely on
realloc().
I also understand that calling realloc() if !is_vmalloc_addr() seems
superfluous, but I don't want this code to make assumptions about the semantics
of the backing allocators.
> + // Only shrink if we would free at least one page.
> + let current_size = self.capacity() * core::mem::size_of::<T>();
> + let target_size = target_cap * core::mem::size_of::<T>();
> + let current_pages = current_size.div_ceil(PAGE_SIZE);
> + let target_pages = target_size.div_ceil(PAGE_SIZE);
> +
> + if current_pages <= target_pages {
> + return Ok(());
> + }
> +
> + if target_cap == 0 {
> + if !self.layout.is_empty() {
> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
> + }
> + self.ptr = NonNull::dangling();
> + self.layout = ArrayLayout::empty();
> + return Ok(());
> + }
> +
> + // SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
> + let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
> +
> + // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
> + // explicit alloc+copy+free can potentially be replaced with realloc.
I'd drop this comment as it reads as if this function should be changed, even
though the TODO comment above already explains that it should be replaced.
> + let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
> +
> + // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
If a safety comment justifies multiple things, we usually structure it as a
list.
> + unsafe {
> + ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
NIT: Please move the semicolon at the end of the unsafe block.
> + }
> +
> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
Same here.
> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
> +
> + // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
> + self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
> + self.layout = new_layout;
> +
> + Ok(())
> + }
> +}
>
> impl<T: Clone, A: Allocator> Vec<T, A> {
> /// Extend the vector by `n` clones of `value`.
>
> --
> 2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
2026-02-12 10:08 ` Danilo Krummrich
@ 2026-02-13 13:00 ` Shivam Kalra
0 siblings, 0 replies; 8+ messages in thread
From: Shivam Kalra @ 2026-02-13 13:00 UTC (permalink / raw)
To: Danilo Krummrich, Shivam Kalra via B4 Relay
Cc: Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On 12/02/26 15:38, Danilo Krummrich wrote:
> On Thu Feb 12, 2026 at 9:17 AM CET, Shivam Kalra via B4 Relay wrote:
>> diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
>> index ac8d6f763ae81..8524f9f3dff0a 100644
>> --- a/rust/kernel/alloc/kvec.rs
>> +++ b/rust/kernel/alloc/kvec.rs
>> @@ -9,7 +9,7 @@
>> };
>> use crate::{
>> fmt,
>> - page::AsPageIter, //
>> + page::{AsPageIter, PAGE_SIZE},
>
> Please keep the kernel import style [1].
>
> [1] https://docs.kernel.org/rust/coding-guidelines.html#imports
>
>> };
>> use core::{
>> borrow::{Borrow, BorrowMut},
>> @@ -734,6 +734,82 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
>> self.truncate(num_kept);
>> }
>> }
>> +// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
>> +// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
>> +// underlying allocators properly support shrinking via realloc.
>> +impl<T> Vec<T, KVmalloc> {
>> + /// Shrinks the capacity of the vector with a lower bound.
>> + ///
>> + /// The capacity will remain at least as large as both the length and the supplied value.
>> + /// If the current capacity is less than the lower limit, this is a no-op.
>> + ///
>> + /// Shrinking only occurs if the operation would free at least one page of memory.
>
> Since this is now specific to Vec<T, KVmalloc>, the correct statement would be
> that for kmalloc() allocations realloc() takes the decision and for vmalloc()
> allocations it will only be shrunk if at least one page can be freed. Please
> also add that in the vmalloc() case, we are doing a deep copy.
>
>> + ///
>> + /// # Examples
>> + ///
>> + /// ```
>> + /// // Allocate enough capacity to span multiple pages.
>> + /// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
>> + /// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
>> + /// v.push(1, GFP_KERNEL)?;
>> + /// v.push(2, GFP_KERNEL)?;
>> + ///
>> + /// v.shrink_to(0, GFP_KERNEL)?;
>> + /// # Ok::<(), Error>(())
>> + /// ```
>> + pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
>> + let target_cap = core::cmp::max(self.len(), min_capacity);
>> +
>> + if self.capacity() <= target_cap {
>> + return Ok(());
>> + }
>> +
>> + if Self::is_zst() {
>> + return Ok(());
>> + }
>
> We should check for is_vmalloc_addr() here and just call realloc() if it is
> false.
>
> The reason is that this will ensure that the semantics of this function will be
> as close as possible to the final implementation, which will fully rely on
> realloc().
>
> I also understand that calling realloc() if !is_vmalloc_addr() seems
> superfluous, but I don't want this code to make assumptions about the semantics
> of the backing allocators.
>
>> + // Only shrink if we would free at least one page.
>> + let current_size = self.capacity() * core::mem::size_of::<T>();
>> + let target_size = target_cap * core::mem::size_of::<T>();
>> + let current_pages = current_size.div_ceil(PAGE_SIZE);
>> + let target_pages = target_size.div_ceil(PAGE_SIZE);
>> +
>> + if current_pages <= target_pages {
>> + return Ok(());
>> + }
>> +
>> + if target_cap == 0 {
>> + if !self.layout.is_empty() {
>> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
>> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
>> + }
>> + self.ptr = NonNull::dangling();
>> + self.layout = ArrayLayout::empty();
>> + return Ok(());
>> + }
>> +
>> + // SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
>> + let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
>> +
>> + // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
>> + // explicit alloc+copy+free can potentially be replaced with realloc.
>
> I'd drop this comment as it reads as if this function should be changed, even
> though the TODO comment above already explains that it should be replaced.
>
>> + let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
>> +
>> + // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
>
> If a safety comment justifies multiple things, we usually structure it as a
> list.
>
>> + unsafe {
>> + ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
>
> NIT: Please move the semicolon at the end of the unsafe block.
>
>> + }
>> +
>> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
>
> Same here.
>
>> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
>> +
>> + // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
>> + self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
>> + self.layout = new_layout;
>> +
>> + Ok(())
>> + }
>> +}
>>
>> impl<T: Clone, A: Allocator> Vec<T, A> {
>> /// Extend the vector by `n` clones of `value`.
>>
>> --
>> 2.43.0
>
Thanks for the feedback.
I will soon send an update on the v5 patch.
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
@ 2026-02-12 8:17 ` Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
2 siblings, 0 replies; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
Add comprehensive KUnit tests for the shrink_to method for KVVec.
The tests verify:
- Basic shrinking from multiple pages to fewer pages with data integrity
preservation
- Empty vector shrinking to zero capacity
- No-op behavior when shrinking to a larger capacity than current
- Respect for min_capacity parameter when larger than vector length
These tests ensure that the shrinking logic correctly identifies when
memory can be reclaimed (by freeing at least one page) and that data
integrity is maintained throughout shrink operations.
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
rust/kernel/alloc/kvec.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 112 insertions(+)
diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index 8524f9f3dff0..d28f8fccd30f 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -1474,4 +1474,116 @@ fn add(value: &mut [bool]) {
func.push_within_capacity(false).unwrap();
}
}
+
+ #[test]
+ fn test_kvvec_shrink_to() {
+ use crate::page::PAGE_SIZE;
+
+ // Calculate elements per page for u32.
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u32>();
+
+ // Create a vector with capacity spanning multiple pages.
+ let mut v = KVVec::<u32>::with_capacity(elements_per_page * 4, GFP_KERNEL).unwrap();
+
+ // Add a few elements.
+ v.push(1, GFP_KERNEL).unwrap();
+ v.push(2, GFP_KERNEL).unwrap();
+ v.push(3, GFP_KERNEL).unwrap();
+
+ let initial_capacity = v.capacity();
+ assert!(initial_capacity >= elements_per_page * 4);
+
+ // Shrink to a capacity that would free at least one page.
+ v.shrink_to(elements_per_page, GFP_KERNEL).unwrap();
+
+ // Capacity should have been reduced.
+ assert!(v.capacity() < initial_capacity);
+ assert!(v.capacity() >= elements_per_page);
+
+ // Elements should be preserved.
+ assert_eq!(v.len(), 3);
+ assert_eq!(v[0], 1);
+ assert_eq!(v[1], 2);
+ assert_eq!(v[2], 3);
+
+ // Shrink to zero (should shrink to len).
+ v.shrink_to(0, GFP_KERNEL).unwrap();
+
+ // Capacity should be at least the length.
+ assert!(v.capacity() >= v.len());
+
+ // Elements should still be preserved.
+ assert_eq!(v.len(), 3);
+ assert_eq!(v[0], 1);
+ assert_eq!(v[1], 2);
+ assert_eq!(v[2], 3);
+ }
+
+ #[test]
+ fn test_kvvec_shrink_to_empty() {
+ use crate::page::PAGE_SIZE;
+
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u64>();
+
+ // Create a vector with large capacity but no elements.
+ let mut v = KVVec::<u64>::with_capacity(elements_per_page * 4, GFP_KERNEL).unwrap();
+
+ assert!(v.is_empty());
+ let initial_capacity = v.capacity();
+
+ // Shrink empty vector to zero.
+ v.shrink_to(0, GFP_KERNEL).unwrap();
+
+ // Should have freed the allocation.
+ assert!(v.capacity() < initial_capacity);
+ assert!(v.is_empty());
+ }
+
+ #[test]
+ fn test_kvvec_shrink_to_no_op() {
+ use crate::page::PAGE_SIZE;
+
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u32>();
+
+ // Create a small vector.
+ let mut v = KVVec::<u32>::with_capacity(elements_per_page, GFP_KERNEL).unwrap();
+ v.push(1, GFP_KERNEL).unwrap();
+
+ let capacity_before = v.capacity();
+
+ // Try to shrink to a capacity larger than current - should be no-op.
+ v.shrink_to(capacity_before + 100, GFP_KERNEL).unwrap();
+
+ assert_eq!(v.capacity(), capacity_before);
+ assert_eq!(v.len(), 1);
+ assert_eq!(v[0], 1);
+ }
+
+ #[test]
+ fn test_kvvec_shrink_to_respects_min_capacity() {
+ use crate::page::PAGE_SIZE;
+
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u32>();
+
+ // Create a vector with large capacity.
+ let mut v = KVVec::<u32>::with_capacity(elements_per_page * 4, GFP_KERNEL).unwrap();
+
+ // Add some elements.
+ for i in 0..10 {
+ v.push(i, GFP_KERNEL).unwrap();
+ }
+
+ // Shrink to a min_capacity larger than length.
+ let min_cap = elements_per_page * 2;
+ v.shrink_to(min_cap, GFP_KERNEL).unwrap();
+
+ // Capacity should be at least min_capacity.
+ assert!(v.capacity() >= min_cap);
+
+ // All elements preserved.
+ assert_eq!(v.len(), 10);
+ for i in 0..10 {
+ assert_eq!(v[i as usize], i);
+ }
+ }
}
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to Shivam Kalra via B4 Relay
@ 2026-02-12 8:17 ` Shivam Kalra via B4 Relay
2026-02-13 16:19 ` Alice Ryhl
2 siblings, 1 reply; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
When a process is deregistered from the binder context, the all_procs
vector may have significant unused capacity. Add logic to shrink the
vector using a conservative strategy that prevents shrink-then-regrow
oscillation.
The shrinking strategy triggers when length drops below 1/4 of capacity,
and shrinks to 1/2 of capacity rather than to the exact length. This
provides hysteresis to avoid repeated reallocations when the process
count fluctuates.
The shrink operation uses GFP_KERNEL and is allowed to fail gracefully
since it is purely an optimization. The vector remains valid and
functional even if shrinking fails.
Suggested-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
drivers/android/binder/context.rs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
index 9cf437c025a20..399dab475728b 100644
--- a/drivers/android/binder/context.rs
+++ b/drivers/android/binder/context.rs
@@ -94,6 +94,17 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
}
let mut manager = self.manager.lock();
manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc));
+
+ // Shrink the vector if it has significant unused capacity to avoid memory waste,
+ // but use a conservative strategy to prevent shrink-then-regrow oscillation.
+ // Only shrink when length drops below 1/4 of capacity, and shrink to 1/2 capacity.
+ let len = manager.all_procs.len();
+ let cap = manager.all_procs.capacity();
+ if len < cap / 4 {
+ // Shrink to half capacity. Ignore allocation failures since this is just an
+ // optimization; the vector remains valid even if shrinking fails.
+ let _ = manager.all_procs.shrink_to(cap / 2, GFP_KERNEL);
+ }
}
pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result {
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
@ 2026-02-13 16:19 ` Alice Ryhl
2026-02-14 7:42 ` Shivam Kalra
0 siblings, 1 reply; 8+ messages in thread
From: Alice Ryhl @ 2026-02-13 16:19 UTC (permalink / raw)
To: Shivam Kalra
Cc: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On Thu, Feb 12, 2026 at 01:47:09PM +0530, Shivam Kalra wrote:
> When a process is deregistered from the binder context, the all_procs
> vector may have significant unused capacity. Add logic to shrink the
> vector using a conservative strategy that prevents shrink-then-regrow
> oscillation.
> The shrinking strategy triggers when length drops below 1/4 of capacity,
> and shrinks to 1/2 of capacity rather than to the exact length. This
> provides hysteresis to avoid repeated reallocations when the process
> count fluctuates.
> The shrink operation uses GFP_KERNEL and is allowed to fail gracefully
> since it is purely an optimization. The vector remains valid and
> functional even if shrinking fails.
>
> Suggested-by: Alice Ryhl <aliceryhl@google.com>
> Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
> ---
> drivers/android/binder/context.rs | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
> index 9cf437c025a20..399dab475728b 100644
> --- a/drivers/android/binder/context.rs
> +++ b/drivers/android/binder/context.rs
> @@ -94,6 +94,17 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
> }
> let mut manager = self.manager.lock();
> manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc));
> +
> + // Shrink the vector if it has significant unused capacity to avoid memory waste,
> + // but use a conservative strategy to prevent shrink-then-regrow oscillation.
> + // Only shrink when length drops below 1/4 of capacity, and shrink to 1/2 capacity.
> + let len = manager.all_procs.len();
> + let cap = manager.all_procs.capacity();
> + if len < cap / 4 {
> + // Shrink to half capacity. Ignore allocation failures since this is just an
> + // optimization; the vector remains valid even if shrinking fails.
> + let _ = manager.all_procs.shrink_to(cap / 2, GFP_KERNEL);
Not a big deal, but perhaps we should write len*2 here instead of cap/2?
That way, if the cap got way far away from the length, it would equalize
right away.
Alice
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes
2026-02-13 16:19 ` Alice Ryhl
@ 2026-02-14 7:42 ` Shivam Kalra
0 siblings, 0 replies; 8+ messages in thread
From: Shivam Kalra @ 2026-02-14 7:42 UTC (permalink / raw)
To: Alice Ryhl
Cc: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On 13/02/26 21:49, Alice Ryhl wrote:
> On Thu, Feb 12, 2026 at 01:47:09PM +0530, Shivam Kalra wrote:
>> When a process is deregistered from the binder context, the all_procs
>> vector may have significant unused capacity. Add logic to shrink the
>> vector using a conservative strategy that prevents shrink-then-regrow
>> oscillation.
>> The shrinking strategy triggers when length drops below 1/4 of capacity,
>> and shrinks to 1/2 of capacity rather than to the exact length. This
>> provides hysteresis to avoid repeated reallocations when the process
>> count fluctuates.
>> The shrink operation uses GFP_KERNEL and is allowed to fail gracefully
>> since it is purely an optimization. The vector remains valid and
>> functional even if shrinking fails.
>>
>> Suggested-by: Alice Ryhl <aliceryhl@google.com>
>> Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
>> ---
>> drivers/android/binder/context.rs | 11 +++++++++++
>> 1 file changed, 11 insertions(+)
>>
>> diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
>> index 9cf437c025a20..399dab475728b 100644
>> --- a/drivers/android/binder/context.rs
>> +++ b/drivers/android/binder/context.rs
>> @@ -94,6 +94,17 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
>> }
>> let mut manager = self.manager.lock();
>> manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc));
>> +
>> + // Shrink the vector if it has significant unused capacity to avoid memory waste,
>> + // but use a conservative strategy to prevent shrink-then-regrow oscillation.
>> + // Only shrink when length drops below 1/4 of capacity, and shrink to 1/2 capacity.
>> + let len = manager.all_procs.len();
>> + let cap = manager.all_procs.capacity();
>> + if len < cap / 4 {
>> + // Shrink to half capacity. Ignore allocation failures since this is just an
>> + // optimization; the vector remains valid even if shrinking fails.
>> + let _ = manager.all_procs.shrink_to(cap / 2, GFP_KERNEL);
>
> Not a big deal, but perhaps we should write len*2 here instead of cap/2?
> That way, if the cap got way far away from the length, it would equalize
> right away.
>
> Alice
Thanks for the feedback. Will add this in v5.
^ permalink raw reply [flat|nested] 8+ messages in thread