mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 17:13:13 -05:00
agc_async_log_fatal starts four children that end in SIGABRT on purpose and requires each to end within 6 s. Setting RLIMIT_CORE to 0 does not stop the kernel from starting a pipe core handler such as apport or systemd-coredump, and the child is not reaped until that handler exits: about 1.1 s per child on an Ubuntu host with apport, and over 6 s when several crashes queue on the handler. Mark the children non-dumpable on Linux, as #6589 did for guest_host_standard_error and exception_runtime.
344 lines
11 KiB
C++
344 lines
11 KiB
C++
#include "prx/libSceAgcDriver/Execution/include/AsyncLog.hpp"
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <csignal>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <thread>
|
|
#ifdef _WIN32
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/resource.h>
|
|
#endif
|
|
#ifdef __linux__
|
|
#include <sys/prctl.h>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
using namespace std::chrono_literals;
|
|
using Clock = AgcDriver::AsyncLog::Clock;
|
|
|
|
void Check(bool condition, const char* message) {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
std::string ReadAll(std::FILE* file) {
|
|
std::fflush(file);
|
|
std::rewind(file);
|
|
std::string text;
|
|
char buffer[4096];
|
|
for (std::size_t read = 0; (read = std::fread(buffer, 1, sizeof(buffer), file)) != 0;) text.append(buffer, read);
|
|
return text;
|
|
}
|
|
|
|
std::string ReadFile(const std::filesystem::path& path) {
|
|
std::ifstream stream(path, std::ios::binary);
|
|
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
|
}
|
|
|
|
void ClearLogEnvironment() {
|
|
#ifdef _WIN32
|
|
_putenv("APS5_LOG_SYNC=");
|
|
_putenv("APS5_LOG_TIMESTAMPS=");
|
|
#else
|
|
unsetenv("APS5_LOG_SYNC");
|
|
unsetenv("APS5_LOG_TIMESTAMPS");
|
|
#endif
|
|
}
|
|
|
|
void LockStream(std::FILE* file) {
|
|
#ifdef _WIN32
|
|
_lock_file(file);
|
|
#else
|
|
flockfile(file);
|
|
#endif
|
|
}
|
|
|
|
void UnlockStream(std::FILE* file) {
|
|
#ifdef _WIN32
|
|
_unlock_file(file);
|
|
#else
|
|
funlockfile(file);
|
|
#endif
|
|
}
|
|
|
|
class HeldStream {
|
|
public:
|
|
explicit HeldStream(std::FILE* file) : file(file) {
|
|
holder = std::thread([this] {
|
|
LockStream(this->file);
|
|
held.store(true);
|
|
while (!releaseRequested.load()) std::this_thread::sleep_for(1ms);
|
|
released.store(true);
|
|
UnlockStream(this->file);
|
|
});
|
|
while (!held.load()) std::this_thread::sleep_for(1ms);
|
|
}
|
|
|
|
HeldStream(const HeldStream&) = delete;
|
|
HeldStream& operator=(const HeldStream&) = delete;
|
|
|
|
~HeldStream() {
|
|
Release();
|
|
holder.join();
|
|
}
|
|
|
|
void Release() {
|
|
releaseRequested.store(true);
|
|
}
|
|
|
|
bool Released() const {
|
|
return released.load();
|
|
}
|
|
|
|
private:
|
|
std::FILE* file;
|
|
std::atomic<bool> held{false};
|
|
std::atomic<bool> releaseRequested{false};
|
|
std::atomic<bool> released{false};
|
|
std::thread holder;
|
|
};
|
|
|
|
std::string LargeLine(std::size_t index, std::size_t bytes) {
|
|
std::string line = "large " + std::to_string(index) + " ";
|
|
line.resize(bytes - 1, 'x');
|
|
line += '\n';
|
|
return line;
|
|
}
|
|
|
|
void OrderPerThread() {
|
|
std::FILE* file = std::tmpfile();
|
|
Check(file != nullptr, "temporary file");
|
|
auto& log = AgcDriver::AsyncLog::Get();
|
|
std::string expected;
|
|
for (int i = 0; i < 2000; ++i) {
|
|
log.Defer(file, "deferred %d %s %llu\n", i, AgcDriver::LogLiteral{"literal"}, static_cast<unsigned long long>(i) * 3ull);
|
|
expected += "deferred " + std::to_string(i) + " literal " + std::to_string(static_cast<unsigned long long>(i) * 3ull) + "\n";
|
|
const std::string plain = "plain " + std::to_string(i) + "\n";
|
|
log.Write(file, plain.data(), plain.size());
|
|
expected += plain;
|
|
}
|
|
log.Flush();
|
|
Check(ReadAll(file) == expected, "deferred and written lines changed order or content");
|
|
std::fclose(file);
|
|
}
|
|
|
|
void LongLine() {
|
|
std::FILE* file = std::tmpfile();
|
|
Check(file != nullptr, "temporary file");
|
|
const std::string longText(3000, 'x');
|
|
AgcDriver::AsyncLog::Get().Defer(file, "%s|%d\n", AgcDriver::LogLiteral{longText.c_str()}, 7);
|
|
AgcDriver::AsyncLog::Get().Flush();
|
|
Check(ReadAll(file) == longText + "|7\n", "a line longer than the stack buffer changed");
|
|
std::fclose(file);
|
|
}
|
|
|
|
void Coalescing() {
|
|
auto& log = AgcDriver::AsyncLog::Get();
|
|
const std::string line = "[gpu] coalescing test line\n";
|
|
int printed = 0;
|
|
for (int i = 0; i < 50; ++i) printed += log.Admit(line) ? 1 : 0;
|
|
Check(printed == 3, "identical lines in one window were not limited to the first three");
|
|
Check(log.Admit("[gpu] another line\n"), "a different line was suppressed");
|
|
}
|
|
|
|
void FlushWaitsForHeldStream() {
|
|
std::FILE* file = std::tmpfile();
|
|
Check(file != nullptr, "temporary file");
|
|
auto& log = AgcDriver::AsyncLog::Get();
|
|
{
|
|
HeldStream held(file);
|
|
log.Defer(file, "held %d %s\n", 7, AgcDriver::LogLiteral{"line"});
|
|
std::thread releaser([&] {
|
|
std::this_thread::sleep_for(2400ms);
|
|
held.Release();
|
|
});
|
|
log.Flush();
|
|
const bool waited = held.Released();
|
|
releaser.join();
|
|
Check(waited, "Flush returned while the logger still waited for the stream");
|
|
}
|
|
Check(ReadAll(file) == "held 7 line\n", "the line queued behind a held stream changed");
|
|
std::fclose(file);
|
|
}
|
|
|
|
void BoundedFlushGivesUp() {
|
|
std::FILE* file = std::tmpfile();
|
|
Check(file != nullptr, "temporary file");
|
|
auto& log = AgcDriver::AsyncLog::Get();
|
|
Check(log.FlushFor(0ms), "FlushFor waited with nothing queued");
|
|
{
|
|
HeldStream held(file);
|
|
log.Defer(file, "bounded %d\n", 1);
|
|
Check(!log.FlushFor(100ms), "FlushFor reported a line written while its stream was held");
|
|
held.Release();
|
|
Check(log.FlushFor(10s), "FlushFor missed the line written after the stream was released");
|
|
}
|
|
Check(ReadAll(file) == "bounded 1\n", "the line queued behind a held stream changed");
|
|
std::fclose(file);
|
|
}
|
|
|
|
void FullQueueBlocksCaller() {
|
|
std::FILE* file = std::tmpfile();
|
|
Check(file != nullptr, "temporary file");
|
|
auto& log = AgcDriver::AsyncLog::Get();
|
|
constexpr std::size_t lineBytes = 1024 * 1024;
|
|
constexpr std::size_t lines = 2 * AgcDriver::AsyncLog::Capacity / lineBytes + 4;
|
|
constexpr std::size_t filled = AgcDriver::AsyncLog::Capacity / lineBytes / 2;
|
|
std::atomic<std::size_t> queued{0};
|
|
bool blocked = false;
|
|
{
|
|
HeldStream held(file);
|
|
std::thread producer([&] {
|
|
for (std::size_t i = 0; i < lines; ++i) {
|
|
const std::string line = LargeLine(i, lineBytes);
|
|
log.Write(file, line.data(), line.size());
|
|
queued.store(i + 1);
|
|
}
|
|
});
|
|
auto last = queued.load();
|
|
auto changedAt = Clock::now();
|
|
const auto deadline = changedAt + 10s;
|
|
while (last < lines && Clock::now() < deadline && (last < filled || Clock::now() - changedAt < 500ms)) {
|
|
std::this_thread::sleep_for(5ms);
|
|
const auto now = queued.load();
|
|
if (now != last) {
|
|
last = now;
|
|
changedAt = Clock::now();
|
|
}
|
|
}
|
|
blocked = last >= filled && last < lines && Clock::now() - changedAt >= 500ms;
|
|
held.Release();
|
|
producer.join();
|
|
}
|
|
Check(blocked, "the caller was not blocked by a full queue");
|
|
log.Flush();
|
|
std::fflush(file);
|
|
std::rewind(file);
|
|
std::string line;
|
|
for (std::size_t i = 0; i < lines; ++i) {
|
|
const std::string expected = LargeLine(i, lineBytes);
|
|
line.resize(expected.size());
|
|
Check(std::fread(line.data(), 1, line.size(), file) == line.size() && line == expected, "a line written through a full queue changed or went missing");
|
|
}
|
|
Check(std::fgetc(file) == EOF, "a full queue wrote extra output");
|
|
std::fclose(file);
|
|
}
|
|
|
|
constexpr int ChildLines = 50;
|
|
|
|
[[noreturn]] void EndChild(const std::string& mode, const char* output) {
|
|
std::FILE* file = std::fopen(output, "wb");
|
|
Check(file != nullptr, "child output file");
|
|
#ifdef _WIN32
|
|
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
|
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
|
#else
|
|
const rlimit noCore{0, 0};
|
|
setrlimit(RLIMIT_CORE, &noCore);
|
|
#endif
|
|
#ifdef __linux__
|
|
prctl(PR_SET_DUMPABLE, 0);
|
|
#endif
|
|
std::thread([] {
|
|
std::this_thread::sleep_for(8s);
|
|
std::_Exit(99);
|
|
}).detach();
|
|
AgcDriver::InstallAsyncLog_nid_no_patch();
|
|
auto& log = AgcDriver::AsyncLog::Get();
|
|
HeldStream held(file);
|
|
for (int i = 0; i < ChildLines; ++i) {
|
|
log.Defer(file, "deferred %d\n", i);
|
|
WriteLogLine(file, "plain %d\n", i);
|
|
}
|
|
std::thread releaser([&] {
|
|
std::this_thread::sleep_for(100ms);
|
|
if (mode != "blocked") held.Release();
|
|
});
|
|
if (mode == "terminate") std::terminate();
|
|
#ifdef _WIN32
|
|
if (mode == "fault") RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, nullptr);
|
|
#else
|
|
if (mode == "raise") {
|
|
std::raise(SIGABRT);
|
|
std::_Exit(0);
|
|
}
|
|
#endif
|
|
std::abort();
|
|
}
|
|
|
|
std::string Quote(const std::string& text) {
|
|
return "\"" + text + "\"";
|
|
}
|
|
|
|
void LinesSurviveEnd(const std::string& self, const std::string& mode) {
|
|
const auto output = std::filesystem::temp_directory_path() / ("aps5-async-log-" + mode + "-" + std::to_string(Clock::now().time_since_epoch().count()) + ".txt");
|
|
std::string command = Quote(self) + " child " + mode + " " + Quote(output.string());
|
|
#ifdef _WIN32
|
|
command = Quote(command);
|
|
#endif
|
|
const auto start = Clock::now();
|
|
const int status = std::system(command.c_str());
|
|
const auto lived = Clock::now() - start;
|
|
std::error_code error;
|
|
const bool started = std::filesystem::exists(output, error);
|
|
const std::string text = started ? ReadFile(output) : std::string();
|
|
std::filesystem::remove(output, error);
|
|
Check(started, "the child did not start");
|
|
Check(status != 0, "the child did not end abnormally");
|
|
Check(lived < 6s, "the child took too long to end");
|
|
if (mode == "blocked") {
|
|
Check(lived >= 1900ms, "the child ended before the flush gave up");
|
|
Check(text.empty(), "the child wrote through a held stream");
|
|
return;
|
|
}
|
|
std::string expected;
|
|
for (int i = 0; i < ChildLines; ++i) expected += "deferred " + std::to_string(i) + "\nplain " + std::to_string(i) + "\n";
|
|
Check(text == expected, "lines queued before the process ended were lost or changed");
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
try {
|
|
ClearLogEnvironment();
|
|
if (argc == 4 && std::strcmp(argv[1], "child") == 0) EndChild(argv[2], argv[3]);
|
|
if (argc == 2 && std::strcmp(argv[1], "fatal") == 0) {
|
|
LinesSurviveEnd(argv[0], "abort");
|
|
LinesSurviveEnd(argv[0], "terminate");
|
|
#ifdef _WIN32
|
|
LinesSurviveEnd(argv[0], "fault");
|
|
#else
|
|
LinesSurviveEnd(argv[0], "raise");
|
|
#endif
|
|
LinesSurviveEnd(argv[0], "blocked");
|
|
return 0;
|
|
}
|
|
OrderPerThread();
|
|
LongLine();
|
|
Coalescing();
|
|
FlushWaitsForHeldStream();
|
|
BoundedFlushGivesUp();
|
|
FullQueueBlocksCaller();
|
|
} catch (const std::exception& error) {
|
|
std::cerr << error.what() << std::endl;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|