Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "config.h" | 5 #include "config.h" |
| 6 #include "bindings/core/v8/ScriptStreamer.h" | 6 #include "bindings/core/v8/ScriptStreamer.h" |
| 7 | 7 |
| 8 #include "bindings/core/v8/ScriptStreamerThread.h" | 8 #include "bindings/core/v8/ScriptStreamerThread.h" |
| 9 #include "bindings/core/v8/V8ScriptRunner.h" | 9 #include "bindings/core/v8/V8ScriptRunner.h" |
| 10 #include "core/dom/Document.h" | 10 #include "core/dom/Document.h" |
| 11 #include "core/dom/Element.h" | 11 #include "core/dom/Element.h" |
| 12 #include "core/dom/PendingScript.h" | 12 #include "core/dom/PendingScript.h" |
| 13 #include "core/fetch/ScriptResource.h" | 13 #include "core/fetch/ScriptResource.h" |
| 14 #include "core/frame/Settings.h" | 14 #include "core/frame/Settings.h" |
| 15 #include "platform/SharedBuffer.h" | 15 #include "platform/SharedBuffer.h" |
| 16 #include "platform/TraceEvent.h" | |
| 16 #include "public/platform/Platform.h" | 17 #include "public/platform/Platform.h" |
| 17 #include "wtf/MainThread.h" | 18 #include "wtf/MainThread.h" |
| 18 #include "wtf/text/TextEncodingRegistry.h" | 19 #include "wtf/text/TextEncodingRegistry.h" |
| 19 | 20 |
| 20 namespace blink { | 21 namespace blink { |
| 21 | 22 |
| 22 // For passing data between the main thread (producer) and the streamer thread | 23 // For passing data between the main thread (producer) and the streamer thread |
| 23 // (consumer). The main thread prepares the data (copies it from Resource) and | 24 // (consumer). The main thread prepares the data (copies it from Resource) and |
| 24 // the streamer thread feeds it to V8. | 25 // the streamer thread feeds it to V8. |
| 25 class SourceStreamDataQueue { | 26 class SourceStreamDataQueue { |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 217 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S criptState* scriptState, PendingScript::Type scriptType) | 218 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S criptState* scriptState, PendingScript::Type scriptType) |
| 218 { | 219 { |
| 219 // We don't yet know whether the script will really be streamed. E.g., | 220 // We don't yet know whether the script will really be streamed. E.g., |
| 220 // suppressing streaming for short scripts is done later. Record only the | 221 // suppressing streaming for short scripts is done later. Record only the |
| 221 // sure negative cases here. | 222 // sure negative cases here. |
| 222 bool startedStreaming = startStreamingInternal(script, settings, scriptState , scriptType); | 223 bool startedStreaming = startStreamingInternal(script, settings, scriptState , scriptType); |
| 223 if (!startedStreaming) | 224 if (!startedStreaming) |
| 224 blink::Platform::current()->histogramEnumeration(startedStreamingHistogr amName(scriptType), 0, 2); | 225 blink::Platform::current()->histogramEnumeration(startedStreamingHistogr amName(scriptType), 0, 2); |
| 225 } | 226 } |
| 226 | 227 |
| 227 void ScriptStreamer::streamingComplete() | 228 void ScriptStreamer::streamingCompleteOnBackgroundThread() |
| 228 { | 229 { |
| 229 ASSERT(isMainThread()); | 230 ASSERT(!isMainThread()); |
| 230 // It's possible that the corresponding Resource was deleted before V8 | 231 MutexLocker locker(m_mutex); |
| 231 // finished streaming. In that case, the data or the notification is not | 232 m_parsingFinished = true; |
| 232 // needed. In addition, if the streaming is suppressed, the non-streaming | 233 // In the blocking case, the main thread is normally waiting at this |
| 233 // code path will resume after the resource has loaded, before the | 234 // point, but it can also happen that the load is not yet finished |
| 234 // background task finishes. | 235 // (e.g., a parse error). In that case, notifyFinished will be called |
| 235 if (m_detached || m_streamingSuppressed) { | 236 // eventually and it will not wait on m_parsingFinishedCondition. |
| 236 deref(); | 237 |
| 237 return; | 238 // In the non-blocking case, notifyFinished might already be called, or it |
| 239 // might be called in the future. In any case, do the cleanup here. | |
| 240 if (m_mainThreadWaitingForParserThread) { | |
| 241 m_parsingFinishedCondition.signal(); | |
| 242 } else { | |
| 243 callOnMainThread(WTF::bind(&ScriptStreamer::streamingComplete, this)); | |
| 238 } | 244 } |
| 239 | |
| 240 // We have now streamed the whole script to V8 and it has parsed the | |
| 241 // script. We're ready for the next step: compiling and executing the | |
| 242 // script. | |
| 243 m_parsingFinished = true; | |
| 244 | |
| 245 notifyFinishedToClient(); | |
| 246 | |
| 247 // The background thread no longer holds an implicit reference. | |
| 248 deref(); | |
| 249 } | 245 } |
| 250 | 246 |
| 251 void ScriptStreamer::cancel() | 247 void ScriptStreamer::cancel() |
| 252 { | 248 { |
| 253 ASSERT(isMainThread()); | 249 ASSERT(isMainThread()); |
| 254 // The upper layer doesn't need the script any more, but streaming might | 250 // The upper layer doesn't need the script any more, but streaming might |
| 255 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets | 251 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets |
| 256 // the control the next time. It can also be that V8 has already completed | 252 // the control the next time. It can also be that V8 has already completed |
| 257 // its operations and streamingComplete will be called soon. | 253 // its operations and streamingComplete will be called soon. |
| 258 m_detached = true; | 254 m_detached = true; |
| 259 m_resource = 0; | 255 m_resource = 0; |
| 260 m_stream->cancel(); | 256 m_stream->cancel(); |
| 261 } | 257 } |
| 262 | 258 |
| 263 void ScriptStreamer::suppressStreaming() | 259 void ScriptStreamer::suppressStreaming() |
| 264 { | 260 { |
| 265 ASSERT(!m_parsingFinished); | 261 MutexLocker locker(m_mutex); |
| 266 ASSERT(!m_loadingFinished); | 262 ASSERT(!m_loadingFinished); |
| 263 // It can be that the parsing task has already finished (e.g., if there was | |
| 264 // a parse error). | |
| 267 m_streamingSuppressed = true; | 265 m_streamingSuppressed = true; |
| 268 } | 266 } |
| 269 | 267 |
| 270 void ScriptStreamer::notifyAppendData(ScriptResource* resource) | 268 void ScriptStreamer::notifyAppendData(ScriptResource* resource) |
| 271 { | 269 { |
| 272 ASSERT(isMainThread()); | 270 ASSERT(isMainThread()); |
| 273 ASSERT(m_resource == resource); | 271 ASSERT(m_resource == resource); |
| 274 if (m_streamingSuppressed) | 272 { |
| 275 return; | 273 MutexLocker locker(m_mutex); |
| 274 if (m_streamingSuppressed) | |
| 275 return; | |
| 276 } | |
| 276 if (!m_haveEnoughDataForStreaming) { | 277 if (!m_haveEnoughDataForStreaming) { |
| 277 // Even if the first data chunk is small, the script can still be big | 278 // Even if the first data chunk is small, the script can still be big |
| 278 // enough - wait until the next data chunk comes before deciding whether | 279 // enough - wait until the next data chunk comes before deciding whether |
| 279 // to start the streaming. | 280 // to start the streaming. |
| 280 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) { | 281 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) { |
| 281 return; | 282 return; |
| 282 } | 283 } |
| 283 m_haveEnoughDataForStreaming = true; | 284 m_haveEnoughDataForStreaming = true; |
| 284 const char* histogramName = startedStreamingHistogramName(m_scriptType); | 285 const char* histogramName = startedStreamingHistogramName(m_scriptType); |
| 285 if (ScriptStreamerThread::shared()->isRunningTask()) { | 286 if (ScriptStreamerThread::shared()->isRunningTask()) { |
| 286 // At the moment we only have one thread for running the tasks. A | 287 // At the moment we only have one thread for running the tasks. A |
| 287 // new task shouldn't be queued before the running task completes, | 288 // new task shouldn't be queued before the running task completes, |
| 288 // because the running task can block and wait for data from the | 289 // because the running task can block and wait for data from the |
| 289 // network. | 290 // network. |
| 290 suppressStreaming(); | 291 suppressStreaming(); |
| 291 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2 ); | 292 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2 ); |
| 292 return; | 293 return; |
| 293 } | 294 } |
| 294 ASSERT(m_task); | 295 ASSERT(m_task); |
| 295 // ScriptStreamer needs to stay alive as long as the background task is | 296 // ScriptStreamer needs to stay alive as long as the background task is |
| 296 // running. This is taken care of with a manual ref() & deref() pair; | 297 // running. This is taken care of with a manual ref() & deref() pair; |
| 297 // the corresponding deref() is in streamingComplete. | 298 // the corresponding deref() is in streamingComplete or in |
| 299 // notifyFinished. | |
| 298 ref(); | 300 ref(); |
| 299 ScriptStreamingTask* task = new ScriptStreamingTask(m_task.release(), th is); | 301 ScriptStreamingTask* task = new ScriptStreamingTask(m_task.release(), th is); |
| 300 ScriptStreamerThread::shared()->postTask(task); | 302 ScriptStreamerThread::shared()->postTask(task); |
| 301 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2); | 303 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2); |
| 302 } | 304 } |
| 303 m_stream->didReceiveData(); | 305 m_stream->didReceiveData(); |
| 304 } | 306 } |
| 305 | 307 |
| 306 void ScriptStreamer::notifyFinished(Resource* resource) | 308 void ScriptStreamer::notifyFinished(Resource* resource) |
| 307 { | 309 { |
| 308 ASSERT(isMainThread()); | 310 ASSERT(isMainThread()); |
| 309 ASSERT(m_resource == resource); | 311 ASSERT(m_resource == resource); |
| 310 // A special case: empty and small scripts. We didn't receive enough data to | 312 // A special case: empty and small scripts. We didn't receive enough data to |
| 311 // start the streaming before this notification. In that case, there won't | 313 // start the streaming before this notification. In that case, there won't |
| 312 // be a "parsing complete" notification either, and we should not wait for | 314 // be a "parsing complete" notification either, and we should not wait for |
| 313 // it. | 315 // it. |
| 314 if (!m_haveEnoughDataForStreaming) { | 316 if (!m_haveEnoughDataForStreaming) { |
| 315 const char* histogramName = startedStreamingHistogramName(m_scriptType); | 317 const char* histogramName = startedStreamingHistogramName(m_scriptType); |
| 316 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2); | 318 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2); |
| 317 suppressStreaming(); | 319 suppressStreaming(); |
| 318 } | 320 } |
| 319 m_stream->didFinishLoading(); | 321 m_stream->didFinishLoading(); |
| 320 m_loadingFinished = true; | 322 m_loadingFinished = true; |
| 323 | |
| 324 if (shouldBlockMainThread()) { | |
| 325 // Make the main thead wait until the streaming is complete, to make | |
| 326 // sure that the script gets the main thread's attention as early as | |
| 327 // possible (for possible compiling, if the client wants to do it | |
| 328 // right away). Note that blocking here is not any worse than the | |
| 329 // non-streaming code path where the main thread eventually blocks | |
| 330 // to parse the script. | |
| 331 TRACE_EVENT0("v8", "v8.mainThreadWaitingForParserThread"); | |
| 332 MutexLocker locker(m_mutex); | |
| 333 while (!isFinished()) { | |
| 334 ASSERT(!m_parsingFinished); | |
| 335 ASSERT(!m_streamingSuppressed); | |
| 336 m_mainThreadWaitingForParserThread = true; | |
| 337 m_parsingFinishedCondition.wait(m_mutex); | |
| 338 } | |
| 339 ASSERT(isFinished()); | |
|
haraken
2014/10/24 13:43:04
This ASSERT won't make sense.
marja
2014/10/24 13:48:23
Done.
| |
| 340 } | |
| 341 | |
| 342 // Calling notifyFinishedToClient can result into the upper layers dropping | |
| 343 // references to ScriptStreamer. Keep it alive until this function ends. | |
| 344 RefPtr<ScriptStreamer> keepAlive(this); | |
|
haraken
2014/10/24 13:43:04
Nit: We normally use 'protect'.
marja
2014/10/24 13:48:23
Done.
| |
| 345 | |
| 321 notifyFinishedToClient(); | 346 notifyFinishedToClient(); |
| 347 | |
| 348 if (m_mainThreadWaitingForParserThread) { | |
| 349 ASSERT(m_parsingFinished); | |
| 350 ASSERT(!m_streamingSuppressed); | |
| 351 // streamingComplete won't be called, so do the ramp-down work | |
| 352 // here. | |
| 353 deref(); | |
| 354 } | |
| 322 } | 355 } |
| 323 | 356 |
| 324 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str eamedSource::Encoding encoding, PendingScript::Type scriptType) | 357 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str eamedSource::Encoding encoding, PendingScript::Type scriptType, ScriptStreamingM ode mode) |
| 325 : m_resource(resource) | 358 : m_resource(resource) |
| 326 , m_detached(false) | 359 , m_detached(false) |
| 327 , m_stream(new SourceStream(this)) | 360 , m_stream(new SourceStream(this)) |
| 328 , m_source(m_stream, encoding) // m_source takes ownership of m_stream. | 361 , m_source(m_stream, encoding) // m_source takes ownership of m_stream. |
| 329 , m_client(0) | 362 , m_client(0) |
| 330 , m_loadingFinished(false) | 363 , m_loadingFinished(false) |
| 331 , m_parsingFinished(false) | 364 , m_parsingFinished(false) |
| 332 , m_haveEnoughDataForStreaming(false) | 365 , m_haveEnoughDataForStreaming(false) |
| 333 , m_streamingSuppressed(false) | 366 , m_streamingSuppressed(false) |
| 334 , m_scriptType(scriptType) | 367 , m_scriptType(scriptType) |
| 368 , m_scriptStreamingMode(mode) | |
| 369 , m_mainThreadWaitingForParserThread(false) | |
| 335 { | 370 { |
| 336 } | 371 } |
| 337 | 372 |
| 373 void ScriptStreamer::streamingComplete() | |
| 374 { | |
| 375 // The background task is completed; do the necessary ramp-down in the main | |
| 376 // thread. | |
| 377 ASSERT(isMainThread()); | |
| 378 | |
| 379 // It's possible that the corresponding Resource was deleted before V8 | |
| 380 // finished streaming. In that case, the data or the notification is not | |
| 381 // needed. In addition, if the streaming is suppressed, the non-streaming | |
| 382 // code path will resume after the resource has loaded, before the | |
| 383 // background task finishes. | |
| 384 if (m_detached || m_streamingSuppressed) { | |
| 385 deref(); | |
| 386 return; | |
| 387 } | |
| 388 | |
| 389 // We have now streamed the whole script to V8 and it has parsed the | |
| 390 // script. We're ready for the next step: compiling and executing the | |
| 391 // script. | |
| 392 notifyFinishedToClient(); | |
| 393 | |
| 394 // The background thread no longer holds an implicit reference. | |
| 395 deref(); | |
| 396 } | |
| 397 | |
| 338 void ScriptStreamer::notifyFinishedToClient() | 398 void ScriptStreamer::notifyFinishedToClient() |
| 339 { | 399 { |
| 340 ASSERT(isMainThread()); | 400 ASSERT(isMainThread()); |
| 341 // Usually, the loading will be finished first, and V8 will still need some | 401 // Usually, the loading will be finished first, and V8 will still need some |
| 342 // time to catch up. But the other way is possible too: if V8 detects a | 402 // time to catch up. But the other way is possible too: if V8 detects a |
| 343 // parse error, the V8 side can complete before loading has finished. Send | 403 // parse error, the V8 side can complete before loading has finished. Send |
| 344 // the notification after both loading and V8 side operations have | 404 // the notification after both loading and V8 side operations have |
| 345 // completed. Here we also check that we have a client: it can happen that a | 405 // completed. Here we also check that we have a client: it can happen that a |
| 346 // function calling notifyFinishedToClient was already scheduled in the task | 406 // function calling notifyFinishedToClient was already scheduled in the task |
| 347 // queue and the upper layer decided that it's not interested in the script | 407 // queue and the upper layer decided that it's not interested in the script |
| 348 // and called removeClient. | 408 // and called removeClient. |
| 349 if (isFinished() && m_client) | 409 { |
| 410 MutexLocker locker(m_mutex); | |
| 411 if (!isFinished()) | |
| 412 return; | |
| 413 } | |
| 414 if (m_client) | |
| 350 m_client->notifyFinished(m_resource); | 415 m_client->notifyFinished(m_resource); |
| 351 } | 416 } |
| 352 | 417 |
| 353 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type sc riptType) | 418 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type sc riptType) |
| 354 { | 419 { |
| 355 switch (scriptType) { | 420 switch (scriptType) { |
| 356 case PendingScript::ParsingBlocking: | 421 case PendingScript::ParsingBlocking: |
| 357 return "WebCore.Scripts.ParsingBlocking.StartedStreaming"; | 422 return "WebCore.Scripts.ParsingBlocking.StartedStreaming"; |
| 358 break; | 423 break; |
| 359 case PendingScript::Deferred: | 424 case PendingScript::Deferred: |
| 360 return "WebCore.Scripts.Deferred.StartedStreaming"; | 425 return "WebCore.Scripts.Deferred.StartedStreaming"; |
| 361 break; | 426 break; |
| 362 case PendingScript::Async: | 427 case PendingScript::Async: |
| 363 return "WebCore.Scripts.Async.StartedStreaming"; | 428 return "WebCore.Scripts.Async.StartedStreaming"; |
| 364 break; | 429 break; |
| 365 default: | 430 default: |
| 366 ASSERT_NOT_REACHED(); | 431 ASSERT_NOT_REACHED(); |
| 367 break; | 432 break; |
| 368 } | 433 } |
| 369 return 0; | 434 return 0; |
| 370 } | 435 } |
| 371 | 436 |
| 372 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* set tings, ScriptState* scriptState, PendingScript::Type scriptType) | 437 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* set tings, ScriptState* scriptState, PendingScript::Type scriptType) |
| 373 { | 438 { |
| 374 ASSERT(isMainThread()); | 439 ASSERT(isMainThread()); |
| 375 if (!settings || !settings->v8ScriptStreamingEnabled()) | 440 if (!settings || !settings->v8ScriptStreamingEnabled()) |
| 376 return false; | 441 return false; |
| 442 if (settings->v8ScriptStreamingMode() == ScriptStreamingModeOnlyAsyncAndDefe r | |
| 443 && scriptType == PendingScript::ParsingBlocking) | |
| 444 return false; | |
| 445 | |
| 377 ScriptResource* resource = script.resource(); | 446 ScriptResource* resource = script.resource(); |
| 378 ASSERT(!resource->isLoaded()); | 447 ASSERT(!resource->isLoaded()); |
| 379 if (!resource->url().protocolIsInHTTPFamily()) | 448 if (!resource->url().protocolIsInHTTPFamily()) |
| 380 return false; | 449 return false; |
| 381 if (resource->resourceToRevalidate()) { | 450 if (resource->resourceToRevalidate()) { |
| 382 // This happens e.g., during reloads. We're actually not going to load | 451 // This happens e.g., during reloads. We're actually not going to load |
| 383 // the current Resource of the PendingScript but switch to another | 452 // the current Resource of the PendingScript but switch to another |
| 384 // Resource -> don't stream. | 453 // Resource -> don't stream. |
| 385 return false; | 454 return false; |
| 386 } | 455 } |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 408 return false; | 477 return false; |
| 409 } | 478 } |
| 410 | 479 |
| 411 if (!scriptState->contextIsValid()) | 480 if (!scriptState->contextIsValid()) |
| 412 return false; | 481 return false; |
| 413 ScriptState::Scope scope(scriptState); | 482 ScriptState::Scope scope(scriptState); |
| 414 | 483 |
| 415 // The Resource might go out of scope if the script is no longer needed. We | 484 // The Resource might go out of scope if the script is no longer needed. We |
| 416 // will soon call PendingScript::setStreamer, which makes the PendingScript | 485 // will soon call PendingScript::setStreamer, which makes the PendingScript |
| 417 // notify the ScriptStreamer when it is destroyed. | 486 // notify the ScriptStreamer when it is destroyed. |
| 418 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco ding, scriptType)); | 487 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco ding, scriptType, settings->v8ScriptStreamingMode())); |
| 419 | 488 |
| 420 // Decide what kind of cached data we should produce while streaming. By | 489 // Decide what kind of cached data we should produce while streaming. By |
| 421 // default, we generate the parser cache for streamed scripts, to emulate | 490 // default, we generate the parser cache for streamed scripts, to emulate |
| 422 // the non-streaming behavior (see V8ScriptRunner::compileScript). | 491 // the non-streaming behavior (see V8ScriptRunner::compileScript). |
| 423 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd uceParserCache; | 492 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd uceParserCache; |
| 424 if (settings->v8CacheOptions() == V8CacheOptionsCode) | 493 if (settings->v8CacheOptions() == V8CacheOptionsCode) |
| 425 compileOption = v8::ScriptCompiler::kProduceCodeCache; | 494 compileOption = v8::ScriptCompiler::kProduceCodeCache; |
| 426 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi leOption); | 495 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi leOption); |
| 427 if (scriptStreamingTask) { | 496 if (scriptStreamingTask) { |
| 428 streamer->m_task = adoptPtr(scriptStreamingTask); | 497 streamer->m_task = adoptPtr(scriptStreamingTask); |
| 429 script.setStreamer(streamer.release()); | 498 script.setStreamer(streamer.release()); |
| 430 return true; | 499 return true; |
| 431 } | 500 } |
| 432 // Otherwise, V8 cannot stream the script. | 501 // Otherwise, V8 cannot stream the script. |
| 433 return false; | 502 return false; |
| 434 } | 503 } |
| 435 | 504 |
| 436 } // namespace blink | 505 } // namespace blink |
| OLD | NEW |