From patchwork Thu Sep 12 09:54:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alice Ryhl X-Patchwork-Id: 13801836 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2467AEEB582 for ; Thu, 12 Sep 2024 09:54:14 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id AD9036B007B; Thu, 12 Sep 2024 05:54:13 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id A89466B0082; Thu, 12 Sep 2024 05:54:13 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 950A26B0083; Thu, 12 Sep 2024 05:54:13 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0012.hostedemail.com [216.40.44.12]) by kanga.kvack.org (Postfix) with ESMTP id 714D86B007B for ; Thu, 12 Sep 2024 05:54:13 -0400 (EDT) Received: from smtpin30.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay06.hostedemail.com (Postfix) with ESMTP id 2354EAB0D5 for ; Thu, 12 Sep 2024 09:54:13 +0000 (UTC) X-FDA: 82555625586.30.5E99180 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) by imf30.hostedemail.com (Postfix) with ESMTP id 5545180002 for ; Thu, 12 Sep 2024 09:54:11 +0000 (UTC) Authentication-Results: imf30.hostedemail.com; dkim=pass header.d=google.com header.s=20230601 header.b=ODWvcPsW; dmarc=pass (policy=reject) header.from=google.com; spf=pass (imf30.hostedemail.com: domain of 3QrriZgkKCH8dolfhu1kojrrjoh.frpolqx0-ppnydfn.ruj@flex--aliceryhl.bounces.google.com designates 209.85.219.201 as permitted sender) smtp.mailfrom=3QrriZgkKCH8dolfhu1kojrrjoh.frpolqx0-ppnydfn.ruj@flex--aliceryhl.bounces.google.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1726134734; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:content-transfer-encoding:in-reply-to: references:dkim-signature; bh=y47+knSopch6MhZlM7gRYsV4lTxYgglb2/fdjTOFy0s=; b=YYLgGtn7LVTa9r56bBu4aR0laOE3KFhURh4cXDD8D9DOy6f6myQrFmMxafs6wm1OSNKqYl 2tBnzref7aYbrdx5gYFqf9ygpmQfu3FpowLSVMgwxG7vVN/CDrC4+DLEWJZGjB23Kt7mdD 8B0H0i2GKw7p4V9cP68DuJLigWVsLIg= ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1726134734; a=rsa-sha256; cv=none; b=2qaQqTo3V94F/5Jh5wDNDAT9wPYrP7uLSW9xs85M/W1IRp8Q9slG9NAwI8zwyRwhiro4kJ EJEHLbjCYZYU4bvlDb+qjRRN0OUWXg12jEpI3ZT5jDBxXO37Rcy2d8YGgifKEyoOf6iytO TYeg0SIFMLd3XgQZvtpIynUTGStRxWk= ARC-Authentication-Results: i=1; imf30.hostedemail.com; dkim=pass header.d=google.com header.s=20230601 header.b=ODWvcPsW; dmarc=pass (policy=reject) header.from=google.com; spf=pass (imf30.hostedemail.com: domain of 3QrriZgkKCH8dolfhu1kojrrjoh.frpolqx0-ppnydfn.ruj@flex--aliceryhl.bounces.google.com designates 209.85.219.201 as permitted sender) smtp.mailfrom=3QrriZgkKCH8dolfhu1kojrrjoh.frpolqx0-ppnydfn.ruj@flex--aliceryhl.bounces.google.com Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-e1ce98dcafaso1431415276.0 for ; Thu, 12 Sep 2024 02:54:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1726134850; x=1726739650; darn=kvack.org; h=cc:to:from:subject:message-id:mime-version:date:from:to:cc:subject :date:message-id:reply-to; bh=y47+knSopch6MhZlM7gRYsV4lTxYgglb2/fdjTOFy0s=; b=ODWvcPsW6EfJTC7e8N7rRXzCTALB36WLv7KKekjkSWFbR7sZq7D7B5wHMSfTTHDX3h eOyLDY3Ux6YdxgSjVHbXryJ7IRPfAKTIuBM/uxwb8YoivloExReiVlvHAYgNp2rmlOr4 9Mgg/BxXIXtqTuCl219kjeC/nsC+8uAsZNnbIhAyasq7xI4zWNSMvIMnx7iJOLYlBP8R agftZaYtMs1YcFxN70fmLxKWbN/6IW2HSiCY5/DKeDjEk6GfFAAupMYeDEkUHjOyUQ3c EPXXmGDaWlUEpq7+QqGJ1WzB2tltoSQhFPuBwrF0J4aWKNa8A2LrZVTBBRQTgooQ3QFw z4FQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1726134850; x=1726739650; h=cc:to:from:subject:message-id:mime-version:date:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=y47+knSopch6MhZlM7gRYsV4lTxYgglb2/fdjTOFy0s=; b=ptTFU86QPrI4eImK7BR3NBffNwaD7hVyzjGVHw9euQRw1U6GqDTuUOoZ0CIY8ifYin dvvSI+1foloDm7A4DT/P0Go0xs9SPI+QA/yCr6BjG7BPkETuflVVn9NKbKoCE6o8tRDY vbqQx23Q7GR0sHFjjVNVm4TZjV/ycVtG6ytWhEt0dS6NjDC76fGmuW6yVpDmFCU3BcUS ZaoOm+mu6qNDktvueR3GCAA+4437oKF5qpIGGGfXIXIfd2O4Q3SxnhXlVH0zEXyFvx6f ajYa4pw3kyFoy1gbnBEUOQkHs5fb3tpyWQDhRombX7mxyXp7WawIqZ7a2PZhKL3kCKVV 7uEw== X-Forwarded-Encrypted: i=1; AJvYcCVhWvzvKy3FaZh/B/L7KIsitwgI5/zWHKLQ0VQMTbuUJFV912irGLpXKB/VN9U9vTtt3jPoKh5LSw==@kvack.org X-Gm-Message-State: AOJu0YyT/uuSjVTVvuaRkvJi12mwF2SM4Eo4RPu0s5n6dWhvC3n++hN9 F9oH2v4qBXyMwmNa95qLpNM+duTN9xy4f8O1bnZNcXjSALc6yx+yTq7i6fyJSsQoIwA5ULVLmQ6 6tilymoysEop4nQ== X-Google-Smtp-Source: AGHT+IGxBUYW4WXIt0Du7NN6h0AQoZ+X885KZUKy7wn0+ZIqugsb7M3Aqv1IehsgFHQqs5OS06P051LdlbXBlgY= X-Received: from aliceryhl.c.googlers.com ([fda3:e722:ac3:cc00:28:9cb1:c0a8:35bd]) (user=aliceryhl job=sendgmr) by 2002:a25:d60d:0:b0:e16:4d66:982e with SMTP id 3f1490d57ef6-e1d9dbd061amr4020276.5.1726134850269; Thu, 12 Sep 2024 02:54:10 -0700 (PDT) Date: Thu, 12 Sep 2024 09:54:01 +0000 Mime-Version: 1.0 X-B4-Tracking: v=1; b=H4sIADi64mYC/6tWKk4tykwtVrJSqFYqSi3LLM7MzwNyDHUUlJIzE vPSU3UzU4B8JSMDIxMDS0ND3eKMosy87NQi3TQLY3PDxDQDgyQzCyWg8oKi1LTMCrBR0bG1tQB Vk15xWgAAAA== X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=15141; i=aliceryhl@google.com; h=from:subject:message-id; bh=Sw3I7jVln0A10IDphpPtCZCNDXDiNpNryEccSG2RrhI=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBm4ro/vmRiz/L5vsjbUOEYo+TO7O6YDluc2sJoG UOa8DQFWFOJAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCZuK6PwAKCRAEWL7uWMY5 RkjKD/4/nihXrQGnwhRzALuu1P3Vyxcu/Wm7qvc6fC2a4t1Z5WQh8Hnwn6KVvMbLThptMGm5Doy GaH/i7ov1+IxYVMTZEMqTHa7OD6lOPZQ4o6RQ/pLco5uK8kdQxZkGGO7J72buL74jRqRcZC0Yc3 M+UgrQJ+2uWqskXnhOMwH0trwmTJfVoNHMMwZ+1avuNvTBi/ZVdFd6yKljOFiv8T0BpClBds9HD IAK05W/hwKHWt75Pyuk9d4bs9vig4h/UdtSynkBehclgs6AoLK9QO5pFisCfVRL7YpnkW3CjhrJ L24Qw097Icl1scop1FuMDjGk77VeQJmBCwTW5+tKfBg9VGS0ttiDNU9AviLkTYrMWjtlr9obfFT CMPXPemyLM1v7I6feIIDZWmCG3SSiO3sPpW3cwmGCu9gCj7C37Xt7j9Mw8q2WgrkE7Ob63Xu4g8 0AjyJ5NQLoU4aOHdf80u0rFslNhu9HHgYEdl9nRNjBEgIOnmxcqjj7FNYBNI2TAyirnvMXP4mmK 7wG5N9n6OlArlG0IqKDtoKJBiIazhQbRtq/I8NNfGrk+GGWXD48xjm4tmjaqXA7ZOkjhjIvvYu3 J5xIxj5B8ZS8DLtGQLWr2YW0dPTwdSl48TGmM5tbREHgA3CdioHB+53Beibsl0knlLilS4X2KTQ LHBe/uR/vxLPPvg== X-Mailer: b4 0.13.0 Message-ID: <20240912-shrinker-v1-1-18b7f1253553@google.com> Subject: [PATCH] rust: shrinker: add shrinker abstraction From: Alice Ryhl To: Miguel Ojeda , Andrew Morton , Dave Chinner , Qi Zheng , Roman Gushchin , Muchun Song Cc: Boqun Feng , Gary Guo , " =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= " , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org, linux-mm@kvack.org, rust-for-linux@vger.kernel.org, Alice Ryhl X-Rspamd-Server: rspam07 X-Rspamd-Queue-Id: 5545180002 X-Stat-Signature: izaxkprypjunxmjyxmaijmsffd5zf15y X-Rspam-User: X-HE-Tag: 1726134851-385359 X-HE-Meta: U2FsdGVkX1/+moL/9oRXssMnPuJMNcAp6U8StiEdDZwu8rpHagly+NHyrpMQnXZckyWgfoauuKvRQ5ZU8DhinKhZCmwPuvjyOuhEweKzX6ID1jKNcQ5wdvnmagigEyFjbTOPfzCrYjYMTcsyUlXJaVroJtA11rxaTBak1wRN64KRVSKvhfM8HHJQkEHuu+NGYJSa+/kMizf4aVqXPFvOczOuvLI9GRdbOZW9HPgS9VH6+IGPBoslI65Ts73CRRDpHIu2m+8koy4ZJkoYk7DqfywLAuosMPp+5vTSKjE5ngcXHlpZ/6rfFB+zC6lZ9J+tPq8ZEDl3SzpJ+gAV3oCBJuHxe8iWgeg17U4m+iPIPdut1VV8hsW85FCsBiPrtg9IJkQq6yJy++7waxsAenQVNG1A2DPG8h/mAwTI+vEAWqkXhca+XwCTRLEVMzi0/eByN3oZ8Kbr70luBIzeUgdG0SarR6rtk7JB0vN6UTwajfJQeyAcmVaXzo+PTb1Fe+4OLwiEtRWyWIOr9L0R06hAtQaqGSV2DRFG8kxoNeuVvYplGy6r4yWL8ey0MG5vN9kZDQEUoq3W0QVbA7OYvFlW2l8GwgtgZp5b86F8bRv5AV4cJ5li8zXCY/qokHY8agT8U2LwWPz1y4cZ//PLtnuENTowankWOAvBIo3V/U6XWNPAzfoi3364cwEo8qTBhjMR8Em+6vVISh9uZA6KgayfpOEXpdykV2wwWLF9XAEVYQtZC5FCJC2xlp+MBdzeCo6znBlAVv/kRzXuY1cyVyhJwlQ7GgImrmk+I2fZz4hxSEFkm42Hg4OEZVpJkBgTkUpwNGo34Dz3v4+/fsr9bw8hzwnn44s3tW9xXr/EsdYIR56FhOREVFb7YFdvLqhFpBNNirjFGJjJXUmlxIlkj44tfA1nSWaOW+ox4RZ+Fscd9cpwrDX6SZ/RmFXRq7NSEwEgtG5sUCS1wVK0QcStpCl /4bRt06d 1pZGrfme3+lIrZCaubjRPQGOCI/KjgYGEmNRJpWY4TaitTdP05zm7pOMSDh0FRnX9+LOZFVx3O8yLV+Uw7r+FBsiK0g+H48E3gHKGY+ypG1QZRhBcjaBalaVWcKP4soORyqZPTQ2RxPvJpZmwU/vPk7IQ60jmNucqRtiXDqlvhhkcYtJ6n5eXHw7afdDc8WDPRiel+OLCva3Q61mqNxnKlDjnBUpnl81B5snBgNUr4G8keuh4n53zr46EN7iYeBbwE7IkpsR9rn62XWhvdE9Nxu6Lrg/CW7kVAxXTOjmuo1prMheHvz1+RBpjq7HWei38mYjahtr+oF+LhJE8Jteih4VXimvYszbO7bidHbKLAt0eVsYwRp7Jn3PVTpJ4WFfinM7IgrEnsabqAvXvVCAVlJRmmysjYC7RvH5aV7YU1dZHDezM2TvGbVXoBUgCzDcnJm0ovf8cAAsayUSoKXqh/qOPMdtEN+rR/gyS9y3tjA3QCr5jc4wewtfv2rTp6/2lZtapZSA/Hc6bG6WTcrZFyHTcQkhDp82GPb57AZrScpQWb38AjbpqG0yd33vxdnlP+tcUR15OM67uAnaVhpo8V65Dii8KpsmeP0iruJSD9H70QbeortchDccrNrIsfWPjGkmrqhUdhuZs1Sw= X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: Rust Binder holds incoming transactions in a read-only mmap'd region where it manually manages the pages. These pages are only in use until the incoming transaction is consumed by userspace, but the kernel will keep the pages around for future transactions. Rust Binder registers a shrinker with the kernel so that it can give back these pages if the system comes under memory pressure. Separate types are provided for registered and unregistered shrinkers. The unregistered shrinker type can be used to configure the shrinker before registering it. Separating it into two types also enables the user to construct the private data between the calls to `shrinker_alloc` and `shrinker_register` and avoid constructing the private data if allocating the shrinker fails. The user specifies the callbacks in use by implementing the Shrinker trait for the type used for the private data. This requires specifying three things: implementations for count_objects and scan_objects, and the pointer type that the private data will be wrapped in. The return values of count_objects and scan_objects are provided using new types called CountObjects and ScanObjects respectively. These types prevent the user from e.g. returning SHRINK_STOP from count_objects or returning SHRINK_EMPTY from scan_objects. Signed-off-by: Alice Ryhl --- rust/bindings/bindings_helper.h | 2 + rust/kernel/lib.rs | 1 + rust/kernel/shrinker.rs | 324 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+) --- base-commit: 93dc3be19450447a3a7090bd1dfb9f3daac3e8d2 change-id: 20240911-shrinker-f8371af00b68 Best regards, diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index ae82e9c941af..7fc958e05dc5 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -31,4 +32,5 @@ const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT; const gfp_t RUST_CONST_HELPER_GFP_NOWAIT = GFP_NOWAIT; const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO; const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM; +const gfp_t RUST_CONST_HELPER___GFP_FS = ___GFP_FS; const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL = BLK_FEAT_ROTATIONAL; diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index f10b06a78b9d..f35eb290f2e0 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -45,6 +45,7 @@ pub mod prelude; pub mod print; pub mod rbtree; +pub mod shrinker; mod static_assert; #[doc(hidden)] pub mod std_vendor; diff --git a/rust/kernel/shrinker.rs b/rust/kernel/shrinker.rs new file mode 100644 index 000000000000..9af726bfe0b1 --- /dev/null +++ b/rust/kernel/shrinker.rs @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Google LLC. + +//! Shrinker for handling memory pressure. +//! +//! C header: [`include/linux/shrinker.h`](srctree/include/linux/shrinker.h) + +use crate::{alloc::AllocError, bindings, c_str, str::CStr, types::ForeignOwnable}; + +use core::{ + ffi::{c_int, c_ulong, c_void}, + marker::PhantomData, + ptr::NonNull, +}; + +const SHRINK_STOP: c_ulong = bindings::SHRINK_STOP as c_ulong; +const SHRINK_EMPTY: c_ulong = bindings::SHRINK_EMPTY as c_ulong; + +/// The default value for the number of seeks needed to recreate an object. +pub const DEFAULT_SEEKS: u32 = bindings::DEFAULT_SEEKS; + +/// An unregistered shrinker. +/// +/// This type can be used to modify the settings of the shrinker before it is registered. +/// +/// # Invariants +/// +/// The `shrinker` pointer references an unregistered shrinker. +pub struct UnregisteredShrinker { + shrinker: NonNull, +} + +// SAFETY: Moving an unregistered shrinker between threads is okay. +unsafe impl Send for UnregisteredShrinker {} +// SAFETY: An unregistered shrinker is thread safe. +unsafe impl Sync for UnregisteredShrinker {} + +impl UnregisteredShrinker { + /// Create a new shrinker. + pub fn alloc(name: &CStr) -> Result { + // SAFETY: Passing `0` as flags is okay. Using `%s` as the format string is okay when we + // pass a nul-terminated string as the string for `%s` to print. + let ptr = + unsafe { bindings::shrinker_alloc(0, c_str!("%s").as_char_ptr(), name.as_char_ptr()) }; + + let shrinker = NonNull::new(ptr).ok_or(AllocError)?; + + // INVARIANT: The creation of the shrinker was successful. + Ok(Self { shrinker }) + } + + /// Create a new shrinker using format arguments for the name. + pub fn alloc_fmt(name: core::fmt::Arguments<'_>) -> Result { + // SAFETY: Passing `0` as flags is okay. Using `%pA` as the format string is okay when we + // pass a `fmt::Arguments` as the value to print. + let ptr = unsafe { + bindings::shrinker_alloc( + 0, + c_str!("%pA").as_char_ptr(), + &name as *const _ as *const c_void, + ) + }; + + let shrinker = NonNull::new(ptr).ok_or(AllocError)?; + + // INVARIANT: The creation of the shrinker was successful. + Ok(Self { shrinker }) + } + + /// Set the number of seeks needed to recreate an object. + pub fn set_seeks(&mut self, seeks: u32) { + unsafe { (*self.shrinker.as_ptr()).seeks = seeks as c_int }; + } + + /// Register the shrinker. + /// + /// The provided pointer is used as the private data, and the type `T` determines the callbacks + /// that the shrinker will use. + pub fn register(self, private_data: T::Ptr) -> RegisteredShrinker { + let shrinker = self.shrinker; + let ptr = shrinker.as_ptr(); + + // The destructor of `self` calls `shrinker_free`, so skip the destructor. + core::mem::forget(self); + + let private_data_ptr = ::into_foreign(private_data); + + // SAFETY: We own the private data, so we can assign to it. + unsafe { (*ptr).private_data = private_data_ptr.cast_mut() }; + // SAFETY: The shrinker is not yet registered, so we can update this field. + unsafe { (*ptr).count_objects = Some(rust_count_objects::) }; + // SAFETY: The shrinker is not yet registered, so we can update this field. + unsafe { (*ptr).scan_objects = Some(rust_scan_objects::) }; + + // SAFETY: The shrinker is unregistered, so it's safe to register it. + unsafe { bindings::shrinker_register(ptr) }; + + RegisteredShrinker { + shrinker, + _phantom: PhantomData, + } + } +} + +impl Drop for UnregisteredShrinker { + fn drop(&mut self) { + // SAFETY: The shrinker is a valid but unregistered shrinker, and we will not use it + // anymore. + unsafe { bindings::shrinker_free(self.shrinker.as_ptr()) }; + } +} + +/// A shrinker that is registered with the kernel. +/// +/// # Invariants +/// +/// The `shrinker` pointer refers to a registered shrinker using `T` as the private data. +pub struct RegisteredShrinker { + shrinker: NonNull, + _phantom: PhantomData, +} + +// SAFETY: This allows you to deregister the shrinker from a different thread, which means that +// private data could be dropped from any thread. +unsafe impl Send for RegisteredShrinker where T::Ptr: Send {} +// SAFETY: The only thing you can do with an immutable reference is access the private data, which +// is okay to access in parallel as the `Shrinker` trait requires the private data to be `Sync`. +unsafe impl Sync for RegisteredShrinker {} + +impl RegisteredShrinker { + /// Access the private data in this shrinker. + pub fn private_data(&self) -> ::Borrowed<'_> { + // SAFETY: We own the private data, so we can access it. + let private = unsafe { (*self.shrinker.as_ptr()).private_data }; + // SAFETY: By the type invariants, the private data is `T`. This access could happen in + // parallel with a shrinker callback, but that's okay as the `Shrinker` trait ensures that + // `T::Ptr` is `Sync`. + unsafe { ::borrow(private) } + } +} + +impl Drop for RegisteredShrinker { + fn drop(&mut self) { + // SAFETY: We own the private data, so we can access it. + let private = unsafe { (*self.shrinker.as_ptr()).private_data }; + // SAFETY: We will not access the shrinker after this call. + unsafe { bindings::shrinker_free(self.shrinker.as_ptr()) }; + // SAFETY: The above call blocked until the completion of any shrinker callbacks, so there + // are no longer any users of the private data. + drop(unsafe { ::from_foreign(private) }); + } +} + +/// Callbacks for a shrinker. +pub trait Shrinker { + /// The pointer type used to store the private data of the shrinker. + /// + /// Needs to be `Sync` because the shrinker callback could access this value immutably from + /// several thread in parallel. + type Ptr: ForeignOwnable + Sync; + + /// Count the number of freeable items in the cache. + /// + /// May be called from atomic context. + fn count_objects( + me: ::Borrowed<'_>, + sc: ShrinkControl<'_>, + ) -> CountObjects; + + /// Count the number of freeable items in the cache. + /// + /// May be called from atomic context. + fn scan_objects( + me: ::Borrowed<'_>, + sc: ShrinkControl<'_>, + ) -> ScanObjects; +} + +/// How many objects are there in the cache? +/// +/// This is used as the return value of [`Shrinker::count_objects`]. +pub struct CountObjects { + inner: c_ulong, +} + +impl CountObjects { + /// Indicates that the number of objects is unknown. + pub const UNKNOWN: Self = Self { inner: 0 }; + + /// Indicates that the number of objects is zero. + pub const EMPTY: Self = Self { + inner: SHRINK_EMPTY, + }; + + /// The maximum possible number of freeable objects. + pub const MAX: Self = Self { + // The shrinker code assumes that it can multiply this value by two without overflow. + inner: c_ulong::MAX / 2, + }; + + /// Creates a new `CountObjects` with the given value. + pub fn from_count(count: usize) -> Self { + if count == 0 { + return Self::EMPTY; + } + + if count > Self::MAX.inner as usize { + return Self::MAX; + } + + Self { + inner: count as c_ulong, + } + } +} + +/// How many objects were freed? +/// +/// This is used as the return value of [`Shrinker::scan_objects`]. +pub struct ScanObjects { + inner: c_ulong, +} + +impl ScanObjects { + /// Indicates that the shrinker should stop trying to free objects from this cache due to + /// potential deadlocks. + pub const STOP: Self = Self { inner: SHRINK_STOP }; + + /// The maximum possible number of freeable objects. + pub const MAX: Self = Self { + // The shrinker code assumes that it can multiply this value by two without overflow. + inner: c_ulong::MAX / 2, + }; + + /// Creates a new `CountObjects` with the given value. + pub fn from_count(count: usize) -> Self { + if count > Self::MAX.inner as usize { + return Self::MAX; + } + + Self { + inner: count as c_ulong, + } + } +} + +/// This struct is used to pass information from page reclaim to the shrinkers. +pub struct ShrinkControl<'a> { + ptr: NonNull, + _phantom: PhantomData<&'a bindings::shrink_control>, +} + +impl<'a> ShrinkControl<'a> { + /// Create a `ShrinkControl` from a raw pointer. + /// + /// # Safety + /// + /// The pointer should point at a valid `shrink_control` for the duration of 'a. + pub unsafe fn from_raw(ptr: *mut bindings::shrink_control) -> Self { + Self { + // SAFETY: Caller promises that this pointer is valid. + ptr: unsafe { NonNull::new_unchecked(ptr) }, + _phantom: PhantomData, + } + } + + /// Determines whether it is safe to recurse into filesystem code. + pub fn gfp_fs(&self) -> bool { + // SAFETY: Okay by type invariants. + let mask = unsafe { (*self.ptr.as_ptr()).gfp_mask }; + + (mask & bindings::__GFP_FS) != 0 + } + + /// Returns the number of objects that `scan_objects` should try to reclaim. + pub fn nr_to_scan(&self) -> usize { + // SAFETY: Okay by type invariants. + unsafe { (*self.ptr.as_ptr()).nr_to_scan as usize } + } + + /// The callback should set this value to the number of objects processed. + pub fn set_nr_scanned(&mut self, val: usize) { + let mut val = val as c_ulong; + // SAFETY: Okay by type invariants. + let max = unsafe { (*self.ptr.as_ptr()).nr_to_scan }; + if val > max { + val = max; + } + + // SAFETY: Okay by type invariants. + unsafe { (*self.ptr.as_ptr()).nr_scanned = val }; + } +} + +unsafe extern "C" fn rust_count_objects( + shrink: *mut bindings::shrinker, + sc: *mut bindings::shrink_control, +) -> c_ulong { + // SAFETY: We own the private data, so we can access it. + let private = unsafe { (*shrink).private_data }; + // SAFETY: This function is only used with shrinkers where `T` is the type of the private data. + let private = unsafe { ::borrow(private) }; + // SAFETY: The caller passes a valid `sc` pointer. + let sc = unsafe { ShrinkControl::from_raw(sc) }; + + let ret = T::count_objects(private, sc); + ret.inner +} + +unsafe extern "C" fn rust_scan_objects( + shrink: *mut bindings::shrinker, + sc: *mut bindings::shrink_control, +) -> c_ulong { + // SAFETY: We own the private data, so we can access it. + let private = unsafe { (*shrink).private_data }; + // SAFETY: This function is only used with shrinkers where `T` is the type of the private data. + let private = unsafe { ::borrow(private) }; + // SAFETY: The caller passes a valid `sc` pointer. + let sc = unsafe { ShrinkControl::from_raw(sc) }; + + let ret = T::scan_objects(private, sc); + ret.inner +}