OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "components/timers/rtc_alarm.h" | |
6 | |
7 #include <sys/timerfd.h> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/files/file_util.h" | |
11 #include "base/lazy_instance.h" | |
12 #include "base/logging.h" | |
13 #include "base/macros.h" | |
14 #include "base/message_loop/message_loop_proxy.h" | |
15 #include "base/threading/thread.h" | |
16 | |
17 namespace timers { | |
18 | |
19 namespace { | |
20 // Helper class to ensure that the IO thread we will use for watching file | |
21 // descriptors is started only once. | |
22 class IOThreadStartHelper { | |
23 public: | |
24 IOThreadStartHelper() : thread_(new base::Thread("RTC Alarm IO Thread")) { | |
25 CHECK(thread_->StartWithOptions( | |
26 base::Thread::Options(base::MessageLoop::TYPE_IO, 0))); | |
27 } | |
28 ~IOThreadStartHelper() {} | |
29 | |
30 base::Thread& operator*() const { return *thread_.get(); } | |
31 | |
32 base::Thread* operator->() const { return thread_.get(); } | |
33 | |
34 private: | |
35 scoped_ptr<base::Thread> thread_; | |
36 }; | |
37 | |
38 base::LazyInstance<IOThreadStartHelper> g_io_thread = LAZY_INSTANCE_INITIALIZER; | |
39 | |
40 } // namespace | |
41 | |
42 RtcAlarm::RtcAlarm() | |
43 : alarm_fd_(timerfd_create(CLOCK_REALTIME_ALARM, 0)), | |
44 origin_event_id_(0), | |
45 io_event_id_(0) { | |
46 } | |
47 | |
48 RtcAlarm::~RtcAlarm() { | |
49 if (alarm_fd_ != -1) | |
50 close(alarm_fd_); | |
51 } | |
52 | |
53 bool RtcAlarm::Init(base::WeakPtr<AlarmTimer> parent) { | |
54 parent_ = parent; | |
55 | |
56 return alarm_fd_ != -1; | |
57 } | |
58 | |
59 void RtcAlarm::Stop() { | |
60 // Make sure that we stop the RTC from a MessageLoopForIO. | |
61 if (!base::MessageLoopForIO::IsCurrent()) { | |
62 g_io_thread.Get()->task_runner()->PostTask( | |
63 FROM_HERE, | |
64 base::Bind(&RtcAlarm::Stop, scoped_refptr<RtcAlarm>(this))); | |
65 return; | |
66 } | |
67 | |
68 // Stop watching for events. | |
69 fd_watcher_.reset(); | |
70 | |
71 // Now clear the timer. | |
72 DCHECK_NE(alarm_fd_, -1); | |
73 itimerspec blank_time = {}; | |
74 timerfd_settime(alarm_fd_, 0, &blank_time, NULL); | |
75 } | |
76 | |
77 void RtcAlarm::Reset(base::TimeDelta delay) { | |
78 // Get a proxy for the current message loop. When the timer fires, we will | |
79 // post tasks to this proxy to let the parent timer know. | |
80 origin_message_loop_ = base::MessageLoopProxy::current(); | |
81 | |
82 // Increment the event id. Used to invalidate any events that have been | |
83 // queued but not yet run since the last time Reset() was called. | |
84 origin_event_id_++; | |
85 | |
86 // Calling timerfd_settime with a zero delay actually clears the timer so if | |
87 // the user has requested a zero delay timer, we need to handle it | |
88 // differently. We queue the task here but we still go ahead and call | |
89 // timerfd_settime with the zero delay anyway to cancel any previous delay | |
90 // that might have been programmed. | |
91 if (delay <= base::TimeDelta::FromMicroseconds(0)) { | |
92 // The timerfd_settime documentation is vague on what happens when it is | |
93 // passed a negative delay. We can sidestep the issue by ensuring that the | |
94 // delay is 0. | |
95 delay = base::TimeDelta::FromMicroseconds(0); | |
96 origin_message_loop_->PostTask(FROM_HERE, | |
97 base::Bind(&RtcAlarm::OnTimerFired, | |
98 scoped_refptr<RtcAlarm>(this), | |
99 origin_event_id_)); | |
100 } | |
101 | |
102 // Make sure that we are running on a MessageLoopForIO. | |
103 if (!base::MessageLoopForIO::IsCurrent()) { | |
104 g_io_thread.Get()->task_runner()->PostTask( | |
105 FROM_HERE, | |
106 base::Bind(&RtcAlarm::ResetImpl, | |
107 scoped_refptr<RtcAlarm>(this), | |
108 delay, | |
109 origin_event_id_)); | |
110 return; | |
111 } | |
112 | |
113 ResetImpl(delay, origin_event_id_); | |
114 } | |
115 | |
116 void RtcAlarm::OnFileCanReadWithoutBlocking(int fd) { | |
117 DCHECK_EQ(alarm_fd_, fd); | |
118 | |
119 // Read from the fd to ack the event. | |
120 char val[sizeof(uint64_t)]; | |
121 base::ReadFromFD(alarm_fd_, val, sizeof(uint64_t)); | |
122 | |
123 // Make sure that the parent timer is informed on the proper message loop. | |
124 if (origin_message_loop_->RunsTasksOnCurrentThread()) { | |
125 OnTimerFired(io_event_id_); | |
126 return; | |
127 } | |
128 | |
129 origin_message_loop_->PostTask(FROM_HERE, | |
130 base::Bind(&RtcAlarm::OnTimerFired, | |
131 scoped_refptr<RtcAlarm>(this), | |
132 io_event_id_)); | |
133 } | |
134 | |
135 void RtcAlarm::OnFileCanWriteWithoutBlocking(int fd) { | |
136 NOTREACHED(); | |
137 } | |
138 | |
139 void RtcAlarm::ResetImpl(base::TimeDelta delay, int event_id) { | |
140 DCHECK(base::MessageLoopForIO::IsCurrent()); | |
141 DCHECK_NE(alarm_fd_, -1); | |
142 | |
143 // Store the event id in the IO thread variable. When the timer fires, we | |
144 // will bind this value to the OnTimerFired callback to ensure that we do the | |
145 // right thing if the timer gets reset. | |
146 io_event_id_ = event_id; | |
147 | |
148 // If we were already watching the fd, this will stop watching it. | |
149 fd_watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher); | |
150 | |
151 // Start watching the fd to see when the timer fires. | |
152 if (!base::MessageLoopForIO::current()->WatchFileDescriptor( | |
153 alarm_fd_, | |
154 false, | |
155 base::MessageLoopForIO::WATCH_READ, | |
156 fd_watcher_.get(), | |
157 this)) { | |
158 LOG(ERROR) << "Error while attempting to watch file descriptor for RTC " | |
159 << "alarm. Timer will not fire."; | |
160 } | |
161 | |
162 // Actually set the timer. This will also clear the pre-existing timer, if | |
163 // any. | |
164 itimerspec alarm_time = {}; | |
165 alarm_time.it_value.tv_sec = delay.InSeconds(); | |
166 alarm_time.it_value.tv_nsec = | |
167 (delay.InMicroseconds() % base::Time::kMicrosecondsPerSecond) * | |
168 base::Time::kNanosecondsPerMicrosecond; | |
169 if (timerfd_settime(alarm_fd_, 0, &alarm_time, NULL) < 0) | |
170 PLOG(ERROR) << "Error while setting alarm time. Timer will not fire"; | |
171 } | |
172 | |
173 void RtcAlarm::OnTimerFired(int event_id) { | |
174 DCHECK(origin_message_loop_->RunsTasksOnCurrentThread()); | |
175 | |
176 // Check to make sure that the timer was not reset in the time between when | |
177 // this task was queued to run and now. If it was reset, then don't do | |
178 // anything. | |
179 if (event_id != origin_event_id_) | |
180 return; | |
181 | |
182 if (parent_) | |
183 parent_->OnTimerFired(); | |
184 } | |
185 | |
186 } // namespace timers | |
OLD | NEW |