Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
trace_timeout.hpp
Go to the documentation of this file.
1#pragma once
2
3// trace_timeout.hpp — the owned, interruptible timeout behind Perfetto's
4// auto-flush (WAH-4).
5//
6// It lives in its own header, compiled UNCONDITIONALLY, for the same reason
7// `tracing_reminder_first_time()` does: Pulp's default build is
8// PULP_TRACING=OFF, so anything defined only inside `#if PULP_TRACING_ENABLED`
9// is never exercised by CI. The lifetime rules here are exactly the ones that
10// were wrong, so they need a gate that actually runs.
11//
12// What it replaces: a detached `std::thread` that slept for N seconds and then
13// called back into process-global tracing state. Two failures, both reachable
14// from an ordinary DAW session:
15//
16// * The plug-in module can be unloaded (`FreeLibrary` / `dlclose`) while that
17// thread still sleeps. It then wakes into freed code — on Windows, inside
18// the loader lock, which is a hard crash rather than a diagnosable fault.
19// * A timer armed for one capture could stop a LATER one. Close and reopen an
20// editor inside the `PULP_TRACE_SECONDS` window and the second capture was
21// silently truncated by the first one's timer.
22//
23// So: cancellation is immediate (a condition variable, not "after the remaining
24// sleep"), the thread is joinable and joined by the final detach, and every
25// timeout carries the session generation it was armed for.
26
27#include <chrono>
28#include <condition_variable>
29#include <cstdint>
30#include <functional>
31#include <mutex>
32#include <thread>
33
34namespace pulp::runtime::detail {
35
42constexpr bool timeout_targets_current_session(std::uint64_t armed_for,
43 std::uint64_t current) noexcept {
44 return armed_for != 0 && armed_for == current;
45}
46
53public:
54 TimeoutController() = default;
59
63 void arm(std::chrono::milliseconds delay, std::uint64_t generation,
64 std::function<void(std::uint64_t)> on_expire) {
66 {
67 std::lock_guard<std::mutex> lk(mu_);
68 cancelled_ = false;
69 }
70 worker_ = std::thread([this, delay, generation,
71 on_expire = std::move(on_expire)] {
72 {
73 std::unique_lock<std::mutex> lk(mu_);
74 // The predicate makes this immune to spurious wakeups AND to a
75 // cancel that lands before the wait even starts — a real race,
76 // because arm() releases mu_ before the thread runs.
77 if (cv_.wait_for(lk, delay, [this] { return cancelled_; }))
78 return;
79 }
80 // Called with mu_ RELEASED: the callback re-enters tracing state
81 // and may block, and holding mu_ here would make cancel_and_join()
82 // wait on it.
83 if (on_expire) on_expire(generation);
84 });
85 }
86
91 if (!worker_.joinable()) return;
92 {
93 std::lock_guard<std::mutex> lk(mu_);
94 cancelled_ = true;
95 }
96 cv_.notify_all();
97 worker_.join();
98 }
99
102 bool armed() const noexcept { return worker_.joinable(); }
103
104private:
105 std::mutex mu_;
106 std::condition_variable cv_;
107 bool cancelled_ = false;
108 std::thread worker_;
109};
110
111} // namespace pulp::runtime::detail
TimeoutController(const TimeoutController &)=delete
void cancel_and_join()
Cancel an armed timeout and join its thread.
Definition trace_timeout.hpp:90
TimeoutController & operator=(const TimeoutController &)=delete
void arm(std::chrono::milliseconds delay, std::uint64_t generation, std::function< void(std::uint64_t)> on_expire)
Arm on_expire(generation) to run after delay.
Definition trace_timeout.hpp:63
bool armed() const noexcept
True while a timeout thread exists (armed, running, or finished but not yet joined).
Definition trace_timeout.hpp:102
~TimeoutController()
Joins any armed timeout. A controller must never outlive its worker.
Definition trace_timeout.hpp:58
Definition assert.hpp:29
constexpr bool timeout_targets_current_session(std::uint64_t armed_for, std::uint64_t current) noexcept
True when a timeout armed for armed_for may still act on the session currently identified by current.
Definition trace_timeout.hpp:42