solana-core 4.0.0-beta.6

Blockchain, Rebuilt for Scale
Documentation
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ResourceLimitError {
    #[cfg(unix)]
    #[error(
        "unable to increase the nofile limit to {desired} from {current}; setrlimit() error: \
         {error}"
    )]
    Nofile {
        desired: libc::rlim_t,
        current: libc::rlim_t,
        error: libc::c_int,
    },
}

#[cfg(not(unix))]
pub fn adjust_nofile_limit(_enforce_nofile_limit: bool) -> Result<(), ResourceLimitError> {
    Ok(())
}

#[cfg(unix)]
pub fn adjust_nofile_limit(enforce_nofile_limit: bool) -> Result<(), ResourceLimitError> {
    // AccountsDB and RocksDB both may have many files open so bump the limit
    // to ensure each database will be able to function properly
    //
    // This should be kept in sync with published validator instructions:
    // https://docs.anza.xyz/operations/guides/validator-start#system-tuning
    let desired_nofile = 1_000_000;

    fn get_nofile() -> libc::rlimit {
        let mut nofile = libc::rlimit {
            rlim_cur: 0,
            rlim_max: 0,
        };
        if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut nofile) } != 0 {
            warn!("getrlimit(RLIMIT_NOFILE) failed");
        }
        nofile
    }

    let mut nofile = get_nofile();
    let current = nofile.rlim_cur;
    if current < desired_nofile {
        nofile.rlim_cur = desired_nofile;
        let return_value = unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &nofile) };
        if return_value != 0 {
            let error = ResourceLimitError::Nofile {
                desired: desired_nofile,
                current,
                error: return_value,
            };

            if cfg!(target_os = "macos") {
                error!(
                    "{error}. On macOS you may need to run |sudo launchctl limit maxfiles \
                     {desired_nofile} {desired_nofile}| first",
                );
            } else {
                error!("{error}");
            };

            if enforce_nofile_limit {
                return Err(error);
            }
        }

        nofile = get_nofile();
    }
    info!("Maximum open file descriptors: {}", nofile.rlim_cur);
    Ok(())
}

/// Check kernel memory lock limit and tires to increase it if necessary.
///
/// Returns `false` when current limit is below `min_required` and cannot be increased.
#[cfg(target_os = "linux")]
fn try_adjust_ulimit_memlock(min_required: usize) -> bool {
    fn get_memlock() -> libc::rlimit {
        let mut memlock = libc::rlimit {
            rlim_cur: 0,
            rlim_max: 0,
        };
        if unsafe { libc::getrlimit(libc::RLIMIT_MEMLOCK, &mut memlock) } != 0 {
            log::warn!("getrlimit(RLIMIT_MEMLOCK) failed");
        }
        memlock
    }

    let mut memlock = get_memlock();
    let current = memlock.rlim_cur as usize;
    if current < min_required {
        memlock.rlim_cur = min_required as u64;
        memlock.rlim_max = min_required as u64;
        if unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &memlock) } != 0 {
            log::warn!(
                "Unable to increase the maximum memory lock limit to {min_required} from {current}"
            );

            if cfg!(target_os = "macos") {
                log::warn!(
                    "On mac OS you may need to run |sudo launchctl limit memlock {min_required} \
                     {min_required}| first"
                );
            }
            return false;
        }

        memlock = get_memlock();
        log::info!("Bumped maximum memory lock limit: {}", memlock.rlim_cur);
    }
    true
}

pub fn check_memlock_limit_for_disk_io(required_size: usize) -> bool {
    #[cfg(target_os = "linux")]
    {
        // memory locked requirement is only necessary on linux where io_uring is used
        try_adjust_ulimit_memlock(required_size)
    }
    #[cfg(not(target_os = "linux"))]
    {
        let _ = required_size;
        false
    }
}