ClickHouse/dbms/src/Interpreters/Compiler.cpp

272 lines
8.0 KiB
C++
Raw Normal View History

#include <Poco/DirectoryIterator.h>
#include <Common/ClickHouseRevision.h>
2017-06-06 17:18:32 +00:00
#include <ext/unlock_guard.h>
#include <Common/SipHash.h>
#include <Common/ShellCommand.h>
#include <Common/StringUtils.h>
#include <IO/Operators.h>
#include <IO/ReadBufferFromString.h>
#include <IO/ReadBufferFromFileDescriptor.h>
#include <IO/WriteBufferFromFile.h>
#include <Interpreters/Compiler.h>
#include <Interpreters/config_compile.h>
namespace ProfileEvents
{
extern const Event CompileAttempt;
extern const Event CompileSuccess;
}
namespace DB
{
namespace ErrorCodes
{
extern const int CANNOT_DLOPEN;
}
Compiler::Compiler(const std::string & path_, size_t threads)
: path(path_), pool(threads)
{
Poco::File(path).createDirectory();
Poco::DirectoryIterator dir_end;
for (Poco::DirectoryIterator dir_it(path); dir_end != dir_it; ++dir_it)
{
std::string name = dir_it.name();
if (endsWith(name, ".so"))
{
files.insert(name.substr(0, name.size() - 3));
}
}
LOG_INFO(log, "Having " << files.size() << " compiled files from previous start.");
}
Compiler::~Compiler()
{
LOG_DEBUG(log, "Waiting for threads to finish.");
pool.wait();
}
static Compiler::HashedKey getHash(const std::string & key)
{
SipHash hash;
auto revision = ClickHouseRevision::get();
hash.update(reinterpret_cast<const char *>(&revision), sizeof(revision));
hash.update(key.data(), key.size());
Compiler::HashedKey res;
2017-07-04 16:10:36 +00:00
hash.get128(res.low, res.high);
return res;
}
2017-04-02 17:37:49 +00:00
/// Without .so extension.
static std::string hashedKeyToFileName(Compiler::HashedKey hashed_key)
{
2017-07-31 21:39:24 +00:00
WriteBufferFromOwnString out;
out << hashed_key.low << '_' << hashed_key.high;
return out.str();
}
SharedLibraryPtr Compiler::getOrCount(
const std::string & key,
UInt32 min_count_to_compile,
const std::string & additional_compiler_flags,
CodeGenerator get_code,
ReadyCallback on_ready)
{
HashedKey hashed_key = getHash(key);
std::lock_guard<std::mutex> lock(mutex);
UInt32 count = ++counts[hashed_key];
2017-04-02 17:37:49 +00:00
/// Is there a ready open library? Or, if the library is in the process of compiling, there will be nullptr.
Libraries::iterator libraries_it = libraries.find(hashed_key);
if (libraries.end() != libraries_it)
{
if (!libraries_it->second)
LOG_INFO(log, "Library " << hashedKeyToFileName(hashed_key) << " is already compiling or compilation was failed.");
2017-04-02 17:37:49 +00:00
/// TODO In this case, after the compilation is finished, the callback will not be called.
return libraries_it->second;
}
2017-04-02 17:37:49 +00:00
/// Is there a file with the library left over from the previous launch?
std::string file_name = hashedKeyToFileName(hashed_key);
Files::iterator files_it = files.find(file_name);
if (files.end() != files_it)
{
std::string so_file_path = path + '/' + file_name + ".so";
LOG_INFO(log, "Loading existing library " << so_file_path);
SharedLibraryPtr lib;
try
{
lib = std::make_shared<SharedLibrary>(so_file_path);
}
catch (const Exception & e)
{
if (e.code() != ErrorCodes::CANNOT_DLOPEN)
throw;
/// Found broken .so file (or file cannot be dlopened by whatever reason).
/// This could happen when filesystem is corrupted after server restart.
/// We remove the file - it will be recompiled on next attempt.
tryLogCurrentException(log);
files.erase(files_it);
Poco::File(so_file_path).remove();
return nullptr;
}
libraries[hashed_key] = lib;
return lib;
}
2017-04-02 17:37:49 +00:00
/// Has min_count_to_compile been reached?
if (count >= min_count_to_compile)
{
2017-04-02 17:37:49 +00:00
/// The min_count_to_compile value of zero indicates the need for synchronous compilation.
2017-04-02 17:37:49 +00:00
/// Are there any free threads?
if (min_count_to_compile == 0 || pool.active() < pool.size())
{
2017-04-02 17:37:49 +00:00
/// Indicates that the library is in the process of compiling.
libraries[hashed_key] = nullptr;
LOG_INFO(log, "Compiling code " << file_name << ", key: " << key);
if (min_count_to_compile == 0)
{
{
ext::unlock_guard<std::mutex> unlock(mutex);
compile(hashed_key, file_name, additional_compiler_flags, get_code, on_ready);
}
return libraries[hashed_key];
}
else
{
pool.schedule([=]
{
try
{
compile(hashed_key, file_name, additional_compiler_flags, get_code, on_ready);
}
catch (...)
{
tryLogCurrentException("Compiler");
}
});
}
}
else
LOG_INFO(log, "All threads are busy.");
}
return nullptr;
}
/// This will guarantee that code will compile only when version of headers match version of running server.
static void addCodeToAssertHeadersMatch(WriteBuffer & out)
{
out <<
"#include <Common/config_version.h>\n"
"#if VERSION_REVISION != " << ClickHouseRevision::get() << "\n"
"#error \"ClickHouse headers revision doesn't match runtime revision of the server.\"\n"
"#endif\n\n";
}
void Compiler::compile(
HashedKey hashed_key,
std::string file_name,
const std::string & additional_compiler_flags,
CodeGenerator get_code,
ReadyCallback on_ready)
{
ProfileEvents::increment(ProfileEvents::CompileAttempt);
std::string prefix = path + "/" + file_name;
std::string cpp_file_path = prefix + ".cpp";
std::string so_file_path = prefix + ".so";
std::string so_tmp_file_path = prefix + ".so.tmp";
{
WriteBufferFromFile out(cpp_file_path);
addCodeToAssertHeadersMatch(out);
out << get_code();
}
std::stringstream command;
2017-04-02 17:37:49 +00:00
/// Slightly uncomfortable.
command <<
"LD_LIBRARY_PATH=" PATH_SHARE "/clickhouse/bin/"
" " INTERNAL_COMPILER_EXECUTABLE
" -B " PATH_SHARE "/clickhouse/bin/"
" " INTERNAL_COMPILER_FLAGS
2017-03-02 13:34:43 +00:00
#if INTERNAL_COMPILER_CUSTOM_ROOT
" -isystem " INTERNAL_COMPILER_HEADERS_ROOT "/usr/local/include/"
" -isystem " INTERNAL_COMPILER_HEADERS_ROOT "/usr/include/"
" -isystem " INTERNAL_COMPILER_HEADERS_ROOT "/usr/include/c++/*/"
" -isystem " INTERNAL_COMPILER_HEADERS_ROOT "/usr/include/x86_64-linux-gnu/"
" -isystem " INTERNAL_COMPILER_HEADERS_ROOT "/usr/include/x86_64-linux-gnu/c++/*/"
" -isystem " INTERNAL_COMPILER_HEADERS_ROOT "/usr/local/lib/clang/*/include/"
2017-03-02 13:34:43 +00:00
#endif
" -I " INTERNAL_COMPILER_HEADERS "/dbms/src/"
" -I " INTERNAL_COMPILER_HEADERS "/contrib/libcityhash/include/"
" -I " INTERNAL_DOUBLE_CONVERSION_INCLUDE_DIR
" -I " INTERNAL_Poco_Foundation_INCLUDE_DIR
" -I " INTERNAL_Boost_INCLUDE_DIRS
" -I " INTERNAL_COMPILER_HEADERS "/libs/libcommon/include/"
" " << additional_compiler_flags <<
" -o " << so_tmp_file_path << " " << cpp_file_path
<< " 2>&1 || echo Exit code: $?";
std::string compile_result;
{
auto process = ShellCommand::execute(command.str());
readStringUntilEOF(compile_result, process->out);
process->wait();
}
if (!compile_result.empty())
throw Exception("Cannot compile code:\n\n" + command.str() + "\n\n" + compile_result);
2017-04-02 17:37:49 +00:00
/// If there was an error before, the file with the code remains for viewing.
Poco::File(cpp_file_path).remove();
Poco::File(so_tmp_file_path).renameTo(so_file_path);
SharedLibraryPtr lib(new SharedLibrary(so_file_path));
{
std::lock_guard<std::mutex> lock(mutex);
libraries[hashed_key] = lib;
}
LOG_INFO(log, "Compiled code " << file_name);
ProfileEvents::increment(ProfileEvents::CompileSuccess);
on_ready(lib);
}
}