All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.nativeaccess.PosixNativeAccess Maven / Gradle / Ivy

The newest version!
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the "Elastic License
 * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
 * Public License v 1"; you may not use this file except in compliance with, at
 * your election, the "Elastic License 2.0", the "GNU Affero General Public
 * License v3.0 only", or the "Server Side Public License, v 1".
 */

package org.elasticsearch.nativeaccess;

import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.PosixCLibrary;
import org.elasticsearch.nativeaccess.lib.VectorLibrary;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.OptionalLong;

abstract class PosixNativeAccess extends AbstractNativeAccess {

    public static final int MCL_CURRENT = 1;
    public static final int ENOMEM = 12;
    public static final int O_RDONLY = 0;
    public static final int O_WRONLY = 1;

    protected final PosixCLibrary libc;
    protected final VectorSimilarityFunctions vectorDistance;
    protected final PosixConstants constants;
    protected final ProcessLimits processLimits;

    PosixNativeAccess(String name, NativeLibraryProvider libraryProvider, PosixConstants constants) {
        super(name, libraryProvider);
        this.libc = libraryProvider.getLibrary(PosixCLibrary.class);
        this.vectorDistance = vectorSimilarityFunctionsOrNull(libraryProvider);
        this.constants = constants;
        this.processLimits = new ProcessLimits(
            getMaxThreads(),
            getRLimit(constants.RLIMIT_AS(), "max size virtual memory"),
            getRLimit(constants.RLIMIT_FSIZE(), "max file size")
        );
    }

    /**
     * Return the maximum number of threads this process may start, or {@link ProcessLimits#UNKNOWN}.
     */
    protected abstract long getMaxThreads();

    /**
     * Return the current rlimit for the given resource.
     * If getrlimit fails, returns {@link ProcessLimits#UNKNOWN}.
     * If the rlimit is unlimited, returns {@link ProcessLimits#UNLIMITED}.
     * */
    protected long getRLimit(int resource, String description) {
        var rlimit = libc.newRLimit();
        if (libc.getrlimit(resource, rlimit) == 0) {
            long value = rlimit.rlim_cur();
            return value == constants.RLIMIT_INFINITY() ? ProcessLimits.UNLIMITED : value;
        } else {
            logger.warn("unable to retrieve " + description + " [" + libc.strerror(libc.errno()) + "]");
            return ProcessLimits.UNKNOWN;
        }
    }

    static VectorSimilarityFunctions vectorSimilarityFunctionsOrNull(NativeLibraryProvider libraryProvider) {
        if (isNativeVectorLibSupported()) {
            var lib = libraryProvider.getLibrary(VectorLibrary.class).getVectorSimilarityFunctions();
            logger.info("Using native vector library; to disable start with -D" + ENABLE_JDK_VECTOR_LIBRARY + "=false");
            return lib;
        }
        return null;
    }

    @Override
    public boolean definitelyRunningAsRoot() {
        return libc.geteuid() == 0;
    }

    @Override
    public ProcessLimits getProcessLimits() {
        return processLimits;
    }

    @Override
    public void tryLockMemory() {
        int result = libc.mlockall(MCL_CURRENT);
        if (result == 0) {
            isMemoryLocked = true;
            return;
        }

        // mlockall failed for some reason
        int errno = libc.errno();
        String errMsg = libc.strerror(errno);
        logger.warn("Unable to lock JVM Memory: error={}, reason={}", errno, errMsg);
        logger.warn("This can result in part of the JVM being swapped out.");

        if (errno == ENOMEM) {

            boolean rlimitSuccess = false;
            long softLimit = 0;
            long hardLimit = 0;

            // we only know RLIMIT_MEMLOCK for these two at the moment.
            var rlimit = libc.newRLimit();
            if (libc.getrlimit(constants.RLIMIT_MEMLOCK(), rlimit) == 0) {
                rlimitSuccess = true;
                softLimit = rlimit.rlim_cur();
                hardLimit = rlimit.rlim_max();
            } else {
                logger.warn("Unable to retrieve resource limits: {}", libc.strerror(libc.errno()));
            }

            if (rlimitSuccess) {
                logger.warn(
                    "Increase RLIMIT_MEMLOCK, soft limit: {}, hard limit: {}",
                    rlimitToString(softLimit),
                    rlimitToString(hardLimit)
                );
                logMemoryLimitInstructions();
            } else {
                logger.warn("Increase RLIMIT_MEMLOCK (ulimit).");
            }
        }
    }

    protected abstract void logMemoryLimitInstructions();

