Add process safety to cl_cache on Linux

Current flow will be to have one synchronization point
config.file. Read remains unblocking, only write(caching)
operation will be blocking (lock on config.file)

Related-To: NEO-4262

Signed-off-by: Diedrich, Kamil <kamil.diedrich@intel.com>
This commit is contained in:
Diedrich, Kamil
2023-03-08 23:15:36 +01:00
committed by Compute-Runtime-Automation
parent c9fdeb200c
commit 26ca64bb28
15 changed files with 1091 additions and 40 deletions

View File

@@ -26,4 +26,15 @@ set(NEO_CORE_COMPILER_INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/tokenized_string.h
)
if(WIN32)
list(APPEND NEO_CORE_COMPILER_INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/windows/compiler_cache_windows.cpp
)
else()
list(APPEND NEO_CORE_COMPILER_INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/linux/compiler_cache_linux.cpp
)
endif()
set_property(GLOBAL PROPERTY NEO_CORE_COMPILER_INTERFACE ${NEO_CORE_COMPILER_INTERFACE})
add_subdirectories()

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2022 Intel Corporation
* Copyright (C) 2019-2023 Intel Corporation
*
* SPDX-License-Identifier: MIT
*
@@ -27,6 +27,7 @@
namespace NEO {
std::mutex CompilerCache::cacheAccessMtx;
const std::string CompilerCache::getCachedFileName(const HardwareInfo &hwInfo, const ArrayRef<const char> input,
const ArrayRef<const char> options, const ArrayRef<const char> internalOptions) {
Hash hash;
@@ -109,20 +110,4 @@ const std::string CompilerCache::getCachedFileName(const HardwareInfo &hwInfo, c
CompilerCache::CompilerCache(const CompilerCacheConfig &cacheConfig)
: config(cacheConfig){};
bool CompilerCache::cacheBinary(const std::string kernelFileHash, const char *pBinary, uint32_t binarySize) {
if (pBinary == nullptr || binarySize == 0) {
return false;
}
std::string filePath = config.cacheDir + PATH_SEPARATOR + kernelFileHash + config.cacheFileExtension;
std::lock_guard<std::mutex> lock(cacheAccessMtx);
return 0 != writeDataToFile(filePath.c_str(), pBinary, binarySize);
}
std::unique_ptr<char[]> CompilerCache::loadCachedBinary(const std::string kernelFileHash, size_t &cachedBinarySize) {
std::string filePath = config.cacheDir + PATH_SEPARATOR + kernelFileHash + config.cacheFileExtension;
std::lock_guard<std::mutex> lock(cacheAccessMtx);
return loadDataFromFile(filePath.c_str(), cachedBinarySize);
}
} // namespace NEO

View File

@@ -13,6 +13,7 @@
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
namespace NEO {
struct HardwareInfo;
@@ -21,6 +22,7 @@ struct CompilerCacheConfig {
bool enabled = true;
std::string cacheFileExtension;
std::string cacheDir;
size_t cacheSize = 0;
};
class CompilerCache {
@@ -36,10 +38,15 @@ class CompilerCache {
const std::string getCachedFileName(const HardwareInfo &hwInfo, ArrayRef<const char> input,
ArrayRef<const char> options, ArrayRef<const char> internalOptions);
MOCKABLE_VIRTUAL bool cacheBinary(const std::string kernelFileHash, const char *pBinary, uint32_t binarySize);
MOCKABLE_VIRTUAL std::unique_ptr<char[]> loadCachedBinary(const std::string kernelFileHash, size_t &cachedBinarySize);
MOCKABLE_VIRTUAL bool cacheBinary(const std::string &kernelFileHash, const char *pBinary, size_t binarySize);
MOCKABLE_VIRTUAL std::unique_ptr<char[]> loadCachedBinary(const std::string &kernelFileHash, size_t &cachedBinarySize);
protected:
MOCKABLE_VIRTUAL bool evictCache();
MOCKABLE_VIRTUAL bool renameTempFileBinaryToProperName(const std::string &oldName, const std::string &kernelFileHash);
MOCKABLE_VIRTUAL bool createUniqueTempFileAndWriteData(char *tmpFilePathTemplate, const char *pBinary, size_t binarySize);
MOCKABLE_VIRTUAL void lockConfigFileAndReadSize(const std::string &configFilePath, int &fd, size_t &directorySize);
static std::mutex cacheAccessMtx;
CompilerCacheConfig config;
};

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2023 Intel Corporation
*
* SPDX-License-Identifier: MIT
*
*/
#include "shared/source/compiler_interface/compiler_cache.h"
#include "shared/source/debug_settings/debug_settings_manager.h"
#include "shared/source/helpers/file_io.h"
#include "shared/source/helpers/string.h"
#include "shared/source/os_interface/linux/sys_calls.h"
#include "shared/source/utilities/io_functions.h"
#include "os_inc.h"
#include <algorithm>
#include <dirent.h>
#include <fcntl.h>
#include <iostream>
#include <sstream>
#include <string_view>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
namespace NEO {
std::string makePath(const std::string &lhs, const std::string &rhs) {
if (lhs.size() == 0) {
return rhs;
}
if (rhs.size() == 0) {
return lhs;
}
if (*lhs.rbegin() == PATH_SEPARATOR) {
return lhs + rhs;
}
return lhs + PATH_SEPARATOR + rhs;
}
int filterFunction(const struct dirent *file) {
std::string_view fileName = file->d_name;
if (fileName.find(".cl_cache") != fileName.npos || fileName.find(".l0_cache") != fileName.npos) {
return 1;
}
return 0;
}
struct ElementsStruct {
std::string path;
struct stat statEl;
};
bool compareByLastAccessTime(const ElementsStruct &a, ElementsStruct &b) {
return a.statEl.st_atime < b.statEl.st_atime;
}
bool CompilerCache::evictCache() {
struct dirent **files = 0;
int filesCount = NEO::SysCalls::scandir(config.cacheDir.c_str(), &files, filterFunction, NULL);
if (filesCount == -1) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Scandir failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
return false;
}
std::vector<ElementsStruct> vec;
vec.reserve(static_cast<size_t>(filesCount));
for (int i = 0; i < filesCount; ++i) {
ElementsStruct fileElement = {};
fileElement.path = makePath(config.cacheDir, files[i]->d_name);
if (NEO::SysCalls::stat(fileElement.path.c_str(), &fileElement.statEl) == 0) {
vec.push_back(std::move(fileElement));
}
}
for (int i = 0; i < filesCount; ++i) {
free(files[i]);
}
free(files);
std::sort(vec.begin(), vec.end(), compareByLastAccessTime);
size_t evictionLimit = config.cacheSize / 3;
size_t evictionSizeCount = 0;
for (size_t i = 0; i < vec.size(); ++i) {
NEO::SysCalls::unlink(vec[i].path);
evictionSizeCount += vec[i].statEl.st_size;
if (evictionSizeCount > evictionLimit) {
return true;
}
}
return true;
}
bool CompilerCache::createUniqueTempFileAndWriteData(char *tmpFilePathTemplate, const char *pBinary, size_t binarySize) {
int fd = NEO::SysCalls::mkstemp(tmpFilePathTemplate);
if (fd == -1) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Creating temporary file failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
return false;
}
if (NEO::SysCalls::pwrite(fd, pBinary, binarySize, 0) == -1) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Writing to temporary file failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
NEO::SysCalls::close(fd);
NEO::SysCalls::unlink(tmpFilePathTemplate);
return false;
}
return NEO::SysCalls::close(fd) == 0;
}
bool CompilerCache::renameTempFileBinaryToProperName(const std::string &oldName, const std::string &kernelFileHash) {
int err = NEO::SysCalls::rename(oldName.c_str(), kernelFileHash.c_str());
if (err < 0) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Rename temp file failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
NEO::SysCalls::unlink(oldName);
return false;
}
return true;
}
void unlockFileAndClose(int fd) {
int lockErr = NEO::SysCalls::flock(fd, LOCK_UN);
if (lockErr < 0) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: unlock file failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
}
NEO::SysCalls::close(fd);
}
void CompilerCache::lockConfigFileAndReadSize(const std::string &configFilePath, int &fd, size_t &directorySize) {
bool countDirectorySize = false;
errno = 0;
fd = NEO::SysCalls::open(configFilePath.c_str(), O_RDWR);
if (fd < 0) {
if (errno == ENOENT) {
fd = NEO::SysCalls::openWithMode(configFilePath.c_str(), O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (fd < 0) {
fd = NEO::SysCalls::open(configFilePath.c_str(), O_RDWR);
} else {
countDirectorySize = true;
}
} else {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Open config file failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
return;
}
}
int lockErr = NEO::SysCalls::flock(fd, LOCK_EX);
if (lockErr < 0) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Lock config file failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
NEO::SysCalls::close(fd);
fd = -1;
return;
}
if (countDirectorySize) {
struct dirent **files = {};
int filesCount = NEO::SysCalls::scandir(config.cacheDir.c_str(), &files, filterFunction, NULL);
if (filesCount == -1) {
unlockFileAndClose(fd);
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Scandir failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
fd = -1;
return;
}
std::vector<ElementsStruct> vec;
vec.reserve(static_cast<size_t>(filesCount));
for (int i = 0; i < filesCount; ++i) {
std::string_view fileName = files[i]->d_name;
if (fileName.find(config.cacheFileExtension) != fileName.npos) {
ElementsStruct fileElement = {};
fileElement.path = makePath(config.cacheDir, files[i]->d_name);
if (NEO::SysCalls::stat(fileElement.path.c_str(), &fileElement.statEl) == 0) {
vec.push_back(std::move(fileElement));
}
}
}
for (int i = 0; i < filesCount; ++i) {
free(files[i]);
}
free(files);
for (auto &element : vec) {
directorySize += element.statEl.st_size;
}
} else {
ssize_t readErr = NEO::SysCalls::pread(fd, &directorySize, sizeof(directorySize), 0);
if (readErr < 0) {
directorySize = 0;
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Read config failed! errno: %d\n", NEO::SysCalls::getProcessId(), errno);
unlockFileAndClose(fd);
fd = -1;
}
}
}
bool CompilerCache::cacheBinary(const std::string &kernelFileHash, const char *pBinary, size_t binarySize) {
if (pBinary == nullptr || binarySize == 0) {
return false;
}
std::unique_lock<std::mutex> lock(cacheAccessMtx);
constexpr std::string_view configFileName = "config.file";
std::string configFilePath = makePath(config.cacheDir, configFileName.data());
std::string filePath = makePath(config.cacheDir, kernelFileHash + config.cacheFileExtension);
int fd = -1;
size_t directorySize = 0u;
lockConfigFileAndReadSize(configFilePath, fd, directorySize);
if (fd < 0) {
return false;
}
struct stat statbuf = {};
if (NEO::SysCalls::stat(filePath, &statbuf) == 0) {
unlockFileAndClose(fd);
return true;
}
size_t maxSize = config.cacheSize;
if (maxSize < directorySize + binarySize) {
if (!evictCache()) {
unlockFileAndClose(fd);
return false;
}
}
std::string tmpFileName = "cl_cache.XXXXXX";
std::string tmpFilePath = makePath(config.cacheDir, tmpFileName);
if (!createUniqueTempFileAndWriteData(tmpFilePath.data(), pBinary, binarySize)) {
unlockFileAndClose(fd);
return false;
}
if (!renameTempFileBinaryToProperName(tmpFilePath, filePath)) {
unlockFileAndClose(fd);
return false;
}
directorySize += binarySize;
NEO::SysCalls::pwrite(fd, &directorySize, sizeof(directorySize), 0);
unlockFileAndClose(fd);
return true;
}
std::unique_ptr<char[]> CompilerCache::loadCachedBinary(const std::string &kernelFileHash, size_t &cachedBinarySize) {
std::string filePath = makePath(config.cacheDir, kernelFileHash + config.cacheFileExtension);
return loadDataFromFile(filePath.c_str(), cachedBinarySize);
}
} // namespace NEO

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Intel Corporation
*
* SPDX-License-Identifier: MIT
*
*/
#include "shared/source/compiler_interface/compiler_cache.h"
#include "shared/source/helpers/file_io.h"
#include "shared/source/utilities/io_functions.h"
#include "os_inc.h"
namespace NEO {
bool CompilerCache::evictCache() {
return true;
}
void CompilerCache::lockConfigFileAndReadSize(const std::string &configFilePath, int &fd, size_t &directorySize) {}
bool CompilerCache::createUniqueTempFileAndWriteData(char *tmpFilePath, const char *pBinary, size_t binarySize) {
return true;
}
bool CompilerCache::renameTempFileBinaryToProperName(const std::string &oldName, const std::string &kernelFileHash) {
return true;
}
bool CompilerCache::cacheBinary(const std::string &kernelFileHash, const char *pBinary, size_t binarySize) {
if (pBinary == nullptr || binarySize == 0) {
return false;
}
std::string filePath = config.cacheDir + PATH_SEPARATOR + kernelFileHash + config.cacheFileExtension;
std::lock_guard<std::mutex> lock(cacheAccessMtx);
return 0 != writeDataToFile(filePath.c_str(), pBinary, binarySize);
}
std::unique_ptr<char[]> CompilerCache::loadCachedBinary(const std::string &kernelFileHash, size_t &cachedBinarySize) {
std::string filePath = config.cacheDir + PATH_SEPARATOR + kernelFileHash + config.cacheFileExtension;
std::lock_guard<std::mutex> lock(cacheAccessMtx);
return loadDataFromFile(filePath.c_str(), cachedBinarySize);
}
} // namespace NEO

