make all threads preemptive. workaround file based emummc hanging due to fs r/w hanging the system.

the fix for file based emummc is to simply sleep between fs r/w to the sd card.
the performance impact is minimal, even with the reduced buffer size.

the above *only* applies for when using file based emummc. not affecting using partition or sysmmc.
This commit is contained in:
ITotalJustice
2025-05-28 14:37:56 +01:00
parent b0ff2eb4b7
commit 6b56b7f7c2
14 changed files with 139 additions and 66 deletions

View File

@@ -630,7 +630,7 @@ auto App::GetReplaceHbmenuEnable() -> bool {
}
auto App::GetInstallEnable() -> bool {
if (IsEmunand()) {
if (IsEmummc()) {
return GetInstallEmummcEnable();
} else {
return GetInstallSysmmcEnable();
@@ -930,6 +930,21 @@ auto App::Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result {
return rc;
}
auto App::IsEmummc() -> bool {
const auto& paths = g_app->m_emummc_paths;
return (paths.file_based_path[0] != '\0') || (paths.nintendo[0] != '\0');
}
auto App::IsParitionBaseEmummc() -> bool {
const auto& paths = g_app->m_emummc_paths;
return (paths.file_based_path[0] == '\0') && (paths.nintendo[0] != '\0');
}
auto App::IsFileBaseEmummc() -> bool {
const auto& paths = g_app->m_emummc_paths;
return (paths.file_based_path[0] != '\0') && (paths.nintendo[0] != '\0');
}
void App::Exit() {
g_app->m_quit = true;
}
@@ -1288,6 +1303,21 @@ App::App(const char* argv0) {
__nx_applet_exit_mode = 1;
}
// get emummc config.
alignas(0x1000) AmsEmummcPaths paths{};
SecmonArgs args{};
args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */
args.X[1] = 0; /* EXO_EMUMMC_MMC_NAND*/
args.X[2] = (u64)&paths; /* out path */
svcCallSecureMonitor(&args);
m_emummc_paths = paths;
log_write("emummc : %u\n", App::IsEmummc());
if (App::IsEmummc()) {
log_write("[emummc] file based path: %s\n", m_emummc_paths.file_based_path);
log_write("[emummc] nintendo path: %s\n", m_emummc_paths.nintendo);
}
fs::FsNativeSd fs;
fs.CreateDirectoryRecursively("/config/sphaira");
fs.CreateDirectory("/config/sphaira/assoc");
@@ -1350,7 +1380,7 @@ App::App(const char* argv0) {
}
if (App::GetMtpEnable()) {
hazeInitialize(haze_callback, 0x2C, 2);
hazeInitialize(haze_callback, PRIO_PREEMPTIVE, 2);
}
if (App::GetFtpEnable()) {

View File

@@ -32,7 +32,7 @@ namespace {
constexpr auto API_AGENT = "TotalJustice";
constexpr u64 CHUNK_SIZE = 1024*1024;
constexpr auto MAX_THREADS = 4;
constexpr int THREAD_PRIO = 0x2C;
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 1;
std::atomic_bool g_running{};
@@ -71,6 +71,10 @@ auto generate_key_from_path(const fs::FsPath& path) -> std::string {
return std::to_string(key);
}
void Yield() {
svcSleepThread(YieldType_WithoutCoreMigration);
}
struct Cache {
using Value = std::pair<std::string, std::string>;
@@ -259,6 +263,7 @@ struct ThreadEntry {
ueventCreate(&m_uevent, true);
R_TRY(threadCreate(&m_thread, ThreadFunc, this, nullptr, 1024*32, THREAD_PRIO, THREAD_CORE));
R_TRY(svcSetThreadCoreMask(m_thread.handle, THREAD_CORE, THREAD_AFFINITY_DEFAULT(THREAD_CORE)));
R_TRY(threadStart(&m_thread));
R_SUCCEED();
}
@@ -371,7 +376,7 @@ auto ProgressCallbackFunc1(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
return 1;
}
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return 0;
}
@@ -386,7 +391,7 @@ auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
return 1;
}
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return 0;
}
@@ -445,7 +450,7 @@ auto ReadFileCallback(char *ptr, size_t size, size_t nmemb, void *userp) -> size
}
data_struct->offset += bytes_read;
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return bytes_read;
}
@@ -461,7 +466,7 @@ auto ReadMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp) -> si
std::memcpy(ptr, data_struct->data.data(), realsize);
data_struct->offset += realsize;
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return realsize;
}
@@ -474,7 +479,7 @@ auto ReadCustomCallback(char *ptr, size_t size, size_t nmemb, void *userp) -> si
auto realsize = size * nmemb;
const auto result = data_struct->m_callback(ptr, realsize);
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return result;
}
@@ -495,7 +500,7 @@ auto WriteMemoryCallback(void *contents, size_t size, size_t num_files, void *us
std::memcpy(data_struct->data.data() + data_struct->offset, contents, realsize);
data_struct->offset += realsize;
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return realsize;
}
@@ -530,7 +535,7 @@ auto WriteFileCallback(void *contents, size_t size, size_t num_files, void *user
data_struct->offset += realsize;
}
svcSleepThread(YieldType_WithoutCoreMigration);
Yield();
return realsize;
}

