use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[derive(Debug, Clone)]
pub struct GcConfig {
hard_limit: usize,
soft_limit: usize,
}
impl GcConfig {
pub fn new() -> Self {
let hard_limit = default_hard_limit();
let soft_limit = (hard_limit as f64 * 0.75) as usize;
Self {
hard_limit,
soft_limit,
}
}
pub fn with_hard_limit(hard_limit: usize) -> Self {
let soft_limit = (hard_limit as f64 * 0.75) as usize;
Self {
hard_limit,
soft_limit,
}
}
pub fn with_limits(soft_limit: usize, hard_limit: usize) -> Self {
Self {
soft_limit,
hard_limit,
}
}
pub fn hard_limit(&self) -> usize {
self.hard_limit
}
pub fn soft_limit(&self) -> usize {
self.soft_limit
}
pub fn soft_limit_exceeded(&self, used: usize) -> bool {
used > self.soft_limit
}
pub fn hard_limit_exceeded(&self, used: usize) -> bool {
used > self.hard_limit
}
}
impl Default for GcConfig {
fn default() -> Self {
Self::new()
}
}
fn default_hard_limit() -> usize {
#[cfg(target_os = "linux")]
fn get_total_ram() -> Option<usize> {
std::fs::read_to_string("/proc/meminfo")
.ok()
.and_then(|content| {
for line in content.lines() {
if line.starts_with("MemTotal:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
return parts[1].parse::<usize>().ok().map(|kb| kb * 1024);
}
}
}
None
})
}
#[cfg(target_os = "macos")]
fn get_total_ram() -> Option<usize> {
use std::ffi::CStr;
let total: u64 = 0;
let mut size = std::mem::size_of::<u64>();
let name_cstr = {
let bytes = b"hw.memsize\0";
CStr::from_bytes_with_nul(bytes).ok()?
};
let ret = unsafe {
let name = name_cstr.as_ptr();
let addr = &total as *const u64 as *mut std::ffi::c_void;
let oldlenp = &mut size as *mut usize;
sysctlbyname(name, addr, oldlenp, std::ptr::null_mut(), 0)
};
if ret == 0 { Some(total as usize) } else { None }
}
#[cfg(target_os = "windows")]
fn get_total_ram() -> Option<usize> {
use std::mem::size_of;
use windows::Win32::System::SystemInformation::GlobalMemoryStatusEx;
use windows::Win32::System::SystemInformation::MEMORYSTATUSEX;
let mut mem_status = MEMORYSTATUSEX::default();
mem_status.dwLength = size_of::<MEMORYSTATUSEX>() as u32;
if unsafe { GlobalMemoryStatusEx(&mut mem_status) }.is_ok() {
Some(mem_status.ullTotalPhys as usize)
} else {
None
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
fn get_total_ram() -> Option<usize> {
None
}
let total_ram = get_total_ram().unwrap_or(256 * 1024 * 1024);
std::cmp::max(total_ram / 4, 256 * 1024 * 1024)
}
#[cfg(target_os = "macos")]
#[link(name = "System")]
unsafe extern "C" {
fn sysctlbyname(
name: *const std::os::raw::c_char,
oldp: *mut std::ffi::c_void,
oldlenp: *mut usize,
newp: *mut std::ffi::c_void,
newlen: usize,
) -> std::os::raw::c_int;
}
pub(crate) struct IsolateCancellation {
in_progress: AtomicBool,
parked_threads: AtomicUsize,
registered_threads: AtomicUsize,
gc_requested: AtomicBool,
}
impl IsolateCancellation {
const fn new() -> Self {
Self {
in_progress: AtomicBool::new(false),
parked_threads: AtomicUsize::new(0),
registered_threads: AtomicUsize::new(0),
gc_requested: AtomicBool::new(false),
}
}
fn in_progress(&self) -> bool {
self.in_progress.load(Ordering::SeqCst)
}
fn park(&self) {
self.parked_threads.fetch_add(1, Ordering::SeqCst);
}
fn unpark(&self) {
self.parked_threads.fetch_sub(1, Ordering::SeqCst);
}
fn parked_threads(&self) -> usize {
self.parked_threads.load(Ordering::SeqCst)
}
fn set_in_progress(&self, value: bool) {
self.in_progress.store(value, Ordering::SeqCst);
}
fn try_begin_collection(&self) -> bool {
self.in_progress
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
}
fn register_thread(&self) {
self.registered_threads.fetch_add(1, Ordering::SeqCst);
}
fn unregister_thread(&self) {
self.registered_threads.fetch_sub(1, Ordering::SeqCst);
}
fn registered_threads(&self) -> usize {
self.registered_threads.load(Ordering::SeqCst)
}
fn request_gc(&self) {
self.gc_requested.store(true, Ordering::SeqCst);
}
fn take_gc_request(&self) -> bool {
self.gc_requested.swap(false, Ordering::SeqCst)
}
fn gc_requested(&self) -> bool {
self.gc_requested.load(Ordering::SeqCst)
}
}
thread_local! {
static ISOLATE_CANCELLATION: IsolateCancellation = const { IsolateCancellation::new() };
}
pub(crate) fn with_cancellation<T>(f: impl FnOnce(&IsolateCancellation) -> T) -> T {
ISOLATE_CANCELLATION.with(f)
}
pub struct GcCancellation;
impl GcCancellation {
pub const fn new() -> Self {
Self
}
pub fn in_progress(&self) -> bool {
with_cancellation(|c| c.in_progress())
}
pub fn park(&self) {
with_cancellation(|c| c.park());
}
pub fn unpark(&self) {
with_cancellation(|c| c.unpark());
}
pub fn parked_threads(&self) -> usize {
with_cancellation(|c| c.parked_threads())
}
pub fn set_in_progress(&self, value: bool) {
with_cancellation(|c| c.set_in_progress(value));
}
pub fn try_begin_collection(&self) -> bool {
with_cancellation(|c| c.try_begin_collection())
}
pub fn register_thread(&self) {
with_cancellation(|c| c.register_thread());
}
pub fn unregister_thread(&self) {
with_cancellation(|c| c.unregister_thread());
}
pub fn registered_threads(&self) -> usize {
with_cancellation(|c| c.registered_threads())
}
pub fn request_gc(&self) {
with_cancellation(|c| c.request_gc());
}
pub fn take_gc_request(&self) -> bool {
with_cancellation(|c| c.take_gc_request())
}
pub fn gc_requested(&self) -> bool {
with_cancellation(|c| c.gc_requested())
}
}
unsafe impl Sync for GcCancellation {}
unsafe impl Send for GcCancellation {}
impl Default for GcCancellation {
fn default() -> Self {
Self::new()
}
}
pub static GC_CANCELLATION: GcCancellation = GcCancellation::new();
pub fn check_cancellation() -> Result<(), GcParked> {
if GC_CANCELLATION.in_progress() {
Err(GcParked)
} else {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GcParked;
impl std::fmt::Display for GcParked {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GC in progress, thread should park")
}
}
impl std::error::Error for GcParked {}