Files
ppsspp/Common/Thread/ThreadManager.h
T
Henrik RydgårdandClaude Sonnet 5 ceb3e9a0ad ThreadManager: fix teardown crash and task leak, remove dead code
- ~ThreadManager() never stopped worker threads before freeing the
  global context. If Teardown() wasn't reached before process exit
  (e.g. an early return between Init() and a caller's shutdown path),
  the still-running threads kept touching freed mutexes/queues/condvars,
  causing intermittent crashes on exit. The destructor now tears down
  if still initialized.
- Teardown() silently leaked any non-cancellable task still sitting in
  a queue (global or per-thread) at shutdown time - such tasks were
  never run nor released, since nothing drains those queues once the
  worker threads have been marked cancelled/joined. Now every queued
  task is properly cancelled-or-warned and released.
- Added an assert against a divide-by-zero in EnqueueTask's round-robin
  fallback, which would trigger if Init() were ever called with zero
  compute threads.
- Removed Task::id()/Kind() and ThreadManager::TryCancelTask(), which
  were unused dead code (TryCancelTask was a no-op with no callers).
- Fixed a stale comment on EnqueueTaskOnThread referencing a parameter
  that doesn't exist, and an auto-typed ternary that misleadingly read
  like a deque array copy (it's a pointer, not a copy).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
2026-08-09 19:03:08 +02:00

84 lines
2.1 KiB
C++

#pragma once
#include <cstdint>
// The new threadpool.
// To help smart scheduling.
enum class TaskType {
CPU_COMPUTE,
IO_BLOCKING, // NOTE: Only these can access scoped storage on Android (they initialize the JNI context).
DEDICATED_THREAD, // These can never get stuck in queue behind others, but are more expensive to launch. Cannot use I/O.
};
enum class TaskPriority {
HIGH = 0,
NORMAL = 1,
LOW = 2,
COUNT,
};
// Implement this to make something that you can run on the thread manager.
class Task {
public:
virtual ~Task() {}
virtual TaskType Type() const = 0;
virtual TaskPriority Priority() const = 0;
virtual void Run() = 0;
virtual bool Cancellable() const { return false; }
virtual void Cancel() {}
virtual void Release() { delete this; }
};
class Waitable {
public:
virtual ~Waitable() {}
virtual void Wait() = 0;
void WaitAndRelease() {
Wait();
delete this;
}
};
struct TaskThreadContext;
struct GlobalThreadContext;
class ThreadManager {
public:
ThreadManager();
~ThreadManager();
// The distinction here is to be able to take hyper-threading into account.
// It gets even trickier when you think about mobile chips with BIG/LITTLE, but we'll
// just ignore it and let the OS handle it.
void Init(int numCores, int numLogicalCoresPerCpu);
void EnqueueTask(Task *task);
// Directly assigns a task to a specific worker thread's private queue, bypassing
// the global queue and load balancing. Used to pin related tasks (e.g. tiles of a
// parallel loop) to distinct threads by index.
void EnqueueTaskOnThread(int threadNum, Task *task);
void Teardown();
bool IsInitialized() const;
// Parallel loops (assumed compute-limited) get one thread per logical core. We have a few extra threads too
// for I/O bounds tasks, that can be run concurrently with those.
int GetNumLooperThreads() const;
private:
void TeardownTask(Task *task);
// This is always pointing to a context, initialized in the constructor.
GlobalThreadContext *global_;
int numThreads_ = 0;
int numComputeThreads_ = 0;
friend struct TaskThreadContext;
};
extern ThreadManager g_threadManager;