diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4bf1c46 --- /dev/null +++ b/Makefile @@ -0,0 +1,222 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +#ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -std=c++14 -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..994a6bb --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +This is a reproduction on randomly freezing threads on libnx. + +The thread pool implementation is taken from https://www.youtube.com/watch?v=eWTGtp3HXiw, but this also happens when using normal std::threads, pthreads and libnx threads. This is just the implementation I use in my app. (I tried the other ones in my app though, too, with the same random freezes.) + +Instructions: + +1. Load up hbmenu and press Y to get NetLoader +2. run `make clean all && while true; do nxlink -s *.nro*; done` (You might need to set `-a ` on nxlink) +3. The app will run and dump stdio to your terminal +4. It will run and quit successfully if it did not freeze. So press Y on the switch to load the app again. +5. At some point it will freeze after spawning a new thread. +6. ??? This is where I am stuck. No idea how to debug this further. diff --git a/include/ThreadPool.hpp b/include/ThreadPool.hpp new file mode 100644 index 0000000..b94eb3c --- /dev/null +++ b/include/ThreadPool.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +using Task = std::function; +class ThreadPool +{ +public: + + explicit ThreadPool(std::size_t numThreads); + + ~ThreadPool(); + + template + auto enqueue(T task)->std::future + { + printf("enq\n"); + auto wrapper = std::make_shared>(std::move(task)); + + { + std::unique_lock lock{mEventMutex}; + mTasks.emplace([=] { + (*wrapper)(); + }); + } + + mEventVar.notify_one(); + return wrapper->get_future(); + } + + std::vector mThreads; + + std::condition_variable mEventVar; + + std::mutex mEventMutex; + bool mStopping = false; + + std::queue mTasks; + +private: + void start(std::size_t numThreads); + + void stop() noexcept; +}; \ No newline at end of file diff --git a/source/ThreadPool.cpp b/source/ThreadPool.cpp new file mode 100644 index 0000000..08afcf8 --- /dev/null +++ b/source/ThreadPool.cpp @@ -0,0 +1,85 @@ +#include "ThreadPool.hpp" + +ThreadPool::ThreadPool(std::size_t numThreads) +{ + start(numThreads); +} + +ThreadPool::~ThreadPool() +{ + stop(); +} + +void ThreadPool::start(std::size_t numThreads) +{ + for (auto i = 0u; i < numThreads; ++i) + { + printf("spawning thread %d/%d\n", i + 1, numThreads); + mThreads.emplace_back([=]{ + u32 prio; + s32 preferred; + u32 mask; + u64 threadId; + Handle h = threadGetCurHandle(); + svcGetThreadPriority(&prio, h); + svcGetThreadId(&threadId, h); + svcGetThreadCoreMask(&preferred, &mask, h); + + printf("[%ld] Prio: %x, preferred CPU: %d, mask: %x\n", threadId, prio, preferred, mask); + + while (true) + { + Task task; + + { + std::unique_lock lock{mEventMutex}; + + mEventVar.wait(lock, [=] { return mStopping || !mTasks.empty(); }); + + if (mStopping && mTasks.empty()) + break; + + task = std::move(mTasks.front()); + mTasks.pop(); + } + + printf("running in thread\n"); + task(); + } + }); + printf("spawned thread %d/%d\n", i + 1, numThreads); + } + printf("all threads\n"); +} + +void ThreadPool::stop() noexcept +{ + { + std::unique_lock lock{mEventMutex}; + mStopping = true; + } + + mEventVar.notify_all(); + + for (auto &thread : mThreads) { + thread.join(); + } +} + +//int main() +//{ +// { +// ThreadPool pool{36}; +// +// for (auto i = 0; i < 36; ++i) +// { +// pool.enqueue([] { +// auto f = 1000000000; +// while (f > 1) +// f /= 1.00000001; +// }); +// } +// } +// +// return 0; +//} \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..55c735c --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include "ThreadPool.hpp" + +#define NUM_THREADS 2 // Set to whatever you want. I set it to 2, to keep way below core count. + +std::vector> vthreads; + +int call_from_thread(u32 threadNr) { + int i = 0; + // Make the thread busy + while (i < 10000) { + i++; + } + return i * threadNr; +} + +void collectResultsBlocking() { + printf("collectResultsBlocking called\n"); + for (u32 i = 0; i < vthreads.size(); i++){ + printf("Thread %d result: %d\n", i, vthreads.at(i).get()); + } + vthreads.clear(); + printf("collectResultsBlocking done\n"); +} + +int main(int argc, char* argv[]) +{ + consoleInit(NULL); + u32 counter = 0; + + socketInitializeDefault(); + nxlinkStdio(); + + printf("Hello World! %d\n", counter); + counter++; + + ThreadPool pool{NUM_THREADS}; // Threadpool is initialized and threads started + + printf("Hello World! %d\n", counter); + counter++; + + for (auto i = 0; i < NUM_THREADS; ++i) + { + // Threads get work + vthreads.push_back(pool.enqueue([=] { + return call_from_thread(i); + })); + } + + printf("Hello World! %d\n", counter); + counter++; + + int i = 0; + // Make the main thread busy, too + while (i < 30000) { + i++; + } + printf("i = %d\n", i); + //Get results from future. + collectResultsBlocking(); + + socketExit(); + consoleExit(NULL); + return 0; +}