diff --git a/CHANGELOG.md b/CHANGELOG.md index 9233c011..1f6b2595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to `plotjuggler_sdk` are recorded here. Versioning policy is in [`CLAUDE.md`](./CLAUDE.md) → "Release Versioning". +## [0.34.1] + +- Fix a data race in `JobControl::armWatchdog`: the self-join guard read the + watchdog `std::thread` member on the watchdog thread while the body thread was + still move-assigning it. The guard now keys on a thread-local flag the watchdog + thread sets before running `on_expire`. Found by running the SDK tests under + ThreadSanitizer. + ## [0.34.0] Host contract: extended: PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT (floor 0.34.0). diff --git a/VERSION b/VERSION index 85e60ed1..cd46610f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.34.0 +0.34.1 diff --git a/pj_base/src/source/provider_job.cpp b/pj_base/src/source/provider_job.cpp index 51dfc761..3b2c0fb9 100644 --- a/pj_base/src/source/provider_job.cpp +++ b/pj_base/src/source/provider_job.cpp @@ -18,6 +18,13 @@ namespace source { namespace detail { +/// True only on a watchdog thread, set before it runs on_expire; the self-join +/// guard in armWatchdog reads it instead of the still-being-assigned thread object. +bool& inWatchdogThread() { + thread_local bool in_watchdog = false; + return in_watchdog; +} + struct JobState { ProviderJob::Body body; decltype(PJ_descriptor_import_callbacks_v1_t::on_dataset) on_dataset = nullptr; @@ -191,8 +198,10 @@ void JobControl::armWatchdog(std::chrono::milliseconds timeout, std::function lock(state->watchdog_mu); started.release(); const bool stopped = state->watchdog_cv.wait_for(lock, timeout, [state] { return state->watchdog_stop; });