1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsIThread.h"
7 #include "nsIThreadPool.h"
8 
9 #include "nsComponentManagerUtils.h"
10 #include "nsThreadUtils.h"
11 #include "nsXPCOMCIDInternal.h"
12 #include "pratom.h"
13 #include "prinrval.h"
14 #include "prmon.h"
15 #include "prthread.h"
16 #include "mozilla/Assertions.h"
17 #include "mozilla/Attributes.h"
18 
19 #include "mozilla/ReentrantMonitor.h"
20 
21 #include "gtest/gtest.h"
22 
23 using namespace mozilla;
24 
25 #define NUMBER_OF_THREADS 4
26 
27 // One hour... because test boxes can be slow!
28 #define IDLE_THREAD_TIMEOUT 3600000
29 
30 namespace TestThreadPoolListener
31 {
32 static nsIThread** gCreatedThreadList = nullptr;
33 static nsIThread** gShutDownThreadList = nullptr;
34 
35 static ReentrantMonitor* gReentrantMonitor = nullptr;
36 
37 static bool gAllRunnablesPosted = false;
38 static bool gAllThreadsCreated = false;
39 static bool gAllThreadsShutDown = false;
40 
41 class Listener final : public nsIThreadPoolListener
42 {
~Listener()43   ~Listener() {}
44 
45 public:
46   NS_DECL_THREADSAFE_ISUPPORTS
47   NS_DECL_NSITHREADPOOLLISTENER
48 };
49 
NS_IMPL_ISUPPORTS(Listener,nsIThreadPoolListener)50 NS_IMPL_ISUPPORTS(Listener, nsIThreadPoolListener)
51 
52 NS_IMETHODIMP
53 Listener::OnThreadCreated()
54 {
55   nsCOMPtr<nsIThread> current(do_GetCurrentThread());
56   EXPECT_TRUE(current) << "Couldn't get current thread!";
57 
58   ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
59 
60   while (!gAllRunnablesPosted) {
61     mon.Wait();
62   }
63 
64   for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
65     nsIThread* thread = gCreatedThreadList[i];
66     EXPECT_NE(thread, current) << "Saw the same thread twice!";
67 
68     if (!thread) {
69       gCreatedThreadList[i] = current;
70       if (i == (NUMBER_OF_THREADS - 1)) {
71         gAllThreadsCreated = true;
72         mon.NotifyAll();
73       }
74       return NS_OK;
75     }
76   }
77 
78   EXPECT_TRUE(false) << "Too many threads!";
79   return NS_ERROR_FAILURE;
80 }
81 
82 NS_IMETHODIMP
OnThreadShuttingDown()83 Listener::OnThreadShuttingDown()
84 {
85   nsCOMPtr<nsIThread> current(do_GetCurrentThread());
86   EXPECT_TRUE(current) << "Couldn't get current thread!";
87 
88   ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
89 
90   for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
91     nsIThread* thread = gShutDownThreadList[i];
92     EXPECT_NE(thread, current) << "Saw the same thread twice!";
93 
94     if (!thread) {
95       gShutDownThreadList[i] = current;
96       if (i == (NUMBER_OF_THREADS - 1)) {
97         gAllThreadsShutDown = true;
98         mon.NotifyAll();
99       }
100       return NS_OK;
101     }
102   }
103 
104   EXPECT_TRUE(false) << "Too many threads!";
105   return NS_ERROR_FAILURE;
106 }
107 
108 class AutoCreateAndDestroyReentrantMonitor
109 {
110 public:
AutoCreateAndDestroyReentrantMonitor(ReentrantMonitor ** aReentrantMonitorPtr)111   explicit AutoCreateAndDestroyReentrantMonitor(ReentrantMonitor** aReentrantMonitorPtr)
112   : mReentrantMonitorPtr(aReentrantMonitorPtr) {
113     *aReentrantMonitorPtr = new ReentrantMonitor("TestThreadPoolListener::AutoMon");
114     MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!");
115   }
116 
~AutoCreateAndDestroyReentrantMonitor()117   ~AutoCreateAndDestroyReentrantMonitor() {
118     delete *mReentrantMonitorPtr;
119     *mReentrantMonitorPtr = nullptr;
120   }
121 
122 private:
123   ReentrantMonitor** mReentrantMonitorPtr;
124 };
125 
TEST(ThreadPoolListener,Test)126 TEST(ThreadPoolListener, Test)
127 {
128   nsIThread* createdThreadList[NUMBER_OF_THREADS] = { nullptr };
129   gCreatedThreadList = createdThreadList;
130 
131   nsIThread* shutDownThreadList[NUMBER_OF_THREADS] = { nullptr };
132   gShutDownThreadList = shutDownThreadList;
133 
134   AutoCreateAndDestroyReentrantMonitor newMon(&gReentrantMonitor);
135   ASSERT_TRUE(gReentrantMonitor);
136 
137   nsresult rv;
138 
139   nsCOMPtr<nsIThreadPool> pool =
140     do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
141   ASSERT_TRUE(NS_SUCCEEDED(rv));
142 
143   rv = pool->SetThreadLimit(NUMBER_OF_THREADS);
144   ASSERT_TRUE(NS_SUCCEEDED(rv));
145 
146   rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS);
147   ASSERT_TRUE(NS_SUCCEEDED(rv));
148 
149   rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT);
150   ASSERT_TRUE(NS_SUCCEEDED(rv));
151 
152   nsCOMPtr<nsIThreadPoolListener> listener = new Listener();
153   ASSERT_TRUE(listener);
154 
155   rv = pool->SetListener(listener);
156   ASSERT_TRUE(NS_SUCCEEDED(rv));
157 
158   {
159     ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
160 
161     for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
162       nsCOMPtr<nsIRunnable> runnable = new Runnable();
163       ASSERT_TRUE(runnable);
164 
165       rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL);
166       ASSERT_TRUE(NS_SUCCEEDED(rv));
167     }
168 
169     gAllRunnablesPosted = true;
170     mon.NotifyAll();
171   }
172 
173   {
174     ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
175     while (!gAllThreadsCreated) {
176       mon.Wait();
177     }
178   }
179 
180   rv = pool->Shutdown();
181   ASSERT_TRUE(NS_SUCCEEDED(rv));
182 
183   {
184     ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
185     while (!gAllThreadsShutDown) {
186       mon.Wait();
187     }
188   }
189 
190   for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
191     nsIThread* created = gCreatedThreadList[i];
192     ASSERT_TRUE(created);
193 
194     bool match = false;
195     for (uint32_t j = 0; j < NUMBER_OF_THREADS; j++) {
196       nsIThread* destroyed = gShutDownThreadList[j];
197       ASSERT_TRUE(destroyed);
198 
199       if (destroyed == created) {
200         match = true;
201         break;
202       }
203     }
204 
205     ASSERT_TRUE(match);
206   }
207 }
208 
209 } // namespace TestThreadPoolListener
210