View File

@@ -30,6 +30,8 @@ struct InstallSharedData {
};
const char* INI_PATH = "/config/ftpsrv/config.ini";
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 2;
FtpSrvConfig g_ftpsrv_config = {0};
volatile bool g_should_exit = false;
bool g_is_running{false};
@@ -357,11 +359,16 @@ bool Init() {
vfs_nx_init(&custom, mount_devices, save_writable, mount_bis);
Result rc;
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*16, 0x2C, 2))) {
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*16, THREAD_PRIO, THREAD_CORE))) {
log_write("[FTP] failed to create nxlink thread: 0x%X\n", rc);
return false;
}
if (R_FAILED(rc = svcSetThreadCoreMask(g_thread.handle, THREAD_CORE, THREAD_AFFINITY_DEFAULT(THREAD_CORE)))) {
log_write("[FTP] failed to set core mask: 0x%X\n", rc);
return false;
}
if (R_FAILED(rc = threadStart(&g_thread))) {
log_write("[FTP] failed to start nxlink thread: 0x%X\n", rc);
threadClose(&g_thread);

View File

@@ -458,7 +458,7 @@ bool nxlinkInitialize(NxlinkCallback callback) {
g_quit = false;
Result rc;
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, 0x2C, 2))) {
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, PRIO_PREEMPTIVE, 2))) {
log_write("failed to create nxlink thread: 0x%X\n", rc);
return false;
}

View File

