diff options
author | hubbe@chromium.org <hubbe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-15 00:07:00 +0000 |
---|---|---|
committer | hubbe@chromium.org <hubbe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-15 00:07:00 +0000 |
commit | dc875dc95290a9a5e4245e24e209a0496038f46c (patch) | |
tree | 30af6bb90080078ebe4adb96fe1c2a50df199f94 /ipc | |
parent | 3a678cfe431c44d9397e4616c676578f58ed69ca (diff) | |
download | chromium_src-dc875dc95290a9a5e4245e24e209a0496038f46c.zip chromium_src-dc875dc95290a9a5e4245e24e209a0496038f46c.tar.gz chromium_src-dc875dc95290a9a5e4245e24e209a0496038f46c.tar.bz2 |
Alternative workaround for mac kernel bug.
BUG=298276
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=227999
Review URL: https://codereview.chromium.org/25325002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@228569 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ipc')
-rw-r--r-- | ipc/file_descriptor_set_posix.cc | 10 | ||||
-rw-r--r-- | ipc/file_descriptor_set_posix.h | 3 | ||||
-rw-r--r-- | ipc/ipc_channel.h | 23 | ||||
-rw-r--r-- | ipc/ipc_channel_nacl.cc | 2 | ||||
-rw-r--r-- | ipc/ipc_channel_nacl.h | 2 | ||||
-rw-r--r-- | ipc/ipc_channel_posix.cc | 136 | ||||
-rw-r--r-- | ipc/ipc_channel_posix.h | 12 | ||||
-rw-r--r-- | ipc/ipc_channel_reader.cc | 12 | ||||
-rw-r--r-- | ipc/ipc_channel_reader.h | 12 | ||||
-rw-r--r-- | ipc/ipc_channel_win.cc | 3 | ||||
-rw-r--r-- | ipc/ipc_channel_win.h | 2 | ||||
-rw-r--r-- | ipc/ipc_send_fds_test.cc | 255 |
12 files changed, 396 insertions, 76 deletions
diff --git a/ipc/file_descriptor_set_posix.cc b/ipc/file_descriptor_set_posix.cc index 584efec..fc15c2d 100644 --- a/ipc/file_descriptor_set_posix.cc +++ b/ipc/file_descriptor_set_posix.cc @@ -126,6 +126,16 @@ void FileDescriptorSet::CommitAll() { consumed_descriptor_highwater_ = 0; } +void FileDescriptorSet::ReleaseFDsToClose(std::vector<int>* fds) { + for (std::vector<base::FileDescriptor>::iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->auto_close) + fds->push_back(i->fd); + } + descriptors_.clear(); + consumed_descriptor_highwater_ = 0; +} + void FileDescriptorSet::SetDescriptors(const int* buffer, unsigned count) { DCHECK(count <= kMaxDescriptorsPerMessage); DCHECK_EQ(descriptors_.size(), 0u); diff --git a/ipc/file_descriptor_set_posix.h b/ipc/file_descriptor_set_posix.h index f9c6033..b413b4a 100644 --- a/ipc/file_descriptor_set_posix.h +++ b/ipc/file_descriptor_set_posix.h @@ -77,6 +77,9 @@ class IPC_EXPORT FileDescriptorSet // Returns true if any contained file descriptors appear to be handles to a // directory. bool ContainsDirectoryDescriptor() const; + // Fetch all filedescriptors with the "auto close" property. + // Used instead of CommitAll() when closing must be handled manually. + void ReleaseFDsToClose(std::vector<int>* fds); // --------------------------------------------------------------------------- diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h index f65a62b..7e09a80 100644 --- a/ipc/ipc_channel.h +++ b/ipc/ipc_channel.h @@ -75,15 +75,22 @@ class IPC_EXPORT Channel : public Sender { #endif }; - // The Hello message is internal to the Channel class. It is sent - // by the peer when the channel is connected. The message contains - // just the process id (pid). The message has a special routing_id - // (MSG_ROUTING_NONE) and type (HELLO_MESSAGE_TYPE). + // Messages internal to the IPC implementation are defined here. + // Uses Maximum value of message type (uint16), to avoid conflicting + // with normal message types, which are enumeration constants starting from 0. enum { - HELLO_MESSAGE_TYPE = kuint16max // Maximum value of message type (uint16), - // to avoid conflicting with normal - // message types, which are enumeration - // constants starting from 0. + // The Hello message is sent by the peer when the channel is connected. + // The message contains just the process id (pid). + // The message has a special routing_id (MSG_ROUTING_NONE) + // and type (HELLO_MESSAGE_TYPE). + HELLO_MESSAGE_TYPE = kuint16max, + // The CLOSE_FD_MESSAGE_TYPE is used in the IPC class to + // work around a bug in sendmsg() on Mac. When an FD is sent + // over the socket, a CLOSE_FD_MESSAGE is sent with hops = 2. + // The client will return the message with hops = 1, *after* it + // has received the message that contains the FD. When we + // receive it again on the sender side, we close the FD. + CLOSE_FD_MESSAGE_TYPE = HELLO_MESSAGE_TYPE - 1 }; // The maximum message size in bytes. Attempting to receive a message of this diff --git a/ipc/ipc_channel_nacl.cc b/ipc/ipc_channel_nacl.cc index 860815e..1ecd571 100644 --- a/ipc/ipc_channel_nacl.cc +++ b/ipc/ipc_channel_nacl.cc @@ -344,7 +344,7 @@ bool Channel::ChannelImpl::DidEmptyInputBuffers() { return input_fds_.empty(); } -void Channel::ChannelImpl::HandleHelloMessage(const Message& msg) { +void Channel::ChannelImpl::HandleInternalMessage(const Message& msg) { // The trusted side IPC::Channel should handle the "hello" handshake; we // should not receive the "Hello" message. NOTREACHED(); diff --git a/ipc/ipc_channel_nacl.h b/ipc/ipc_channel_nacl.h index 7c8960f..a21730e 100644 --- a/ipc/ipc_channel_nacl.h +++ b/ipc/ipc_channel_nacl.h @@ -60,7 +60,7 @@ class Channel::ChannelImpl : public internal::ChannelReader { int* bytes_read) OVERRIDE; virtual bool WillDispatchInputMessage(Message* msg) OVERRIDE; virtual bool DidEmptyInputBuffers() OVERRIDE; - virtual void HandleHelloMessage(const Message& msg) OVERRIDE; + virtual void HandleInternalMessage(const Message& msg) OVERRIDE; Mode mode_; bool waiting_connect_; diff --git a/ipc/ipc_channel_posix.cc b/ipc/ipc_channel_posix.cc index 98a7cd8..a74178a 100644 --- a/ipc/ipc_channel_posix.cc +++ b/ipc/ipc_channel_posix.cc @@ -347,6 +347,27 @@ bool Channel::ChannelImpl::Connect() { return did_connect; } +void Channel::ChannelImpl::CloseFileDescriptors(Message* msg) { +#if defined(OS_MACOSX) + // There is a bug on OSX which makes it dangerous to close + // a file descriptor while it is in transit. So instead we + // store the file descriptor in a set and send a message to + // the recipient, which is queued AFTER the message that + // sent the FD. The recipient will reply to the message, + // letting us know that it is now safe to close the file + // descriptor. For more information, see: + // http://crbug.com/298276 + std::vector<int> to_close; + msg->file_descriptor_set()->ReleaseFDsToClose(&to_close); + for (size_t i = 0; i < to_close.size(); i++) { + fds_to_close_.insert(to_close[i]); + QueueCloseFDMessage(to_close[i], 2); + } +#else + msg->file_descriptor_set()->CommitAll(); +#endif +} + bool Channel::ChannelImpl::ProcessOutgoingMessages() { DCHECK(!waiting_connect_); // Why are we trying to send messages if there's // no connection? @@ -419,7 +440,7 @@ bool Channel::ChannelImpl::ProcessOutgoingMessages() { msgh.msg_iov = &iov; msgh.msg_controllen = 0; if (bytes_written > 0) { - msg->file_descriptor_set()->CommitAll(); + CloseFileDescriptors(msg); } } #endif // IPC_USES_READWRITE @@ -440,7 +461,7 @@ bool Channel::ChannelImpl::ProcessOutgoingMessages() { } } if (bytes_written > 0) - msg->file_descriptor_set()->CommitAll(); + CloseFileDescriptors(msg); if (bytes_written < 0 && !SocketWriteErrorIsRecoverable()) { #if defined(OS_MACOSX) @@ -575,6 +596,17 @@ void Channel::ChannelImpl::ResetToAcceptingConnectionState() { // Close any outstanding, received file descriptors. ClearInputFDs(); + +#if defined(OS_MACOSX) + // Clear any outstanding, sent file descriptors. + for (std::set<int>::iterator i = fds_to_close_.begin(); + i != fds_to_close_.end(); + ++i) { + if (HANDLE_EINTR(close(*i)) < 0) + PLOG(ERROR) << "close"; + } + fds_to_close_.clear(); +#endif } // static @@ -592,7 +624,6 @@ void Channel::ChannelImpl::SetGlobalPid(int pid) { // Called by libevent when we can read from the pipe without blocking. void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { - bool send_server_hello_msg = false; if (fd == server_listen_pipe_) { int new_pipe = 0; if (!ServerAcceptConnection(server_listen_pipe_, &new_pipe) || @@ -631,18 +662,16 @@ void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { if (!AcceptConnection()) { NOTREACHED() << "AcceptConnection should not fail on server"; } - send_server_hello_msg = true; waiting_connect_ = false; } else if (fd == pipe_) { if (waiting_connect_ && (mode_ & MODE_SERVER_FLAG)) { - send_server_hello_msg = true; waiting_connect_ = false; } if (!ProcessIncomingMessages()) { // ClosePipeOnError may delete this object, so we mustn't call // ProcessOutgoingMessages. - send_server_hello_msg = false; ClosePipeOnError(); + return; } } else { NOTREACHED() << "Unknown pipe " << fd; @@ -651,9 +680,11 @@ void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { // If we're a server and handshaking, then we want to make sure that we // only send our handshake message after we've processed the client's. // This gives us a chance to kill the client if the incoming handshake - // is invalid. - if (send_server_hello_msg) { - ProcessOutgoingMessages(); + // is invalid. This also flushes any closefd messagse. + if (!is_blocked_on_write_) { + if (!ProcessOutgoingMessages()) { + ClosePipeOnError(); + } } } @@ -902,29 +933,80 @@ void Channel::ChannelImpl::ClearInputFDs() { input_fds_.clear(); } -void Channel::ChannelImpl::HandleHelloMessage(const Message& msg) { +void Channel::ChannelImpl::QueueCloseFDMessage(int fd, int hops) { + switch (hops) { + case 1: + case 2: { + // Create the message + scoped_ptr<Message> msg(new Message(MSG_ROUTING_NONE, + CLOSE_FD_MESSAGE_TYPE, + IPC::Message::PRIORITY_NORMAL)); + if (!msg->WriteInt(hops - 1) || !msg->WriteInt(fd)) { + NOTREACHED() << "Unable to pickle close fd."; + } + // Send(msg.release()); + output_queue_.push(msg.release()); + break; + } + + default: + NOTREACHED(); + break; + } +} + +void Channel::ChannelImpl::HandleInternalMessage(const Message& msg) { // The Hello message contains only the process id. PickleIterator iter(msg); - int pid; - if (!msg.ReadInt(&iter, &pid)) - NOTREACHED(); -#if defined(IPC_USES_READWRITE) - if (mode_ & MODE_SERVER_FLAG) { - // With IPC_USES_READWRITE, the Hello message from the client to the - // server also contains the fd_pipe_, which will be used for all - // subsequent file descriptor passing. - DCHECK_EQ(msg.file_descriptor_set()->size(), 1U); - base::FileDescriptor descriptor; - if (!msg.ReadFileDescriptor(&iter, &descriptor)) { + switch (msg.type()) { + default: NOTREACHED(); - } - fd_pipe_ = descriptor.fd; - CHECK(descriptor.auto_close); - } + break; + + case Channel::HELLO_MESSAGE_TYPE: + int pid; + if (!msg.ReadInt(&iter, &pid)) + NOTREACHED(); + +#if defined(IPC_USES_READWRITE) + if (mode_ & MODE_SERVER_FLAG) { + // With IPC_USES_READWRITE, the Hello message from the client to the + // server also contains the fd_pipe_, which will be used for all + // subsequent file descriptor passing. + DCHECK_EQ(msg.file_descriptor_set()->size(), 1U); + base::FileDescriptor descriptor; + if (!msg.ReadFileDescriptor(&iter, &descriptor)) { + NOTREACHED(); + } + fd_pipe_ = descriptor.fd; + CHECK(descriptor.auto_close); + } #endif // IPC_USES_READWRITE - peer_pid_ = pid; - listener()->OnChannelConnected(pid); + peer_pid_ = pid; + listener()->OnChannelConnected(pid); + break; + +#if defined(OS_MACOSX) + case Channel::CLOSE_FD_MESSAGE_TYPE: + int fd, hops; + if (!msg.ReadInt(&iter, &hops)) + NOTREACHED(); + if (!msg.ReadInt(&iter, &fd)) + NOTREACHED(); + if (hops == 0) { + if (fds_to_close_.erase(fd) > 0) { + if (HANDLE_EINTR(close(fd)) < 0) + PLOG(ERROR) << "close"; + } else { + NOTREACHED(); + } + } else { + QueueCloseFDMessage(fd, hops); + } + break; +#endif + } } void Channel::ChannelImpl::Close() { diff --git a/ipc/ipc_channel_posix.h b/ipc/ipc_channel_posix.h index 645a130..1e587c1 100644 --- a/ipc/ipc_channel_posix.h +++ b/ipc/ipc_channel_posix.h @@ -10,6 +10,7 @@ #include <sys/socket.h> // for CMSG macros #include <queue> +#include <set> #include <string> #include <vector> @@ -80,6 +81,8 @@ class Channel::ChannelImpl : public internal::ChannelReader, void ClosePipeOnError(); int GetHelloMessageProcId(); void QueueHelloMessage(); + void CloseFileDescriptors(Message* msg); + void QueueCloseFDMessage(int fd, int hops); // ChannelReader implementation. virtual ReadState ReadData(char* buffer, @@ -87,7 +90,7 @@ class Channel::ChannelImpl : public internal::ChannelReader, int* bytes_read) OVERRIDE; virtual bool WillDispatchInputMessage(Message* msg) OVERRIDE; virtual bool DidEmptyInputBuffers() OVERRIDE; - virtual void HandleHelloMessage(const Message& msg) OVERRIDE; + virtual void HandleInternalMessage(const Message& msg) OVERRIDE; #if defined(IPC_USES_READWRITE) // Reads the next message from the fd_pipe_ and appends them to the @@ -184,6 +187,13 @@ class Channel::ChannelImpl : public internal::ChannelReader, // implementation! std::vector<int> input_fds_; +#if defined(OS_MACOSX) + // On OSX, sent FDs must not be closed until we get an ack. + // Keep track of sent FDs here to make sure the remote is not + // trying to bamboozle us. + std::set<int> fds_to_close_; +#endif + // True if we are responsible for unlinking the unix domain socket file. bool must_unlink_; diff --git a/ipc/ipc_channel_reader.cc b/ipc/ipc_channel_reader.cc index 9055deb..2ee7449 100644 --- a/ipc/ipc_channel_reader.cc +++ b/ipc/ipc_channel_reader.cc @@ -38,9 +38,15 @@ bool ChannelReader::AsyncReadComplete(int bytes_read) { return DispatchInputData(input_buf_, bytes_read); } +bool ChannelReader::IsInternalMessage(const Message& m) const { + return m.routing_id() == MSG_ROUTING_NONE && + m.type() >= Channel::CLOSE_FD_MESSAGE_TYPE && + m.type() <= Channel::HELLO_MESSAGE_TYPE; +} + bool ChannelReader::IsHelloMessage(const Message& m) const { return m.routing_id() == MSG_ROUTING_NONE && - m.type() == Channel::HELLO_MESSAGE_TYPE; + m.type() == Channel::HELLO_MESSAGE_TYPE; } bool ChannelReader::DispatchInputData(const char* input_data, @@ -84,8 +90,8 @@ bool ChannelReader::DispatchInputData(const char* input_data, "line", IPC_MESSAGE_ID_LINE(m.type())); #endif m.TraceMessageEnd(); - if (IsHelloMessage(m)) - HandleHelloMessage(m); + if (IsInternalMessage(m)) + HandleInternalMessage(m); else listener_->OnMessageReceived(m); p = message_tail; diff --git a/ipc/ipc_channel_reader.h b/ipc/ipc_channel_reader.h index 9c398bd..1303846 100644 --- a/ipc/ipc_channel_reader.h +++ b/ipc/ipc_channel_reader.h @@ -42,8 +42,12 @@ class ChannelReader { // data. See ReadData for more. bool AsyncReadComplete(int bytes_read); - // Returns true if the given message is the "hello" message sent on channel - // set-up. + // Returns true if the given message is internal to the IPC implementation, + // like the "hello" message sent on channel set-up. + bool IsInternalMessage(const Message& m) const; + + // Returns true if the given message is an Hello message + // sent on channel set-up. bool IsHelloMessage(const Message& m) const; protected: @@ -76,8 +80,8 @@ class ChannelReader { // though there could be more data ready to be read from the OS. virtual bool DidEmptyInputBuffers() = 0; - // Handles the first message sent over the pipe which contains setup info. - virtual void HandleHelloMessage(const Message& msg) = 0; + // Handles internal messages, like the hello message sent on channel startup. + virtual void HandleInternalMessage(const Message& msg) = 0; private: // Takes the given data received from the IPC channel and dispatches any diff --git a/ipc/ipc_channel_win.cc b/ipc/ipc_channel_win.cc index 6d7cfe8..8c08500 100644 --- a/ipc/ipc_channel_win.cc +++ b/ipc/ipc_channel_win.cc @@ -152,7 +152,8 @@ bool Channel::ChannelImpl::WillDispatchInputMessage(Message* msg) { return true; } -void Channel::ChannelImpl::HandleHelloMessage(const Message& msg) { +void Channel::ChannelImpl::HandleInternalMessage(const Message& msg) { + DCHECK_EQ(msg.type(), static_cast<unsigned>(Channel::HELLO_MESSAGE_TYPE)); // The hello message contains one parameter containing the PID. PickleIterator it(msg); int32 claimed_pid; diff --git a/ipc/ipc_channel_win.h b/ipc/ipc_channel_win.h index 711f57f..a544f8b 100644 --- a/ipc/ipc_channel_win.h +++ b/ipc/ipc_channel_win.h @@ -41,7 +41,7 @@ class Channel::ChannelImpl : public internal::ChannelReader, int* bytes_read) OVERRIDE; virtual bool WillDispatchInputMessage(Message* msg) OVERRIDE; bool DidEmptyInputBuffers() OVERRIDE; - virtual void HandleHelloMessage(const Message& msg) OVERRIDE; + virtual void HandleInternalMessage(const Message& msg) OVERRIDE; static const string16 PipeName(const std::string& channel_id, int32* secret); diff --git a/ipc/ipc_send_fds_test.cc b/ipc/ipc_send_fds_test.cc index 4cddc1c..20c3ed5 100644 --- a/ipc/ipc_send_fds_test.cc +++ b/ipc/ipc_send_fds_test.cc @@ -11,13 +11,18 @@ extern "C" { } #endif #include <fcntl.h> +#include <sys/socket.h> #include <sys/stat.h> #include <unistd.h> +#include <queue> + +#include "base/callback.h" #include "base/file_descriptor_posix.h" #include "base/message_loop/message_loop.h" #include "base/pickle.h" #include "base/posix/eintr_wrapper.h" +#include "base/synchronization/waitable_event.h" #include "ipc/ipc_message_utils.h" #include "ipc/ipc_test_base.h" @@ -26,57 +31,67 @@ namespace { const unsigned kNumFDsToSend = 20; const char* kDevZeroPath = "/dev/zero"; -static void VerifyAndCloseDescriptor(int fd, ino_t inode_num) { - // Check that we can read from the FD. - char buf; - ssize_t amt_read = read(fd, &buf, 1); - ASSERT_EQ(amt_read, 1); - ASSERT_EQ(buf, 0); // /dev/zero always reads 0 bytes. - - struct stat st; - ASSERT_EQ(fstat(fd, &st), 0); - - ASSERT_EQ(close(fd), 0); - - // Compare inode numbers to check that the file sent over the wire is actually - // the one expected. - ASSERT_EQ(inode_num, st.st_ino); -} - -class MyChannelDescriptorListener : public IPC::Listener { +class MyChannelDescriptorListenerBase : public IPC::Listener { public: - explicit MyChannelDescriptorListener(ino_t expected_inode_num) - : expected_inode_num_(expected_inode_num), - num_fds_received_(0) {} - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { PickleIterator iter(message); - ++num_fds_received_; base::FileDescriptor descriptor; IPC::ParamTraits<base::FileDescriptor>::Read(&message, &iter, &descriptor); - VerifyAndCloseDescriptor(descriptor.fd, expected_inode_num_); - if (num_fds_received_ == kNumFDsToSend) - base::MessageLoop::current()->Quit(); - + HandleFD(descriptor.fd); return true; } - virtual void OnChannelError() OVERRIDE { - base::MessageLoop::current()->Quit(); + protected: + virtual void HandleFD(int fd) = 0; +}; + +class MyChannelDescriptorListener : public MyChannelDescriptorListenerBase { + public: + explicit MyChannelDescriptorListener(ino_t expected_inode_num) + : MyChannelDescriptorListenerBase(), + expected_inode_num_(expected_inode_num), + num_fds_received_(0) { } bool GotExpectedNumberOfDescriptors() const { return num_fds_received_ == kNumFDsToSend; } + virtual void OnChannelError() OVERRIDE { + base::MessageLoop::current()->Quit(); + } + + protected: + virtual void HandleFD(int fd) OVERRIDE { + // Check that we can read from the FD. + char buf; + ssize_t amt_read = read(fd, &buf, 1); + ASSERT_EQ(amt_read, 1); + ASSERT_EQ(buf, 0); // /dev/zero always reads 0 bytes. + + struct stat st; + ASSERT_EQ(fstat(fd, &st), 0); + + ASSERT_EQ(close(fd), 0); + + // Compare inode numbers to check that the file sent over the wire is + // actually the one expected. + ASSERT_EQ(expected_inode_num_, st.st_ino); + + ++num_fds_received_; + if (num_fds_received_ == kNumFDsToSend) + base::MessageLoop::current()->Quit(); + } + private: ino_t expected_inode_num_; unsigned num_fds_received_; }; + class IPCSendFdsTest : public IPCTestBase { protected: void RunServer() { @@ -178,6 +193,188 @@ MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsSandboxedClient) { } #endif // defined(OS_MACOSX) + +class MyCBListener : public MyChannelDescriptorListenerBase { + public: + MyCBListener(base::Callback<void(int)> cb, int fds_to_send) + : MyChannelDescriptorListenerBase(), + cb_(cb) { + } + + protected: + virtual void HandleFD(int fd) OVERRIDE { + cb_.Run(fd); + } + private: + base::Callback<void(int)> cb_; +}; + +std::pair<int, int> make_socket_pair() { + int pipe_fds[2]; + CHECK_EQ(0, HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds))); + return std::pair<int, int>(pipe_fds[0], pipe_fds[1]); +} + +static void null_cb(int unused_fd) { + NOTREACHED(); +} + +class PipeChannelHelper { + public: + PipeChannelHelper(base::Thread* in_thread, + base::Thread* out_thread, + base::Callback<void(int)> cb, + int fds_to_send) : + in_thread_(in_thread), + out_thread_(out_thread), + cb_listener_(cb, fds_to_send), + null_listener_(base::Bind(&null_cb), 0) { + } + + void Init() { + IPC::ChannelHandle in_handle("IN"); + in.reset(new IPC::Channel(in_handle, + IPC::Channel::MODE_SERVER, + &null_listener_)); + base::FileDescriptor out_fd(in->TakeClientFileDescriptor(), false); + IPC::ChannelHandle out_handle("OUT", out_fd); + out.reset(new IPC::Channel(out_handle, + IPC::Channel::MODE_CLIENT, + &cb_listener_)); + // PostTask the connect calls to make sure the callbacks happens + // on the right threads. + in_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PipeChannelHelper::Connect, in.get())); + out_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PipeChannelHelper::Connect, out.get())); + } + + static void DestroyChannel(scoped_ptr<IPC::Channel> *c, + base::WaitableEvent *event) { + c->reset(0); + event->Signal(); + } + + ~PipeChannelHelper() { + base::WaitableEvent a(true, false); + base::WaitableEvent b(true, false); + in_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PipeChannelHelper::DestroyChannel, &in, &a)); + out_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PipeChannelHelper::DestroyChannel, &out, &b)); + a.Wait(); + b.Wait(); + } + + static void Connect(IPC::Channel *channel) { + EXPECT_TRUE(channel->Connect()); + } + + void Send(int fd) { + CHECK_EQ(base::MessageLoop::current(), in_thread_->message_loop()); + + ASSERT_GE(fd, 0); + base::FileDescriptor descriptor(fd, true); + + IPC::Message* message = + new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL); + IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor); + ASSERT_TRUE(in->Send(message)); + } + + private: + scoped_ptr<IPC::Channel> in, out; + base::Thread* in_thread_; + base::Thread* out_thread_; + MyCBListener cb_listener_; + MyCBListener null_listener_; +}; + +// This test is meant to provoke a kernel bug on OSX, and to prove +// that the workaround for it is working. It sets up two pipes and three +// threads, the producer thread creates socketpairs and sends one of the fds +// over pipe1 to the middleman thread. The middleman thread simply takes the fd +// sends it over pipe2 to the consumer thread. The consumer thread writes a byte +// to each fd it receives and then closes the pipe. The producer thread reads +// the bytes back from each pair of pipes and make sure that everything worked. +// This feedback mechanism makes sure that not too many file descriptors are +// in flight at the same time. For more info on the bug, see: +// http://crbug.com/298276 +class IPCMultiSendingFdsTest : public testing::Test { + public: + IPCMultiSendingFdsTest() : received_(true, false) {} + + void Producer(PipeChannelHelper* dest, + base::Thread* t, + int pipes_to_send) { + for (int i = 0; i < pipes_to_send; i++) { + received_.Reset(); + std::pair<int, int> pipe_fds = make_socket_pair(); + t->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PipeChannelHelper::Send, + base::Unretained(dest), + pipe_fds.second)); + char tmp = 'x'; + CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds.first, &tmp, 1))); + CHECK_EQ(0, HANDLE_EINTR(close(pipe_fds.first))); + received_.Wait(); + } + } + + void ConsumerHandleFD(int fd) { + char tmp = 'y'; + CHECK_EQ(1, HANDLE_EINTR(read(fd, &tmp, 1))); + CHECK_EQ(tmp, 'x'); + CHECK_EQ(0, HANDLE_EINTR(close(fd))); + received_.Signal(); + } + + base::Thread* CreateThread(const char* name) { + base::Thread* ret = new base::Thread(name); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + ret->StartWithOptions(options); + return ret; + } + + void Run() { + // On my mac, this test fails roughly 35 times per + // million sends with low load, but much more with high load. + // Unless the workaround is in place. With 10000 sends, we + // should see at least a 3% failure rate. + const int pipes_to_send = 20000; + scoped_ptr<base::Thread> producer(CreateThread("producer")); + scoped_ptr<base::Thread> middleman(CreateThread("middleman")); + scoped_ptr<base::Thread> consumer(CreateThread("consumer")); + PipeChannelHelper pipe1( + middleman.get(), + consumer.get(), + base::Bind(&IPCMultiSendingFdsTest::ConsumerHandleFD, + base::Unretained(this)), + pipes_to_send); + PipeChannelHelper pipe2( + producer.get(), + middleman.get(), + base::Bind(&PipeChannelHelper::Send, base::Unretained(&pipe1)), + pipes_to_send); + pipe1.Init(); + pipe2.Init(); + Producer(&pipe2, producer.get(), pipes_to_send); + } + + private: + base::WaitableEvent received_; +}; + +TEST_F(IPCMultiSendingFdsTest, StressTest) { + Run(); +} + } // namespace #endif // defined(OS_POSIX) |