Support high precision in TaskQueueGcd delayed tasks TaskQueueGcd previously ignored PostDelayedTaskTraits::high_precision and scheduled all delayed tasks with dispatch_after_f(), which is subject to OS timer coalescing (up to 100 ms under background policy). Schedule high-precision delayed tasks using a one-shot DISPATCH_TIMER_STRICT timer source with leeway capped at 1 ms. Low- precision tasks continue to use dispatch_after_f(). Bug: webrtc:470337728 Change-Id: Iafd6b1296d6bcd2446c3fade1f624907128e684a Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/510942 Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org> Reviewed-by: Markus Handell <handellm@webrtc.org> Cr-Commit-Position: refs/heads/main@{#49042}
diff --git a/api/task_queue/task_queue_test.cc b/api/task_queue/task_queue_test.cc index ba07e39..98815ab 100644 --- a/api/task_queue/task_queue_test.cc +++ b/api/task_queue/task_queue_test.cc
@@ -30,7 +30,8 @@ #include "absl/strings/string_view.h" #include "api/make_ref_counted.h" #include "api/ref_count.h" -#if defined(WEBRTC_WIN) +#if !defined(WEBRTC_CHROMIUM_BUILD) && \ + (defined(WEBRTC_WIN) || defined(WEBRTC_MAC) || defined(WEBRTC_IOS)) #include "api/task_queue/default_task_queue_factory.h" #endif #include "api/task_queue/task_queue_base.h" @@ -417,6 +418,68 @@ } #endif // !defined(WEBRTC_CHROMIUM_BUILD) && defined(WEBRTC_WIN) +#if !defined(WEBRTC_CHROMIUM_BUILD) && \ + (defined(WEBRTC_MAC) || defined(WEBRTC_IOS)) +// Only TaskQueueGcd (the default factory on macOS and iOS) distinguishes +// PostDelayedHighPrecisionTask() from PostDelayedTask() in standalone WebRTC. +// Change these to `TEST_P(TaskQueueTest, ...)` using GetParam() when all +// implementations support high-precision delayed tasks and Priority::kHigh. +TEST(TaskQueueTest, PostDelayedHighPrecisionZero) { + std::unique_ptr<TaskQueueFactory> factory = + CreateDefaultTaskQueueFactory(nullptr); + Event event; + auto queue = CreateTaskQueue(factory, "PostDelayedHighPrecisionZero", + TaskQueueFactory::Priority::kHigh); + + queue->PostDelayedHighPrecisionTask([&event] { event.Set(); }, + TimeDelta::Zero()); + EXPECT_TRUE(event.Wait(TimeDelta::Seconds(1))); +} + +TEST(TaskQueueTest, PostDelayedHighPrecision) { + std::unique_ptr<TaskQueueFactory> factory = + CreateDefaultTaskQueueFactory(nullptr); + Event event; + auto queue = CreateTaskQueue(factory, "PostDelayedHighPrecision", + TaskQueueFactory::Priority::kHigh); + + int64_t start = TimeMillis(); + int64_t end = 0; + queue->PostDelayedHighPrecisionTask( + [&event, &queue, &end] { + EXPECT_TRUE(queue->IsCurrent()); + end = TimeMillis(); + event.Set(); + }, + TimeDelta::Millis(100)); + EXPECT_TRUE(event.Wait(TimeDelta::Seconds(1))); + EXPECT_GE(end - start, 90u); + EXPECT_NEAR(end - start, 190u, 100u); // Accept 90-290. +} + +TEST(TaskQueueTest, PostMultipleDelayedHighPrecision) { + std::unique_ptr<TaskQueueFactory> factory = + CreateDefaultTaskQueueFactory(nullptr); + auto queue = CreateTaskQueue(factory, "PostMultipleDelayedHighPrecision", + TaskQueueFactory::Priority::kHigh); + + std::vector<Event> events(100); + for (int i = 0; i < 100; ++i) { + Event* event = &events[i]; + queue->PostDelayedHighPrecisionTask( + [event, &queue] { + EXPECT_TRUE(queue->IsCurrent()); + event->Set(); + }, + TimeDelta::Millis(i)); + } + + for (Event& e : events) + EXPECT_TRUE(e.Wait(TimeDelta::Seconds(1))); +} +#endif // !defined(WEBRTC_CHROMIUM_BUILD) && (defined(WEBRTC_MAC) || + // defined(WEBRTC_IOS)) + #ifdef BUILD_EXPERIMENTAL_TASK_QUEUE_COROUTINE_TESTS struct TaskQueueCoroutine {
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index a89ec8f..c415f5b 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn
@@ -2638,6 +2638,7 @@ ":rtc_event", ":rtc_task_queue_gcd", "../api/task_queue", + "../api/units:time_delta", "../test:test_support", ] }
diff --git a/rtc_base/task_queue_gcd.cc b/rtc_base/task_queue_gcd.cc index 9e31530..92234f2 100644 --- a/rtc_base/task_queue_gcd.cc +++ b/rtc_base/task_queue_gcd.cc
@@ -15,18 +15,20 @@ #include "rtc_base/task_queue_gcd.h" #include <dispatch/dispatch.h> -#include <string.h> +#include <algorithm> +#include <cstdint> #include <memory> #include <string> +#include <utility> #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "api/location.h" #include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" #include "api/units/time_delta.h" #include "rtc_base/checks.h" -#include "rtc_base/logging.h" #include "rtc_base/system/gcd_helpers.h" namespace webrtc { @@ -48,6 +50,13 @@ } } +// Returns the leeway of the strict timer that runs a high precision task. Like +// for dispatch_after(), the leeway is 10% of the delay, but it's capped at +// 1 ms. +uint64_t HighPrecisionLeewayNs(TimeDelta delay) { + return std::min(delay / 10, TimeDelta::Millis(1)).ns<uint64_t>(); +} + class TaskQueueGcd final : public TaskQueueBase { public: TaskQueueGcd(absl::string_view queue_name, int gcd_priority); @@ -71,10 +80,14 @@ TaskQueueGcd* const queue; absl::AnyInvocable<void() &&> task; + // Only set for tasks that are run by a strict timer. Released when the + // timer fires. + dispatch_source_t timer = nullptr; }; ~TaskQueueGcd() override; static void RunTask(void* task_context); + static void RunTimerTask(void* task_context); static void SetNotActive(void* task_queue); static void DeleteQueue(void* task_queue); @@ -131,8 +144,32 @@ const PostDelayedTaskTraits& traits, const Location& location) { auto* context = new TaskContext(this, std::move(task)); - dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, delay.us() * NSEC_PER_USEC), - queue_, context, &RunTask); + const dispatch_time_t when = + dispatch_time(DISPATCH_TIME_NOW, delay.us() * NSEC_PER_USEC); + // dispatch_after() posts tasks without a positive delay right away, so a + // timer is only needed for high precision tasks with a positive delay. + if (!traits.high_precision || delay <= TimeDelta::Zero()) { + dispatch_after_f(when, queue_, context, &RunTask); + return; + } + + // The OS may coalesce the timers of dispatch_after() with other timers, which + // can delay tasks by much more than the timer leeway, up to 100 ms when the + // process runs with background policy. A strict timer asks the OS to observe + // the leeway instead. Like the timer behind dispatch_after(), the timer + // source retains its target queue, `queue_`, until it's released. + dispatch_source_t timer = dispatch_source_create( + DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, queue_); + RTC_CHECK(timer); + context->timer = timer; + // The event handler is called with the context that the source has when the + // handler is set. + dispatch_set_context(timer, context); + dispatch_source_set_event_handler_f(timer, &RunTimerTask); + dispatch_source_set_timer(timer, when, DISPATCH_TIME_FOREVER, + HighPrecisionLeewayNs(delay)); + // `context` may be deleted on `queue_` as soon as the timer is activated. + dispatch_activate(timer); } // static @@ -148,6 +185,18 @@ } // static +void TaskQueueGcd::RunTimerTask(void* task_context) { + auto* tc = static_cast<TaskContext*>(task_context); + // The timer is one-shot. Cancel it so that it never fires again and release + // it before running the task. GCD keeps the source alive while this handler + // runs. + dispatch_source_cancel(tc->timer); + dispatch_release(tc->timer); + tc->timer = nullptr; + RunTask(tc); // Takes ownership of `tc`. +} + +// static void TaskQueueGcd::SetNotActive(void* task_queue) { static_cast<TaskQueueGcd*>(task_queue)->is_active_ = false; }
diff --git a/rtc_base/task_queue_gcd_unittest.cc b/rtc_base/task_queue_gcd_unittest.cc index a4efe77..9292e8d 100644 --- a/rtc_base/task_queue_gcd_unittest.cc +++ b/rtc_base/task_queue_gcd_unittest.cc
@@ -11,10 +11,10 @@ #include "rtc_base/task_queue_gcd.h" #include <memory> -#include <utility> #include "api/task_queue/task_queue_base.h" #include "api/task_queue/task_queue_factory.h" +#include "api/units/time_delta.h" #include "rtc_base/event.h" #include "test/gtest.h" @@ -34,5 +34,20 @@ EXPECT_TRUE(done.Wait(TimeDelta::Seconds(1))); } +TEST(TaskQueueGcdTest, DeleteFromHighPrecisionTask) { + std::unique_ptr<TaskQueueFactory> factory = CreateTaskQueueGcdFactory(); + auto queue = factory->CreateTaskQueue("DeleteFromHighPrecisionTask", + TaskQueueFactory::Priority::kNormal); + Event done; + queue->PostDelayedHighPrecisionTask( + [&queue, &done] { + // This will call TaskQueueGcd::Delete() from the current queue. + queue.reset(); + done.Set(); + }, + TimeDelta::Millis(1)); + EXPECT_TRUE(done.Wait(TimeDelta::Seconds(1))); +} + } // namespace } // namespace webrtc