|  | /* | 
|  | *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  | #include "net/dcsctp/timer/task_queue_timeout.h" | 
|  |  | 
|  | #include "api/sequence_checker.h" | 
|  | #include "api/task_queue/pending_task_safety_flag.h" | 
|  | #include "api/task_queue/task_queue_base.h" | 
|  | #include "api/units/time_delta.h" | 
|  | #include "api/units/timestamp.h" | 
|  | #include "net/dcsctp/public/types.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/logging.h" | 
|  |  | 
|  | namespace dcsctp { | 
|  | using ::webrtc::TimeDelta; | 
|  | using ::webrtc::Timestamp; | 
|  |  | 
|  | TaskQueueTimeoutFactory::TaskQueueTimeout::TaskQueueTimeout( | 
|  | TaskQueueTimeoutFactory& parent, | 
|  | webrtc::TaskQueueBase::DelayPrecision precision) | 
|  | : parent_(parent), | 
|  | precision_(precision), | 
|  | pending_task_safety_flag_(webrtc::PendingTaskSafetyFlag::Create()) {} | 
|  |  | 
|  | TaskQueueTimeoutFactory::TaskQueueTimeout::~TaskQueueTimeout() { | 
|  | RTC_DCHECK_RUN_ON(&parent_.thread_checker_); | 
|  | pending_task_safety_flag_->SetNotAlive(); | 
|  | } | 
|  |  | 
|  | void TaskQueueTimeoutFactory::TaskQueueTimeout::Start(DurationMs duration_ms, | 
|  | TimeoutID timeout_id) { | 
|  | RTC_DCHECK_RUN_ON(&parent_.thread_checker_); | 
|  | RTC_DCHECK(timeout_expiration_.IsPlusInfinity()); | 
|  | timeout_expiration_ = parent_.Now() + duration_ms.ToTimeDelta(); | 
|  | timeout_id_ = timeout_id; | 
|  |  | 
|  | if (timeout_expiration_ >= posted_task_expiration_) { | 
|  | // There is already a running task, and it's scheduled to expire sooner than | 
|  | // the new expiration time. Don't do anything; The `timeout_expiration_` has | 
|  | // already been updated and if the delayed task _does_ expire and the timer | 
|  | // hasn't been stopped, that will be noticed in the timeout handler, and the | 
|  | // task will be re-scheduled. Most timers are stopped before they expire. | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!posted_task_expiration_.IsPlusInfinity()) { | 
|  | RTC_DLOG(LS_VERBOSE) << "New timeout duration is less than scheduled - " | 
|  | "ghosting old delayed task."; | 
|  | // There is already a scheduled delayed task, but its expiration time is | 
|  | // further away than the new expiration, so it can't be used. It will be | 
|  | // "killed" by replacing the safety flag. This is not expected to happen | 
|  | // especially often; Mainly when a timer did exponential backoff and | 
|  | // later recovered. | 
|  | pending_task_safety_flag_->SetNotAlive(); | 
|  | pending_task_safety_flag_ = webrtc::PendingTaskSafetyFlag::Create(); | 
|  | } | 
|  |  | 
|  | posted_task_expiration_ = timeout_expiration_; | 
|  | parent_.task_queue_.PostDelayedTaskWithPrecision( | 
|  | precision_, | 
|  | webrtc::SafeTask(pending_task_safety_flag_, | 
|  | [timeout_id, this]() { | 
|  | RTC_DLOG(LS_VERBOSE) | 
|  | << "Timout expired: " << timeout_id.value(); | 
|  | RTC_DCHECK_RUN_ON(&parent_.thread_checker_); | 
|  | RTC_DCHECK(!posted_task_expiration_.IsPlusInfinity()); | 
|  | posted_task_expiration_ = Timestamp::PlusInfinity(); | 
|  |  | 
|  | if (timeout_expiration_.IsPlusInfinity()) { | 
|  | // The timeout was stopped before it expired. Very | 
|  | // common. | 
|  | } else { | 
|  | // Note that the timeout might have been restarted, | 
|  | // which updated `timeout_expiration_` but left the | 
|  | // scheduled task running. So if it's not quite time | 
|  | // to trigger the timeout yet, schedule a new delayed | 
|  | // task with what's remaining and retry at that point | 
|  | // in time. | 
|  | TimeDelta remaining = | 
|  | timeout_expiration_ - parent_.Now(); | 
|  | timeout_expiration_ = Timestamp::PlusInfinity(); | 
|  | if (remaining > TimeDelta::Zero()) { | 
|  | Start(DurationMs(remaining.ms()), timeout_id_); | 
|  | } else { | 
|  | // It has actually triggered. | 
|  | RTC_DLOG(LS_VERBOSE) | 
|  | << "Timout triggered: " << timeout_id.value(); | 
|  | parent_.on_expired_(timeout_id_); | 
|  | } | 
|  | } | 
|  | }), | 
|  | webrtc::TimeDelta::Millis(duration_ms.value())); | 
|  | } | 
|  |  | 
|  | void TaskQueueTimeoutFactory::TaskQueueTimeout::Stop() { | 
|  | // As the TaskQueue doesn't support deleting a posted task, just mark the | 
|  | // timeout as not running. | 
|  | RTC_DCHECK_RUN_ON(&parent_.thread_checker_); | 
|  | timeout_expiration_ = Timestamp::PlusInfinity(); | 
|  | } | 
|  |  | 
|  | }  // namespace dcsctp |