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 
8 #include "nsComponentManagerUtils.h"
9 #include "nsThreadPool.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 static nsIThread** gCreatedThreadList = nullptr;
32 static nsIThread** gShutDownThreadList = nullptr;
33 
34 static ReentrantMonitor* gReentrantMonitor = nullptr;
35 
36 static bool gAllRunnablesPosted = false;
37 static bool gAllThreadsCreated = false;
38 static bool gAllThreadsShutDown = false;
39 
40 class Listener final : public nsIThreadPoolListener {
41   ~Listener() = default;
42 
43  public:
44   NS_DECL_THREADSAFE_ISUPPORTS
45   NS_DECL_NSITHREADPOOLLISTENER
46 };
47 
NS_IMPL_ISUPPORTS(Listener,nsIThreadPoolListener)48 NS_IMPL_ISUPPORTS(Listener, nsIThreadPoolListener)
49 
50 NS_IMETHODIMP
51 Listener::OnThreadCreated() {
52   nsCOMPtr<nsIThread> current(do_GetCurrentThread());
53   EXPECT_TRUE(current) << "Couldn't get current thread!";
54 
55   ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
56 
57   while (!gAllRunnablesPosted) {
58     mon.Wait();
59   }
60 
61   for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
62     nsIThread* thread = gCreatedThreadList[i];
63     EXPECT_NE(thread, current) << "Saw the same thread twice!";
64 
65     if (!thread) {
66       gCreatedThreadList[i] = current;
67       if (i == (NUMBER_OF_THREADS - 1)) {
68         gAllThreadsCreated = true;
69         mon.NotifyAll();
70       }
71       return NS_OK;
72     }
73   }
74 
75   EXPECT_TRUE(false) << "Too many threads!";
76   return NS_ERROR_FAILURE;
77 }
78 
79 NS_IMETHODIMP
OnThreadShuttingDown()80 Listener::OnThreadShuttingDown() {
81   nsCOMPtr<nsIThread> current(do_GetCurrentThread());
82   EXPECT_TRUE(current) << "Couldn't get current thread!";
83 
84   ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
85 
86   for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
87     nsIThread* thread = gShutDownThreadList[i];
88     EXPECT_NE(thread, current) << "Saw the same thread twice!";
89 
90     if (!thread) {
91       gShutDownThreadList[i] = current;
92       if (i == (NUMBER_OF_THREADS - 1)) {
93         gAllThreadsShutDown = true;
94         mon.NotifyAll();
95       }
96       return NS_OK;
97     }
98   }
99 
100   EXPECT_TRUE(false) << "Too many threads!";
101   return NS_ERROR_FAILURE;
102 }
103 
104 class AutoCreateAndDestroyReentrantMonitor {
105  public:
AutoCreateAndDestroyReentrantMonitor(ReentrantMonitor ** aReentrantMonitorPtr)106   explicit AutoCreateAndDestroyReentrantMonitor(
107       ReentrantMonitor** aReentrantMonitorPtr)
108       : mReentrantMonitorPtr(aReentrantMonitorPtr) {
109     *aReentrantMonitorPtr =
110         new ReentrantMonitor("TestThreadPoolListener::AutoMon");
111     MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!");
112   }
113 
~AutoCreateAndDestroyReentrantMonitor()114   ~AutoCreateAndDestroyReentrantMonitor() {
115     delete *mReentrantMonitorPtr;
116     *mReentrantMonitorPtr = nullptr;
117   }
118 
119  private:
120   ReentrantMonitor** mReentrantMonitorPtr;
121 };
122 
TEST(ThreadPoolListener,Test)123 TEST(ThreadPoolListener, Test)
124 {
125   nsIThread* createdThreadList[NUMBER_OF_THREADS] = {nullptr};
126   gCreatedThreadList = createdThreadList;
127 
128   nsIThread* shutDownThreadList[NUMBER_OF_THREADS] = {nullptr};
129   gShutDownThreadList = shutDownThreadList;
130 
131   AutoCreateAndDestroyReentrantMonitor newMon(&gReentrantMonitor);
132   ASSERT_TRUE(gReentrantMonitor);
133 
134   nsresult rv;
135 
136   nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
137 
138   rv = pool->SetThreadLimit(NUMBER_OF_THREADS);
139   ASSERT_TRUE(NS_SUCCEEDED(rv));
140 
141   rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS);
142   ASSERT_TRUE(NS_SUCCEEDED(rv));
143 
144   rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT);
145   ASSERT_TRUE(NS_SUCCEEDED(rv));
146 
147   nsCOMPtr<nsIThreadPoolListener> listener = new Listener();
148   ASSERT_TRUE(listener);
149 
150   rv = pool->SetListener(listener);
151   ASSERT_TRUE(NS_SUCCEEDED(rv));
152 
153   {
154     ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
155 
156     for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
157       nsCOMPtr<nsIRunnable> runnable = new Runnable("TestRunnable");
158       ASSERT_TRUE(runnable);
159 
160       rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL);
161       ASSERT_TRUE(NS_SUCCEEDED(rv));
162     }
163 
164     gAllRunnablesPosted = true;
165     mon.NotifyAll();
166   }
167 
168   {
169     ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
170     while (!gAllThreadsCreated) {
171       mon.Wait();
172     }
173   }
174 
175   rv = pool->Shutdown();
176   ASSERT_TRUE(NS_SUCCEEDED(rv));
177 
178   {
179     ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
180     while (!gAllThreadsShutDown) {
181       mon.Wait();
182     }
183   }
184 
185   for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
186     nsIThread* created = gCreatedThreadList[i];
187     ASSERT_TRUE(created);
188 
189     bool match = false;
190     for (uint32_t j = 0; j < NUMBER_OF_THREADS; j++) {
191       nsIThread* destroyed = gShutDownThreadList[j];
192       ASSERT_TRUE(destroyed);
193 
194       if (destroyed == created) {
195         match = true;
196         break;
197       }
198     }
199 
200     ASSERT_TRUE(match);
201   }
202 }
203 
204 }  // namespace TestThreadPoolListener
205