Index: ipc/ipc_send_fds_test.cc |
diff --git a/ipc/ipc_send_fds_test.cc b/ipc/ipc_send_fds_test.cc |
index 4cddc1c2274d2bceaf4955fec93df09d147fbabb..20c3ed5cb5f3ca5015fa4a089fd977293e11f79e 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) |