@@ -145,8 +145,14 @@ ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, Wr
condvarInit(std::addressof(can_pull_write));
write_size = size;
read_buffer_size = READ_BUFFER_MAX;
max_buffer_size = READ_BUFFER_MAX;
if (App::IsFileBaseEmummc()) {
read_buffer_size = 1024 * 512;
max_buffer_size = 1024 * 512;
} else {
read_buffer_size = READ_BUFFER_MAX;
max_buffer_size = READ_BUFFER_MAX;
}
}
auto ThreadData::GetResults() -> Result {
@@ -312,11 +318,11 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
ThreadData t_data{pbox, size, rfunc, wfunc};
Thread t_read{};
R_TRY(threadCreate(&t_read, readFunc, std::addressof(t_data), nullptr, 1024*64, 0x20, READ_THREAD_CORE));
R_TRY(threadCreate(&t_read, readFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, READ_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_read));
Thread t_write{};
R_TRY(threadCreate(&t_write, writeFunc, std::addressof(t_data), nullptr, 1024*64, 0x20, WRITE_THREAD_CORE));
R_TRY(threadCreate(&t_write, writeFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, WRITE_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_write));
const auto start_threads = [&]() -> Result {

View File

@@ -27,6 +27,9 @@
namespace sphaira::ui::menu::game {
namespace {
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 1;
constexpr u32 ContentMetaTypeToContentFlag(u8 meta_type) {
if (meta_type & 0x80) {
return 1 << (meta_type - 0x80);
@@ -649,9 +652,7 @@ void ThreadData::Run() {
}
// sleep after every other entry loaded.
if (i) {
svcSleepThread(1e+6*2); // 2ms
}
svcSleepThread(2e+6); // 2ms
const auto result = LoadControlEntry(ids[i]);
mutexLock(&m_mutex_result);
@@ -875,7 +876,8 @@ Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
e.Open();
}
threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, 0x30, 1);
threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, THREAD_PRIO, THREAD_CORE);
svcSetThreadCoreMask(m_thread.handle, THREAD_CORE, THREAD_AFFINITY_DEFAULT(THREAD_CORE));
threadStart(&m_thread);
}

View File

@@ -646,6 +646,7 @@ Result Menu::GcMount() {
if (m_option_index == 2) {
SetPop();
} else {
log_write("[GC] pressed A\n");
if (!m_mounted) {
return;
}
@@ -658,6 +659,7 @@ Result Menu::GcMount() {
"OK"_i18n
));
} else {
log_write("[GC] doing install A\n");
App::Push(std::make_shared<ui::ProgressBox>(m_icon, "Installing "_i18n, m_entries[m_entry_index].lang_entry.name, [this](auto pbox) -> Result {
auto source = std::make_shared<GcSource>(m_entries[m_entry_index], m_fs.get());
return yati::InstallFromCollections(pbox, source, source->m_collections, source->m_config);

View File

@@ -77,7 +77,7 @@ Menu::Menu(u32 flags) : MenuBase{"USB"_i18n, flags} {
mutexInit(&m_mutex);
if (m_state != State::Failed) {
threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, 0x2C, 1);
threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, PRIO_PREEMPTIVE, 1);
threadStart(&m_thread);
}
}

View File

@@ -254,6 +254,9 @@ auto ProgressBox::ShouldExitResult() -> Result {
}
auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
const auto is_file_based_emummc = App::IsFileBaseEmummc();
const auto is_both_native = fs_src->IsNative() && fs_dst->IsNative();
fs::File src_file;
R_TRY(fs_src->OpenFile(src_path, FsOpenMode_Read, &src_file));
@@ -271,7 +274,13 @@ auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src
R_TRY(dst_file.SetSize(src_size));
s64 offset{};
std::vector<u8> buf(1024*1024*4); // 4MiB
std::vector<u8> buf;
if (is_file_based_emummc) {
buf.resize(1024*512); // 512KiB
} else {
buf.resize(1024*1024*4); // 4MiB
}
while (offset < src_size) {
R_TRY(ShouldExitResult());
@@ -280,11 +289,19 @@ auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src
R_TRY(src_file.Read(offset, buf.data(), buf.size(), 0, &bytes_read));
Yield();
if (is_both_native && is_file_based_emummc) {
svcSleepThread(2e+6); // 2ms
}
R_TRY(dst_file.Write(offset, buf.data(), bytes_read, FsWriteOption_None));
Yield();
UpdateTransfer(offset, src_size);
offset += bytes_read;
if (is_both_native && is_file_based_emummc) {
svcSleepThread(2e+6); // 2ms
}
}
R_SUCCEED();

View File

@@ -140,7 +140,13 @@ struct ThreadData {
// this will be updated with the actual size from nca header.
write_size = nca->size;
read_buffer_size = 1024*1024*4;
// reduce buffer size to preve
if (App::IsFileBaseEmummc()) {
read_buffer_size = 1024 * 512;
} else {
read_buffer_size = 1024*1024*4;
}
max_buffer_size = std::max(read_buffer_size, INFLATE_BUFFER_MAX);
}
@@ -689,12 +695,27 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
Result Yati::writeFuncInternal(ThreadData* t) {
std::vector<u8> buf;
buf.reserve(t->max_buffer_size);
const auto is_file_based_emummc = App::IsFileBaseEmummc();
while (t->write_offset < t->write_size && R_SUCCEEDED(t->GetResults())) {
s64 dummy_off;
R_TRY(t->GetWriteBuf(buf, dummy_off));
R_TRY(ncmContentStorageWritePlaceHolder(std::addressof(cs), std::addressof(t->nca->placeholder_id), t->write_offset, buf.data(), buf.size()));
t->write_offset += buf.size();
s64 off{};
while (off < buf.size() && t->write_offset < t->write_size && R_SUCCEEDED(t->GetResults())) {
const auto wsize = std::min<s64>(t->read_buffer_size, buf.size() - off);
R_TRY(ncmContentStorageWritePlaceHolder(std::addressof(cs), std::addressof(t->nca->placeholder_id), t->write_offset + off, buf.data() + off, wsize));
off += wsize;
t->write_offset += wsize;
// todo: check how much time elapsed and sleep the diff
// rather than always sleeping a fixed amount.
// ie, writing a small buffer (nca header) should not sleep the full 2 ms.
if (is_file_based_emummc) {
svcSleepThread(2e+6); // 2ms
}
}
}
log_write("finished write thread!\n");
@@ -834,15 +855,15 @@ Result Yati::InstallNcaInternal(std::span<TikCollection> tickets, NcaCollection&
// #define WRITE_THREAD_CORE 2
Thread t_read{};
R_TRY(threadCreate(&t_read, readFunc, std::addressof(t_data), nullptr, 1024*64, 0x20, READ_THREAD_CORE));
R_TRY(threadCreate(&t_read, readFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, READ_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_read));
Thread t_decompress{};
R_TRY(threadCreate(&t_decompress, decompressFunc, std::addressof(t_data), nullptr, 1024*64, 0x20, DECOMPRESS_THREAD_CORE));
R_TRY(threadCreate(&t_decompress, decompressFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, DECOMPRESS_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_decompress));
Thread t_write{};
R_TRY(threadCreate(&t_write, writeFunc, std::addressof(t_data), nullptr, 1024*64, 0x20, WRITE_THREAD_CORE));
R_TRY(threadCreate(&t_write, writeFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, WRITE_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_write));
log_write("starting threads\n");