kernel: two-phase thread shutdown

Replace `exit_delete_all_threads_and_wait()` with two separate operations:
- `process_exit()` blocks until all guest threads have exited (host-only)
- `request_process_exit()` fires the registered callback and returns immediately, safe to call from guest threads.

Threads are now SDL_DetachThread'd at creation. `thread_deleted_cond` on `KernelState` is used for join semantics in `process_exit()`.

Remove `shutting_down` from sync primitives; `exit_delete()` wakes blocked threads via `update_status(run)`, which all `status_cond` predicates already check.

`process_exit_callback` carries an `optional<AppLaunchRequest>` so `LoadExec` relaunches flow through the same path as normal exits. `AppLaunchRequest`/`AppLaunchReason` extracted to `emuenv/app_launch_request.h` so kernel can include it without a circular dependency.

After this, further simplification to `ThreadStatus` will be done in https://github.com/Vita3K/Vita3K/pull/3922.
This commit is contained in:
Sergi (セルジ)
2026-05-14 13:41:17 +09:00
committed by Gamid
parent 74cae4a3f7
commit 63aaa5051e
16 changed files with 109 additions and 176 deletions
+1 -1
View File
@@ -443,7 +443,7 @@ void deinit(EmuEnvState &state) {
state.net.abort_all();
state.http.shutdown_connections();
state.kernel.exit_delete_all_threads_and_wait();
state.kernel.process_exit();
state.motion.reset_runtime();
+1 -4
View File
@@ -104,13 +104,10 @@ void wait_vblank(DisplayState &display, KernelState &kernel, const ThreadStatePt
}
wait_thread->status_cond.wait(thread_lock, [&]() {
return wait_thread->status == ThreadStatus::run || kernel.shutting_down.load(std::memory_order_relaxed);
return wait_thread->status == ThreadStatus::run;
});
}
if (kernel.shutting_down.load(std::memory_order_relaxed))
return;
if (is_cb) {
for (auto &[_, cb] : display.vblank_callbacks) {
if (cb->get_owner_thread_id() == wait_thread->id) {
@@ -0,0 +1,34 @@
// Vita3K emulator project
// Copyright (C) 2026 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <string>
#include <vector>
enum class AppLaunchReason {
User,
LoadExec,
ProcessExit,
};
struct AppLaunchRequest {
std::string app_path{};
std::string self_path{};
std::vector<std::string> argv{};
AppLaunchReason reason = AppLaunchReason::User;
};
+1 -11
View File
@@ -81,17 +81,7 @@ typedef int SceUID;
using NIDSet = std::set<uint32_t>;
enum class AppLaunchReason {
User,
LoadExec
};
struct AppLaunchRequest {
std::string app_path{};
std::string self_path{};
std::vector<std::string> argv{};
AppLaunchReason reason = AppLaunchReason::User;
};
#include <emuenv/app_launch_request.h>
/**
* @brief State of the emulated PlayStation Vita environment
+1
View File
@@ -811,6 +811,7 @@ bool MainWindow::handle_pending_app_launch_request() {
return false;
on_game_closed();
if (request->reason != AppLaunchReason::ProcessExit)
boot_game(*request, false);
return true;
}
+4
View File
@@ -26,6 +26,7 @@
#include <dialog/state.h>
#include <display/functions.h>
#include <display/state.h>
#include <emuenv/state.h>
#include <io/functions.h>
#include <io/vfs.h>
#include <kernel/state.h>
@@ -365,6 +366,9 @@ static ExitCode load_app_impl(SceUID &main_module_id, EmuEnvState &emuenv, const
const auto call_import = [&emuenv](CPUState &cpu, uint32_t nid, SceUID thread_id) {
::call_import(emuenv, cpu, nid, thread_id);
};
emuenv.kernel.process_exit_callback = [&emuenv](int res, std::optional<AppLaunchRequest> relaunch) {
emuenv.post_app_launch_request(relaunch.value_or(AppLaunchRequest{ .reason = AppLaunchReason::ProcessExit }));
};
if (!emuenv.kernel.init(emuenv.mem, call_import, emuenv.cfg.current_config.cpu_opt)) {
LOG_WARN("Failed to init kernel!");
return KernelInitFailed;
+1 -1
View File
@@ -24,7 +24,7 @@ add_library(
${SOURCE_LIST}
)
target_include_directories(kernel PUBLIC include)
target_include_directories(kernel PUBLIC include ${CMAKE_CURRENT_SOURCE_DIR}/../emuenv/include)
target_link_libraries(kernel PUBLIC rtc cpu mem util nids)
target_link_libraries(kernel PRIVATE patch SDL3::SDL3 miniz vita-toolchain)
if(TRACY_ENABLE_ON_CORE_COMPONENTS)
+12 -12
View File
@@ -31,17 +31,19 @@
#include <util/containers.h>
#include <util/types.h>
#include <emuenv/app_launch_request.h>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <mutex>
#include <optional>
#include <vector>
struct ThreadState;
struct MemState;
struct SDL_Thread;
struct CodecEngineBlock;
struct KernelModule {
@@ -55,8 +57,6 @@ typedef std::shared_ptr<ThreadState> ThreadStatePtr;
typedef std::map<SceUID, CodecEngineBlock> CodecEngineBlocks;
typedef std::map<SceUID, Ptr<Ptr<void>>> SlotToAddress;
typedef std::map<SceUID, ThreadStatePtr> ThreadStatePtrs;
typedef std::shared_ptr<SDL_Thread> ThreadPtr;
typedef std::map<SceUID, ThreadPtr> ThreadPtrs;
typedef std::map<SceUID, SceKernelModulePtr> SceKernelModuleInfoPtrs;
typedef std::map<SceUID, CallbackPtr> CallbackPtrs;
typedef unordered_map_fast<uint32_t, Address> ExportNids;
@@ -157,13 +157,7 @@ struct KernelState {
// kubridge exception handlers (DABT=0, PABT=1, UNDEF=2)
static constexpr int EXCEPTION_HANDLER_MAX = 3;
std::atomic<Address> exception_handlers[EXCEPTION_HANDLER_MAX]{};
std::mutex thread_lifecycle_mutex;
std::map<SceUID, SDL_Thread *> host_threads;
std::mutex host_threads_mutex;
std::atomic<bool> shutting_down{ false };
std::mutex shutdown_mutex;
std::condition_variable shutdown_condvar; // for delay_thread
std::condition_variable thread_deleted_cond;
SceUID get_next_uid() {
return next_uid++;
@@ -178,11 +172,17 @@ struct KernelState {
ThreadStatePtr get_thread(SceUID thread_id);
Ptr<Ptr<void>> get_thread_tls_addr(MemState &mem, SceUID thread_id, int key);
void exit_delete_all_threads_and_wait();
bool is_threads_paused() { return !paused_threads_status.empty(); }
void pause_threads();
void resume_threads();
// Kill all guest threads and block until they have exited. Must only be called from a host thread.
void process_exit();
std::function<void(int, std::optional<AppLaunchRequest>)> process_exit_callback;
// Request process exit. Safe to call from a guest thread. Returns immediately.
// The registered process_exit_callback is invoked to notify the host layer.
void request_process_exit(int res, std::optional<AppLaunchRequest> relaunch = std::nullopt);
void set_memory_watch(bool enabled);
void invalidate_jit_cache(Address start, size_t length);
SceKernelModuleInfo *find_module_by_addr(Address address);
@@ -52,8 +52,6 @@ struct ThreadSignal {
void wait();
bool send();
std::atomic<bool> *shutting_down = nullptr;
private:
std::mutex mutex;
std::condition_variable recv_cond;
+14 -67
View File
@@ -74,9 +74,12 @@ static int SDLCALL thread_function(void *data) {
thread->run_loop();
const uint32_t r0 = read_reg(*thread->cpu, 0);
{
std::lock_guard<std::mutex> lock(params.kernel->mutex);
params.kernel->threads.erase(thread->id);
params.kernel->corenum_allocator.free_corenum(get_processor_id(*thread->cpu));
params.kernel->thread_deleted_cond.notify_all();
}
return r0;
}
@@ -144,25 +147,12 @@ ThreadStatePtr KernelState::create_thread(MemState &mem, const char *name, Ptr<c
}
ThreadStatePtr KernelState::create_thread(MemState &mem, const char *name, Ptr<const void> entry_point, int init_priority, SceInt32 affinity_mask, int stack_size, const SceKernelThreadOptParam *option) {
if (shutting_down.load(std::memory_order_relaxed)) {
return nullptr;
}
ThreadStatePtr thread = std::make_shared<ThreadState>(get_next_uid(), *this, mem);
if (thread->init(name, entry_point, init_priority, affinity_mask, stack_size, option) < 0)
return nullptr;
const auto release_corenum = [&]() {
corenum_allocator.free_corenum(get_processor_id(*thread->cpu));
};
const std::lock_guard<std::mutex> lifecycle_lock(thread_lifecycle_mutex);
{
const std::lock_guard<std::mutex> lock(mutex);
if (shutting_down.load(std::memory_order_acquire)) {
release_corenum();
return nullptr;
}
threads.emplace(thread->id, thread);
}
@@ -171,15 +161,10 @@ ThreadStatePtr KernelState::create_thread(MemState &mem, const char *name, Ptr<c
params.thid = thread->id;
params.host_may_destroy_params = SDL_CreateSemaphore(0);
SDL_Thread *sdl_thread = SDL_CreateThread(&thread_function, thread->name.c_str(), &params);
SDL_DetachThread(SDL_CreateThread(&thread_function, thread->name.c_str(), &params));
SDL_WaitSemaphore(params.host_may_destroy_params);
SDL_DestroySemaphore(params.host_may_destroy_params);
{
const std::lock_guard<std::mutex> ht_lock(host_threads_mutex);
// TODO: lazy cleanup i forgor
host_threads.emplace(thread->id, sdl_thread);
}
return thread;
}
@@ -195,55 +180,22 @@ Ptr<Ptr<void>> KernelState::get_thread_tls_addr(MemState &mem, SceUID thread_id,
return address;
}
void KernelState::exit_delete_all_threads_and_wait() {
{
const std::lock_guard<std::mutex> lifecycle_lock(thread_lifecycle_mutex);
shutting_down.store(true, std::memory_order_release);
}
void KernelState::request_process_exit(int res, std::optional<AppLaunchRequest> relaunch) {
if (process_exit_callback)
process_exit_callback(res, std::move(relaunch));
}
void KernelState::process_exit() {
{
const std::lock_guard<std::mutex> lock(mutex);
for (auto &[_, timer] : timers) {
std::lock_guard<std::mutex> lock(mutex);
for (auto &[_, timer] : timers)
timer->condvar.notify_all();
}
}
shutdown_condvar.notify_all();
{
const std::lock_guard<std::mutex> lock(mutex);
for (auto &[_, thread] : threads)
thread->exit_delete(false);
}
std::map<SceUID, SDL_Thread *> threads_to_join;
SDL_Thread *self_thread = nullptr;
SceUID self_thread_id = SCE_KERNEL_ERROR_ILLEGAL_THREAD_ID;
const SDL_ThreadID current_thread_id = SDL_GetCurrentThreadID();
{
const std::lock_guard<std::mutex> lock(host_threads_mutex);
threads_to_join.swap(host_threads);
}
for (auto it = threads_to_join.begin(); it != threads_to_join.end(); ++it) {
if (SDL_GetThreadID(it->second) != current_thread_id)
continue;
self_thread = it->second;
self_thread_id = it->first;
threads_to_join.erase(it);
break;
}
if (self_thread) {
LOG_DEBUG("Detaching current host thread to avoid self-join (guest thread {}, host thread {})", self_thread_id, current_thread_id);
SDL_DetachThread(self_thread);
}
LOG_DEBUG("Joining {} host threads", threads_to_join.size());
for (auto &[id, sdl_thread] : threads_to_join) {
SDL_WaitThread(sdl_thread, nullptr);
}
std::unique_lock<std::mutex> lock(mutex);
thread_deleted_cond.wait(lock, [this] { return threads.empty(); });
}
void KernelState::pause_threads() {
@@ -265,10 +217,7 @@ void KernelState::resume_threads() {
}
void KernelState::deinit(MemState &mem) {
{
const std::lock_guard<std::mutex> lock(host_threads_mutex);
assert(host_threads.empty());
}
process_exit();
threads.clear();
simple_events.clear();
@@ -323,8 +272,6 @@ void KernelState::deinit(MemState &mem) {
next_uid = 1;
shutting_down.store(false, std::memory_order_relaxed);
paused_threads_status.clear();
}
+10 -39
View File
@@ -81,17 +81,6 @@ inline static int handle_timeout(KernelState &kernel, const ThreadStatePtr &thre
std::unique_lock<std::mutex> &primitive_lock, WaitingThreadQueuePtr &queue,
const ThreadDataQueueInterator<WaitingThreadData> &data_it, const char *export_name,
SceUInt *const timeout) {
if (kernel.shutting_down.load(std::memory_order_relaxed)) {
thread_lock.lock();
if (thread->status == ThreadStatus::wait)
thread->update_status(ThreadStatus::run);
thread_lock.unlock();
auto found = queue->find(thread);
if (found != queue->end())
queue->erase(found);
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
if (timeout) {
bool status = false;
auto start = std::chrono::steady_clock::now();
@@ -472,23 +461,21 @@ SceInt32 timer_waitorpoll(KernelState &kernel, const char *export_name, SceUID t
const auto data_it = timer->waiting_threads->push(data);
bool got_event = false;
while (!got_event && !kernel.shutting_down.load(std::memory_order_relaxed)) {
while (!got_event) {
uint64_t wait_time = timer->next_event - current_time;
// wait before we got an event and we are the first thread in the waiting list
timer->condvar.wait_for(lock, std::chrono::microseconds(wait_time), [&] {
return kernel.shutting_down.load(std::memory_order_relaxed)
return thread->status == ThreadStatus::run
|| (*timer->waiting_threads->begin()).thread->id == thread_id;
});
current_time = get_current_time();
got_event = timer->event_set || current_time > timer->next_event;
}
if (kernel.shutting_down.load(std::memory_order_relaxed)) {
if (thread->status == ThreadStatus::run) {
timer->waiting_threads->erase(data_it);
thread->update_status(ThreadStatus::run, ThreadStatus::wait);
timer->condvar.notify_all();
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
current_time = get_current_time();
got_event = timer->event_set || current_time > timer->next_event;
}
timer->waiting_threads->pop();
thread->update_status(ThreadStatus::run, ThreadStatus::wait);
@@ -1766,12 +1753,8 @@ SceSize msgpipe_recv(KernelState &kernel, const char *export_name, SceUID thread
// FIXME sleep on SimpleEvent
msgpipe_lock.unlock(); // Unlock message pipe object, else we'll deadlock
thread->status_cond.wait(thread_lock, [&] {
return thread->status == ThreadStatus::run || kernel.shutting_down.load(std::memory_order_relaxed);
return thread->status == ThreadStatus::run;
});
if (kernel.shutting_down.load(std::memory_order_relaxed)) {
thread->update_status(ThreadStatus::run);
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
if (msgpipe->beingDeleted) { // if beingDeleted then message pipe is locked, so we can't lock again
std::atomic_fetch_add(&msgpipe->remainingThreads, static_cast<size_t>(-1));
return SCE_KERNEL_ERROR_WAIT_DELETE;
@@ -1784,12 +1767,8 @@ SceSize msgpipe_recv(KernelState &kernel, const char *export_name, SceUID thread
} else { // There's a timeout - wait until we can fill buffer or timeout
msgpipe_lock.unlock(); // Unlock message pipe object, else we'll deadlock
auto status = thread->status_cond.wait_for(thread_lock, std::chrono::microseconds{ *pTimeout }, [&] {
return thread->status == ThreadStatus::run || kernel.shutting_down.load(std::memory_order_relaxed);
return thread->status == ThreadStatus::run;
});
if (kernel.shutting_down.load(std::memory_order_relaxed)) {
thread->update_status(ThreadStatus::run);
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
if (msgpipe->beingDeleted) {
std::atomic_fetch_add(&msgpipe->remainingThreads, static_cast<size_t>(-1));
return SCE_KERNEL_ERROR_WAIT_DELETE;
@@ -1881,12 +1860,8 @@ SceSize msgpipe_send(KernelState &kernel, const char *export_name, SceUID thread
// FIXME sleep on SimpleEvent
msgpipe_lock.unlock(); // Unlock message pipe object, else we'll deadlock
thread->status_cond.wait(thread_lock, [&] {
return thread->status == ThreadStatus::run || kernel.shutting_down.load(std::memory_order_relaxed);
return thread->status == ThreadStatus::run;
});
if (kernel.shutting_down.load(std::memory_order_relaxed)) {
thread->update_status(ThreadStatus::run);
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
if (msgpipe->beingDeleted) { // if beingDeleted then message pipe is locked, so we can't lock again
std::atomic_fetch_add(&msgpipe->remainingThreads, static_cast<size_t>(-1));
return SCE_KERNEL_ERROR_WAIT_DELETE;
@@ -1900,12 +1875,8 @@ SceSize msgpipe_send(KernelState &kernel, const char *export_name, SceUID thread
} else { // There's a timeout - wait until we can fill buffer or timeout
msgpipe_lock.unlock(); // Unlock message pipe object, else we'll deadlock
auto status = thread->status_cond.wait_for(thread_lock, std::chrono::microseconds{ *pTimeout }, [&] {
return thread->status == ThreadStatus::run || kernel.shutting_down.load(std::memory_order_relaxed);
return thread->status == ThreadStatus::run;
});
if (kernel.shutting_down.load(std::memory_order_relaxed)) {
thread->update_status(ThreadStatus::run);
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
if (msgpipe->beingDeleted) {
std::atomic_fetch_add(&msgpipe->remainingThreads, static_cast<size_t>(-1));
return SCE_KERNEL_ERROR_WAIT_DELETE;
+7 -8
View File
@@ -31,7 +31,7 @@
void ThreadSignal::wait() {
std::unique_lock<std::mutex> lock(mutex);
recv_cond.wait(lock, [&]() { return signaled || (shutting_down && shutting_down->load(std::memory_order_relaxed)); });
recv_cond.wait(lock, [&]() { return signaled; });
signaled = false;
}
@@ -183,8 +183,7 @@ void ThreadState::exit_delete(bool exit) {
stop(*cpu);
}
// Wake if thread waiting on status_cond
if (status == ThreadStatus::wait)
// Thread may enter a sync primitive wait after this runs, so always notify
update_status(ThreadStatus::run);
// Wake if thread waiting on sceKernelWaitSignal
@@ -426,13 +425,10 @@ uint32_t ThreadState::run_guest_function(Address callback_address, SceSize args,
{
// wait for the function to return
std::unique_lock<std::mutex> lock(mutex);
if (status != ThreadStatus::dormant || to_do == ThreadToDo::run) {
status_cond.wait(lock, [&]() {
return kernel.shutting_down.load(std::memory_order_relaxed)
|| (status == ThreadStatus::dormant && to_do != ThreadToDo::run);
return status == ThreadStatus::dormant && to_do != ThreadToDo::run;
});
}
}
entry_point = old_entry_point;
return returned_value;
@@ -442,13 +438,16 @@ ThreadState::ThreadState(SceUID id, KernelState &kernel, MemState &mem)
: id(id)
, kernel(kernel)
, mem(mem) {
signal.shutting_down = &kernel.shutting_down;
}
void ThreadState::update_status(ThreadStatus status, std::optional<ThreadStatus> expected) {
if (expected)
assert(expected.value() == this->status);
// Keep status as run after removal so handle_timeout's predicate is immediately satisfied
if (status == ThreadStatus::wait && to_do == ThreadToDo::remove)
return;
this->status = status;
status_cond.notify_all();
+1 -4
View File
@@ -17,7 +17,6 @@
#include "SceAppMgr.h"
#include <app/functions.h>
#include <io/state.h>
#include <kernel/state.h>
#include <packages/sfo.h>
@@ -436,9 +435,7 @@ EXPORT(SceInt32, _sceAppMgrLoadExec, const char *appPath, Ptr<char> const argv[]
return RET_ERROR(SCE_APPMGR_ERROR_TOO_LONG_ARGV);
}
emuenv.kernel.exit_delete_all_threads_and_wait();
app::request_in_process_launch(emuenv, { .app_path = emuenv.io.app_path, .self_path = std::move(exec_path), .argv = std::move(exec_argv), .reason = AppLaunchReason::LoadExec });
emuenv.kernel.request_process_exit(0, AppLaunchRequest{ .app_path = emuenv.io.app_path, .self_path = std::move(exec_path), .argv = std::move(exec_argv), .reason = AppLaunchReason::LoadExec });
return SCE_KERNEL_OK;
}
@@ -849,10 +849,8 @@ static int wait_thread_end(KernelState &kernel, ThreadStatePtr &waiter, ThreadSt
target->waiting_threads.push_back(waiter);
}
waiter->status_cond.wait(waiter_lock, [&]() {
return waiter->status == ThreadStatus::run || kernel.shutting_down.load(std::memory_order_relaxed);
return waiter->status == ThreadStatus::run;
});
if (kernel.shutting_down.load(std::memory_order_relaxed))
return SCE_KERNEL_ERROR_WAIT_CANCEL;
return 0;
}
@@ -1052,16 +1050,17 @@ EXPORT(int, sceKernelCreateThreadForUser, const char *name, SceKernelThreadEntry
return thread->id;
}
static int delay_thread(KernelState &kernel, SceUInt delay_us) {
static int delay_thread(KernelState &kernel, SceUID thread_id, SceUInt delay_us) {
if (delay_us == 0)
return SCE_KERNEL_ERROR_INVALID_ARGUMENT;
std::unique_lock<std::mutex> lock(kernel.shutdown_mutex);
if (kernel.shutdown_condvar.wait_for(lock, std::chrono::microseconds(delay_us),
[&] { return kernel.shutting_down.load(std::memory_order_relaxed); })) {
return SCE_KERNEL_ERROR_WAIT_CANCEL;
}
const ThreadStatePtr thread = kernel.get_thread(thread_id);
std::unique_lock<std::mutex> lock(thread->mutex);
thread->update_status(ThreadStatus::wait);
thread->status_cond.wait_for(lock, std::chrono::microseconds(delay_us),
[&] { return thread->status == ThreadStatus::run; });
if (thread->status != ThreadStatus::run)
thread->update_status(ThreadStatus::run);
return SCE_KERNEL_OK;
}
@@ -1072,21 +1071,21 @@ static int delay_thread_cb(EmuEnvState &emuenv, SceUID thread_id, SceUInt delay_
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
if (delay_us > elapsed.count()) // If we spent less time than requested processing callbacks, sleep the remaining time
return delay_thread(emuenv.kernel, delay_us - elapsed.count());
return delay_thread(emuenv.kernel, thread_id, delay_us - elapsed.count());
else // Else return directly
return SCE_KERNEL_OK;
}
EXPORT(int, sceKernelDelayThread, SceUInt delay) {
TRACY_FUNC(sceKernelDelayThread, delay);
return delay_thread(emuenv.kernel, delay);
return delay_thread(emuenv.kernel, thread_id, delay);
}
EXPORT(int, sceKernelDelayThread200, SceUInt delay) {
TRACY_FUNC(sceKernelDelayThread200, delay);
if (delay < 201)
delay = 201;
return delay_thread(emuenv.kernel, delay);
return delay_thread(emuenv.kernel, thread_id, delay);
}
EXPORT(int, sceKernelDelayThreadCB, SceUInt delay) {
+1 -3
View File
@@ -40,14 +40,12 @@ EXPORT(int, sceDbgAssertionHandler, const char *filename, int line, bool do_stop
LOG_INFO("file {}, line {}, {}", filename, line, buffer.data());
if (do_stop)
emuenv.kernel.exit_delete_all_threads_and_wait();
emuenv.kernel.request_process_exit(0);
if (!result) {
return SCE_KERNEL_ERROR_INVALID_ARGUMENT;
}
assert(!do_stop);
return 0;
}
+1 -3
View File
@@ -1290,9 +1290,7 @@ EXPORT(int, sceKernelDeleteLwMutex, Ptr<SceKernelLwMutexWork> workarea) {
EXPORT(int, sceKernelExitProcess, int res) {
TRACY_FUNC(sceKernelExitProcess, res);
// TODO Handle exit code?
emuenv.kernel.exit_delete_all_threads_and_wait();
emuenv.kernel.request_process_exit(res);
return SCE_KERNEL_OK;
}