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

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: Fixed up mac side so that it works on 10.5 as well. 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 "base/basictypes.h"
15 #include "base/eintr_wrapper.h"
16 #include "base/file_path.h"
17 #include "base/logging.h"
18 #include "base/mac/mac_util.h"
19 #include "base/mac/scoped_nsautorelease_pool.h"
20 #include "base/message_loop.h"
21 #include "base/message_pump_libevent.h"
22 #include "base/path_service.h"
23 #include "base/stringprintf.h"
24 #include "base/synchronization/lock.h"
25 #include "base/sys_string_conversions.h"
26 #include "base/sys_info.h"
27 #include "base/threading/simple_thread.h"
28 #include "chrome/common/chrome_paths.h"
29
30 // Enable this to build with leopard_switchboard_thread
31 #define USE_LEOPARD_SWITCHBOARD_THREAD 1
Mark Mentovai 2011/01/12 22:42:36 You don’t want to check this in, do you?
32
33 namespace {
34
35 std::string AddPrefixToNotification(const std::string& name,
36 multi_process_notification::Domain domain) {
37 // The ordering of the components in the string returned by this function
38 // is important. Read "NAMESPACE CONVENTIONS" in 'man 3 notify' for details.
39 base::mac::ScopedNSAutoreleasePool pool;
40 NSBundle* bundle = base::mac::MainAppBundle();
41 NSString* ns_bundle_id = [bundle bundleIdentifier];
42 std::string bundle_id = base::SysNSStringToUTF8(ns_bundle_id);
43 std::string domain_string;
44 switch (domain) {
45 case multi_process_notification::ProfileDomain: {
46 FilePath user_data_dir;
47 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
48 NOTREACHED();
49 }
50 domain_string = StringPrintf("user.uid.%u.%s.",
51 getuid(), user_data_dir.value().c_str());
52 break;
53 }
54
55 case multi_process_notification::UserDomain:
56 domain_string = StringPrintf("user.uid.%u.", getuid());
57 break;
58
59 case multi_process_notification::SystemDomain:
60 break;
61 }
62 return domain_string + bundle_id + "." + name;
63 }
64
65 bool UseLeopardSwitchboardThread() {
66 #if USE_LEOPARD_SWITCHBOARD_THREAD
67 return true;
68 #endif // USE_LEOPARD_SWITCHBOARD_THREAD
69 int32 major_version, minor_version, bugfix_version;
70 base::SysInfo::OperatingSystemVersionNumbers(
71 &major_version, &minor_version, &bugfix_version);
72 return major_version <= 10 && minor_version <= 5;
Mark Mentovai 2011/01/12 22:42:36 This comparison is wrong. It’s probably not wrong
73 }
74
75 } // namespace
76
77 namespace multi_process_notification {
78
79 bool Post(const std::string& name, Domain domain) {
80 std::string notification = AddPrefixToNotification(name, domain);
81 uint32_t status = notify_post(notification.c_str());
82 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
83 return status == NOTIFY_STATUS_OK;
84 }
85
86 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5
87 #error LeopardSwitchboardThread can be removed
88 #endif // MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5
89
90 // LeopardSwitchboardThread exists because the file descriptors returned by
91 // notify_register_file_descriptor can't be monitored using kqueues on 10.5
92 // ( http://openradar.appspot.com/8854692 ) and libevent uses kqueue to watch
93 // file descriptors in IOMessageLoop.
94 // This solution is to have a separate thread that monitors the file descriptor
95 // returned by notify_register_file_descriptor using select, and then to
96 // notify the MessageLoopForIO using a different file descriptor allocated by
97 // socketpair that can be monitored using kqueues in libevent. This thread
98 // only runs on 10.5, as 10.6 kqueues can monitor the notify file descriptors
99 // without any problems.
100
101 // LeopardSwitchboardThread creates three file descriptors:
102 // internal_fd_: which communicates from the thread to external threads
Mark Mentovai 2011/01/12 22:42:36 This is confusing because it introduces “external
103 // external_fd_: which communicates from external threads to the thread
104 // notify_fd_: which is the file descriptor returned from
105 // notify_register_file_descriptor
106 //
107 // The thread itself sits in a select loop waiting on internal_fd_, and
108 // notify_fd_ for input. If it gets ANY input on internal_fd_ it exits.
109 // If it gets input on notify_fd_ it sends the input through to external_fd_.
110 // External_fd_ is monitored by MessageLoopForIO so that the lookup of any
111 // matching listeners in entries_, and the triggering of those listenerrs,
112 // occurs in the MessageLoopForIO thread.
113 //
114 // Lookups are linear right now, and could be optimized if they ever become
115 // a performance issue.
116 class LeopardSwitchboardThread
117 : public base::MessagePumpLibevent::Watcher,
118 public base::SimpleThread,
119 public MessageLoop::DestructionObserver {
120 public:
121 LeopardSwitchboardThread();
122 virtual ~LeopardSwitchboardThread();
123
124 bool Init();
125
126 int AddListener(ListenerImpl* listener, const std::string& notification);
127 bool RemoveListener(ListenerImpl* listener, const std::string& notification);
128
129 bool finished() { return finished_; }
Mark Mentovai 2011/01/12 22:42:36 bool finished() const { return finished_; }
130
131 // SimpleThread overrides
132 virtual void Run();
133
134 // Watcher overrides
135 virtual void OnFileCanReadWithoutBlocking(int fd);
136 virtual void OnFileCanWriteWithoutBlocking(int fd);
137
138 // DestructionObserver overrides
139 virtual void WillDestroyCurrentMessageLoop();
140
141 private:
142 // Describe entries in our entries list for matching tokens to notifications
Mark Mentovai 2011/01/12 22:42:36 “Describe entries in our entries list?” I don’t un
143 // and vice-versa.
144 typedef struct {
Mark Mentovai 2011/01/12 22:42:36 “typedef struct” is so C-ish. This can just be “st
145 int token_;
146 std::string notification_;
147 ListenerImpl* listener_;
148 } SwitchboardEntry;
149
150 enum {
151 kKillThreadMessage = 0xdecea5e
Mark Mentovai 2011/01/12 22:42:36 I like this constant.
152 };
153
154 int internal_fd_;
155 int external_fd_;
156 int notify_fd_;
157 int notify_fd_token_;
158 mutable bool finished_;
159 fd_set fd_set_;
160
161 // all accesses to entries_ must be controlled by entries_lock_.
162 std::vector<SwitchboardEntry> entries_;
163 Lock entries_lock_;
164 base::MessagePumpLibevent::FileDescriptorWatcher watcher_;
165 };
166
167 class ListenerImpl : public base::MessagePumpLibevent::Watcher {
168 public:
169 ListenerImpl(const std::string& name,
170 Domain domain,
171 Listener::Delegate* delegate);
172 virtual ~ListenerImpl();
173
174 bool Start();
175 void OnListen();
176
177 // Watcher overrides
178 virtual void OnFileCanReadWithoutBlocking(int fd);
179 virtual void OnFileCanWriteWithoutBlocking(int fd);
180
181 private:
182 std::string name_;
183 Domain domain_;
184 Listener::Delegate* delegate_;
185 int fd_;
186 int token_;
187 Lock switchboard_lock_;
188 static LeopardSwitchboardThread* g_switchboard_thread_;
189 base::MessagePumpLibevent::FileDescriptorWatcher watcher_;
190
191 DISALLOW_COPY_AND_ASSIGN(ListenerImpl);
192 };
193
194 LeopardSwitchboardThread::LeopardSwitchboardThread()
195 : base::SimpleThread("LeopardSwitchboardThread"), internal_fd_(-1),
196 external_fd_(-1), notify_fd_(-1), notify_fd_token_(-1), finished_(false) {
197 }
198
199 LeopardSwitchboardThread::~LeopardSwitchboardThread() {
200 if (internal_fd_ != -1) {
201 close(internal_fd_);
202 }
203 if (external_fd_ != -1) {
204 close(external_fd_);
205 }
206
207 // Cancelling this notification takes care of closing notify_fd_.
208 uint32_t status = notify_cancel(notify_fd_token_);
Mark Mentovai 2011/01/12 22:42:36 Is this safe if notify_register_file_descriptor in
209 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
210 }
211
212 bool LeopardSwitchboardThread::Init() {
213 // Create a pair of sockets for communicating with the thread
214 // The file descriptors returned from socketpair can be kqueue'd on 10.5.
215 int sockets[2];
216 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
217 PLOG(ERROR) << "socketpair";
218 return false;
219 }
220 internal_fd_ = sockets[0];
221 external_fd_ = sockets[1];
222
223 // Register a bogus notification so that there is single notify_fd_ to
224 // monitor. This runs a small risk of overflowing the notification buffer
225 // if notifications are used heavily (see man 3 notify), however it greatly
226 // simplifies the select loop code as there are only 2 file descriptors
227 // that need to be monitored, and there is no need to add/remove file
228 // descriptors from fd_set_ as listeners are added and removed.
229 // This also keep the total fd usage on 10.5 to three for all
230 // notifications. The 10.6 implementation will use one fd per notification,
231 // but doesn't run the risk of notification buffer overflow. If fds ever
232 // become tight, the 10.6 code could be changed to use only one fd for
233 // all notifications.
234 std::string notification = StringPrintf("LeopardSwitchboardThread.%d",
235 getpid());
236 notification = AddPrefixToNotification(notification, ProfileDomain);
237 uint32_t status = notify_register_file_descriptor(
238 notification.c_str(), &notify_fd_, 0, &notify_fd_token_);
239 if (status != NOTIFY_STATUS_OK) {
240 return false;
241 }
242
243 FD_ZERO(&fd_set_);
244 FD_SET(internal_fd_, &fd_set_);
245 FD_SET(notify_fd_, &fd_set_);
246
247 MessageLoopForIO* io_loop = MessageLoopForIO::current();
248
249 // Watch for destruction of the MessageLoopForIO that this switchboard is
250 // registered on. If it gets destroyed the thread is joined and all current
251 // listeners become invalid. The next time a listener is created (on a new
252 // MessageLoopForIO) a new switchboard thread will be created. This isn't an
253 // issue in the app as there is always one (and only one) MessageLoopForIO,
254 // however unit tests create and kill MessageLoops with impunity and this case
255 // needs to be handled for tests to work.
256 io_loop->AddDestructionObserver(this);
257 return io_loop->WatchFileDescriptor(
258 external_fd_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
259 }
260
261 void LeopardSwitchboardThread::WillDestroyCurrentMessageLoop() {
262 DCHECK_EQ(MessageLoop::current(), MessageLoopForIO::current());
263 watcher_.StopWatchingFileDescriptor();
264
265 // Send the appropriate message to end our thread, and then wait for it
Mark Mentovai 2011/01/12 22:42:36 our -> the
266 // to finish before continuing.
267 int message = kKillThreadMessage;
268 write(external_fd_, &message, sizeof(message));
269 Join();
270 }
271
272 void LeopardSwitchboardThread::Run() {
273 DCHECK(!finished_);
274 int nfds = (internal_fd_ > notify_fd_ ? internal_fd_ : notify_fd_) + 1;
Mark Mentovai 2011/01/12 22:42:36 std::max from <algorithm>?
275 while (1) {
276 fd_set working_set;
277 FD_COPY(&fd_set_, &working_set);
278 int count = HANDLE_EINTR(select(nfds, &working_set, NULL, NULL, NULL));
279 if (count == 0) continue;
Mark Mentovai 2011/01/12 22:42:36 I wonder if you actually expect this to happen wit
280 if (count < 0) {
281 PLOG(ERROR) << "Exiting LeopardSwitchboardThread. Select error.";
Mark Mentovai 2011/01/12 22:42:36 The messages in this function are very verbose. Al
282 break;
283 }
284 if (FD_ISSET(notify_fd_, &working_set)) {
285 int token;
286 int status = HANDLE_EINTR(read(notify_fd_, &token, sizeof(token)));
287 if (status < 0) {
288 PLOG(ERROR) << "Exiting LeopardSwitchboardThread. Read error.";
289 break;
290 } else if (status == 0) {
291 LOG(ERROR) << "Exiting LeopardSwitchboardThread. Notify fd closed.";
292 break;
293 } else if (status != sizeof(token)) {
294 LOG(ERROR) << "Exiting LeopardSwitchboardThread. "
295 << "Read from notify wrong size: " << status;
296 break;
297 } else if (token == notify_fd_token_) {
298 LOG(ERROR) << "Exiting LeopardSwitchboardThread. Invalid token sent: "
Mark Mentovai 2011/01/12 22:42:36 This LOG doesn’t seem to reflect what actually hap
299 << token;
300 }
301 status = HANDLE_EINTR(write(internal_fd_, &token, sizeof(token)));
302 if (status < 0) {
303 PLOG(ERROR) << "write";
Mark Mentovai 2011/01/12 22:42:36 You gave flowery descriptions of the rest of your
304 break;
305 } else if (status == 0) {
306 LOG(ERROR) << "Exiting LeopardSwitchboardThread. External_fd_ closed.";
Mark Mentovai 2011/01/12 22:42:36 Don’t capitalize variable names just because they
307 break;
308 } else if (status != sizeof(token)) {
309 LOG(ERROR) << "Exiting LeopardSwitchboardThread. "
310 << "Write from notify wrong size: " << status;
Mark Mentovai 2011/01/12 22:42:36 “Read from notify” made sense, because I read it a
311 break;
312 }
313 }
314 if (FD_ISSET(internal_fd_, &working_set)) {
315 int value = -1;
Mark Mentovai 2011/01/12 22:42:36 No need to initialize this.
316 int status = HANDLE_EINTR(read(internal_fd_, &value, sizeof(value)));
317 if (status < 0) {
318 PLOG(ERROR) << "Exiting LeopardSwitchboardThread. Read error.";
319 } else if (status == 0) {
320 LOG(ERROR) << "Exiting LeopardSwitchboardThread. Internal_fd_ closed.";
Mark Mentovai 2011/01/12 22:42:36 Don’t capitalize variable names just because they
321 } else if (value != kKillThreadMessage) {
322 LOG(ERROR) << "Exiting LeopardSwitchboardThread. Unknown message sent: "
323 << value;
324 }
325 break;
326 }
327 }
328 finished_ = true;
329 }
330
331 int LeopardSwitchboardThread::AddListener(ListenerImpl* listener,
332 const std::string& notification) {
333 DCHECK(!finished());
334 base::AutoLock autolock(entries_lock_);
335 for (std::vector<SwitchboardEntry>::iterator i = entries_.begin();
336 i < entries_.end(); ++i) {
337 if (i->listener_ == listener && i->notification_ == notification) {
338 LOG(ERROR) << "Listener " << listener
339 << " already registered for '" << notification << "'.";
340 return -1;
Mark Mentovai 2011/01/12 22:42:36 Are tokens just opaque junk? If so, is -1 a possib
341 }
342 }
343 int token = -1;
344 uint32_t status = notify_register_file_descriptor(
345 notification.c_str(), &notify_fd_, NOTIFY_REUSE, &token);
346 if (status != NOTIFY_STATUS_OK) {
347 LOG(ERROR) << "Unable to notify_register_file_descriptor for '"
348 << notification << "' status: " << status;
349 return -1;
350 }
351 SwitchboardEntry entry;
352 entry.token_ = token;
353 entry.notification_ = notification;
354 entry.listener_ = listener;
355 entries_.push_back(entry);
356 return token;
357 }
358
359 bool LeopardSwitchboardThread::RemoveListener(ListenerImpl* listener,
360 const std::string& notification) {
361 DCHECK(!finished());
362 base::AutoLock autolock(entries_lock_);
363 for (std::vector<SwitchboardEntry>::iterator i = entries_.begin();
364 i < entries_.end(); ++i) {
365 if (i->listener_ == listener && i->notification_ == notification) {
366 uint32_t status = notify_cancel(i->token_);
367 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
368 entries_.erase(i);
369 return true;
370 }
371 }
372 LOG(ERROR) << "Unable to remove listener '" << listener
373 << "' for '" << notification << "'.";
374 return false;
375 }
376
377 void LeopardSwitchboardThread::OnFileCanReadWithoutBlocking(int fd) {
378 DCHECK_EQ(fd, external_fd_);
379 int token = 0;
380 if (HANDLE_EINTR(read(external_fd_, &token, sizeof(token))) >= 0) {
Mark Mentovai 2011/01/12 22:42:36 Shouldn’t happen, but this should really check |==
381 // Have to swap to native endianness <http://openradar.appspot.com/8821081>.
382 token = static_cast<int>(ntohl(token));
383 base::AutoLock autolock(entries_lock_);
384 bool found_token = false;
385 for (std::vector<SwitchboardEntry>::iterator i = entries_.begin();
386 i < entries_.end(); ++i) {
387 if (i->token_ == token) {
388 found_token = true;
389 i->listener_->OnListen();
390 }
391 }
392 if (!found_token) {
393 LOG(ERROR) << "read unknown token " << token;
394 }
395 }
396 }
397
398 void LeopardSwitchboardThread::OnFileCanWriteWithoutBlocking(int fd) {
399 NOTREACHED();
400 }
401
402 LeopardSwitchboardThread* ListenerImpl::g_switchboard_thread_ = NULL;
403
404 ListenerImpl::ListenerImpl(
405 const std::string& name, Domain domain, Listener::Delegate* delegate)
406 : name_(name), domain_(domain), delegate_(delegate), fd_(-1), token_(-1) {
407 }
408
409 ListenerImpl::~ListenerImpl() {
410 if (!UseLeopardSwitchboardThread()) {
411 if (token_ != -1) {
412 uint32_t status = notify_cancel(token_);
413 DCHECK_EQ(status, static_cast<uint32_t>(NOTIFY_STATUS_OK));
414 token_ = -1;
415 }
416 }
417 }
418
419 bool ListenerImpl::Start() {
420 DCHECK_EQ(fd_, -1);
421 DCHECK_EQ(token_, -1);
422 std::string notification = AddPrefixToNotification(name_, domain_);
423
424 // On 10.5 use a LeopardSwitchboardThread, on 10.6 use notifications
425 // directly. See comment above the LeopardSwitchboardThread class declaration
426 // for a more detailed explanation.
427 if (UseLeopardSwitchboardThread()) {
428 if (!g_switchboard_thread_ || g_switchboard_thread_->finished()) {
Mark Mentovai 2011/01/12 22:42:36 This is a little ugly. Maybe the switchboard class
429 base::AutoLock autolock(switchboard_lock_);
430 if (g_switchboard_thread_ && g_switchboard_thread_->finished()) {
431 delete g_switchboard_thread_;
Mark Mentovai 2011/01/12 22:42:36 I haven’t thought too much about this, but I’ll as
432 g_switchboard_thread_ = NULL;
433 }
434 if (!g_switchboard_thread_) {
435 g_switchboard_thread_ = new LeopardSwitchboardThread();
436 if (!g_switchboard_thread_->Init()) {
437 delete g_switchboard_thread_;
438 g_switchboard_thread_ = NULL;
439 LOG(ERROR) << "Unable to start switchboard thread";
440 return false;
441 } else {
442 g_switchboard_thread_->Start();
443 }
444 }
445 }
446 return g_switchboard_thread_->AddListener(this, notification);
447 } else {
448 uint32_t status = notify_register_file_descriptor(
449 notification.c_str(), &fd_, 0, &token_);
450 if (status != NOTIFY_STATUS_OK) {
451 LOG(ERROR) << "Unable to notify_register_file_descriptor for '"
452 << notification << "' Status: " << status;
453 return false;
454 }
455
456 MessageLoopForIO* io_loop = MessageLoopForIO::current();
457 return io_loop->WatchFileDescriptor(
458 fd_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
459 }
460 }
461
462 void ListenerImpl::OnFileCanReadWithoutBlocking(int fd) {
463 DCHECK(!UseLeopardSwitchboardThread());
464 DCHECK_EQ(fd, fd_);
465 int token = 0;
466 if (HANDLE_EINTR(read(fd_, &token, sizeof(token))) >= 0) {
467 // Have to swap to native endianness <http://openradar.appspot.com/8821081>.
468 token = static_cast<int>(ntohl(token));
469 if (token == token_) {
470 delegate_->OnNotificationReceived(name_, domain_);
471 } else {
472 LOG(ERROR) << "Unexpected value " << token;
473 }
474 }
475 }
476
477 void ListenerImpl::OnListen() {
478 DCHECK(UseLeopardSwitchboardThread());
479 delegate_->OnNotificationReceived(name_, domain_);
480 }
481
482 void ListenerImpl::OnFileCanWriteWithoutBlocking(int fd) {
483 NOTREACHED();
484 }
485
486 Listener::Listener(
487 const std::string& name, Domain domain, Listener::Delegate* delegate)
488 : impl_(new ListenerImpl(name, domain, delegate)) {
489 }
490
491 Listener::~Listener() {
492 }
493
494 bool Listener::Start() {
495 return impl_->Start();
496 }
497
498 } // namespace multi_process_notification
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