* fix(wallet): prevent exit-hang on Windows from detached Tor/I2P threads
Embedded Tor and embedded I2P each ran on a background std::thread that was
.detach()'d at startup. The teardown paths (CTorEmbedded::Stop,
CI2PEmbedded::Stop) only flipped a running-flag — they did not signal the
thread to exit, and on Windows there is no signal mechanism in tor_api 0.4.x.
Result on Windows: when the user closed the wallet, Shutdown() completed its
bookkeeping and main() returned 0, but the process could not exit because the
detached thread was still in the Tor event loop / i2pd io_context. End Task
(TerminateProcess) was the only escape; the GUI appeared completely stuck.
Fixes:
- tor_embedded.h/.cpp: keep the Tor thread handle; Stop() now raise(SIGTERM)
on Linux, then joins the thread with a 5s timeout, then TerminateThread
(Win) / pthread_cancel + pthread_join (Linux) as a last resort.
- i2p_embedded.h/.cpp: same pattern — capture the bootstrap thread and join
it in Stop() with a 5s timeout fallback.
- init.cpp Shutdown(): spawn a 30s watchdog thread that calls ExitProcess(1)
if the graceful teardown takes too long. Belt-and-suspenders against any
future deadlock in the exit path.
- trianglesgui.cpp closeEvent(): second close attempt while the first
exit is still running immediately calls ExitProcess(2) / _exit(2).
User escape hatch when the graceful exit hangs.
All non-consensus (threading/process lifecycle only). Build via CI; not local.
Notes: notes/wallet-close-hang-fix-2026-07-07.md
* fix(i2p): drop leftover .detach() that broke build (lambda now joinable)
* fix(i2p): clean up after .detach() removal (trailing comment, blank line)
* fix(tor): MINGW std::thread is pthread-based, use pthread_cancel/join on MINGW
MINGW std::thread::native_handle_type is unsigned long long (pthread_t
emulation), not HANDLE. Mixing pthread handles with Win32
WaitForSingleObject/TerminateThread fails to compile on MINGW with
'invalid conversion' errors.
Use the same pthread_cancel/pthread_join path on Linux and MINGW; keep
TerminateThread only for MSVC builds where native_handle() returns a
real Win32 HANDLE.
---------
Co-authored-by: krystie <krystie>
Tor's atomic state-write is: write state.tmp, then rename to state.
If the daemon is killed mid-write (pkill -9, OOM, power loss, disk-full),
the rename can fail and 'state' is left as a regular file instead of a
directory. On next start, Tor's config validator refuses to use it:
[warn] State file '...' is not a file? Failing.
[err] set_options: Bug: Acting on config options left us in a broken state. Dying.
[err] Reading config failed--see warnings above.
The daemon then reports 'Tor failed to start. Triangles requires Tor to
operate.' (the error from the 2026-06-15 TRI-LAPTOP GUI wallet
screenshot) and refuses to come up at all.
Hit before on DNS3 (2026-05-24, fixed by manual 'mv state state.file.bak;
mkdir state' on the operator) and on the laptop today. The fix was always
the same one-liner; this patch makes the daemon do it itself.
Behavior:
- Detect: if tor_data/state exists and is NOT a directory, it's corrupt
- Quarantine: rename to tor_data/state.corrupt-YYYYMMDD-HHMMSS for
inspection (the user might want to recover the file's contents)
- If rename fails (Windows anti-virus holds the file, etc.), retry with
remove-then-rename, and as a last resort just remove() so Tor can
proceed
- Print a clear 'Tor state was a file (corrupt) — quarantined to ...'
log line so the operator can see it happened
- Tor then recreates state/ as a fresh directory and bootstraps normally
This is a pure-additive change (no existing behavior modified). Builds
clean with the existing C++20 + libtor.a toolchain. No version bump
needed - will roll into the next CI cycle.
Tor's config validator refuses to start a hidden service on any directory
whose permissions are not 0700. The daemon's fs::create_directories() honors
the process umask (0022 on Linux), leaving hidden_service/ at 0755. tor_run_main()
returned -1 with:
[warn] Permissions on directory .../hidden_service are too permissive.
[warn] Failed to parse/validate config: Failed to configure rendezvous options.
This was misdiagnosed as a libtor.a build problem (the June 4 rebuild was a
red herring). The real fix is two fs::permissions() calls after create_directories().
Reproduced with a standalone harness linking libtor.a, fixed, SOCKS port 19099
came up in 1 second and Tor began bootstrapping normally.
Also force 0700 on the DataDirectory itself - same validator, same rule.
Refs: SKILL.md pitfall #59
Migration from boost to std-library equivalents and removal of unreachable
code paths. Touches infrastructure only — no consensus rule or wallet
serialization changes.
Dead code removed:
- IRC bootstrap (irc.cpp/h, 417 lines): orphan from pre-Tor era, no callers.
- Alert system (alert.cpp/h + sendalert RPC + Qt UI signal, ~500 lines):
retired post-V5 fork; old peers' alert messages now hit the unknown-cmd
default branch, logged + ignored.
- Legacy P2P handlers in main.cpp: "checkpoint" (already a no-op stub since
V5 fork master-key removal), "checkorder"/"reply" (2010-era Receive-by-IP
feature), plus their unused supporting structures (CRequestTracker,
PushRequest overloads, mapRequests/cs_mapRequests, mapReuseKey).
- Unreachable RPCs clearwallettransactions and scanforalltxns (~175 lines):
defined in rpcwallet.cpp but never registered in the dispatch table.
- Stale -alertnotify CLI help text (option was advertised but never wired).
boost::filesystem -> std::filesystem (C++17):
- 30 source files, 5 headers. namespace fs = boost::filesystem swapped to
namespace fs = std::filesystem; boost::filesystem::ifstream/ofstream
replaced with std::ifstream/ofstream (path-aware in C++17);
fs::system_complete -> fs::absolute; boost::filesystem::filesystem_error
-> std::filesystem::filesystem_error.
- Build system: dropped Boost::filesystem from link libs and Boost
components; PCH includes updated.
- Added explicit <filesystem> includes where types were previously
available only transitively (db.h, rpcblockchain.cpp).
boost::thread -> std::thread (12 files):
- sync.h CCriticalSection/CWaitableCriticalSection now alias
std::recursive_mutex/std::mutex. boost::unique_lock and
boost::condition_variable / boost::mutex::scoped_lock swapped to std
equivalents; sync.cpp boost::thread_specific_ptr -> thread_local
std::unique_ptr.
- init.cpp boost::thread_group rewritten as std::vector<std::thread> with
manual join loop. boost::thread::hardware_concurrency ->
std::thread::hardware_concurrency.
- main.cpp/wallet.cpp -blocknotify/-walletnotify shell-out threads now use
std::thread(...).detach() — fixes a latent bug where modern boost::thread
destructor would call std::terminate on the joinable thread.
- util.cpp NewThread now catches std::system_error.
- No interruption_point/interrupt usage anywhere — pure mechanical swap.
boost::chrono / boost::posix_time -> std::chrono (3 of 5 files):
- util.h: MilliSleep, GetTimeMillis, GetTimeMicros rewritten on std::chrono
(system_clock for epoch math, sleep_for for delays).
- snapshotnet.cpp: sleep_for swapped.
- DoS_tests.cpp: timing harness uses steady_clock.
- Skipped: rpcdump.cpp (boost::posix_time::time_input_facet has no clean
std::get_time equivalent) and qt/qtipcserver.cpp (locked to
boost::posix_time by boost::interprocess::message_queue::timed_receive).
Other housekeeping:
- Dropped unnecessary "using namespace boost;" from txdb-leveldb.cpp,
txdb-rocksdb.cpp, walletdb.cpp, db.cpp (verified no unqualified boost
names in those TUs).
- Removed unused extern declaration for clearwallettransactions.
Build fixes for non-unity builds on MinGW64/GCC 15:
- net.cpp: dropped stale #include "irc.h".
- addrman.cpp + main.cpp: explicit <cmath> include for sqrt/pow (was
arriving transitively via boost headers).
- rpcblockchain.cpp + init.cpp: defensive #undef STRICT/ADVISORY/PERMISSIVE
since windows.h macros collide with the Checkpoints:: enum values when
std headers reorder include flow.
- tor_embed_hooks.cpp: triangles_tor_check_interrupted now polls fShutdown
instead of boost::this_thread::interruption_requested (we never used
boost interruption — the hook was always effectively a no-op).
- snapshotnet.cpp: fs::remove error handle uses std::error_code.
- serialize.h: added <ios> for std::ios::badbit/failbit (was relying on
transitive include via boost).
Note: unity builds currently fail on this branch due to std::byte (C++17)
colliding with COM 'byte' typedef from shlobj.h when 'using namespace std;'
from earlier files in the unity slice leaks into util.cpp's parse of
shlobj.h. Build with -DENABLE_UNITY_BUILD=OFF (the default).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop seven planning/strategy/upgrade-notes docs that have outlived their
usefulness, plus the dangling CODEX-TOR-GUIDE.md reference in
tor_embedded.cpp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename CTorProcess::GetLastError() and CTorEmbedded::GetLastError() to
GetStartupError() so they don't shadow the Win32 GetLastError() API,
which caused a std::string-to-DWORD conversion error on Windows.
- Qualify the one Win32 call as ::GetLastError() for clarity.
- Pass torError.c_str() to strprintf instead of std::string, fixing
Clang's -Wnon-pod-varargs error on macOS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CMakeLists: add --start-group linking for libtor.a and its deps
(libevent, openssl, zlib, lzma, zstd) with --allow-multiple-definition
for mixed static/dynamic OpenSSL on Windows
- CMakeLists: define USE_UPNP=0 only when USE_UPNP is off (not via
#ifdef-incompatible define)
- net.cpp: guard USE_UPNP reference with #ifdef for builds without UPnP
- rpcwallet.cpp: replace nonexistent LogPrintf with printf
- tor_embedded.cpp: fix SOCKET type mismatch on Windows (SOCKET vs int)
- .gitignore: add testnet-sync/ directory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codex changes: delegate hidden service management to the actual Tor
backend instead of generating keys the wallet never served. The new
AttachToBackendService() reads the hostname Tor creates, and the
torrc/process plumbing properly gates HiddenService directives behind
the -torhiddenservice flag.
Additional fixes:
- Back up hs_ed25519_secret_key (96 bytes) to wallet.dat so the onion
identity survives deletion of tor_data/
- Restore the key before Tor starts so the same .onion address is
regenerated automatically
- Add ThreadTorMaintenance: checks Tor health every 30s, auto-restarts
with exponential backoff on crash, re-attaches the hidden service
and re-registers the onion address with AddLocal()
- Seeder maintenance: every 30 min re-announces to peers and refreshes
known seeder lists (when -torseeder is enabled)
- Clean up ScheduleSeederReannouncement() stub (real work now in thread)
- Respect -torsocks port in onion proxy registration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>