Index: ipc/ipc_sync_channel_unittest.cc |
diff --git a/ipc/ipc_sync_channel_unittest.cc b/ipc/ipc_sync_channel_unittest.cc |
index e7903f48abdf27ab66e089f1c552c7ae0cca5989..7ffbf79f6fe21b806ee79eabd1eeeb772a671ab9 100644 |
--- a/ipc/ipc_sync_channel_unittest.cc |
+++ b/ipc/ipc_sync_channel_unittest.cc |
@@ -1334,4 +1334,262 @@ TEST_F(IPCSyncChannelTest, RestrictedDispatch) { |
EXPECT_EQ(3, success); |
} |
+//----------------------------------------------------------------------------- |
+ |
+// This test case inspired by crbug.com/108491 |
+// We create two servers that use the same ListenerThread but have |
+// SetRestrictDispatchToSameChannel set to true. |
+// We create clients, then use some specific WaitableEvent wait/signalling to |
+// ensure that messages get dispatched in a way that causes a deadlock due to |
+// a nested dispatch and an eligible message in a higher-level dispatch's |
+// delayed_queue. Specifically, we start with client1 about so send an |
piman
2012/01/10 20:52:14
typo: "about so send" -> "about to send"
|
+// unblocking message to server1, while the shared listener thread for the |
+// servers server1 and server2 is about to send a non-unblocking message to |
+// client1. At the same time, client2 will be about to send an unblocking |
+// message to server2. Server1 will handle the client1->server1 message by |
+// telling server2 to send a non-unblocking message to client2. |
+// What should happen is that the send to server2 should find the pending, |
+// same-context client2->server2 message to dispatch, causing client2 to |
+// unblock then handle the server2->client2 message, so that the shared |
+// servers' listener thread can then respond to the client1->server1 message. |
+// Then client1 can handle the non-unblocking server1->client1 message. |
+// The old code would end up in a state where the server2->client2 message is |
+// sent, but the client2->server2 message (which is eligible for dispatch, and |
+// which is what client2 is waiting for) is stashed in a local delayed_queue |
+// that has server1's channel context, causing a deadlock. |
+// WaitableEvents in the events array are used to: |
+// event 0: indicate to client1 that server listener is in OnDoServerTask |
+// event 1: indicate to client1 that client2 listener is in OnDoClient2Task |
+// event 2: indicate to server1 that client2 listener is in OnDoClient2Task |
+// event 3: indicate to client2 that server listener is in OnDoServerTask |
+ |
+namespace { |
+ |
+class RestrictedDispatchDeadlockServer : public Worker { |
+ public: |
+ RestrictedDispatchDeadlockServer(int server_num, |
+ WaitableEvent* server_ready_event, |
+ WaitableEvent** events, |
+ RestrictedDispatchDeadlockServer* peer) |
+ : Worker(server_num == 1 ? "channel1" : "channel2", Channel::MODE_SERVER), |
+ server_num_(server_num), |
+ server_ready_event_(server_ready_event), |
+ events_(events), |
+ peer_(peer), |
+ client_kicked_(false) { } |
+ |
+ void OnDoServerTask() { |
+ events_[3]->Signal(); |
+ events_[2]->Wait(); |
+ events_[0]->Signal(); |
+ SendMessageToClient(); |
+ } |
+ |
+ void Run() { |
+ channel()->SetRestrictDispatchToSameChannel(true); |
+ server_ready_event_->Signal(); |
+ } |
+ |
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); } |
+ |
+ private: |
+ bool OnMessageReceived(const Message& message) { |
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockServer, message) |
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) |
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done) |
+ IPC_END_MESSAGE_MAP() |
+ return true; |
+ } |
+ |
+ void OnNoArgs() { |
+ if (server_num_ == 1) { |
+ DCHECK(peer_ != NULL); |
+ peer_->SendMessageToClient(); |
+ } |
+ } |
+ |
+ void SendMessageToClient() { |
+ Message* msg = new SyncChannelTestMsg_NoArgs; |
+ msg->set_unblock(false); |
+ DCHECK(!msg->should_unblock()); |
+ Send(msg); |
+ } |
+ |
+ int server_num_; |
+ WaitableEvent* server_ready_event_; |
+ WaitableEvent** events_; |
+ RestrictedDispatchDeadlockServer* peer_; |
+ bool client_kicked_; |
+}; |
+ |
+class RestrictedDispatchDeadlockClient2 : public Worker { |
+ public: |
+ RestrictedDispatchDeadlockClient2(RestrictedDispatchDeadlockServer* server, |
+ WaitableEvent* server_ready_event, |
+ WaitableEvent** events) |
+ : Worker("channel2", Channel::MODE_CLIENT), |
+ server_(server), |
+ server_ready_event_(server_ready_event), |
+ events_(events), |
+ received_msg_(false), |
+ received_noarg_reply_(false), |
+ done_issued_(false) {} |
+ |
+ void Run() { |
+ server_ready_event_->Wait(); |
+ } |
+ |
+ void OnDoClient2Task() { |
+ events_[3]->Wait(); |
+ events_[1]->Signal(); |
+ events_[2]->Signal(); |
+ DCHECK(received_msg_ == false); |
+ |
+ Message* message = new SyncChannelTestMsg_NoArgs; |
+ message->set_unblock(true); |
+ Send(message); |
+ received_noarg_reply_ = true; |
+ } |
+ |
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); } |
+ private: |
+ bool OnMessageReceived(const Message& message) { |
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockClient2, message) |
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) |
+ IPC_END_MESSAGE_MAP() |
+ return true; |
+ } |
+ |
+ void OnNoArgs() { |
+ received_msg_ = true; |
+ PossiblyDone(); |
+ } |
+ |
+ void PossiblyDone() { |
+ if (received_noarg_reply_ && received_msg_) { |
+ DCHECK(done_issued_ == false); |
+ done_issued_ = true; |
+ Send(new SyncChannelTestMsg_Done); |
+ Done(); |
+ } |
+ } |
+ |
+ RestrictedDispatchDeadlockServer* server_; |
+ WaitableEvent* server_ready_event_; |
+ WaitableEvent** events_; |
+ bool received_msg_; |
+ bool received_noarg_reply_; |
+ bool done_issued_; |
+}; |
+ |
+class RestrictedDispatchDeadlockClient1 : public Worker { |
+ public: |
+ RestrictedDispatchDeadlockClient1(RestrictedDispatchDeadlockServer* server, |
+ RestrictedDispatchDeadlockClient2* peer, |
+ WaitableEvent* server_ready_event, |
+ WaitableEvent** events) |
+ : Worker("channel1", Channel::MODE_CLIENT), |
+ server_(server), |
+ peer_(peer), |
+ server_ready_event_(server_ready_event), |
+ events_(events), |
+ received_msg_(false), |
+ received_noarg_reply_(false), |
+ done_issued_(false) {} |
+ |
+ void Run() { |
+ server_ready_event_->Wait(); |
+ server_->ListenerThread()->message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&RestrictedDispatchDeadlockServer::OnDoServerTask, server_)); |
+ peer_->ListenerThread()->message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&RestrictedDispatchDeadlockClient2::OnDoClient2Task, peer_)); |
+ events_[0]->Wait(); |
+ events_[1]->Wait(); |
+ DCHECK(received_msg_ == false); |
+ |
+ Message* message = new SyncChannelTestMsg_NoArgs; |
+ message->set_unblock(true); |
+ Send(message); |
+ received_noarg_reply_ = true; |
+ PossiblyDone(); |
+ } |
+ |
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); } |
+ private: |
+ bool OnMessageReceived(const Message& message) { |
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockClient1, message) |
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) |
+ IPC_END_MESSAGE_MAP() |
+ return true; |
+ } |
+ |
+ void OnNoArgs() { |
+ received_msg_ = true; |
+ PossiblyDone(); |
+ } |
+ |
+ void PossiblyDone() { |
+ if (received_noarg_reply_ && received_msg_) { |
+ DCHECK(done_issued_ == false); |
+ done_issued_ = true; |
+ Send(new SyncChannelTestMsg_Done); |
+ Done(); |
+ } |
+ } |
+ |
+ RestrictedDispatchDeadlockServer* server_; |
+ RestrictedDispatchDeadlockClient2* peer_; |
+ WaitableEvent* server_ready_event_; |
+ WaitableEvent** events_; |
+ bool received_msg_; |
+ bool received_noarg_reply_; |
+ bool done_issued_; |
+}; |
+ |
+} // namespace |
+ |
+TEST_F(IPCSyncChannelTest, RestrictedDispatchDeadlock) { |
+ std::vector<Worker*> workers; |
+ |
+ // A shared worker thread so that server1 and server2 run on one thread. |
+ base::Thread worker_thread("RestrictedDispatchDeadlock"); |
+ ASSERT_TRUE(worker_thread.Start()); |
+ |
+ WaitableEvent server1_ready(false, false); |
+ WaitableEvent server2_ready(false, false); |
+ |
+ WaitableEvent event0(false, false); |
+ WaitableEvent event1(false, false); |
+ WaitableEvent event2(false, false); |
+ WaitableEvent event3(false, false); |
+ WaitableEvent* events[4] = {&event0, &event1, &event2, &event3}; |
+ |
+ RestrictedDispatchDeadlockServer* server1; |
+ RestrictedDispatchDeadlockServer* server2; |
+ RestrictedDispatchDeadlockClient1* client1; |
+ RestrictedDispatchDeadlockClient2* client2; |
+ |
+ server2 = new RestrictedDispatchDeadlockServer(2, &server2_ready, events, |
+ NULL); |
+ server2->OverrideThread(&worker_thread); |
+ workers.push_back(server2); |
+ |
+ client2 = new RestrictedDispatchDeadlockClient2(server2, &server2_ready, |
+ events); |
+ workers.push_back(client2); |
+ |
+ server1 = new RestrictedDispatchDeadlockServer(1, &server1_ready, events, |
+ server2); |
+ server1->OverrideThread(&worker_thread); |
+ workers.push_back(server1); |
+ |
+ client1 = new RestrictedDispatchDeadlockClient1(server1, client2, |
+ &server1_ready, events); |
+ workers.push_back(client1); |
+ |
+ RunTest(workers); |
+} |
+ |
} // namespace IPC |