View File

@@ -8,6 +8,7 @@
#pragma once
#include "shared/source/os_interface/sys_calls_common.h"
#include <dirent.h>
#include <iostream>
#include <poll.h>
#include <sys/mman.h>
@@ -17,6 +18,7 @@ namespace NEO {
namespace SysCalls {
int close(int fileDescriptor);
int open(const char *file, int flags);
int openWithMode(const char *file, int flags, int mode);
void *dlopen(const char *filename, int flag);
int ioctl(int fileDescriptor, unsigned long int request, void *arg);
int getDevicePath(int deviceFd, char *buf, size_t &bufSize);
@@ -33,6 +35,16 @@ ssize_t write(int fd, void *buf, size_t count);
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, int arg);
char *realpath(const char *path, char *buf);
int stat(const std::string &filePath, struct stat *statbuf);
int pipe(int pipefd[2]);
int flock(int fd, int flag);
int mkstemp(char *filePath);
int rename(const char *currName, const char *dstName);
int scandir(const char *dirp,
struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **,
const struct dirent **));
int unlink(const std::string &pathname);
} // namespace SysCalls
} // namespace NEO

View File

@@ -8,14 +8,17 @@
#include "shared/source/os_interface/linux/sys_calls.h"
#include <cstdlib>
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <iostream>
#include <poll.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
namespace NEO {
@@ -43,6 +46,9 @@ int close(int fileDescriptor) {
int open(const char *file, int flags) {
return ::open(file, flags);
}
int openWithMode(const char *file, int flags, int mode) {
return ::open(file, flags, mode);
}
int ioctl(int fileDescriptor, unsigned long int request, void *arg) {
return ::ioctl(fileDescriptor, request, arg);
}
@@ -114,9 +120,37 @@ char *realpath(const char *path, char *buf) {
return ::realpath(path, buf);
}
int stat(const std::string &filePath, struct stat *statbuf) {
return ::stat(filePath.c_str(), statbuf);
}
int pipe(int pipeFd[2]) {
return ::pipe(pipeFd);
}
int mkstemp(char *filePath) {
return ::mkstemp(filePath);
}
int flock(int fd, int flag) {
return ::flock(fd, flag);
}
int rename(const char *currName, const char *dstName) {
return ::rename(currName, dstName);
}
int scandir(const char *dirp,
struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **,
const struct dirent **)) {
return ::scandir(dirp, namelist, filter, compar);
}
int unlink(const std::string &pathname) {
return ::unlink(pathname.c_str());
}
} // namespace SysCalls
} // namespace NEO