OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "config.h" |
| 6 #include "core/workers/WorkerThread.h" |
| 7 |
| 8 #include "bindings/core/v8/ScriptWrappable.h" |
| 9 #include "core/inspector/ConsoleMessage.h" |
| 10 #include "core/workers/SharedWorkerGlobalScope.h" |
| 11 #include "core/workers/SharedWorkerThread.h" |
| 12 #include "core/workers/WorkerGlobalScope.h" |
| 13 #include "core/workers/WorkerReportingProxy.h" |
| 14 #include "core/workers/WorkerThreadStartupData.h" |
| 15 #include "public/platform/WebScheduler.h" |
| 16 #include "wtf/ThreadingPrimitives.h" |
| 17 #include <gmock/gmock.h> |
| 18 #include <gtest/gtest.h> |
| 19 |
| 20 using testing::_; |
| 21 using testing::Invoke; |
| 22 using testing::Return; |
| 23 using testing::Mock; |
| 24 |
| 25 namespace blink { |
| 26 |
| 27 namespace { |
| 28 class MockWorkerLoaderProxy : public WorkerLoaderProxy { |
| 29 public: |
| 30 MockWorkerLoaderProxy() : WorkerLoaderProxy(nullptr) { } |
| 31 ~MockWorkerLoaderProxy() override { } |
| 32 |
| 33 MOCK_METHOD1(postTaskToLoader, void(PassOwnPtr<ExecutionContextTask>)); |
| 34 MOCK_METHOD1(postTaskToWorkerGlobalScope, bool(PassOwnPtr<ExecutionContextTa
sk>)); |
| 35 }; |
| 36 |
| 37 class MockWorkerReportingProxy : public WorkerReportingProxy { |
| 38 public: |
| 39 MockWorkerReportingProxy() { } |
| 40 ~MockWorkerReportingProxy() override { } |
| 41 |
| 42 MOCK_METHOD5(reportException, void(const String& errorMessage, int lineNumbe
r, int columnNumber, const String& sourceURL, int exceptionId)); |
| 43 MOCK_METHOD1(reportConsoleMessage, void(PassRefPtrWillBeRawPtr<ConsoleMessag
e>)); |
| 44 MOCK_METHOD1(postMessageToPageInspector, void(const String&)); |
| 45 MOCK_METHOD0(postWorkerConsoleAgentEnabled, void()); |
| 46 MOCK_METHOD1(didEvaluateWorkerScript, void(bool success)); |
| 47 MOCK_METHOD1(workerGlobalScopeStarted, void(WorkerGlobalScope*)); |
| 48 MOCK_METHOD0(workerGlobalScopeClosed, void()); |
| 49 MOCK_METHOD0(workerThreadTerminated, void()); |
| 50 MOCK_METHOD0(willDestroyWorkerGlobalScope, void()); |
| 51 }; |
| 52 |
| 53 class FakeWorkerGlobalScope : public WorkerGlobalScope { |
| 54 public: |
| 55 typedef WorkerGlobalScope Base; |
| 56 |
| 57 FakeWorkerGlobalScope(const KURL& url, const String& userAgent, WorkerThread
* thread, const SecurityOrigin* starterOrigin, PassOwnPtrWillBeRawPtr<WorkerClie
nts> workerClients) |
| 58 : WorkerGlobalScope(url, userAgent, thread, monotonicallyIncreasingTime(
), starterOrigin, workerClients) |
| 59 { |
| 60 } |
| 61 |
| 62 ~FakeWorkerGlobalScope() override |
| 63 { |
| 64 } |
| 65 |
| 66 virtual bool isSharedWorkerGlobalScope() const override { return true; } |
| 67 |
| 68 // EventTarget |
| 69 virtual const AtomicString& interfaceName() const override |
| 70 { |
| 71 return EventTargetNames::SharedWorkerGlobalScope; |
| 72 } |
| 73 |
| 74 virtual void logExceptionToConsole(const String&, int , const String&, int,
int, PassRefPtrWillBeRawPtr<ScriptCallStack>) override |
| 75 { |
| 76 } |
| 77 |
| 78 // Setters/Getters for attributes in SharedWorkerGlobalScope.idl |
| 79 DEFINE_ATTRIBUTE_EVENT_LISTENER(connect); |
| 80 String name() const { return "FakeWorkerGlobalScope"; } |
| 81 |
| 82 DECLARE_VIRTUAL_TRACE(); |
| 83 }; |
| 84 |
| 85 DEFINE_TRACE(FakeWorkerGlobalScope) |
| 86 { |
| 87 WorkerGlobalScope::trace(visitor); |
| 88 } |
| 89 |
| 90 class WorkerThreadForTest : public WorkerThread { |
| 91 public: |
| 92 WorkerThreadForTest( |
| 93 WorkerLoaderProxy* mockWorkerLoaderProxy, |
| 94 WorkerReportingProxy& mockWorkerReportingProxy, |
| 95 PassOwnPtrWillBeRawPtr<WorkerThreadStartupData> workerThreadStartupData) |
| 96 : WorkerThread("Test Thread", mockWorkerLoaderProxy, mockWorkerReporting
Proxy, workerThreadStartupData) |
| 97 { |
| 98 } |
| 99 |
| 100 ~WorkerThreadForTest() override { } |
| 101 |
| 102 // NOTE we use the WebSchduler to post tasks in this test because it's not a
ffected by the terminated() method, which |
| 103 // tasks posted via the WorkerTrhead directly are. |
| 104 using WorkerThread::schedulerForTesting; |
| 105 using WorkerThread::threadForTesting; |
| 106 |
| 107 MOCK_METHOD1(doIdleGc, bool(double deadlineSeconds)); |
| 108 MOCK_METHOD0(terminated, bool()); |
| 109 |
| 110 PassRefPtrWillBeRawPtr<WorkerGlobalScope> createWorkerGlobalScope(PassOwnPtr
<WorkerThreadStartupData> startupData) override |
| 111 { |
| 112 return adoptRefWillBeNoop(new FakeWorkerGlobalScope(startupData->m_scrip
tURL, startupData->m_userAgent, this, startupData->m_starterOrigin, startupData-
>m_workerClients.release())); |
| 113 } |
| 114 }; |
| 115 |
| 116 class WakeupTask : public WebThread::Task { |
| 117 public: |
| 118 WakeupTask() { } |
| 119 |
| 120 ~WakeupTask() override { } |
| 121 |
| 122 void run() override { } |
| 123 }; |
| 124 |
| 125 |
| 126 class PostDelayedWakeupTask : public WebThread::Task { |
| 127 public: |
| 128 PostDelayedWakeupTask(WebScheduler* scheduler, long long delay) : m_schedule
r(scheduler), m_delay(delay) { } |
| 129 |
| 130 ~PostDelayedWakeupTask() override { } |
| 131 |
| 132 void run() override |
| 133 { |
| 134 m_scheduler->postTimerTask(FROM_HERE, new WakeupTask(), m_delay); |
| 135 } |
| 136 |
| 137 WebScheduler* m_scheduler; // NOT OWNED |
| 138 long long m_delay; |
| 139 }; |
| 140 |
| 141 class SignalTask : public WebThread::Task { |
| 142 public: |
| 143 SignalTask(ThreadCondition* completion, Mutex* mutex) : m_completion(complet
ion), m_mutex(mutex) { } |
| 144 |
| 145 ~SignalTask() override { } |
| 146 |
| 147 void run() override |
| 148 { |
| 149 WTF::Locker<Mutex> lock(*m_mutex); |
| 150 m_completion->signal(); |
| 151 } |
| 152 |
| 153 private: |
| 154 ThreadCondition* m_completion; // NOT OWNED |
| 155 Mutex* m_mutex; // NOT OWNED |
| 156 }; |
| 157 |
| 158 } // namespace |
| 159 |
| 160 class WorkerThreadTest : public testing::Test { |
| 161 public: |
| 162 void SetUp() override |
| 163 { |
| 164 m_mockWorkerLoaderProxy = new MockWorkerLoaderProxy(); |
| 165 m_mockWorkerReportingProxy = adoptPtr(new MockWorkerReportingProxy()); |
| 166 m_securityOrigin = SecurityOrigin::create(KURL(ParsedURLString, "http://
fake.url/")); |
| 167 m_workerThread = adoptRef(new WorkerThreadForTest( |
| 168 m_mockWorkerLoaderProxy.get(), |
| 169 *m_mockWorkerReportingProxy, |
| 170 WorkerThreadStartupData::create( |
| 171 KURL(ParsedURLString, "http://fake.url/"), |
| 172 "fake user agent", |
| 173 "//fake source code", |
| 174 nullptr, |
| 175 DontPauseWorkerGlobalScopeOnStart, |
| 176 "contentSecurityPolicy", |
| 177 ContentSecurityPolicyHeaderTypeReport, |
| 178 m_securityOrigin.get(), |
| 179 WorkerClients::create(), |
| 180 V8CacheOptionsDefault))); |
| 181 ExpectWorkerLifetimeReportingCalls(); |
| 182 } |
| 183 |
| 184 void StartAndWaitForInit() |
| 185 { |
| 186 ThreadCondition complete; |
| 187 Mutex mutex; |
| 188 |
| 189 m_workerThread->start(); |
| 190 m_workerThread->threadForTesting()->postTask(FROM_HERE, new SignalTask(&
complete, &mutex)); |
| 191 |
| 192 { |
| 193 WTF::Locker<Mutex> lock(mutex); |
| 194 complete.wait(mutex); |
| 195 } |
| 196 } |
| 197 |
| 198 void PostWakeUpTask(long long waitMs) |
| 199 { |
| 200 WebScheduler* scheduler = m_workerThread->schedulerForTesting(); |
| 201 |
| 202 // The idle task will get posted on an after wake up queue, so we need a
nother task |
| 203 // posted at the right time to wake the system up. We don't know the ri
ght delay here |
| 204 // since the thread can take a variable length of time to be responsive,
however this |
| 205 // isn't a problem when posting a delayed task from within a task on the
worker thread. |
| 206 scheduler->postLoadingTask(FROM_HERE, new PostDelayedWakeupTask(schedule
r, waitMs)); |
| 207 } |
| 208 |
| 209 protected: |
| 210 void ExpectWorkerLifetimeReportingCalls() |
| 211 { |
| 212 EXPECT_CALL(*m_mockWorkerReportingProxy, workerGlobalScopeStarted(_)).Ti
mes(1); |
| 213 EXPECT_CALL(*m_mockWorkerReportingProxy, didEvaluateWorkerScript(true)).
Times(1); |
| 214 EXPECT_CALL(*m_mockWorkerReportingProxy, workerThreadTerminated()).Times
(1); |
| 215 EXPECT_CALL(*m_mockWorkerReportingProxy, willDestroyWorkerGlobalScope())
.Times(1); |
| 216 } |
| 217 |
| 218 RefPtr<SecurityOrigin> m_securityOrigin; |
| 219 RefPtr<MockWorkerLoaderProxy> m_mockWorkerLoaderProxy; |
| 220 OwnPtr<MockWorkerReportingProxy> m_mockWorkerReportingProxy; |
| 221 RefPtr<WorkerThreadForTest> m_workerThread; |
| 222 }; |
| 223 |
| 224 TEST_F(WorkerThreadTest, GcOccursWhileIdle) |
| 225 { |
| 226 ThreadCondition gcDone; |
| 227 Mutex mutex; |
| 228 |
| 229 ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
| 230 [&gcDone, &mutex](double) |
| 231 { |
| 232 WTF::Locker<Mutex> lock(mutex); |
| 233 gcDone.signal(); |
| 234 return false; |
| 235 })); |
| 236 |
| 237 EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
| 238 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
| 239 |
| 240 StartAndWaitForInit(); |
| 241 PostWakeUpTask(310ul); // 10ms after the quiescent period ends. |
| 242 |
| 243 { |
| 244 WTF::Locker<Mutex> lock(mutex); |
| 245 gcDone.wait(mutex); |
| 246 } |
| 247 |
| 248 m_workerThread->terminateAndWait(); |
| 249 }; |
| 250 |
| 251 class RepeatingTask : public WebThread::Task { |
| 252 public: |
| 253 RepeatingTask(WebScheduler* scheduler, ThreadCondition* completion, Mutex* m
utex) |
| 254 : RepeatingTask(scheduler, completion, mutex, 0) { } |
| 255 |
| 256 ~RepeatingTask() override { } |
| 257 |
| 258 void run() override |
| 259 { |
| 260 m_taskCount++; |
| 261 if (m_taskCount == 10) { |
| 262 WTF::Locker<Mutex> lock(*m_mutex); |
| 263 m_completion->signal(); |
| 264 } |
| 265 |
| 266 m_scheduler->postTimerTask( |
| 267 FROM_HERE, new RepeatingTask(m_scheduler, m_completion, m_mutex, m_t
askCount), 50ul); |
| 268 m_scheduler->postLoadingTask(FROM_HERE, new WakeupTask()); |
| 269 |
| 270 } |
| 271 |
| 272 private: |
| 273 RepeatingTask(WebScheduler* scheduler, ThreadCondition* completion, Mutex* m
utex, int taskCount) |
| 274 : m_scheduler(scheduler) |
| 275 , m_completion(completion) |
| 276 , m_mutex(mutex) |
| 277 , m_taskCount(taskCount) |
| 278 { } |
| 279 |
| 280 WebScheduler* m_scheduler; // NOT OWNED |
| 281 ThreadCondition* m_completion; |
| 282 Mutex* m_mutex; |
| 283 int m_taskCount; |
| 284 }; |
| 285 |
| 286 TEST_F(WorkerThreadTest, GcDoesNotOccurIfGapBetweenDelayedTasksIsTooSmall) |
| 287 { |
| 288 ThreadCondition completion; |
| 289 Mutex mutex; |
| 290 |
| 291 EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(0); |
| 292 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
| 293 |
| 294 StartAndWaitForInit(); |
| 295 |
| 296 WebScheduler* scheduler = m_workerThread->schedulerForTesting(); |
| 297 |
| 298 // Post a repeating task that should prevent any GC from happening. |
| 299 scheduler->postLoadingTask(FROM_HERE, new RepeatingTask(scheduler, &completi
on, &mutex)); |
| 300 |
| 301 { |
| 302 WTF::Locker<Mutex> lock(mutex); |
| 303 completion.wait(mutex); |
| 304 } |
| 305 |
| 306 // Make sure doIdleGc has not been called by this stage. |
| 307 Mock::VerifyAndClearExpectations(m_workerThread.get()); |
| 308 |
| 309 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
| 310 m_workerThread->terminateAndWait(); |
| 311 } |
| 312 |
| 313 TEST_F(WorkerThreadTest, LongGcDeadline_NoFutureTasks) |
| 314 { |
| 315 ThreadCondition gcDone; |
| 316 Mutex mutex; |
| 317 double deadlineLength = 0; |
| 318 |
| 319 ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
| 320 [&gcDone, &mutex, &deadlineLength](double deadline) |
| 321 { |
| 322 WTF::Locker<Mutex> lock(mutex); |
| 323 gcDone.signal(); |
| 324 deadlineLength = deadline -Platform::current()->monotonicallyIncreas
ingTime(); |
| 325 return false; |
| 326 })); |
| 327 |
| 328 EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
| 329 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
| 330 |
| 331 StartAndWaitForInit(); |
| 332 PostWakeUpTask(310ul); |
| 333 |
| 334 { |
| 335 WTF::Locker<Mutex> lock(mutex); |
| 336 gcDone.wait(mutex); |
| 337 |
| 338 // The deadline should be close to 1s in duration if there are no tasks
that need to run soon. |
| 339 EXPECT_GT(deadlineLength, 0.9); |
| 340 } |
| 341 |
| 342 m_workerThread->terminateAndWait(); |
| 343 } |
| 344 |
| 345 TEST_F(WorkerThreadTest, LongGcDeadline_NextTaskAfterIdlePeriod) |
| 346 { |
| 347 ThreadCondition gcDone; |
| 348 Mutex mutex; |
| 349 double deadlineLength = 0; |
| 350 |
| 351 ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
| 352 [&gcDone, &mutex, &deadlineLength](double deadline) |
| 353 { |
| 354 WTF::Locker<Mutex> lock(mutex); |
| 355 gcDone.signal(); |
| 356 deadlineLength = deadline -Platform::current()->monotonicallyIncreas
ingTime(); |
| 357 return false; |
| 358 })); |
| 359 |
| 360 EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
| 361 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
| 362 |
| 363 StartAndWaitForInit(); |
| 364 PostWakeUpTask(310ul); |
| 365 PostWakeUpTask(675ul); // Task that runs shortly after the 50ms idle period
ends. |
| 366 |
| 367 { |
| 368 WTF::Locker<Mutex> lock(mutex); |
| 369 gcDone.wait(mutex); |
| 370 |
| 371 // The worker thread calls canExceedIdleDeadlineIfRequired which only co
nsiders if |
| 372 // there are any delayed tasks scheduled for the current long idle perio
d. Since the |
| 373 // next task is in the following idle period, a long gc deadline is allo
wed. |
| 374 EXPECT_GT(deadlineLength, 0.9); |
| 375 } |
| 376 |
| 377 m_workerThread->terminateAndWait(); |
| 378 } |
| 379 |
| 380 TEST_F(WorkerThreadTest, ShortGcDeadline) |
| 381 { |
| 382 ThreadCondition gcDone; |
| 383 Mutex mutex; |
| 384 double deadlineLength = 0; |
| 385 |
| 386 ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
| 387 [&gcDone, &mutex, &deadlineLength](double deadline) |
| 388 { |
| 389 WTF::Locker<Mutex> lock(mutex); |
| 390 gcDone.signal(); |
| 391 deadlineLength = deadline -Platform::current()->monotonicallyIncreas
ingTime(); |
| 392 return false; |
| 393 })); |
| 394 |
| 395 EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
| 396 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
| 397 |
| 398 StartAndWaitForInit(); |
| 399 PostWakeUpTask(310ul); |
| 400 PostWakeUpTask(625ul); // Task that runs during the idle period. |
| 401 |
| 402 { |
| 403 WTF::Locker<Mutex> lock(mutex); |
| 404 gcDone.wait(mutex); |
| 405 |
| 406 // The deadline should be < 50ms if there's a task that needs to run dur
ing the idle period. |
| 407 EXPECT_LT(deadlineLength, 0.025); |
| 408 } |
| 409 |
| 410 m_workerThread->terminateAndWait(); |
| 411 } |
| 412 |
| 413 class PostDelayedSignalTask : public WebThread::Task { |
| 414 public: |
| 415 PostDelayedSignalTask(WebScheduler* scheduler, ThreadCondition* completion,
Mutex* mutex, long long delay) |
| 416 : m_scheduler(scheduler) |
| 417 , m_completion(completion) |
| 418 , m_mutex(mutex) |
| 419 , m_delay(delay) { } |
| 420 |
| 421 ~PostDelayedSignalTask() override { } |
| 422 |
| 423 void run() override |
| 424 { |
| 425 m_scheduler->postTimerTask(FROM_HERE, new SignalTask(m_completion, m_mut
ex), m_delay); |
| 426 } |
| 427 |
| 428 WebScheduler* m_scheduler; // NOT OWNED |
| 429 ThreadCondition* m_completion; // NOT OWNED |
| 430 Mutex* m_mutex; // NOT OWNED |
| 431 long long m_delay; |
| 432 }; |
| 433 |
| 434 TEST_F(WorkerThreadTest, TerminationPreventsGc) |
| 435 { |
| 436 ThreadCondition completion; |
| 437 Mutex mutex; |
| 438 |
| 439 EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(0); |
| 440 EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(true)); |
| 441 |
| 442 StartAndWaitForInit(); |
| 443 PostWakeUpTask(310ul); // 10ms after the quiescent period ends. |
| 444 |
| 445 // Give pleanty of time for the GC to run before exiting. |
| 446 WebScheduler* scheduler = m_workerThread->schedulerForTesting(); |
| 447 scheduler->postLoadingTask(FROM_HERE, new PostDelayedSignalTask(scheduler, &
completion, &mutex, 700ul)); |
| 448 |
| 449 { |
| 450 WTF::Locker<Mutex> lock(mutex); |
| 451 completion.wait(mutex); |
| 452 } |
| 453 |
| 454 m_workerThread->terminateAndWait(); |
| 455 }; |
| 456 |
| 457 } // namespace blink |
OLD | NEW |