    @Override
    public OptionalLong allocatedSizeInBytes(Path path) {
        assert Files.isRegularFile(path) : path;
        var stats = libc.newStat64(constants.statStructSize(), constants.statStructSizeOffset(), constants.statStructBlocksOffset());

        int fd = libc.open(path.toAbsolutePath().toString(), O_RDONLY);
        if (fd == -1) {
            logger.warn("Could not open file [" + path + "] to get allocated size: " + libc.strerror(libc.errno()));
            return OptionalLong.empty();
        }

        if (libc.fstat64(fd, stats) != 0) {
            logger.warn("Could not get stats for file [" + path + "] to get allocated size: " + libc.strerror(libc.errno()));
            return OptionalLong.empty();
        }
        if (libc.close(fd) != 0) {
            logger.warn("Failed to close file [" + path + "] after getting stats: " + libc.strerror(libc.errno()));
        }
        return OptionalLong.of(stats.st_blocks() * 512);
    }

    @SuppressForbidden(reason = "Using mkdirs")
    @Override
    public void tryPreallocate(Path file, long newSize) {
        var absolutePath = file.toAbsolutePath();
        var directory = absolutePath.getParent();
        directory.toFile().mkdirs();
        // get fd and current size, then pass to OS variant
        // We pass down O_CREAT, so open will create the file if it does not exist.
        // From the open man page (https://www.man7.org/linux/man-pages/man2/open.2.html):
        // - The mode parameter is needed when specifying O_CREAT
        // - The effective mode is modified by the process's umask: in the absence of a default ACL, the mode of the created file is
        // (mode & ~umask).
        // We choose to pass down 0666 (r/w permission for user/group/others) to mimic what the JDK does for its open operations;
        // see for example the fileOpen implementation in libjava:
        // https://github.com/openjdk/jdk/blob/98562166e4a4c8921709014423c6cbc993aa0d97/src/java.base/unix/native/libjava/io_util_md.c#L105
        int fd = libc.open(absolutePath.toString(), O_WRONLY | constants.O_CREAT(), 0666);
        if (fd == -1) {
            logger.warn("Could not open file [" + file + "] to preallocate size: " + libc.strerror(libc.errno()));
            return;
        }

        var stats = libc.newStat64(constants.statStructSize(), constants.statStructSizeOffset(), constants.statStructBlocksOffset());
        if (libc.fstat64(fd, stats) != 0) {
            logger.warn("Could not get stats for file [" + file + "] to preallocate size: " + libc.strerror(libc.errno()));
        } else {
            if (nativePreallocate(fd, stats.st_size(), newSize)) {
                logger.debug("pre-allocated file [{}] to {} bytes", file, newSize);
            } // OS specific preallocate logs its own errors
        }

        if (libc.close(fd) != 0) {
            logger.warn("Could not close file [" + file + "] after trying to preallocate size: " + libc.strerror(libc.errno()));
        }
    }

    protected abstract boolean nativePreallocate(int fd, long currentSize, long newSize);

    @Override
    public Optional getVectorSimilarityFunctions() {
        return Optional.ofNullable(vectorDistance);
    }

    String rlimitToString(long value) {
        if (value == constants.RLIMIT_INFINITY()) {
            return "unlimited";
        } else {
            return Long.toUnsignedString(value);
        }
    }

    static boolean isNativeVectorLibSupported() {
        return Runtime.version().feature() >= 21 && (isMacOrLinuxAarch64() || isLinuxAmd64()) && checkEnableSystemProperty();
    }

    /**
     * Returns true iff the architecture is x64 (amd64) and the OS Linux (the OS we currently support for the native lib).
     */
    static boolean isLinuxAmd64() {
        String name = System.getProperty("os.name");
        return (name.startsWith("Linux")) && System.getProperty("os.arch").equals("amd64");
    }

    /** Returns true iff the OS is Mac or Linux, and the architecture is aarch64. */
    static boolean isMacOrLinuxAarch64() {
        String name = System.getProperty("os.name");
        return (name.startsWith("Mac") || name.startsWith("Linux")) && System.getProperty("os.arch").equals("aarch64");
    }

    /** -Dorg.elasticsearch.nativeaccess.enableVectorLibrary=false to disable.*/
    static final String ENABLE_JDK_VECTOR_LIBRARY = "org.elasticsearch.nativeaccess.enableVectorLibrary";

    static boolean checkEnableSystemProperty() {
        return Optional.ofNullable(System.getProperty(ENABLE_JDK_VECTOR_LIBRARY)).map(Boolean::valueOf).orElse(Boolean.TRUE);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy