OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // There are three classes involved here. There's NetworkChangeNotifierMac, | 5 #include "net/base/network_change_notifier_mac.h" |
6 // which is the Mac specific implementation of NetworkChangeNotifier. It is the | |
7 // class with which clients can register themselves as network change | |
8 // observers. There's NetworkChangeNotifierThread, which is a base::Thread | |
9 // subclass of MessageLoop::TYPE_UI (since it needs a CFRunLoop) that contains | |
10 // the NetworkChangeNotifierImpl. NetworkChangeNotifierImpl is the object | |
11 // that receives the actual OS X notifications and posts them to the | |
12 // NetworkChangeNotifierMac's message loop, so that NetworkChangeNotifierMac | |
13 // can notify all its observers. | |
14 // | |
15 // When NetworkChangeNotifierMac is being deleted, it will delete the | |
16 // NetworkChangeNotifierThread, which will Stop() it and also delete the | |
17 // NetworkChangeNotifierImpl. Therefore, NetworkChangeNotifierImpl and | |
18 // NetworkChangeNotifierThread's lifetimes generally begin after and end before | |
19 // NetworkChangeNotifierMac. There is an edge case where a notification task | |
20 // gets posted to the IO thread, thereby maintaining a reference to | |
21 // NetworkChangeNotifierImpl beyond the lifetime of NetworkChangeNotifierThread. | |
22 // In this case, the notification is cancelled, and NetworkChangeNotifierImpl | |
23 // will be deleted once all notification tasks that reference it have been run. | |
24 | 6 |
25 #include "net/base/network_change_notifier_mac.h" | |
26 #include <SystemConfiguration/SCDynamicStore.h> | |
27 #include <SystemConfiguration/SCDynamicStoreKey.h> | 7 #include <SystemConfiguration/SCDynamicStoreKey.h> |
28 #include <SystemConfiguration/SCSchemaDefinitions.h> | 8 #include <SystemConfiguration/SCSchemaDefinitions.h> |
29 #include <algorithm> | 9 #include <algorithm> |
30 #include "base/logging.h" | 10 |
31 #include "base/message_loop.h" | |
32 #include "base/scoped_cftyperef.h" | |
33 #include "base/thread.h" | 11 #include "base/thread.h" |
34 | 12 |
| 13 // We only post tasks to a child thread we own, so we don't need refcounting. |
| 14 DISABLE_RUNNABLE_METHOD_REFCOUNT(net::NetworkChangeNotifierMac); |
| 15 |
35 namespace net { | 16 namespace net { |
36 | 17 |
37 namespace { | 18 NetworkChangeNotifierMac::NetworkChangeNotifierMac() |
38 | 19 : notifier_thread_(new base::Thread("NetworkChangeNotifier")) { |
39 // NetworkChangeNotifierImpl should be created on a thread with a CFRunLoop, | 20 // We create this notifier thread because the notification implementation |
40 // since it requires one to pump notifications. However, it also runs some | 21 // needs a thread with a CFRunLoop, and there's no guarantee that |
41 // methods on |notifier_loop_|, because it cannot post calls to |notifier_| | 22 // MessageLoop::current() meets that criterion. |
42 // since NetworkChangeNotifier is not ref counted in a thread safe manner. | 23 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); |
43 class NetworkChangeNotifierImpl | 24 notifier_thread_->StartWithOptions(thread_options); |
44 : public base::RefCountedThreadSafe<NetworkChangeNotifierImpl> { | 25 // TODO(willchan): Look to see if there's a better signal for when it's ok to |
45 public: | 26 // initialize this, rather than just delaying it by a fixed time. |
46 NetworkChangeNotifierImpl(MessageLoop* notifier_loop, | 27 const int kNotifierThreadInitializationDelayMS = 1000; |
47 NetworkChangeNotifierMac* notifier); | 28 notifier_thread_->message_loop()->PostDelayedTask(FROM_HERE, |
48 | 29 NewRunnableMethod(this, &NetworkChangeNotifierMac::Init), |
49 void Shutdown(); | 30 kNotifierThreadInitializationDelayMS); |
50 | |
51 private: | |
52 friend class base::RefCountedThreadSafe<NetworkChangeNotifierImpl>; | |
53 ~NetworkChangeNotifierImpl(); | |
54 | |
55 static void DynamicStoreCallback(SCDynamicStoreRef /* store */, | |
56 CFArrayRef changed_keys, | |
57 void* config); | |
58 | |
59 void OnNetworkConfigChange(CFArrayRef changed_keys); | |
60 | |
61 // Runs on |notifier_loop_|. | |
62 void OnIPAddressChanged(); | |
63 | |
64 // Raw pointers. Note that |notifier_| _must_ outlive the | |
65 // NetworkChangeNotifierImpl. For lifecycle management details, read the | |
66 // comment at the top of the file. | |
67 MessageLoop* const notifier_loop_; | |
68 NetworkChangeNotifierMac* notifier_; | |
69 | |
70 scoped_cftyperef<CFRunLoopSourceRef> source_; | |
71 | |
72 DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierImpl); | |
73 }; | |
74 | |
75 NetworkChangeNotifierImpl::NetworkChangeNotifierImpl( | |
76 MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier) | |
77 : notifier_loop_(notifier_loop), | |
78 notifier_(notifier) { | |
79 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); | |
80 SCDynamicStoreContext context = { | |
81 0, // Version 0. | |
82 this, // User data. | |
83 NULL, // This is not reference counted. No retain function. | |
84 NULL, // This is not reference counted. No release function. | |
85 NULL, // No description for this. | |
86 }; | |
87 | |
88 // Get a reference to the dynamic store. | |
89 scoped_cftyperef<SCDynamicStoreRef> store( | |
90 SCDynamicStoreCreate(NULL /* use default allocator */, | |
91 CFSTR("org.chromium"), | |
92 DynamicStoreCallback, &context)); | |
93 | |
94 // Create a run loop source for the dynamic store. | |
95 source_.reset(SCDynamicStoreCreateRunLoopSource( | |
96 NULL /* use default allocator */, | |
97 store.get(), | |
98 0 /* 0 sounds like a fine source order to me! */)); | |
99 | |
100 // Add the run loop source to the current run loop. | |
101 CFRunLoopAddSource(CFRunLoopGetCurrent(), | |
102 source_.get(), | |
103 kCFRunLoopCommonModes); | |
104 | |
105 // Set up the notification keys. | |
106 scoped_cftyperef<CFMutableArrayRef> notification_keys( | |
107 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); | |
108 | |
109 // Monitor interface changes. | |
110 scoped_cftyperef<CFStringRef> key( | |
111 SCDynamicStoreKeyCreateNetworkGlobalEntity( | |
112 NULL /* default allocator */, kSCDynamicStoreDomainState, | |
113 kSCEntNetInterface)); | |
114 CFArrayAppendValue(notification_keys.get(), key.get()); | |
115 | |
116 // Monitor IP address changes. | |
117 | |
118 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( | |
119 NULL /* default allocator */, kSCDynamicStoreDomainState, | |
120 kSCEntNetIPv4)); | |
121 CFArrayAppendValue(notification_keys.get(), key.get()); | |
122 | |
123 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( | |
124 NULL /* default allocator */, kSCDynamicStoreDomainState, | |
125 kSCEntNetIPv6)); | |
126 CFArrayAppendValue(notification_keys.get(), key.get()); | |
127 | |
128 // Ok, let's ask for notifications! | |
129 // TODO(willchan): Figure out a proper way to handle this rather than crash. | |
130 CHECK(SCDynamicStoreSetNotificationKeys( | |
131 store.get(), notification_keys.get(), NULL)); | |
132 } | 31 } |
133 | 32 |
134 NetworkChangeNotifierImpl::~NetworkChangeNotifierImpl() { | 33 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() { |
135 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), | 34 // We don't need to explicitly Stop(), but doing so allows us to sanity- |
136 source_.get(), | 35 // check that the notifier thread shut down properly. |
137 kCFRunLoopCommonModes); | 36 notifier_thread_->Stop(); |
138 } | 37 DCHECK(run_loop_source_ == NULL); |
139 | |
140 void NetworkChangeNotifierImpl::Shutdown() { | |
141 CHECK(notifier_); | |
142 notifier_ = NULL; | |
143 } | 38 } |
144 | 39 |
145 // static | 40 // static |
146 void NetworkChangeNotifierImpl::DynamicStoreCallback( | 41 void NetworkChangeNotifierMac::DynamicStoreCallback( |
147 SCDynamicStoreRef /* store */, | 42 SCDynamicStoreRef /* store */, |
148 CFArrayRef changed_keys, | 43 CFArrayRef changed_keys, |
149 void* config) { | 44 void* config) { |
150 NetworkChangeNotifierImpl* net_config = | 45 NetworkChangeNotifierMac* net_config = |
151 static_cast<NetworkChangeNotifierImpl*>(config); | 46 static_cast<NetworkChangeNotifierMac*>(config); |
152 net_config->OnNetworkConfigChange(changed_keys); | 47 net_config->OnNetworkConfigChange(changed_keys); |
153 } | 48 } |
154 | 49 |
155 void NetworkChangeNotifierImpl::OnNetworkConfigChange(CFArrayRef changed_keys) { | 50 void NetworkChangeNotifierMac::WillDestroyCurrentMessageLoop() { |
| 51 DCHECK(notifier_thread_ != NULL); |
| 52 // We can't check the notifier_thread_'s message_loop(), as it's now 0. |
| 53 // DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current()); |
| 54 |
| 55 DCHECK(run_loop_source_ != NULL); |
| 56 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(), |
| 57 kCFRunLoopCommonModes); |
| 58 run_loop_source_.reset(); |
| 59 } |
| 60 |
| 61 void NetworkChangeNotifierMac::Init() { |
| 62 DCHECK(notifier_thread_ != NULL); |
| 63 DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current()); |
| 64 |
| 65 // Add a run loop source for a dynamic store to the current run loop. |
| 66 SCDynamicStoreContext context = { |
| 67 0, // Version 0. |
| 68 this, // User data. |
| 69 NULL, // This is not reference counted. No retain function. |
| 70 NULL, // This is not reference counted. No release function. |
| 71 NULL, // No description for this. |
| 72 }; |
| 73 scoped_cftyperef<SCDynamicStoreRef> store(SCDynamicStoreCreate( |
| 74 NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context)); |
| 75 run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource( |
| 76 NULL, store.get(), 0)); |
| 77 CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(), |
| 78 kCFRunLoopCommonModes); |
| 79 |
| 80 // Set up notifications for interface and IP address changes. |
| 81 scoped_cftyperef<CFMutableArrayRef> notification_keys( |
| 82 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); |
| 83 scoped_cftyperef<CFStringRef> key(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| 84 NULL, kSCDynamicStoreDomainState, kSCEntNetInterface)); |
| 85 CFArrayAppendValue(notification_keys.get(), key.get()); |
| 86 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| 87 NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)); |
| 88 CFArrayAppendValue(notification_keys.get(), key.get()); |
| 89 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| 90 NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)); |
| 91 CFArrayAppendValue(notification_keys.get(), key.get()); |
| 92 |
| 93 // Set the notification keys. This starts us receiving notifications. |
| 94 bool ret = SCDynamicStoreSetNotificationKeys( |
| 95 store.get(), notification_keys.get(), NULL); |
| 96 // TODO(willchan): Figure out a proper way to handle this rather than crash. |
| 97 CHECK(ret); |
| 98 |
| 99 MessageLoop::current()->AddDestructionObserver(this); |
| 100 } |
| 101 |
| 102 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) { |
| 103 DCHECK(notifier_thread_ != NULL); |
| 104 DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current()); |
| 105 |
156 for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { | 106 for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { |
157 CFStringRef key = static_cast<CFStringRef>( | 107 CFStringRef key = static_cast<CFStringRef>( |
158 CFArrayGetValueAtIndex(changed_keys, i)); | 108 CFArrayGetValueAtIndex(changed_keys, i)); |
159 if (CFStringHasSuffix(key, kSCEntNetIPv4) || | 109 if (CFStringHasSuffix(key, kSCEntNetIPv4) || |
160 CFStringHasSuffix(key, kSCEntNetIPv6)) { | 110 CFStringHasSuffix(key, kSCEntNetIPv6)) { |
161 notifier_loop_->PostTask( | 111 NotifyObserversOfIPAddressChange(); |
162 FROM_HERE, | 112 return; |
163 NewRunnableMethod( | 113 } |
164 this, | 114 if (CFStringHasSuffix(key, kSCEntNetInterface)) { |
165 &NetworkChangeNotifierImpl::OnIPAddressChanged)); | |
166 } else if (CFStringHasSuffix(key, kSCEntNetInterface)) { | |
167 // TODO(willchan): Does not appear to be working. Look into this. | 115 // TODO(willchan): Does not appear to be working. Look into this. |
168 // Perhaps this isn't needed anyway. | 116 // Perhaps this isn't needed anyway. |
169 } else { | 117 } else { |
170 NOTREACHED(); | 118 NOTREACHED(); |
171 } | 119 } |
172 } | 120 } |
173 } | 121 } |
174 | 122 |
175 void NetworkChangeNotifierImpl::OnIPAddressChanged() { | |
176 // If |notifier_| doesn't exist, then that means we're shutting down, so | |
177 // notifications are all cancelled. | |
178 if (notifier_) | |
179 notifier_->OnIPAddressChanged(); | |
180 } | |
181 | |
182 class NetworkChangeNotifierThread : public base::Thread { | |
183 public: | |
184 NetworkChangeNotifierThread(MessageLoop* notifier_loop, | |
185 NetworkChangeNotifierMac* notifier); | |
186 ~NetworkChangeNotifierThread(); | |
187 | |
188 protected: | |
189 virtual void Init(); | |
190 | |
191 private: | |
192 MessageLoop* const notifier_loop_; | |
193 NetworkChangeNotifierMac* const notifier_; | |
194 scoped_refptr<NetworkChangeNotifierImpl> notifier_impl_; | |
195 | |
196 DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierThread); | |
197 }; | |
198 | |
199 NetworkChangeNotifierThread::NetworkChangeNotifierThread( | |
200 MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier) | |
201 : base::Thread("NetworkChangeNotifier"), | |
202 notifier_loop_(notifier_loop), | |
203 notifier_(notifier) {} | |
204 | |
205 NetworkChangeNotifierThread::~NetworkChangeNotifierThread() { | |
206 notifier_impl_->Shutdown(); | |
207 Stop(); | |
208 } | |
209 | |
210 // Note that |notifier_impl_| is initialized on the network change | |
211 // notifier thread, not whatever thread constructs the | |
212 // NetworkChangeNotifierThread object. This is important, because this thread | |
213 // is the one that has a CFRunLoop. | |
214 void NetworkChangeNotifierThread::Init() { | |
215 notifier_impl_ = | |
216 new NetworkChangeNotifierImpl(notifier_loop_, notifier_); | |
217 } | |
218 | |
219 } // namespace | |
220 | |
221 NetworkChangeNotifierMac::NetworkChangeNotifierMac() | |
222 : notifier_thread_(NULL), | |
223 method_factory_(this) { | |
224 // TODO(willchan): Look to see if there's a better signal for when it's ok to | |
225 // initialize this, rather than just delaying it by a fixed time. | |
226 const int kNotifierThreadInitializationDelayMS = 1000; | |
227 MessageLoop* loop = MessageLoop::current(); | |
228 loop->PostDelayedTask( | |
229 FROM_HERE, | |
230 method_factory_.NewRunnableMethod( | |
231 &NetworkChangeNotifierMac::InitializeNotifierThread, loop), | |
232 kNotifierThreadInitializationDelayMS); | |
233 } | |
234 | |
235 void NetworkChangeNotifierMac::OnIPAddressChanged() { | |
236 DCHECK(CalledOnValidThread()); | |
237 FOR_EACH_OBSERVER(Observer, observers_, OnIPAddressChanged()); | |
238 } | |
239 | |
240 void NetworkChangeNotifierMac::AddObserver(Observer* observer) { | |
241 DCHECK(CalledOnValidThread()); | |
242 observers_.AddObserver(observer); | |
243 } | |
244 | |
245 void NetworkChangeNotifierMac::RemoveObserver(Observer* observer) { | |
246 DCHECK(CalledOnValidThread()); | |
247 observers_.RemoveObserver(observer); | |
248 } | |
249 | |
250 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() { | |
251 DCHECK(CalledOnValidThread()); | |
252 } | |
253 | |
254 void NetworkChangeNotifierMac::InitializeNotifierThread(MessageLoop* loop) { | |
255 DCHECK(CalledOnValidThread()); | |
256 notifier_thread_.reset(new NetworkChangeNotifierThread(loop, this)); | |
257 base::Thread::Options thread_options; | |
258 thread_options.message_loop_type = MessageLoop::TYPE_UI; | |
259 notifier_thread_->StartWithOptions(thread_options); | |
260 } | |
261 | |
262 } // namespace net | 123 } // namespace net |
OLD | NEW |