Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(506)

Side by Side Diff: chrome/common/multi_process_notification_mac.mm

Issue 5970015: Add multi-process notification class. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Now receives notifications on the thread that calls Start on the listener Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 "chrome/common/multi_process_notification.h"
6
7 #import <Foundation/Foundation.h>
8 #include <notify.h>
9 #include <sys/select.h>
10 #include <sys/socket.h>
11 #include <sys/types.h>
12 #include <unistd.h>
13
14 #include <algorithm>
15
16 #include "base/basictypes.h"
17 #include "base/eintr_wrapper.h"
18 #include "base/file_path.h"
19 #include "base/logging.h"
20 #include "base/mac/mac_util.h"
21 #include "base/mac/scoped_nsautorelease_pool.h"
22 #include "base/message_loop_proxy.h"
23 #include "base/message_pump_libevent.h"
24 #include "base/path_service.h"
25 #include "base/ref_counted.h"
26 #include "base/stringprintf.h"
27 #include "base/synchronization/lock.h"
28 #include "base/sys_string_conversions.h"
29 #include "base/sys_info.h"
30 #include "base/threading/simple_thread.h"
31 #include "chrome/browser/browser_thread.h"
32 #include "chrome/common/chrome_paths.h"
33
34 // Enable this to build with leopard_switchboard_thread
35 #define USE_LEOPARD_SWITCHBOARD_THREAD 1
Mark Mentovai 2011/01/13 23:10:34 Turn this back off.
dmac 2011/01/13 23:17:43 Done.
36
37 namespace {
38
39 std::string AddPrefixToNotification(const std::string& name,
40 multi_process_notification::Domain domain) {
41 // The ordering of the components in the string returned by this function
42 // is important. Read "NAMESPACE CONVENTIONS" in 'man 3 notify' for details.
43 base::mac::ScopedNSAutoreleasePool pool;
44 NSBundle* bundle = base::mac::MainAppBundle();
45 NSString* ns_bundle_id = [bundle bundleIdentifier];
46 std::string bundle_id = base::SysNSStringToUTF8(ns_bundle_id);
47 std::string domain_string;
48 switch (domain) {
49 case multi_process_notification::ProfileDomain: {
50 FilePath user_data_dir;
51 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
52 NOTREACHED();
53 }
54 domain_string = StringPrintf("user.uid.%u.%s.",
55 getuid(), user_data_dir.value().c_str());
56 break;
57 }
58
59 case multi_process_notification::UserDomain:
60 domain_string = StringPrintf("user.uid.%u.", getuid());
61 break;
62
63 case multi_process_notification::SystemDomain:
64 break;
65 }
66 return domain_string + bundle_id + "." + name;
67 }
68
69 bool UseLeopardSwitchboardThread() {
70 #if USE_LEOPARD_SWITCHBOARD_THREAD
71 return true;
72 #endif // USE_LEOPARD_SWITCHBOARD_THREAD
73 int32 major_version, minor_version, bugfix_version;
74 base::SysInfo::OperatingSystemVersionNumbers(
75 &major_version, &minor_version, &bugfix_version);
76 return major_version < 10 || (major_version == 10 && minor_version <= 5);
77 }
78
79 } // namespace
80
81 namespace multi_process_notification {
82
83 bool Post(const std::string& name, Domain domain) {
84 std::string notification = AddPrefixToNotification(name, domain);
85 uint32_t status = notify_post(notification.c_str());
86 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
87 return status == NOTIFY_STATUS_OK;
88 }
89
90 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5
91 #error LeopardSwitchboardThread can be removed
92 #endif // MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5
93
94 // LeopardSwitchboardThread exists because the file descriptors returned by
95 // notify_register_file_descriptor can't be monitored using kqueues on 10.5
96 // ( http://openradar.appspot.com/8854692 ) and libevent uses kqueue to watch
97 // file descriptors in IOMessageLoop.
98 // This solution is to have a separate thread that monitors the file descriptor
99 // returned by notify_register_file_descriptor using select, and then to
100 // notify the MessageLoopForIO using a different file descriptor allocated by
101 // socketpair that can be monitored using kqueues in libevent. This thread
102 // only runs on 10.5, as 10.6 kqueues can monitor the notify file descriptors
103 // without any problems.
104
105 // LeopardSwitchboardThread creates three file descriptors:
106 // internal_fd_: which communicates from the switchboard thread to other threads
107 // external_fd_: which communicates from other threads to the switchboard thread
108 // notify_fd_: which is the file descriptor returned from
109 // notify_register_file_descriptor
110 //
111 // The thread itself sits in a select loop waiting on internal_fd_, and
112 // notify_fd_ for input. If it gets ANY input on internal_fd_ it exits.
113 // If it gets input on notify_fd_ it sends the input through to external_fd_.
114 // External_fd_ is monitored by MessageLoopForIO so that the lookup of any
115 // matching listeners in entries_, and the triggering of those listeners,
116 // occurs in the MessageLoopForIO thread.
117 //
118 // Lookups are linear right now, and could be optimized if they ever become
119 // a performance issue.
120 class LeopardSwitchboardThread
121 : public base::MessagePumpLibevent::Watcher,
122 public base::SimpleThread,
123 public MessageLoop::DestructionObserver {
124 public:
125 LeopardSwitchboardThread();
126 virtual ~LeopardSwitchboardThread();
127
128 bool Init();
129
130 bool AddListener(ListenerImpl* listener,
131 const std::string& notification);
132 bool RemoveListener(ListenerImpl* listener, const std::string& notification);
133
134 bool finished() const { return finished_; }
135
136 // SimpleThread overrides
137 virtual void Run();
138
139 // Watcher overrides
140 virtual void OnFileCanReadWithoutBlocking(int fd);
141 virtual void OnFileCanWriteWithoutBlocking(int fd);
142
143 // DestructionObserver overrides
144 virtual void WillDestroyCurrentMessageLoop();
145
146 private:
147 // User to match tokens to notifications and vice-versa.
Mark Mentovai 2011/01/13 23:10:34 “User” should be “Used”.
dmac 2011/01/13 23:17:43 Done.
148 struct SwitchboardEntry {
149 int token_;
150 std::string notification_;
151 ListenerImpl* listener_;
152 };
153
154 enum {
155 kKillThreadMessage = 0xdecea5e
156 };
157
158 int internal_fd_;
159 int external_fd_;
160 int notify_fd_;
161 int notify_fd_token_;
162 mutable bool finished_;
163 fd_set fd_set_;
164
165 // all accesses to entries_ must be controlled by entries_lock_.
166 std::vector<SwitchboardEntry> entries_;
167 Lock entries_lock_;
168 base::MessagePumpLibevent::FileDescriptorWatcher watcher_;
169 };
170
171 class ListenerImpl : public base::MessagePumpLibevent::Watcher {
172 public:
173 ListenerImpl(const std::string& name,
174 Domain domain,
175 Listener::Delegate* delegate);
176 virtual ~ListenerImpl();
177
178 bool Start();
179 void OnListen();
180
181 // Watcher overrides
182 virtual void OnFileCanReadWithoutBlocking(int fd);
183 virtual void OnFileCanWriteWithoutBlocking(int fd);
184
185 private:
186 std::string name_;
187 Domain domain_;
188 Listener::Delegate* delegate_;
189 int fd_;
190 int token_;
191 Lock switchboard_lock_;
192 static LeopardSwitchboardThread* g_switchboard_thread_;
193 base::MessagePumpLibevent::FileDescriptorWatcher watcher_;
194 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
195
196 void StartLeopard();
197 void StartSnowLeopard();
198 DISALLOW_COPY_AND_ASSIGN(ListenerImpl);
199 };
200
201
202 LeopardSwitchboardThread::LeopardSwitchboardThread()
203 : base::SimpleThread("LeopardSwitchboardThread"), internal_fd_(-1),
204 external_fd_(-1), notify_fd_(-1), notify_fd_token_(-1), finished_(false) {
205 }
206
207 LeopardSwitchboardThread::~LeopardSwitchboardThread() {
208 if (internal_fd_ != -1) {
209 close(internal_fd_);
210 }
211 if (external_fd_ != -1) {
212 close(external_fd_);
213 }
214 if (notify_fd_token_ != -1) {
215 // Cancelling this notification takes care of closing notify_fd_.
216 uint32_t status = notify_cancel(notify_fd_token_);
217 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
218 }
219 }
220
221 bool LeopardSwitchboardThread::Init() {
222 // Create a pair of sockets for communicating with the thread
223 // The file descriptors returned from socketpair can be kqueue'd on 10.5.
224 int sockets[2];
225 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
226 PLOG(ERROR) << "socketpair";
227 return false;
228 }
229 internal_fd_ = sockets[0];
230 external_fd_ = sockets[1];
231
232 // Register a bogus notification so that there is single notify_fd_ to
233 // monitor. This runs a small risk of overflowing the notification buffer
234 // if notifications are used heavily (see man 3 notify), however it greatly
235 // simplifies the select loop code as there are only 2 file descriptors
236 // that need to be monitored, and there is no need to add/remove file
237 // descriptors from fd_set_ as listeners are added and removed.
238 // This also keep the total fd usage on 10.5 to three for all
239 // notifications. The 10.6 implementation will use one fd per notification,
240 // but doesn't run the risk of notification buffer overflow. If fds ever
241 // become tight, the 10.6 code could be changed to use only one fd for
242 // all notifications.
243 std::string notification = StringPrintf("LeopardSwitchboardThread.%d",
244 getpid());
245 notification = AddPrefixToNotification(notification, ProfileDomain);
246 uint32_t status = notify_register_file_descriptor(
247 notification.c_str(), &notify_fd_, 0, &notify_fd_token_);
248 if (status != NOTIFY_STATUS_OK) {
249 return false;
250 }
251
252 FD_ZERO(&fd_set_);
253 FD_SET(internal_fd_, &fd_set_);
254 FD_SET(notify_fd_, &fd_set_);
255
256 MessageLoopForIO* io_loop = MessageLoopForIO::current();
257 // Watch for destruction of the BrowserThread::IO message loop so that
258 // the thread can be exited cleanly.
Mark Mentovai 2011/01/13 23:10:34 the thread can be stopped cleanly. “Exit” sounds t
dmac 2011/01/13 23:17:43 Done.
259 io_loop->AddDestructionObserver(this);
260 return io_loop->WatchFileDescriptor(
261 external_fd_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
262 }
263
264 void LeopardSwitchboardThread::WillDestroyCurrentMessageLoop() {
265 DCHECK_EQ(MessageLoop::current(), MessageLoopForIO::current());
266 watcher_.StopWatchingFileDescriptor();
267
268 // Send the appropriate message to end our thread, and then wait for it
269 // to finish before continuing.
270 int message = kKillThreadMessage;
271 write(external_fd_, &message, sizeof(message));
272 Join();
273 }
274
275 void LeopardSwitchboardThread::Run() {
276 DCHECK(!finished_);
277 int nfds = std::max(internal_fd_, notify_fd_) + 1;
278 while (1) {
279 fd_set working_set;
280 FD_COPY(&fd_set_, &working_set);
281 int count = HANDLE_EINTR(select(nfds, &working_set, NULL, NULL, NULL));
282 if (count < 0) {
283 PLOG(ERROR) << "select";
284 break;
285 } else if (count == 0) {
286 DLOG(INFO) << "select timed out";
287 continue;
288 }
289 if (FD_ISSET(notify_fd_, &working_set)) {
290 int token;
291 int status = HANDLE_EINTR(read(notify_fd_, &token, sizeof(token)));
292 if (status < 0) {
293 PLOG(ERROR) << "read";
294 break;
295 } else if (status == 0) {
296 LOG(ERROR) << "notify fd closed";
297 break;
298 } else if (status != sizeof(token)) {
299 LOG(ERROR) << "read wrong size: " << status;
300 break;
301 } else if (token == notify_fd_token_) {
302 LOG(ERROR) << "invalid token: " << token;
303 }
304 status = HANDLE_EINTR(write(internal_fd_, &token, sizeof(token)));
305 if (status < 0) {
306 PLOG(ERROR) << "write";
307 break;
308 } else if (status == 0) {
309 LOG(ERROR) << "external_fd_ closed";
310 break;
311 } else if (status != sizeof(token)) {
312 LOG(ERROR) << "write wrong size: " << status;
313 break;
314 }
315 }
316 if (FD_ISSET(internal_fd_, &working_set)) {
317 int value;
318 int status = HANDLE_EINTR(read(internal_fd_, &value, sizeof(value)));
319 if (status < 0) {
320 PLOG(ERROR) << "read";
321 } else if (status == 0) {
322 LOG(ERROR) << "internal_fd_ closed";
323 } else if (value != kKillThreadMessage) {
324 LOG(ERROR) << "unknown message sent: " << value;
325 }
326 break;
327 }
328 }
329 finished_ = true;
330 }
331
332 bool LeopardSwitchboardThread::AddListener(ListenerImpl* listener,
333 const std::string& notification) {
334 DCHECK(!finished());
335 base::AutoLock autolock(entries_lock_);
336 for (std::vector<SwitchboardEntry>::iterator i = entries_.begin();
337 i < entries_.end(); ++i) {
338 if (i->listener_ == listener && i->notification_ == notification) {
339 LOG(ERROR) << "listener " << listener
340 << " already registered for '" << notification << "'";
341 return false;
342 }
343 }
344 int token;
345 uint32_t status = notify_register_file_descriptor(
346 notification.c_str(), &notify_fd_, NOTIFY_REUSE, &token);
347 if (status != NOTIFY_STATUS_OK) {
348 LOG(ERROR) << "unable to notify_register_file_descriptor for '"
349 << notification << "' status: " << status;
350 return false;
351 }
352 SwitchboardEntry entry;
353 entry.token_ = token;
354 entry.notification_ = notification;
355 entry.listener_ = listener;
356 entries_.push_back(entry);
357 return true;
358 }
359
360 bool LeopardSwitchboardThread::RemoveListener(ListenerImpl* listener,
361 const std::string& notification) {
362 DCHECK(!finished());
363 base::AutoLock autolock(entries_lock_);
364 for (std::vector<SwitchboardEntry>::iterator i = entries_.begin();
365 i < entries_.end(); ++i) {
366 if (i->listener_ == listener && i->notification_ == notification) {
367 uint32_t status = notify_cancel(i->token_);
368 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
369 entries_.erase(i);
370 return true;
371 }
372 }
373 LOG(ERROR) << "unable to remove listener '" << listener
374 << "' for '" << notification << "'.";
375 return false;
376 }
377
378 void LeopardSwitchboardThread::OnFileCanReadWithoutBlocking(int fd) {
379 DCHECK_EQ(MessageLoop::current(), MessageLoopForIO::current());
380 DCHECK_EQ(fd, external_fd_);
381 int token;
382 int status = HANDLE_EINTR(read(fd, &token, sizeof(token)));
383 if (status < 0) {
384 PLOG(ERROR) << "read";
385 } else if (status == 0) {
386 LOG(ERROR) << "external_fd_ closed";
387 } else if (status != sizeof(token)) {
388 LOG(ERROR) << "unexpected read size " << status;
389 } else {
390 // Have to swap to native endianness <http://openradar.appspot.com/8821081>.
391 token = static_cast<int>(ntohl(token));
392 base::AutoLock autolock(entries_lock_);
393 bool found_token = false;
394 for (std::vector<SwitchboardEntry>::iterator i = entries_.begin();
395 i < entries_.end(); ++i) {
396 if (i->token_ == token) {
397 found_token = true;
398 i->listener_->OnListen();
399 }
400 }
401 if (!found_token) {
402 LOG(ERROR) << "read unknown token " << token;
403 }
404 }
405 }
406
407 void LeopardSwitchboardThread::OnFileCanWriteWithoutBlocking(int fd) {
408 NOTREACHED();
409 }
410
411 LeopardSwitchboardThread* ListenerImpl::g_switchboard_thread_ = NULL;
412
413 ListenerImpl::ListenerImpl(
414 const std::string& name, Domain domain, Listener::Delegate* delegate)
415 : name_(name), domain_(domain), delegate_(delegate), fd_(-1), token_(-1) {
416 }
417
418 ListenerImpl::~ListenerImpl() {
419 if (!UseLeopardSwitchboardThread()) {
420 if (token_ != -1) {
421 uint32_t status = notify_cancel(token_);
422 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
423 token_ = -1;
424 }
425 } else {
426 base::AutoLock autolock(switchboard_lock_);
427 if (g_switchboard_thread_) {
428 std::string notification = AddPrefixToNotification(name_, domain_);
429 CHECK(g_switchboard_thread_->RemoveListener(this, notification));
430 }
431 }
432 }
433
434 bool ListenerImpl::Start() {
435 DCHECK_EQ(fd_, -1);
436 DCHECK_EQ(token_, -1);
437 message_loop_proxy_ = base::MessageLoopProxy::CreateForCurrentThread();
438 Task* task;
439 if(UseLeopardSwitchboardThread()) {
440 task = NewRunnableMethod(this, &ListenerImpl::StartLeopard);
441 } else {
442 task = NewRunnableMethod(this, &ListenerImpl::StartSnowLeopard);
443 }
444 return BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task);
445 }
446
447 void ListenerImpl::StartLeopard() {
448 DCHECK(UseLeopardSwitchboardThread());
449 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
450 bool success = true;
451 {
452 base::AutoLock autolock(switchboard_lock_);
453 if (g_switchboard_thread_ && g_switchboard_thread_->HasBeenJoined()) {
454 delete g_switchboard_thread_;
455 g_switchboard_thread_ = NULL;
456 }
457 DCHECK(!(g_switchboard_thread_ && g_switchboard_thread_->finished()));
458 if (!g_switchboard_thread_) {
459 g_switchboard_thread_ = new LeopardSwitchboardThread();
460 success = g_switchboard_thread_->Init();
461 if (success) {
462 g_switchboard_thread_->Start();
463 }
464 }
465 if (success) {
466 std::string notification = AddPrefixToNotification(name_, domain_);
467 success = g_switchboard_thread_->AddListener(this, notification);
468 }
469 }
470 Task* task =
471 new Listener::ListenerStartedTask(name_, domain_, delegate_, success);
472 CHECK(message_loop_proxy_->PostTask(FROM_HERE, task));
473 }
474
475 void ListenerImpl::StartSnowLeopard() {
476 DCHECK(!UseLeopardSwitchboardThread());
477 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
478 bool success = true;
479 std::string notification = AddPrefixToNotification(name_, domain_);
480 uint32_t status = notify_register_file_descriptor(
481 notification.c_str(), &fd_, 0, &token_);
482 if (status != NOTIFY_STATUS_OK) {
483 LOG(ERROR) << "unable to notify_register_file_descriptor for '"
484 << notification << "' Status: " << status;
485 success = false;
486 }
487 if (success) {
488 MessageLoopForIO* io_loop = MessageLoopForIO::current();
489 success = io_loop->WatchFileDescriptor(
490 fd_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
491 }
492 Task* task =
493 new Listener::ListenerStartedTask(name_, domain_, delegate_, success);
494 CHECK(message_loop_proxy_->PostTask(FROM_HERE, task));
495 }
496
497 void ListenerImpl::OnFileCanReadWithoutBlocking(int fd) {
498 DCHECK(!UseLeopardSwitchboardThread());
499 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
500 DCHECK_EQ(fd, fd_);
501 int token;
502 int status = HANDLE_EINTR(read(fd, &token, sizeof(token)));
503 if (status < 0) {
504 PLOG(ERROR) << "read";
505 } else if (status == 0) {
506 LOG(ERROR) << "external_fd_ closed";
507 } else if (status != sizeof(token)) {
508 LOG(ERROR) << "unexpected read size " << status;
509 } else {
510 // Have to swap to native endianness <http://openradar.appspot.com/8821081>.
511 token = static_cast<int>(ntohl(token));
512 if (token == token_) {
513 OnListen();
514 } else {
515 LOG(ERROR) << "unexpected value " << token;
516 }
517 }
518 }
519
520 void ListenerImpl::OnListen() {
521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
522 Task* task =
523 new Listener::NotificationReceivedTask(name_, domain_, delegate_);
524 CHECK(message_loop_proxy_->PostTask(FROM_HERE, task));
525 }
526
527 void ListenerImpl::OnFileCanWriteWithoutBlocking(int fd) {
528 NOTREACHED();
529 }
530
531 Listener::Listener(
532 const std::string& name, Domain domain, Listener::Delegate* delegate)
533 : impl_(new ListenerImpl(name, domain, delegate)) {
534 }
535
536 Listener::~Listener() {
537 }
538
539 bool Listener::Start() {
540 return impl_->Start();
541 }
542
543 } // namespace multi_process_notification
544
545 DISABLE_RUNNABLE_METHOD_REFCOUNT(multi_process_notification::ListenerImpl);
546
Mark Mentovai 2011/01/13 23:10:34 Dump the blank line at EOF.
dmac 2011/01/13 23:17:43 Done.
OLDNEW
« no previous file with comments | « chrome/common/multi_process_notification_linux.cc ('k') | chrome/common/multi_process_notification_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698