[K/N] Logging for runtime
This commit is contained in:
committed by
Space
parent
516c2933ce
commit
a279b2230f
@@ -359,6 +359,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
putIfNotNull(RUNTIME_LOGS, arguments.runtimeLogs)
|
||||||
|
|
||||||
parseBinaryOptions(arguments, configuration).forEach { optionWithValue ->
|
parseBinaryOptions(arguments, configuration).forEach { optionWithValue ->
|
||||||
configuration.put(optionWithValue)
|
configuration.put(optionWithValue)
|
||||||
|
|||||||
+3
@@ -348,6 +348,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
|
|||||||
)
|
)
|
||||||
var binaryOptions: Array<String>? = null
|
var binaryOptions: Array<String>? = null
|
||||||
|
|
||||||
|
@Argument(value = "-Xruntime-logs", valueDescription = "<tag1=level1,tag2=level2,...>", description = "Enable logging for runtime with tags.")
|
||||||
|
var runtimeLogs: String? = null
|
||||||
|
|
||||||
override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap<AnalysisFlag<*>, Any> =
|
override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap<AnalysisFlag<*>, Any> =
|
||||||
super.configureAnalysisFlags(collector, languageVersion).also {
|
super.configureAnalysisFlags(collector, languageVersion).also {
|
||||||
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
|
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
|
||||||
|
|||||||
+1
@@ -62,6 +62,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
|||||||
val gcAggressive: Boolean get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR_AGRESSIVE)!!
|
val gcAggressive: Boolean get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR_AGRESSIVE)!!
|
||||||
val runtimeAssertsMode: RuntimeAssertsMode get() = configuration.get(BinaryOptions.runtimeAssertionsMode) ?: RuntimeAssertsMode.IGNORE
|
val runtimeAssertsMode: RuntimeAssertsMode get() = configuration.get(BinaryOptions.runtimeAssertionsMode) ?: RuntimeAssertsMode.IGNORE
|
||||||
val workerExceptionHandling: WorkerExceptionHandling get() = configuration.get(KonanConfigKeys.WORKER_EXCEPTION_HANDLING)!!
|
val workerExceptionHandling: WorkerExceptionHandling get() = configuration.get(KonanConfigKeys.WORKER_EXCEPTION_HANDLING)!!
|
||||||
|
val runtimeLogs: String? get() = configuration.get(KonanConfigKeys.RUNTIME_LOGS)
|
||||||
|
|
||||||
val needVerifyIr: Boolean
|
val needVerifyIr: Boolean
|
||||||
get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true
|
get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true
|
||||||
|
|||||||
+1
@@ -165,6 +165,7 @@ class KonanConfigKeys {
|
|||||||
val EXTERNAL_DEPENDENCIES: CompilerConfigurationKey<String?> =
|
val EXTERNAL_DEPENDENCIES: CompilerConfigurationKey<String?> =
|
||||||
CompilerConfigurationKey.create("use external dependencies to enhance IR linker error messages")
|
CompilerConfigurationKey.create("use external dependencies to enhance IR linker error messages")
|
||||||
val LLVM_VARIANT: CompilerConfigurationKey<LlvmVariant?> = CompilerConfigurationKey.create("llvm variant")
|
val LLVM_VARIANT: CompilerConfigurationKey<LlvmVariant?> = CompilerConfigurationKey.create("llvm variant")
|
||||||
|
val RUNTIME_LOGS: CompilerConfigurationKey<String> = CompilerConfigurationKey.create("enable runtime logging")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -2590,6 +2590,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
|
|
||||||
setRuntimeConstGlobal("KonanNeedDebugInfo", Int32(if (context.shouldContainDebugInfo()) 1 else 0))
|
setRuntimeConstGlobal("KonanNeedDebugInfo", Int32(if (context.shouldContainDebugInfo()) 1 else 0))
|
||||||
setRuntimeConstGlobal("Kotlin_runtimeAssertsMode", Int32(context.config.runtimeAssertsMode.value))
|
setRuntimeConstGlobal("Kotlin_runtimeAssertsMode", Int32(context.config.runtimeAssertsMode.value))
|
||||||
|
val runtimeLogs = context.config.runtimeLogs?.let {
|
||||||
|
context.llvm.staticData.cStringLiteral(it)
|
||||||
|
} ?: NullPointer(int8Type)
|
||||||
|
setRuntimeConstGlobal("Kotlin_runtimeLogs", runtimeLogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Globals set this way cannot be const, but are overridable when producing final executable.
|
// Globals set this way cannot be const, but are overridable when producing final executable.
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
|
|
||||||
#include "SameThreadMarkAndSweep.hpp"
|
#include "SameThreadMarkAndSweep.hpp"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
#include "CompilerConstants.hpp"
|
#include "CompilerConstants.hpp"
|
||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
|
#include "Logging.hpp"
|
||||||
#include "MarkAndSweepUtils.hpp"
|
#include "MarkAndSweepUtils.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "RootSet.hpp"
|
#include "RootSet.hpp"
|
||||||
@@ -68,6 +71,7 @@ void gc::SameThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) no
|
|||||||
if (threadData_.suspensionData().suspendIfRequested()) {
|
if (threadData_.suspensionData().suspendIfRequested()) {
|
||||||
allocatedBytes_ = 0;
|
allocatedBytes_ = 0;
|
||||||
} else if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
|
} else if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Attempt to GC at SafePointAllocation size=%zu", size);
|
||||||
allocatedBytes_ = 0;
|
allocatedBytes_ = 0;
|
||||||
PerformFullGC();
|
PerformFullGC();
|
||||||
}
|
}
|
||||||
@@ -92,16 +96,22 @@ void gc::SameThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
|||||||
// TODO: These will actually need to be run on a separate thread.
|
// TODO: These will actually need to be run on a separate thread.
|
||||||
// TODO: Cannot use `threadData_` here, because there's no way to transform `mm::ThreadData` into `MemoryState*`.
|
// TODO: Cannot use `threadData_` here, because there's no way to transform `mm::ThreadData` into `MemoryState*`.
|
||||||
AssertThreadState(ThreadState::kRunnable);
|
AssertThreadState(ThreadState::kRunnable);
|
||||||
|
RuntimeLogDebug({kTagGC}, "Starting to run finalizers");
|
||||||
|
auto timeBeforeUs = konan::getTimeMicros();
|
||||||
finalizerQueue.Finalize();
|
finalizerQueue.Finalize();
|
||||||
|
auto timeAfterUs = konan::getTimeMicros();
|
||||||
|
RuntimeLogInfo({kTagGC}, "Finished running finalizers in %" PRIu64 " microseconds", timeAfterUs - timeBeforeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SameThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
void gc::SameThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Attempt to GC on OOM");
|
||||||
PerformFullGC();
|
PerformFullGC();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SameThreadMarkAndSweep::ThreadData::SafePointRegularSlowPath() noexcept {
|
void gc::SameThreadMarkAndSweep::ThreadData::SafePointRegularSlowPath() noexcept {
|
||||||
safePointsCounter_ = 0;
|
safePointsCounter_ = 0;
|
||||||
if (konan::getTimeMicros() - timeOfLastGcUs_ >= gc_.GetCooldownThresholdUs()) {
|
if (konan::getTimeMicros() - timeOfLastGcUs_ >= gc_.GetCooldownThresholdUs()) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Attempt to GC at SafePointRegular");
|
||||||
timeOfLastGcUs_ = konan::getTimeMicros();
|
timeOfLastGcUs_ = konan::getTimeMicros();
|
||||||
PerformFullGC();
|
PerformFullGC();
|
||||||
}
|
}
|
||||||
@@ -128,34 +138,88 @@ gc::SameThreadMarkAndSweep::SameThreadMarkAndSweep() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mm::ObjectFactory<gc::SameThreadMarkAndSweep>::FinalizerQueue gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
mm::ObjectFactory<gc::SameThreadMarkAndSweep>::FinalizerQueue gc::SameThreadMarkAndSweep::PerformFullGC() noexcept {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Attempt to suspend threads by thread %d", konan::currentThreadId());
|
||||||
|
auto timeStartUs = konan::getTimeMicros();
|
||||||
bool didSuspend = mm::SuspendThreads();
|
bool didSuspend = mm::SuspendThreads();
|
||||||
|
auto timeSuspendUs = konan::getTimeMicros();
|
||||||
if (!didSuspend) {
|
if (!didSuspend) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Failed to suspend threads");
|
||||||
// Somebody else suspended the threads, and so ran a GC.
|
// Somebody else suspended the threads, and so ran a GC.
|
||||||
// TODO: This breaks if suspension is used by something apart from GC.
|
// TODO: This breaks if suspension is used by something apart from GC.
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
RuntimeLogDebug({kTagGC}, "Suspended all threads in %" PRIu64 " microseconds", timeSuspendUs - timeStartUs);
|
||||||
|
|
||||||
|
RuntimeLogInfo({kTagGC}, "Started GC epoch %zu. Time since last GC %" PRIu64 " microseconds", epoch_, timeStartUs - lastGCTimestampUs_);
|
||||||
KStdVector<ObjHeader*> graySet;
|
KStdVector<ObjHeader*> graySet;
|
||||||
for (auto& thread : mm::GlobalData::Instance().threadRegistry().LockForIter()) {
|
for (auto& thread : mm::GlobalData::Instance().threadRegistry().LockForIter()) {
|
||||||
// TODO: Maybe it's more efficient to do by the suspending thread?
|
// TODO: Maybe it's more efficient to do by the suspending thread?
|
||||||
thread.Publish();
|
thread.Publish();
|
||||||
for (auto* object : mm::ThreadRootSet(thread)) {
|
size_t stack = 0;
|
||||||
if (!isNullOrMarker(object)) {
|
size_t tls = 0;
|
||||||
graySet.push_back(object);
|
for (auto value : mm::ThreadRootSet(thread)) {
|
||||||
|
if (!isNullOrMarker(value.object)) {
|
||||||
|
graySet.push_back(value.object);
|
||||||
|
switch (value.source) {
|
||||||
|
case mm::ThreadRootSet::Source::kStack:
|
||||||
|
++stack;
|
||||||
|
break;
|
||||||
|
case mm::ThreadRootSet::Source::kTLS:
|
||||||
|
++tls;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeLogDebug({kTagGC}, "Collected root set for thread stack=%zu tls=%zu", stack, tls);
|
||||||
|
}
|
||||||
|
mm::StableRefRegistry::Instance().ProcessDeletions();
|
||||||
|
size_t global = 0;
|
||||||
|
size_t stableRef = 0;
|
||||||
|
for (auto value : mm::GlobalRootSet()) {
|
||||||
|
if (!isNullOrMarker(value.object)) {
|
||||||
|
graySet.push_back(value.object);
|
||||||
|
switch (value.source) {
|
||||||
|
case mm::GlobalRootSet::Source::kGlobal:
|
||||||
|
++global;
|
||||||
|
break;
|
||||||
|
case mm::GlobalRootSet::Source::kStableRef:
|
||||||
|
++stableRef;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mm::StableRefRegistry::Instance().ProcessDeletions();
|
auto timeRootSetUs = konan::getTimeMicros();
|
||||||
for (auto* object : mm::GlobalRootSet()) {
|
RuntimeLogDebug({kTagGC}, "Collected global root set global=%zu stableRef=%zu", global, stableRef);
|
||||||
if (!isNullOrMarker(object)) {
|
|
||||||
graySet.push_back(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Can be unsafe, because we've stopped the world.
|
||||||
|
auto objectsCountBefore = mm::GlobalData::Instance().objectFactory().GetSizeUnsafe();
|
||||||
|
|
||||||
|
RuntimeLogInfo({kTagGC}, "Collected root set of size %zu of which %zu are stable refs in %" PRIu64 " microseconds", graySet.size(), stableRef, timeRootSetUs - timeSuspendUs);
|
||||||
gc::Mark<MarkTraits>(std::move(graySet));
|
gc::Mark<MarkTraits>(std::move(graySet));
|
||||||
|
auto timeMarkUs = konan::getTimeMicros();
|
||||||
|
RuntimeLogDebug({kTagGC}, "Marked in %" PRIu64 " microseconds", timeMarkUs - timeRootSetUs);
|
||||||
auto finalizerQueue = gc::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
|
auto finalizerQueue = gc::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
|
||||||
|
auto timeSweepUs = konan::getTimeMicros();
|
||||||
|
RuntimeLogDebug({kTagGC}, "Sweeped in %" PRIu64 " microseconds", timeSweepUs - timeMarkUs);
|
||||||
|
|
||||||
|
// Can be unsafe, because we've stopped the world.
|
||||||
|
auto objectsCountAfter = mm::GlobalData::Instance().objectFactory().GetSizeUnsafe();
|
||||||
|
|
||||||
mm::ResumeThreads();
|
mm::ResumeThreads();
|
||||||
|
auto timeResumeUs = konan::getTimeMicros();
|
||||||
|
|
||||||
|
RuntimeLogDebug({kTagGC}, "Resumed threads in %" PRIu64 " microseconds.", timeResumeUs - timeSweepUs);
|
||||||
|
|
||||||
|
auto finalizersCount = finalizerQueue.size();
|
||||||
|
auto collectedCount = objectsCountBefore - objectsCountAfter - finalizersCount;
|
||||||
|
|
||||||
|
RuntimeLogInfo(
|
||||||
|
{kTagGC},
|
||||||
|
"Finished GC epoch %zu. Collected %zu objects, to be finalized %zu objects, %zu objects remain. Total pause time %" PRIu64
|
||||||
|
" microseconds",
|
||||||
|
epoch_, collectedCount, finalizersCount, objectsCountAfter, timeResumeUs - timeStartUs);
|
||||||
|
++epoch_;
|
||||||
|
lastGCTimestampUs_ = timeResumeUs;
|
||||||
|
|
||||||
return finalizerQueue;
|
return finalizerQueue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
mm::ObjectFactory<SameThreadMarkAndSweep>::FinalizerQueue PerformFullGC() noexcept;
|
mm::ObjectFactory<SameThreadMarkAndSweep>::FinalizerQueue PerformFullGC() noexcept;
|
||||||
|
|
||||||
|
size_t epoch_ = 0;
|
||||||
|
uint64_t lastGCTimestampUs_ = 0;
|
||||||
|
|
||||||
size_t threshold_ = 100000; // Roughly 1 safepoint per 10ms (on a subset of examples on one particular machine).
|
size_t threshold_ = 100000; // Roughly 1 safepoint per 10ms (on a subset of examples on one particular machine).
|
||||||
size_t allocationThresholdBytes_ = 10 * 1024 * 1024; // 10MiB by default.
|
size_t allocationThresholdBytes_ = 10 * 1024 * 1024; // 10MiB by default.
|
||||||
uint64_t cooldownThresholdUs_ = 200 * 1000; // 200 milliseconds by default.
|
uint64_t cooldownThresholdUs_ = 200 * 1000; // 200 milliseconds by default.
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ bool operator!=(
|
|||||||
template <class T>
|
template <class T>
|
||||||
class KonanDeleter {
|
class KonanDeleter {
|
||||||
public:
|
public:
|
||||||
|
KonanDeleter() = default;
|
||||||
|
|
||||||
|
// This is needed for `KStdUniquePtr` covariance: if `U*` converts to `T*` then `KStdUniquePtr<U>` converts to `KStdUniquePtr<T>`.
|
||||||
|
template <class U, std::enable_if_t<std::is_convertible_v<U*, T*>, std::nullptr_t> = nullptr>
|
||||||
|
KonanDeleter(KonanDeleter<U>) {}
|
||||||
|
|
||||||
void operator()(T* instance) noexcept { konanDestructInstance(instance); }
|
void operator()(T* instance) noexcept { konanDestructInstance(instance); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,18 @@
|
|||||||
#define RUNTIME_COMPILER_CONSTANTS_H
|
#define RUNTIME_COMPILER_CONSTANTS_H
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#if __has_include(<string_view>)
|
||||||
|
#include <string_view>
|
||||||
|
#elif __has_include(<experimental/string_view>)
|
||||||
|
// TODO: Remove when wasm32 is gone.
|
||||||
|
#include <xlocale.h>
|
||||||
|
#include <experimental/string_view>
|
||||||
|
namespace std {
|
||||||
|
using string_view = std::experimental::string_view;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "No <string_view>"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
|
||||||
@@ -15,6 +27,7 @@
|
|||||||
// These are defined by setRuntimeConstGlobals in IrToBitcode.kt
|
// These are defined by setRuntimeConstGlobals in IrToBitcode.kt
|
||||||
extern "C" const int32_t KonanNeedDebugInfo;
|
extern "C" const int32_t KonanNeedDebugInfo;
|
||||||
extern "C" const int32_t Kotlin_runtimeAssertsMode;
|
extern "C" const int32_t Kotlin_runtimeAssertsMode;
|
||||||
|
extern "C" const char* const Kotlin_runtimeLogs;
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
namespace compiler {
|
namespace compiler {
|
||||||
@@ -52,6 +65,10 @@ ALWAYS_INLINE inline RuntimeAssertsMode runtimeAssertsMode() noexcept {
|
|||||||
|
|
||||||
WorkerExceptionHandling workerExceptionHandling() noexcept;
|
WorkerExceptionHandling workerExceptionHandling() noexcept;
|
||||||
|
|
||||||
|
ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept {
|
||||||
|
return Kotlin_runtimeLogs == nullptr ? std::string_view() : std::string_view(Kotlin_runtimeLogs);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace compiler
|
} // namespace compiler
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Logging.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
// TODO: Remove when wasm32 is gone.
|
||||||
|
#include <experimental/optional>
|
||||||
|
namespace std {
|
||||||
|
template <typename T>
|
||||||
|
using optional = std::experimental::optional<T>;
|
||||||
|
inline constexpr auto nullopt = std::experimental::nullopt;
|
||||||
|
} // namespace std
|
||||||
|
#else
|
||||||
|
#error "No <optional>"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Format.h"
|
||||||
|
#include "KAssert.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ParseResult {
|
||||||
|
std::optional<T> value;
|
||||||
|
std::string_view rest;
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseResult<std::string_view> ParseTag(std::string_view input) noexcept {
|
||||||
|
auto position = input.find('=');
|
||||||
|
if (position == std::string_view::npos || position == 0) {
|
||||||
|
return {std::nullopt, input};
|
||||||
|
}
|
||||||
|
return {input.substr(0, position), input.substr(position + 1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseResult<std::string_view> ParseLevelString(std::string_view input) noexcept {
|
||||||
|
auto position = input.find(',');
|
||||||
|
if (position == 0) {
|
||||||
|
return {std::nullopt, input};
|
||||||
|
}
|
||||||
|
if (position == std::string_view::npos) {
|
||||||
|
return {input, std::string_view()};
|
||||||
|
}
|
||||||
|
return {input.substr(0, position), input.substr(position + 1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<logging::Level> ParseLevel(std::string_view levelString) noexcept {
|
||||||
|
if (levelString == "debug") return logging::Level::kDebug;
|
||||||
|
if (levelString == "info") return logging::Level::kInfo;
|
||||||
|
if (levelString == "warning") return logging::Level::kWarning;
|
||||||
|
if (levelString == "error") return logging::Level::kError;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdOrderedMap<KStdString, logging::Level> ParseTagsFilter(std::string_view tagsFilter) noexcept {
|
||||||
|
if (tagsFilter.empty()) return {};
|
||||||
|
KStdOrderedMap<KStdString, logging::Level> result;
|
||||||
|
std::string_view rest = tagsFilter;
|
||||||
|
while (!rest.empty()) {
|
||||||
|
auto tag = ParseTag(rest);
|
||||||
|
rest = tag.rest;
|
||||||
|
if (tag.value == std::nullopt) {
|
||||||
|
konan::consoleErrorf("Failed to parse tag at: '");
|
||||||
|
konan::consoleErrorUtf8(rest.data(), rest.size());
|
||||||
|
konan::consoleErrorf("'. No logging will be performed\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto levelString = ParseLevelString(rest);
|
||||||
|
rest = levelString.rest;
|
||||||
|
auto level = levelString.value ? ParseLevel(*levelString.value) : std::nullopt;
|
||||||
|
if (level == std::nullopt) {
|
||||||
|
konan::consoleErrorf("Failed to parse level at: '");
|
||||||
|
konan::consoleErrorUtf8(rest.data(), rest.size());
|
||||||
|
konan::consoleErrorf("'. No logging will be performed\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
result.emplace(KStdString(tag.value->data(), tag.value->size()), *level);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogFilter : public logging::internal::LogFilter {
|
||||||
|
public:
|
||||||
|
explicit LogFilter(std::string_view tagsFilter) noexcept : tagLevelMap_(ParseTagsFilter(tagsFilter)) {}
|
||||||
|
|
||||||
|
bool Empty() const noexcept override { return tagLevelMap_.empty(); }
|
||||||
|
|
||||||
|
bool Enabled(logging::Level level, std_support::span<const char* const> tags) const noexcept override {
|
||||||
|
for (auto tag : tags) {
|
||||||
|
auto it = tagLevelMap_.find(tag);
|
||||||
|
if (it != tagLevelMap_.end()) {
|
||||||
|
if (it->second <= level) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// TODO: Make it more efficient.
|
||||||
|
KStdOrderedMap<KStdString, logging::Level> tagLevelMap_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StderrLogger : public logging::internal::Logger {
|
||||||
|
public:
|
||||||
|
void Log(logging::Level level, std_support::span<const char* const> tags, std::string_view message) const noexcept override {
|
||||||
|
konan::consoleErrorUtf8(message.data(), message.size());
|
||||||
|
konan::consoleErrorf("\n");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std_support::span<char> FormatLevel(std_support::span<char> buffer, logging::Level level) noexcept {
|
||||||
|
switch (level) {
|
||||||
|
case logging::Level::kDebug:
|
||||||
|
return FormatToSpan(buffer, "[DEBUG]");
|
||||||
|
case logging::Level::kInfo:
|
||||||
|
return FormatToSpan(buffer, "[INFO]");
|
||||||
|
case logging::Level::kWarning:
|
||||||
|
return FormatToSpan(buffer, "[WARN]");
|
||||||
|
case logging::Level::kError:
|
||||||
|
return FormatToSpan(buffer, "[ERROR]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std_support::span<char> FormatTags(std_support::span<char> buffer, std_support::span<const char* const> tags) noexcept {
|
||||||
|
// `tags` cannot be empty.
|
||||||
|
auto firstTag = tags.front();
|
||||||
|
buffer = FormatToSpan(buffer, "[%s", firstTag);
|
||||||
|
for (auto tag : tags.subspan(1)) {
|
||||||
|
buffer = FormatToSpan(buffer, ",%s", tag);
|
||||||
|
}
|
||||||
|
return FormatToSpan(buffer, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
KStdUniquePtr<logging::internal::LogFilter> logging::internal::CreateLogFilter(std::string_view tagsFilter) noexcept {
|
||||||
|
return ::make_unique<::LogFilter>(tagsFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdUniquePtr<logging::internal::Logger> logging::internal::CreateStderrLogger() noexcept {
|
||||||
|
return ::make_unique<StderrLogger>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std_support::span<char> logging::internal::FormatLogEntry(
|
||||||
|
std_support::span<char> buffer,
|
||||||
|
logging::Level level,
|
||||||
|
std_support::span<const char* const> tags,
|
||||||
|
const char* format,
|
||||||
|
std::va_list args) noexcept {
|
||||||
|
buffer = FormatLevel(buffer, level);
|
||||||
|
buffer = FormatTags(buffer, tags);
|
||||||
|
buffer = FormatToSpan(buffer, " ");
|
||||||
|
buffer = VFormatToSpan(buffer, format, args);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logging::internal::Log(
|
||||||
|
const LogFilter& logFilter,
|
||||||
|
const Logger& logger,
|
||||||
|
Level level,
|
||||||
|
std_support::span<const char* const> tags,
|
||||||
|
const char* format,
|
||||||
|
std::va_list args) noexcept {
|
||||||
|
if (!logFilter.Enabled(level, tags)) return;
|
||||||
|
// TODO: This might be suboptimal.
|
||||||
|
std::array<char, 1024> logEntry;
|
||||||
|
auto rest = FormatLogEntry(logEntry, level, tags, format, args);
|
||||||
|
logger.Log(level, tags, std::string_view(logEntry.data(), rest.data() - logEntry.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void logging::Log(Level level, std::initializer_list<const char*> tags, const char* format, ...) noexcept {
|
||||||
|
std::va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
VLog(level, tags, format, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logging::VLog(Level level, std::initializer_list<const char*> tags, const char* format, std::va_list args) noexcept {
|
||||||
|
static auto logFilter = internal::CreateLogFilter(compiler::runtimeLogs());
|
||||||
|
static auto logger = internal::CreateStderrLogger();
|
||||||
|
RuntimeAssert(tags.size() > 0, "Cannot Log without tags");
|
||||||
|
std_support::span<const char* const> tagsSpan(std::data(tags), std::size(tags));
|
||||||
|
internal::Log(*logFilter, *logger, level, tagsSpan, format, args);
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RUNTIME_LOGGING_H
|
||||||
|
#define RUNTIME_LOGGING_H
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <initializer_list>
|
||||||
|
#if __has_include(<string_view>)
|
||||||
|
#include <string_view>
|
||||||
|
#elif __has_include(<experimental/string_view>)
|
||||||
|
// TODO: Remove when wasm32 is gone.
|
||||||
|
#include <xlocale.h>
|
||||||
|
#include <experimental/string_view>
|
||||||
|
namespace std {
|
||||||
|
using string_view = std::experimental::string_view;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "No <string_view>"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "CompilerConstants.hpp"
|
||||||
|
#include "cpp_support/Span.hpp"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
namespace kotlin {
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
enum class Level {
|
||||||
|
kDebug,
|
||||||
|
kInfo,
|
||||||
|
kWarning,
|
||||||
|
kError,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
class LogFilter {
|
||||||
|
public:
|
||||||
|
virtual ~LogFilter() = default;
|
||||||
|
|
||||||
|
virtual bool Empty() const noexcept = 0;
|
||||||
|
virtual bool Enabled(Level level, std_support::span<const char* const> tags) const noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
KStdUniquePtr<LogFilter> CreateLogFilter(std::string_view tagsFilter) noexcept;
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
virtual ~Logger() = default;
|
||||||
|
|
||||||
|
virtual void Log(Level level, std_support::span<const char* const> tags, std::string_view message) const noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
KStdUniquePtr<Logger> CreateStderrLogger() noexcept;
|
||||||
|
|
||||||
|
std_support::span<char> FormatLogEntry(
|
||||||
|
std_support::span<char> buffer,
|
||||||
|
Level level,
|
||||||
|
std_support::span<const char* const> tags,
|
||||||
|
const char* format,
|
||||||
|
std::va_list args) noexcept;
|
||||||
|
|
||||||
|
void Log(
|
||||||
|
const LogFilter& logFilter,
|
||||||
|
const Logger& logger,
|
||||||
|
Level level,
|
||||||
|
std_support::span<const char* const> tags,
|
||||||
|
const char* format,
|
||||||
|
std::va_list args) noexcept;
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
__attribute__((format(printf, 3, 4))) void Log(Level level, std::initializer_list<const char*> tags, const char* format, ...) noexcept;
|
||||||
|
void VLog(Level level, std::initializer_list<const char*> tags, const char* format, std::va_list args) noexcept;
|
||||||
|
|
||||||
|
} // namespace logging
|
||||||
|
|
||||||
|
// Well known tags.
|
||||||
|
// These are defined outside of logging namespace for simpler usage.
|
||||||
|
|
||||||
|
inline constexpr const char* kTagGC = "gc";
|
||||||
|
inline constexpr const char* kTagMM = "mm";
|
||||||
|
|
||||||
|
} // namespace kotlin
|
||||||
|
|
||||||
|
#endif // RUNTIME_LOGGING_H
|
||||||
|
|
||||||
|
// Using macros to simplify forwarding of varargs without breaking __attribute__((format)) and to avoid
|
||||||
|
// evaluating args in `...` if logging is disabled.
|
||||||
|
|
||||||
|
#define RuntimeLog(level, tags, format, ...) \
|
||||||
|
do { \
|
||||||
|
if (!::kotlin::compiler::runtimeLogs().empty()) { \
|
||||||
|
::kotlin::logging::Log(level, tags, format, ##__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define RuntimeVLog(level, tags, format, args) \
|
||||||
|
do { \
|
||||||
|
if (!::kotlin::compiler::runtimeLogs().empty()) { \
|
||||||
|
::kotlin::logging::VLog(level, tags, format, args); \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define RuntimeLogDebug(tags, format, ...) RuntimeLog(::kotlin::logging::Level::kDebug, tags, format, ##__VA_ARGS__)
|
||||||
|
#define RuntimeLogInfo(tags, format, ...) RuntimeLog(::kotlin::logging::Level::kInfo, tags, format, ##__VA_ARGS__)
|
||||||
|
#define RuntimeLogWarning(tags, format, ...) RuntimeLog(::kotlin::logging::Level::kWarning, tags, format, ##__VA_ARGS__)
|
||||||
|
#define RuntimeLogError(tags, format, ...) RuntimeLog(::kotlin::logging::Level::kError, tags, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define RuntimeVLogDebug(tags, format, args) RuntimeVLog(::kotlin::logging::Level::kDebug, tags, format, args)
|
||||||
|
#define RuntimeVLogInfo(tags, format, args) RuntimeVLog(::kotlin::logging::Level::kInfo, tags, format, args)
|
||||||
|
#define RuntimeVLogWarning(tags, format, args) RuntimeVLog(::kotlin::logging::Level::kWarning, tags, format, args)
|
||||||
|
#define RuntimeVLogError(tags, format, args) RuntimeVLog(::kotlin::logging::Level::kError, tags, format, args)
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Logging.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std_support::span<char> FormatLogEntry(
|
||||||
|
std_support::span<char> buffer, logging::Level level, std::initializer_list<const char*> tags, const char* format, ...) {
|
||||||
|
std_support::span<const char* const> tagsSpan(std::data(tags), std::size(tags));
|
||||||
|
std::va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
auto result = logging::internal::FormatLogEntry(buffer, level, tagsSpan, format, args);
|
||||||
|
va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogFilter {
|
||||||
|
public:
|
||||||
|
explicit LogFilter(std::string_view filter) : logFilter_(logging::internal::CreateLogFilter(filter)) {}
|
||||||
|
|
||||||
|
bool Empty() const { return logFilter_->Empty(); }
|
||||||
|
|
||||||
|
bool Enabled(logging::Level level, std::initializer_list<const char*> tags) const {
|
||||||
|
std_support::span<const char* const> tagsSpan(std::data(tags), std::size(tags));
|
||||||
|
return logFilter_->Enabled(level, tagsSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KStdUniquePtr<logging::internal::LogFilter> logFilter_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockLogFilter : public logging::internal::LogFilter {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(bool, Empty, (), (const, noexcept, override));
|
||||||
|
MOCK_METHOD(bool, Enabled, (logging::Level, std_support::span<const char* const>), (const, noexcept, override));
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockLogger : public logging::internal::Logger {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(void, Log, (logging::Level, std_support::span<const char* const>, std::string_view), (const, noexcept, override));
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Debug_OneTag) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kDebug, {"t1"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[DEBUG][t1] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Debug_TwoTags) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kDebug, {"t1", "t2"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[DEBUG][t1,t2] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Info_OneTag) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kInfo, {"t1"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[INFO][t1] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Info_TwoTags) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kInfo, {"t1", "t2"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[INFO][t1,t2] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Warning_OneTag) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kWarning, {"t1"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[WARN][t1] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Warning_TwoTags) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kWarning, {"t1", "t2"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[WARN][t1,t2] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Error_OneTag) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kError, {"t1"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[ERROR][t1] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, FormatLogEntry_Error_TwoTags) {
|
||||||
|
std::array<char, 1024> buffer;
|
||||||
|
FormatLogEntry(buffer, logging::Level::kError, {"t1", "t2"}, "Log #%d", 42);
|
||||||
|
EXPECT_THAT(buffer.data(), testing::StrEq("[ERROR][t1,t2] Log #42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingDeathTest, StderrLogger) {
|
||||||
|
auto logger = logging::internal::CreateStderrLogger();
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
logger->Log(logging::Level::kInfo, {}, "Message for the log");
|
||||||
|
std::abort();
|
||||||
|
},
|
||||||
|
"Message for the log");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, LogFilter_Empty) {
|
||||||
|
LogFilter filter("");
|
||||||
|
EXPECT_TRUE(filter.Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, LogFilter_EnableOne) {
|
||||||
|
LogFilter filter("t1=info");
|
||||||
|
EXPECT_FALSE(filter.Empty());
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kInfo, {"t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t1"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t2"}));
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kInfo, {"t2"}));
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kWarning, {"t2"}));
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kError, {"t2"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t1", "t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kInfo, {"t1", "t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t1", "t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t1", "t2"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t2", "t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kInfo, {"t2", "t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t2", "t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t2", "t1"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, LogFilter_EnableTwo) {
|
||||||
|
LogFilter filter("t1=info,t2=warning");
|
||||||
|
EXPECT_FALSE(filter.Empty());
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kInfo, {"t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t1"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t2"}));
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kInfo, {"t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t2"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t1", "t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kInfo, {"t1", "t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t1", "t2"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t1", "t2"}));
|
||||||
|
|
||||||
|
EXPECT_FALSE(filter.Enabled(logging::Level::kDebug, {"t2", "t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kInfo, {"t2", "t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kWarning, {"t2", "t1"}));
|
||||||
|
EXPECT_TRUE(filter.Enabled(logging::Level::kError, {"t2", "t1"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LoggingTest, LogFilter_Broken) {
|
||||||
|
EXPECT_TRUE(LogFilter("t1").Empty());
|
||||||
|
EXPECT_TRUE(LogFilter("t1=").Empty());
|
||||||
|
EXPECT_TRUE(LogFilter("t1=oops").Empty());
|
||||||
|
EXPECT_TRUE(LogFilter("t1=info,t2").Empty());
|
||||||
|
EXPECT_TRUE(LogFilter("t1=info,t2=").Empty());
|
||||||
|
EXPECT_TRUE(LogFilter("t1=info,t2=oops").Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class LoggingLogTest : public testing::Test {
|
||||||
|
public:
|
||||||
|
void Log(logging::Level level, std::initializer_list<const char*> tags, const char* format, ...) {
|
||||||
|
std::va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
logging::internal::Log(
|
||||||
|
logFilter_, logger_, level, std_support::span<const char* const>(std::data(tags), std::size(tags)), format, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
MockLogFilter& logFilter() { return logFilter_; }
|
||||||
|
MockLogger& logger() { return logger_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
testing::StrictMock<MockLogFilter> logFilter_;
|
||||||
|
testing::StrictMock<MockLogger> logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
MATCHER_P(TagsAre, tags, "") {
|
||||||
|
KStdVector<std::string_view> actualTags;
|
||||||
|
for (auto tag : arg) {
|
||||||
|
actualTags.push_back(tag);
|
||||||
|
}
|
||||||
|
return testing::ExplainMatchResult(testing::ElementsAreArray(tags), actualTags, result_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_F(LoggingLogTest, Log_Fail) {
|
||||||
|
constexpr auto level = logging::Level::kInfo;
|
||||||
|
const std::initializer_list<const char*> tags = {"t1", "t2"};
|
||||||
|
EXPECT_CALL(logFilter(), Enabled(level, TagsAre(tags))).WillOnce(testing::Return(false));
|
||||||
|
Log(level, tags, "Message %d", 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LoggingLogTest, Log_Success) {
|
||||||
|
constexpr auto level = logging::Level::kInfo;
|
||||||
|
const std::initializer_list<const char*> tags = {"t1", "t2"};
|
||||||
|
EXPECT_CALL(logFilter(), Enabled(level, TagsAre(tags))).WillOnce(testing::Return(true));
|
||||||
|
EXPECT_CALL(logger(), Log(level, TagsAre(tags), "[INFO][t1,t2] Message 42"));
|
||||||
|
Log(level, tags, "Message %d", 42);
|
||||||
|
}
|
||||||
@@ -512,7 +512,7 @@ extern "C" {
|
|||||||
int _ZNSt3__112__next_primeEj(unsigned long n) {
|
int _ZNSt3__112__next_primeEj(unsigned long n) {
|
||||||
return _ZNSt3__212__next_primeEj(n);
|
return _ZNSt3__212__next_primeEj(n);
|
||||||
}
|
}
|
||||||
void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function) {
|
void __assert_fail(const char* assertion, const char* file, int line, const char* function) {
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
konan::snprintf(buf, sizeof(buf), "%s:%d in %s: runtime assert: %s\n", file, line, function, assertion);
|
konan::snprintf(buf, sizeof(buf), "%s:%d in %s: runtime assert: %s\n", file, line, function, assertion);
|
||||||
Konan_abort(buf);
|
Konan_abort(buf);
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ public:
|
|||||||
|
|
||||||
~Producer() { Publish(); }
|
~Producer() { Publish(); }
|
||||||
|
|
||||||
|
size_t size() const noexcept { return size_; }
|
||||||
|
|
||||||
Node& Insert(size_t dataSize) noexcept {
|
Node& Insert(size_t dataSize) noexcept {
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
auto node = Node::Create(allocator_, dataSize);
|
auto node = Node::Create(allocator_, dataSize);
|
||||||
@@ -137,6 +139,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
last_ = nodePtr;
|
last_ = nodePtr;
|
||||||
|
++size_;
|
||||||
RuntimeAssert(root_ != nullptr, "Must not be empty");
|
RuntimeAssert(root_ != nullptr, "Must not be empty");
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
return *nodePtr;
|
return *nodePtr;
|
||||||
@@ -172,6 +175,8 @@ public:
|
|||||||
|
|
||||||
owner_.last_ = last_;
|
owner_.last_ = last_;
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
|
owner_.size_ += size_;
|
||||||
|
size_ = 0;
|
||||||
|
|
||||||
RuntimeAssert(root_ == nullptr, "Must be empty");
|
RuntimeAssert(root_ == nullptr, "Must be empty");
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
@@ -186,6 +191,7 @@ public:
|
|||||||
// Since it's only for tests, no need to worry about stack overflows.
|
// Since it's only for tests, no need to worry about stack overflows.
|
||||||
root_.reset();
|
root_.reset();
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
|
size_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -204,6 +210,7 @@ public:
|
|||||||
Allocator allocator_;
|
Allocator allocator_;
|
||||||
unique_ptr<Node> root_;
|
unique_ptr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
|
size_t size_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
@@ -263,6 +270,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t size() const noexcept { return size_; }
|
||||||
|
|
||||||
Iterator begin() noexcept { return Iterator(root_.get()); }
|
Iterator begin() noexcept { return Iterator(root_.get()); }
|
||||||
Iterator end() noexcept { return Iterator(nullptr); }
|
Iterator end() noexcept { return Iterator(nullptr); }
|
||||||
|
|
||||||
@@ -279,6 +288,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
last_ = nodePtr;
|
last_ = nodePtr;
|
||||||
|
++size_;
|
||||||
AssertCorrect();
|
AssertCorrect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,12 +303,15 @@ public:
|
|||||||
|
|
||||||
unique_ptr<Node> root_;
|
unique_ptr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
|
size_t size_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Iterable : private MoveOnly {
|
class Iterable : private MoveOnly {
|
||||||
public:
|
public:
|
||||||
explicit Iterable(ObjectFactoryStorage& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {}
|
explicit Iterable(ObjectFactoryStorage& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {}
|
||||||
|
|
||||||
|
size_t size() const noexcept { return owner_.size_; }
|
||||||
|
|
||||||
Iterator begin() noexcept { return Iterator(nullptr, owner_.root_.get()); }
|
Iterator begin() noexcept { return Iterator(nullptr, owner_.root_.get()); }
|
||||||
Iterator end() noexcept { return Iterator(owner_.last_, nullptr); }
|
Iterator end() noexcept { return Iterator(owner_.last_, nullptr); }
|
||||||
|
|
||||||
@@ -326,9 +339,12 @@ public:
|
|||||||
// Lock `ObjectFactoryStorage` for safe iteration.
|
// Lock `ObjectFactoryStorage` for safe iteration.
|
||||||
Iterable LockForIter() noexcept { return Iterable(*this); }
|
Iterable LockForIter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
|
size_t GetSizeUnsafe() const noexcept { return size_; }
|
||||||
|
|
||||||
void ClearForTests() {
|
void ClearForTests() {
|
||||||
root_.reset();
|
root_.reset();
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
|
size_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -344,6 +360,7 @@ private:
|
|||||||
if (!root_) {
|
if (!root_) {
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
}
|
}
|
||||||
|
--size_;
|
||||||
AssertCorrectUnsafe();
|
AssertCorrectUnsafe();
|
||||||
return {std::move(node), root_.get()};
|
return {std::move(node), root_.get()};
|
||||||
}
|
}
|
||||||
@@ -353,6 +370,7 @@ private:
|
|||||||
if (!previousNode->next_) {
|
if (!previousNode->next_) {
|
||||||
last_ = previousNode;
|
last_ = previousNode;
|
||||||
}
|
}
|
||||||
|
--size_;
|
||||||
|
|
||||||
AssertCorrectUnsafe();
|
AssertCorrectUnsafe();
|
||||||
return {std::move(node), previousNode->next_.get()};
|
return {std::move(node), previousNode->next_.get()};
|
||||||
@@ -370,6 +388,7 @@ private:
|
|||||||
|
|
||||||
unique_ptr<Node> root_;
|
unique_ptr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
|
size_t size_ = 0;
|
||||||
SpinLock mutex_;
|
SpinLock mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -602,6 +621,8 @@ public:
|
|||||||
FinalizerQueue& owner_;
|
FinalizerQueue& owner_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
size_t size() const noexcept { return consumer_.size(); }
|
||||||
|
|
||||||
// TODO: Consider running it in the destructor instead.
|
// TODO: Consider running it in the destructor instead.
|
||||||
void Finalize() noexcept {
|
void Finalize() noexcept {
|
||||||
for (auto node : Iterable(*this)) {
|
for (auto node : Iterable(*this)) {
|
||||||
@@ -640,6 +661,8 @@ public:
|
|||||||
// Lock ObjectFactory for safe iteration.
|
// Lock ObjectFactory for safe iteration.
|
||||||
Iterable LockForIter() noexcept { return Iterable(*this); }
|
Iterable LockForIter() noexcept { return Iterable(*this); }
|
||||||
|
|
||||||
|
size_t GetSizeUnsafe() const noexcept { return storage_.GetSizeUnsafe(); }
|
||||||
|
|
||||||
void ClearForTests() { storage_.ClearForTests(); }
|
void ClearForTests() { storage_.ClearForTests(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ TEST(ObjectFactoryStorageTest, Empty) {
|
|||||||
auto actual = Collect(storage);
|
auto actual = Collect(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::IsEmpty());
|
EXPECT_THAT(actual, testing::IsEmpty());
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, DoNotPublish) {
|
TEST(ObjectFactoryStorageTest, DoNotPublish) {
|
||||||
@@ -105,6 +106,8 @@ TEST(ObjectFactoryStorageTest, DoNotPublish) {
|
|||||||
auto actual = Collect(storage);
|
auto actual = Collect(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::IsEmpty());
|
EXPECT_THAT(actual, testing::IsEmpty());
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||||
|
EXPECT_THAT(producer.size(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, Publish) {
|
TEST(ObjectFactoryStorageTest, Publish) {
|
||||||
@@ -123,6 +126,9 @@ TEST(ObjectFactoryStorageTest, Publish) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 2, 10, 20));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 2, 10, 20));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 4);
|
||||||
|
EXPECT_THAT(producer1.size(), 0);
|
||||||
|
EXPECT_THAT(producer2.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, PublishDifferentTypes) {
|
TEST(ObjectFactoryStorageTest, PublishDifferentTypes) {
|
||||||
@@ -156,6 +162,8 @@ TEST(ObjectFactoryStorageTest, PublishDifferentTypes) {
|
|||||||
EXPECT_THAT(maxAlign.value, 8);
|
EXPECT_THAT(maxAlign.value, 8);
|
||||||
++it;
|
++it;
|
||||||
EXPECT_THAT(it, actual.end());
|
EXPECT_THAT(it, actual.end());
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 5);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, PublishSeveralTimes) {
|
TEST(ObjectFactoryStorageTest, PublishSeveralTimes) {
|
||||||
@@ -182,6 +190,8 @@ TEST(ObjectFactoryStorageTest, PublishSeveralTimes) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 2, 3, 4, 5));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 2, 3, 4, 5));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 5);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, PublishInDestructor) {
|
TEST(ObjectFactoryStorageTest, PublishInDestructor) {
|
||||||
@@ -196,6 +206,7 @@ TEST(ObjectFactoryStorageTest, PublishInDestructor) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, FindNode) {
|
TEST(ObjectFactoryStorageTest, FindNode) {
|
||||||
@@ -235,6 +246,8 @@ TEST(ObjectFactoryStorageTest, EraseFirst) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(2, 3));
|
EXPECT_THAT(actual, testing::ElementsAre(2, 3));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, EraseMiddle) {
|
TEST(ObjectFactoryStorageTest, EraseMiddle) {
|
||||||
@@ -261,6 +274,8 @@ TEST(ObjectFactoryStorageTest, EraseMiddle) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 3));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 3));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, EraseLast) {
|
TEST(ObjectFactoryStorageTest, EraseLast) {
|
||||||
@@ -287,6 +302,8 @@ TEST(ObjectFactoryStorageTest, EraseLast) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, EraseAll) {
|
TEST(ObjectFactoryStorageTest, EraseAll) {
|
||||||
@@ -309,6 +326,8 @@ TEST(ObjectFactoryStorageTest, EraseAll) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::IsEmpty());
|
EXPECT_THAT(actual, testing::IsEmpty());
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, EraseTheOnlyElement) {
|
TEST(ObjectFactoryStorageTest, EraseTheOnlyElement) {
|
||||||
@@ -329,6 +348,8 @@ TEST(ObjectFactoryStorageTest, EraseTheOnlyElement) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::IsEmpty());
|
EXPECT_THAT(actual, testing::IsEmpty());
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, MoveFirst) {
|
TEST(ObjectFactoryStorageTest, MoveFirst) {
|
||||||
@@ -358,6 +379,9 @@ TEST(ObjectFactoryStorageTest, MoveFirst) {
|
|||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(2, 3));
|
EXPECT_THAT(actual, testing::ElementsAre(2, 3));
|
||||||
EXPECT_THAT(actualConsumer, testing::ElementsAre(1));
|
EXPECT_THAT(actualConsumer, testing::ElementsAre(1));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
|
EXPECT_THAT(consumer.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, MoveMiddle) {
|
TEST(ObjectFactoryStorageTest, MoveMiddle) {
|
||||||
@@ -387,6 +411,9 @@ TEST(ObjectFactoryStorageTest, MoveMiddle) {
|
|||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 3));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 3));
|
||||||
EXPECT_THAT(actualConsumer, testing::ElementsAre(2));
|
EXPECT_THAT(actualConsumer, testing::ElementsAre(2));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
|
EXPECT_THAT(consumer.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, MoveLast) {
|
TEST(ObjectFactoryStorageTest, MoveLast) {
|
||||||
@@ -416,6 +443,9 @@ TEST(ObjectFactoryStorageTest, MoveLast) {
|
|||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 2));
|
||||||
EXPECT_THAT(actualConsumer, testing::ElementsAre(3));
|
EXPECT_THAT(actualConsumer, testing::ElementsAre(3));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 2);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
|
EXPECT_THAT(consumer.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, MoveAll) {
|
TEST(ObjectFactoryStorageTest, MoveAll) {
|
||||||
@@ -441,6 +471,9 @@ TEST(ObjectFactoryStorageTest, MoveAll) {
|
|||||||
|
|
||||||
EXPECT_THAT(actual, testing::IsEmpty());
|
EXPECT_THAT(actual, testing::IsEmpty());
|
||||||
EXPECT_THAT(actualConsumer, testing::ElementsAre(1, 2, 3));
|
EXPECT_THAT(actualConsumer, testing::ElementsAre(1, 2, 3));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
|
EXPECT_THAT(consumer.size(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, MoveTheOnlyElement) {
|
TEST(ObjectFactoryStorageTest, MoveTheOnlyElement) {
|
||||||
@@ -464,6 +497,9 @@ TEST(ObjectFactoryStorageTest, MoveTheOnlyElement) {
|
|||||||
|
|
||||||
EXPECT_THAT(actual, testing::IsEmpty());
|
EXPECT_THAT(actual, testing::IsEmpty());
|
||||||
EXPECT_THAT(actualConsumer, testing::ElementsAre(1));
|
EXPECT_THAT(actualConsumer, testing::ElementsAre(1));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
|
EXPECT_THAT(consumer.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, MoveAndErase) {
|
TEST(ObjectFactoryStorageTest, MoveAndErase) {
|
||||||
@@ -497,6 +533,9 @@ TEST(ObjectFactoryStorageTest, MoveAndErase) {
|
|||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(1, 4, 7));
|
EXPECT_THAT(actual, testing::ElementsAre(1, 4, 7));
|
||||||
EXPECT_THAT(actualConsumer, testing::ElementsAre(3, 6, 9));
|
EXPECT_THAT(actualConsumer, testing::ElementsAre(3, 6, 9));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), 3);
|
||||||
|
EXPECT_THAT(producer.size(), 0);
|
||||||
|
EXPECT_THAT(consumer.size(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
|
TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
|
||||||
@@ -529,6 +568,7 @@ TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
|
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), expected.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
|
TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
|
||||||
@@ -588,6 +628,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
|
|||||||
auto actualAfter = Collect<int>(storage);
|
auto actualAfter = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actualAfter, testing::UnorderedElementsAreArray(expectedAfter));
|
EXPECT_THAT(actualAfter, testing::UnorderedElementsAreArray(expectedAfter));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), expectedAfter.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
||||||
@@ -647,6 +688,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
|
|||||||
auto actual = Collect<int>(storage);
|
auto actual = Collect<int>(storage);
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expectedAfter));
|
EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expectedAfter));
|
||||||
|
EXPECT_THAT(storage.GetSizeUnsafe(), expectedAfter.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
using mm::internal::AllocatorWithGC;
|
using mm::internal::AllocatorWithGC;
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ mm::ThreadRootSet::Iterator::Iterator(begin_t, ThreadRootSet& owner) noexcept :
|
|||||||
|
|
||||||
mm::ThreadRootSet::Iterator::Iterator(end_t, ThreadRootSet& owner) noexcept : owner_(owner), phase_(Phase::kDone) {}
|
mm::ThreadRootSet::Iterator::Iterator(end_t, ThreadRootSet& owner) noexcept : owner_(owner), phase_(Phase::kDone) {}
|
||||||
|
|
||||||
ObjHeader*& mm::ThreadRootSet::Iterator::operator*() noexcept {
|
mm::ThreadRootSet::Value mm::ThreadRootSet::Iterator::operator*() noexcept {
|
||||||
switch (phase_) {
|
switch (phase_) {
|
||||||
case Phase::kStack:
|
case Phase::kStack:
|
||||||
return *stackIterator_;
|
return {*stackIterator_, Source::kStack};
|
||||||
case Phase::kTLS:
|
case Phase::kTLS:
|
||||||
return **tlsIterator_;
|
return {**tlsIterator_, Source::kTLS};
|
||||||
case Phase::kDone:
|
case Phase::kDone:
|
||||||
RuntimeFail("Cannot dereference");
|
RuntimeFail("Cannot dereference");
|
||||||
}
|
}
|
||||||
@@ -84,12 +84,12 @@ mm::GlobalRootSet::Iterator::Iterator(begin_t, GlobalRootSet& owner) noexcept :
|
|||||||
|
|
||||||
mm::GlobalRootSet::Iterator::Iterator(end_t, GlobalRootSet& owner) noexcept : owner_(owner), phase_(Phase::kDone) {}
|
mm::GlobalRootSet::Iterator::Iterator(end_t, GlobalRootSet& owner) noexcept : owner_(owner), phase_(Phase::kDone) {}
|
||||||
|
|
||||||
ObjHeader*& mm::GlobalRootSet::Iterator::operator*() noexcept {
|
mm::GlobalRootSet::Value mm::GlobalRootSet::Iterator::operator*() noexcept {
|
||||||
switch (phase_) {
|
switch (phase_) {
|
||||||
case Phase::kGlobals:
|
case Phase::kGlobals:
|
||||||
return **globalsIterator_;
|
return {**globalsIterator_, Source::kGlobal};
|
||||||
case Phase::kStableRefs:
|
case Phase::kStableRefs:
|
||||||
return *stableRefsIterator_;
|
return {*stableRefsIterator_, Source::kStableRef};
|
||||||
case Phase::kDone:
|
case Phase::kDone:
|
||||||
RuntimeFail("Cannot dereference");
|
RuntimeFail("Cannot dereference");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,18 @@ class ThreadData;
|
|||||||
|
|
||||||
class ThreadRootSet {
|
class ThreadRootSet {
|
||||||
public:
|
public:
|
||||||
|
enum class Source {
|
||||||
|
kStack,
|
||||||
|
kTLS,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Value {
|
||||||
|
ObjHeader*& object;
|
||||||
|
Source source;
|
||||||
|
|
||||||
|
bool operator==(const Value& rhs) const noexcept { return object == rhs.object && source == rhs.source; }
|
||||||
|
};
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
public:
|
public:
|
||||||
struct begin_t {};
|
struct begin_t {};
|
||||||
@@ -31,7 +43,7 @@ public:
|
|||||||
Iterator(begin_t, ThreadRootSet& owner) noexcept;
|
Iterator(begin_t, ThreadRootSet& owner) noexcept;
|
||||||
Iterator(end_t, ThreadRootSet& owner) noexcept;
|
Iterator(end_t, ThreadRootSet& owner) noexcept;
|
||||||
|
|
||||||
ObjHeader*& operator*() noexcept;
|
Value operator*() noexcept;
|
||||||
|
|
||||||
Iterator& operator++() noexcept;
|
Iterator& operator++() noexcept;
|
||||||
|
|
||||||
@@ -68,6 +80,18 @@ private:
|
|||||||
|
|
||||||
class GlobalRootSet {
|
class GlobalRootSet {
|
||||||
public:
|
public:
|
||||||
|
enum class Source {
|
||||||
|
kGlobal,
|
||||||
|
kStableRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Value {
|
||||||
|
ObjHeader*& object;
|
||||||
|
Source source;
|
||||||
|
|
||||||
|
bool operator==(const Value& rhs) const noexcept { return object == rhs.object && source == rhs.source; }
|
||||||
|
};
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
public:
|
public:
|
||||||
struct begin_t {};
|
struct begin_t {};
|
||||||
@@ -79,7 +103,7 @@ public:
|
|||||||
Iterator(begin_t, GlobalRootSet& owner) noexcept;
|
Iterator(begin_t, GlobalRootSet& owner) noexcept;
|
||||||
Iterator(end_t, GlobalRootSet& owner) noexcept;
|
Iterator(end_t, GlobalRootSet& owner) noexcept;
|
||||||
|
|
||||||
ObjHeader*& operator*() noexcept;
|
Value operator*() noexcept;
|
||||||
|
|
||||||
Iterator& operator++() noexcept;
|
Iterator& operator++() noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -59,12 +59,18 @@ TEST(ThreadRootSetTest, Basic) {
|
|||||||
|
|
||||||
mm::ThreadRootSet iter(stack, tls);
|
mm::ThreadRootSet iter(stack, tls);
|
||||||
|
|
||||||
KStdVector<ObjHeader*> actual;
|
KStdVector<mm::ThreadRootSet::Value> actual;
|
||||||
for (auto& object : iter) {
|
for (auto object : iter) {
|
||||||
actual.push_back(object);
|
actual.push_back(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(entry[0], entry[1], *tls.Lookup(&key, 0), *tls.Lookup(&key, 1), *tls.Lookup(&key, 2)));
|
auto asStack = [](ObjHeader*& object) -> mm::ThreadRootSet::Value { return {object, mm::ThreadRootSet::Source::kStack}; };
|
||||||
|
auto asTLS = [](ObjHeader*& object) -> mm::ThreadRootSet::Value { return {object, mm::ThreadRootSet::Source::kTLS}; };
|
||||||
|
EXPECT_THAT(
|
||||||
|
actual,
|
||||||
|
testing::ElementsAre(
|
||||||
|
asStack(entry[0]), asStack(entry[1]), asTLS(*tls.Lookup(&key, 0)), asTLS(*tls.Lookup(&key, 1)),
|
||||||
|
asTLS(*tls.Lookup(&key, 2))));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ThreadRootSetTest, Empty) {
|
TEST(ThreadRootSetTest, Empty) {
|
||||||
@@ -73,8 +79,8 @@ TEST(ThreadRootSetTest, Empty) {
|
|||||||
|
|
||||||
mm::ThreadRootSet iter(stack, tls);
|
mm::ThreadRootSet iter(stack, tls);
|
||||||
|
|
||||||
KStdVector<ObjHeader*> actual;
|
KStdVector<mm::ThreadRootSet::Value> actual;
|
||||||
for (auto& object : iter) {
|
for (auto object : iter) {
|
||||||
actual.push_back(object);
|
actual.push_back(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,12 +109,17 @@ TEST(GlobalRootSetTest, Basic) {
|
|||||||
|
|
||||||
mm::GlobalRootSet iter(globals, stableRefs);
|
mm::GlobalRootSet iter(globals, stableRefs);
|
||||||
|
|
||||||
KStdVector<ObjHeader*> actual;
|
KStdVector<mm::GlobalRootSet::Value> actual;
|
||||||
for (auto& object : iter) {
|
for (auto object : iter) {
|
||||||
actual.push_back(object);
|
actual.push_back(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_THAT(actual, testing::ElementsAre(global1, global2, stableRef1, stableRef2, stableRef3));
|
auto asGlobal = [](ObjHeader*& object) -> mm::GlobalRootSet::Value { return {object, mm::GlobalRootSet::Source::kGlobal}; };
|
||||||
|
auto asStableRef = [](ObjHeader*& object) -> mm::GlobalRootSet::Value { return {object, mm::GlobalRootSet::Source::kStableRef}; };
|
||||||
|
EXPECT_THAT(
|
||||||
|
actual,
|
||||||
|
testing::ElementsAre(
|
||||||
|
asGlobal(global1), asGlobal(global2), asStableRef(stableRef1), asStableRef(stableRef2), asStableRef(stableRef3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GlobalRootSetTest, Empty) {
|
TEST(GlobalRootSetTest, Empty) {
|
||||||
@@ -117,8 +128,8 @@ TEST(GlobalRootSetTest, Empty) {
|
|||||||
|
|
||||||
mm::GlobalRootSet iter(globals, stableRefs);
|
mm::GlobalRootSet iter(globals, stableRefs);
|
||||||
|
|
||||||
KStdVector<ObjHeader*> actual;
|
KStdVector<mm::GlobalRootSet::Value> actual;
|
||||||
for (auto& object : iter) {
|
for (auto object : iter) {
|
||||||
actual.push_back(object);
|
actual.push_back(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#define RUNTIME_MM_THREAD_DATA_H
|
#define RUNTIME_MM_THREAD_DATA_H
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
#include "GlobalsRegistry.hpp"
|
#include "GlobalsRegistry.hpp"
|
||||||
@@ -29,7 +28,7 @@ namespace mm {
|
|||||||
// Pin it in memory to prevent accidental copying.
|
// Pin it in memory to prevent accidental copying.
|
||||||
class ThreadData final : private Pinned {
|
class ThreadData final : private Pinned {
|
||||||
public:
|
public:
|
||||||
ThreadData(pthread_t threadId) noexcept :
|
explicit ThreadData(int threadId) noexcept :
|
||||||
threadId_(threadId),
|
threadId_(threadId),
|
||||||
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
||||||
stableRefThreadQueue_(StableRefRegistry::Instance()),
|
stableRefThreadQueue_(StableRefRegistry::Instance()),
|
||||||
@@ -39,7 +38,7 @@ public:
|
|||||||
|
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
pthread_t threadId() const noexcept { return threadId_; }
|
int threadId() const noexcept { return threadId_; }
|
||||||
|
|
||||||
GlobalsRegistry::ThreadQueue& globalsThreadQueue() noexcept { return globalsThreadQueue_; }
|
GlobalsRegistry::ThreadQueue& globalsThreadQueue() noexcept { return globalsThreadQueue_; }
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const pthread_t threadId_;
|
const int threadId_;
|
||||||
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
|
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
|
||||||
ThreadLocalStorage tls_;
|
ThreadLocalStorage tls_;
|
||||||
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
|
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "ThreadRegistry.hpp"
|
#include "ThreadRegistry.hpp"
|
||||||
|
|
||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
|
#include "Porting.h"
|
||||||
#include "ThreadData.hpp"
|
#include "ThreadData.hpp"
|
||||||
#include "ThreadState.hpp"
|
#include "ThreadState.hpp"
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ mm::ThreadRegistry& mm::ThreadRegistry::Instance() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mm::ThreadRegistry::Node* mm::ThreadRegistry::RegisterCurrentThread() noexcept {
|
mm::ThreadRegistry::Node* mm::ThreadRegistry::RegisterCurrentThread() noexcept {
|
||||||
auto* threadDataNode = list_.Emplace(pthread_self());
|
auto* threadDataNode = list_.Emplace(konan::currentThreadId());
|
||||||
AssertThreadState(threadDataNode->Get(), ThreadState::kNative);
|
AssertThreadState(threadDataNode->Get(), ThreadState::kNative);
|
||||||
Node*& currentDataNode = currentThreadDataNode_;
|
Node*& currentDataNode = currentThreadDataNode_;
|
||||||
RuntimeAssert(!IsCurrentThreadRegistered(), "This thread already had some data assigned to it.");
|
RuntimeAssert(!IsCurrentThreadRegistered(), "This thread already had some data assigned to it.");
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ TEST(ThreadRegistryTest, RegisterCurrentThread) {
|
|||||||
} registration;
|
} registration;
|
||||||
|
|
||||||
auto* threadData = registration.node->Get();
|
auto* threadData = registration.node->Get();
|
||||||
EXPECT_EQ(pthread_self(), threadData->threadId());
|
EXPECT_EQ(konan::currentThreadId(), threadData->threadId());
|
||||||
EXPECT_EQ(threadData, mm::ThreadRegistry::Instance().CurrentThreadData());
|
EXPECT_EQ(threadData, mm::ThreadRegistry::Instance().CurrentThreadData());
|
||||||
});
|
});
|
||||||
t.join();
|
t.join();
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ TEST(ThreadStateDeathTest, IncorrectStateSwitchWithDifferentFunctions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(ThreadStateDeathTest, StateSwitchCorrectness) {
|
TEST(ThreadStateDeathTest, StateSwitchCorrectness) {
|
||||||
mm::ThreadData threadData(pthread_self());
|
mm::ThreadData threadData(0);
|
||||||
|
|
||||||
// Allowed state switches: runnable <-> native
|
// Allowed state switches: runnable <-> native
|
||||||
threadData.setState(ThreadState::kRunnable);
|
threadData.setState(ThreadState::kRunnable);
|
||||||
@@ -233,4 +233,4 @@ TEST(ThreadStateDeathTest, MovingReentrantGuard) {
|
|||||||
testing::ExitedWithCode(0),
|
testing::ExitedWithCode(0),
|
||||||
testing::Not(testing::ContainsRegex("runtime assert: Illegal thread state switch.")));
|
testing::Not(testing::ContainsRegex("runtime assert: Illegal thread state switch.")));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "Logging.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool isSuspendedOrNative(kotlin::mm::ThreadData& thread) noexcept {
|
bool isSuspendedOrNative(kotlin::mm::ThreadData& thread) noexcept {
|
||||||
@@ -54,8 +56,11 @@ NO_EXTERNAL_CALLS_CHECK bool kotlin::mm::ThreadSuspensionData::suspendIfRequeste
|
|||||||
NO_EXTERNAL_CALLS_CHECK bool kotlin::mm::ThreadSuspensionData::suspendIfRequestedSlowPath() noexcept {
|
NO_EXTERNAL_CALLS_CHECK bool kotlin::mm::ThreadSuspensionData::suspendIfRequestedSlowPath() noexcept {
|
||||||
std::unique_lock lock(gSuspensionMutex);
|
std::unique_lock lock(gSuspensionMutex);
|
||||||
if (IsThreadSuspensionRequested()) {
|
if (IsThreadSuspensionRequested()) {
|
||||||
|
auto threadId = konan::currentThreadId();
|
||||||
|
RuntimeLogDebug({kTagGC, kTagMM}, "Suspending thread %d", threadId);
|
||||||
AutoReset scopedAssign(&suspended_, true);
|
AutoReset scopedAssign(&suspended_, true);
|
||||||
gSuspendsionCondVar.wait(lock, []() { return !IsThreadSuspensionRequested(); });
|
gSuspendsionCondVar.wait(lock, []() { return !IsThreadSuspensionRequested(); });
|
||||||
|
RuntimeLogDebug({kTagGC, kTagMM}, "Resuming thread %d", threadId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ extern "C" {
|
|||||||
|
|
||||||
extern const int32_t KonanNeedDebugInfo = 1;
|
extern const int32_t KonanNeedDebugInfo = 1;
|
||||||
extern const int32_t Kotlin_runtimeAssertsMode = static_cast<int32_t>(kotlin::compiler::RuntimeAssertsMode::kPanic);
|
extern const int32_t Kotlin_runtimeAssertsMode = static_cast<int32_t>(kotlin::compiler::RuntimeAssertsMode::kPanic);
|
||||||
|
extern const char* const Kotlin_runtimeLogs = nullptr;
|
||||||
|
|
||||||
extern const TypeInfo* theAnyTypeInfo = theAnyTypeInfoHolder.typeInfo();
|
extern const TypeInfo* theAnyTypeInfo = theAnyTypeInfoHolder.typeInfo();
|
||||||
extern const TypeInfo* theArrayTypeInfo = theArrayTypeInfoHolder.typeInfo();
|
extern const TypeInfo* theArrayTypeInfo = theArrayTypeInfoHolder.typeInfo();
|
||||||
|
|||||||
Reference in New Issue
Block a user