Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: Source/modules/fetch/DataConsumerTeeTest.cpp

Issue 1195563002: Introduce DataConsumerHandleTee (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 "modules/fetch/DataConsumerTee.h"
7
8 #include "bindings/core/v8/ScriptState.h"
9 #include "core/testing/DummyPageHolder.h"
10 #include "core/testing/NullExecutionContext.h"
11 #include "gin/public/isolate_holder.h"
12 #include "platform/Task.h"
13 #include "platform/ThreadSafeFunctional.h"
14 #include "platform/WebThreadSupportingGC.h"
15 #include "public/platform/Platform.h"
16 #include "public/platform/WebThread.h"
17 #include "public/platform/WebTraceLocation.h"
18 #include "public/platform/WebWaitableEvent.h"
19 #include "wtf/Deque.h"
20 #include "wtf/PassRefPtr.h"
21 #include "wtf/RefPtr.h"
22 #include "wtf/ThreadSafeRefCounted.h"
23 #include "wtf/ThreadingPrimitives.h"
24 #include "wtf/Vector.h"
25
26 #include <gtest/gtest.h>
27 #include <string.h>
28 #include <v8.h>
29
30 namespace blink {
31 namespace {
32
33 using Result = WebDataConsumerHandle::Result;
34 const WebDataConsumerHandle::Flags kNone = WebDataConsumerHandle::FlagNone;
35 const Result kOk = WebDataConsumerHandle::Ok;
36 const Result kShouldWait = WebDataConsumerHandle::ShouldWait;
37 const Result kDone = WebDataConsumerHandle::Done;
38 const Result kUnexpectedError = WebDataConsumerHandle::UnexpectedError;
39
40 class Command final {
41 public:
42 enum Name {
43 Data,
44 Done,
45 Error,
46 Wait,
47 };
48
49 Command(Name name) : m_name(name) { }
50 Command(Name name, const Vector<char>& body) : m_name(name), m_body(body) { }
51 Command(Name name, const char* body, size_t size) : m_name(name)
52 {
53 m_body.append(body, size);
54 }
55 Command(Name name, const char* body) : Command(name, body, strlen(body)) { }
56 Name name() const { return m_name; }
57 const Vector<char>& body() const { return m_body; }
58
59 private:
60 const Name m_name;
61 Vector<char> m_body;
62 };
63
64 // Handle stores commands via |add| and replays the stored commends when read.
65 class Handle final : public WebDataConsumerHandle {
66 public:
67 class Context final : public ThreadSafeRefCounted<Context> {
68 public:
69 static PassRefPtr<Context> create() { return adoptRef(new Context); }
70
71 // This function cannot be called after creating a tee.
72 void add(const Command& command)
73 {
74 MutexLocker locker(m_mutex);
75 m_commands.append(command);
76 }
77
78 void attachReader(WebDataConsumerHandle::Client* client)
79 {
80 MutexLocker locker(m_mutex);
81 ASSERT(!m_readerThread);
82 ASSERT(!m_client);
83 m_readerThread = Platform::current()->currentThread();
84 m_client = client;
85
86 if (m_client && !(isEmpty() && m_result == kShouldWait))
87 notify();
88 }
89 void detachReader()
90 {
91 MutexLocker locker(m_mutex);
92 ASSERT(m_readerThread && m_readerThread->isCurrentThread());
93 m_readerThread = nullptr;
94 m_client = nullptr;
95 if (!m_isHandleAttached)
96 m_detached->signal();
97 }
98
99 void detachHandle()
100 {
101 MutexLocker locker(m_mutex);
102 m_isHandleAttached = false;
103 if (!m_readerThread)
104 m_detached->signal();
105 }
106
107 Result beginRead(const void** buffer, Flags, size_t* available)
108 {
109 MutexLocker locker(m_mutex);
110 *buffer = nullptr;
111 *available = 0;
112 if (isEmpty())
113 return m_result;
114
115 const Command& command = top();
116 Result result = Ok;
117 switch (command.name()) {
118 case Command::Data: {
119 auto& body = command.body();
120 *available = body.size() - offset();
121 *buffer = body.data() + offset();
122 result = Ok;
123 break;
124 }
125 case Command::Done:
126 m_result = result = Done;
127 consume(0);
128 break;
129 case Command::Wait:
130 consume(0);
131 result = ShouldWait;
132 notify();
133 break;
134 case Command::Error:
135 m_result = result = UnexpectedError;
136 consume(0);
137 break;
138 }
139 return result;
140 }
141 Result endRead(size_t readSize)
142 {
143 MutexLocker locker(m_mutex);
144 consume(readSize);
145 return Ok;
146 }
147
148 WebWaitableEvent* detached() { return m_detached.get(); }
149
150 private:
151 Context()
152 : m_offset(0)
153 , m_readerThread(nullptr)
154 , m_client(nullptr)
155 , m_result(ShouldWait)
156 , m_isHandleAttached(true)
157 , m_detached(adoptPtr(Platform::current()->createWaitableEvent()))
158 {
159 }
160
161 bool isEmpty() const { return m_commands.isEmpty(); }
162 const Command& top()
163 {
164 ASSERT(!isEmpty());
165 return m_commands.first();
166 }
167
168 void consume(size_t size)
169 {
170 ASSERT(!isEmpty());
171 ASSERT(size + m_offset <= top().body().size());
172 bool fullyConsumed = (size + m_offset >= top().body().size());
173 if (fullyConsumed) {
174 m_offset = 0;
175 m_commands.removeFirst();
176 } else {
177 m_offset += size;
178 }
179 }
180
181 size_t offset() const { return m_offset; }
182
183 void notify()
184 {
185 if (!m_client)
186 return;
187 ASSERT(m_readerThread);
188 m_readerThread->postTask(FROM_HERE, new Task(threadSafeBind(&Context ::notifyInternal, this)));
189 }
190
191 void notifyInternal()
192 {
193 {
194 MutexLocker locker(m_mutex);
195 if (!m_client || !m_readerThread->isCurrentThread()) {
196 // There is no client, or a new reader is attached.
197 return;
198 }
199 }
200 // The reading thread is the current thread.
201 m_client->didGetReadable();
202 }
203
204 Deque<Command> m_commands;
205 size_t m_offset;
206 WebThread* m_readerThread;
207 Client* m_client;
208 Result m_result;
209 bool m_isHandleAttached;
210 Mutex m_mutex;
211 OwnPtr<WebWaitableEvent> m_detached;
212 };
213
214 class ReaderImpl final : public Reader {
215 public:
216 ReaderImpl(PassRefPtr<Context> context, Client* client)
217 : m_context(context)
218 {
219 m_context->attachReader(client);
220 }
221 ~ReaderImpl()
222 {
223 m_context->detachReader();
224 }
225
226 Result read(void* buffer, size_t size, Flags flags, size_t* readSize) ov erride
227 {
228 const void* src = nullptr;
229 Result result = beginRead(&src, flags, readSize);
230 if (result != Ok)
231 return result;
232 *readSize = std::min(*readSize, size);
233 memcpy(buffer, src, *readSize);
234 return endRead(*readSize);
235 }
236 Result beginRead(const void** buffer, Flags flags, size_t* available) ov erride
237 {
238 return m_context->beginRead(buffer, flags, available);
239 }
240 Result endRead(size_t readSize) override
241 {
242 return m_context->endRead(readSize);
243 }
244
245 private:
246 RefPtr<Context> m_context;
247 };
248
249 Handle() : m_context(Context::create()) { }
250 ~Handle()
251 {
252 m_context->detachHandle();
253 }
254
255 ReaderImpl* obtainReaderInternal(Client* client) override { return new Reade rImpl(m_context, client); }
256
257 // Add a command to this handle. This function must be called on the
258 // creator thread. This function must be called BEFORE any reader is
259 // obtained.
260 void add(const Command& command)
261 {
262 m_context->add(command);
263 }
264
265 Context* context() { return m_context.get(); };
266
267 private:
268 RefPtr<Context> m_context;
269 };
270
271 class TestingThread final {
272 public:
273 explicit TestingThread(const char* name)
274 : m_thread(WebThreadSupportingGC::create(name))
275 , m_waitableEvent(adoptPtr(Platform::current()->createWaitableEvent()))
276 {
277 m_thread->postTask(FROM_HERE, new Task(threadSafeBind(&TestingThread::in itialize, AllowCrossThreadAccess(this))));
278 m_waitableEvent->wait();
279 }
280
281 ~TestingThread()
282 {
283 m_thread->postTask(FROM_HERE, new Task(threadSafeBind(&TestingThread::sh utdown, AllowCrossThreadAccess(this))));
284 m_waitableEvent->wait();
285 }
286
287 WebThreadSupportingGC* thread() { return m_thread.get(); }
288 ExecutionContext* executionContext() { return m_executionContext.get(); }
289 ScriptState* scriptState() { return m_scriptState.get(); }
290 v8::Isolate* isolate() { return m_isolateHolder->isolate(); }
291
292 private:
293 void initialize()
294 {
295 m_isolateHolder = adoptPtr(new gin::IsolateHolder());
296 m_thread->initialize();
297 isolate()->Enter();
298 v8::HandleScope handleScope(isolate());
299 v8::Local<v8::Context> context = v8::Context::New(isolate());
300 m_scriptState = ScriptState::create(context, DOMWrapperWorld::create(iso late()));
301 m_executionContext = adoptRefWillBeNoop(new NullExecutionContext());
302 m_waitableEvent->signal();
303 }
304
305 void shutdown()
306 {
307 m_executionContext = nullptr;
308 m_scriptState = nullptr;
309 isolate()->Exit();
310 m_thread->shutdown();
311 m_isolateHolder = nullptr;
312 m_waitableEvent->signal();
313 }
314
315 OwnPtr<WebThreadSupportingGC> m_thread;
316 OwnPtr<WebWaitableEvent> m_waitableEvent;
317 RefPtrWillBePersistent<NullExecutionContext> m_executionContext;
318 OwnPtr<gin::IsolateHolder> m_isolateHolder;
319 RefPtr<ScriptState> m_scriptState;
320 };
321
322 class HandleReader : public WebDataConsumerHandle::Client {
323 public:
324 HandleReader() : m_finalResult(kOk) { }
325
326 // Need to wait for the event signal after this function is called.
327 void start(PassOwnPtr<WebDataConsumerHandle> handle)
328 {
329 m_thread = adoptPtr(new TestingThread("reading thread"));
330 m_waitableEvent = adoptPtr(Platform::current()->createWaitableEvent());
331 m_thread->thread()->postTask(FROM_HERE, new Task(threadSafeBind(&HandleR eader::obtainReader, AllowCrossThreadAccess(this), handle)));
332 }
333
334 void didGetReadable() override
335 {
336 Result r = kOk;
337 char buffer[3];
338 while (true) {
339 size_t size;
340 r = m_reader->read(buffer, sizeof(buffer), kNone, &size);
341 if (r == kShouldWait)
342 return;
343 if (r != kOk)
344 break;
345 m_readString.append(String(buffer, size));
346 }
347 m_finalResult = r;
348 m_reader = nullptr;
349 m_waitableEvent->signal();
350 }
351
352 WebWaitableEvent* waitableEvent() { return m_waitableEvent.get(); }
353
354 // These should be accessed after the thread joins.
355 const String& readString() const { return m_readString; }
356 Result finalResult() const { return m_finalResult; }
357
358 private:
359 void obtainReader(PassOwnPtr<WebDataConsumerHandle> handle)
360 {
361 m_reader = handle->obtainReader(this);
362 }
363
364 OwnPtr<TestingThread> m_thread;
365 OwnPtr<WebDataConsumerHandle::Reader> m_reader;
366 String m_readString;
367 Result m_finalResult;
368 OwnPtr<WebWaitableEvent> m_waitableEvent;
369 };
370
371 class HandleTwoPhaseReader : public WebDataConsumerHandle::Client {
372 public:
373 HandleTwoPhaseReader() : m_finalResult(kOk) { }
374
375 // Need to wait for the event signal after this function is called.
376 void start(PassOwnPtr<WebDataConsumerHandle> handle)
377 {
378 m_thread = adoptPtr(new TestingThread("reading thread"));
379 m_waitableEvent = adoptPtr(Platform::current()->createWaitableEvent());
380 m_thread->thread()->postTask(FROM_HERE, new Task(threadSafeBind(&HandleT woPhaseReader::obtainReader, AllowCrossThreadAccess(this), handle)));
381 }
382
383 void didGetReadable() override
384 {
385 Result r = kOk;
386 while (true) {
387 const void* buffer = nullptr;
388 size_t size;
389 r = m_reader->beginRead(&buffer, kNone, &size);
390 if (r == kShouldWait)
391 return;
392 if (r != kOk)
393 break;
394 // Read smaller than availabe in order to test |endRead|.
395 size_t readSize = std::max(size * 2 / 3, static_cast<size_t>(1));
396 m_readString.append(String(static_cast<const char*>(buffer), readSiz e));
397 m_reader->endRead(readSize);
398 }
399 m_finalResult = r;
400 m_reader = nullptr;
401 m_waitableEvent->signal();
402 }
403
404 WebWaitableEvent* waitableEvent() { return m_waitableEvent.get(); }
405
406 // These should be accessed after the thread joins.
407 const String& readString() const { return m_readString; }
408 Result finalResult() const { return m_finalResult; }
409
410 private:
411 void obtainReader(PassOwnPtr<WebDataConsumerHandle> handle)
412 {
413 m_reader = handle->obtainReader(this);
414 }
415
416 OwnPtr<TestingThread> m_thread;
417 OwnPtr<WebDataConsumerHandle::Reader> m_reader;
418 String m_readString;
419 Result m_finalResult;
420 OwnPtr<WebWaitableEvent> m_waitableEvent;
421 };
422
423 class TeeCreationThread {
424 public:
425 void run(PassOwnPtr<WebDataConsumerHandle> src, OwnPtr<WebDataConsumerHandle >* dest1, OwnPtr<WebDataConsumerHandle>* dest2)
426 {
427 m_thread = adoptPtr(new TestingThread("src thread"));
428 m_waitableEvent = adoptPtr(Platform::current()->createWaitableEvent());
429 m_thread->thread()->postTask(FROM_HERE, new Task(threadSafeBind(&TeeCrea tionThread::runInternal, AllowCrossThreadAccess(this), src, AllowCrossThreadAcce ss(dest1), AllowCrossThreadAccess(dest2))));
430 m_waitableEvent->wait();
431 }
432
433 TestingThread* thread() { return m_thread.get(); }
434
435 private:
436 void runInternal(PassOwnPtr<WebDataConsumerHandle> src, OwnPtr<WebDataConsum erHandle>* dest1, OwnPtr<WebDataConsumerHandle>* dest2)
437 {
438 DataConsumerTee::create(m_thread->executionContext(), src, dest1, dest2) ;
439 m_waitableEvent->signal();
440 }
441
442 OwnPtr<TestingThread> m_thread;
443 OwnPtr<WebWaitableEvent> m_waitableEvent;
444 };
445
446 TEST(DataConsumerTeeTest, CreateDone)
447 {
448 OwnPtr<Handle> src(adoptPtr(new Handle));
449 OwnPtr<WebDataConsumerHandle> dest1, dest2;
450
451 src->add(Command(Command::Done));
452
453 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
454 t->run(src.release(), &dest1, &dest2);
455
456 ASSERT_TRUE(dest1);
457 ASSERT_TRUE(dest2);
458
459 HandleReader r1, r2;
460 r1.start(dest1.release());
461 r2.start(dest2.release());
462
463 r1.waitableEvent()->wait();
464 r2.waitableEvent()->wait();
465
466 EXPECT_EQ(kDone, r1.finalResult());
467 EXPECT_EQ(String(), r1.readString());
468
469 EXPECT_EQ(kDone, r2.finalResult());
470 EXPECT_EQ(String(), r2.readString());
471 }
472
473 TEST(DataConsumerTeeTest, Read)
474 {
475 OwnPtr<Handle> src(adoptPtr(new Handle));
476 OwnPtr<WebDataConsumerHandle> dest1, dest2;
477
478 src->add(Command(Command::Wait));
479 src->add(Command(Command::Data, "hello, "));
480 src->add(Command(Command::Wait));
481 src->add(Command(Command::Data, "world"));
482 src->add(Command(Command::Wait));
483 src->add(Command(Command::Wait));
484 src->add(Command(Command::Done));
485
486 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
487 t->run(src.release(), &dest1, &dest2);
488
489 ASSERT_TRUE(dest1);
490 ASSERT_TRUE(dest2);
491
492 HandleReader r1, r2;
493 r1.start(dest1.release());
494 r2.start(dest2.release());
495
496 r1.waitableEvent()->wait();
497 r2.waitableEvent()->wait();
498
499 EXPECT_EQ(kDone, r1.finalResult());
500 EXPECT_EQ("hello, world", r1.readString());
501
502 EXPECT_EQ(kDone, r2.finalResult());
503 EXPECT_EQ("hello, world", r2.readString());
504 }
505
506 TEST(DataConsumerTeeTest, TwoPhaseRead)
507 {
508 OwnPtr<Handle> src(adoptPtr(new Handle));
509 OwnPtr<WebDataConsumerHandle> dest1, dest2;
510
511 src->add(Command(Command::Wait));
512 src->add(Command(Command::Data, "hello, "));
513 src->add(Command(Command::Wait));
514 src->add(Command(Command::Wait));
515 src->add(Command(Command::Wait));
516 src->add(Command(Command::Data, "world"));
517 src->add(Command(Command::Wait));
518 src->add(Command(Command::Done));
519
520 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
521 t->run(src.release(), &dest1, &dest2);
522
523 ASSERT_TRUE(dest1);
524 ASSERT_TRUE(dest2);
525
526 HandleTwoPhaseReader r1, r2;
527 r1.start(dest1.release());
528 r2.start(dest2.release());
529
530 r1.waitableEvent()->wait();
531 r2.waitableEvent()->wait();
532
533 EXPECT_EQ(kDone, r1.finalResult());
534 EXPECT_EQ("hello, world", r1.readString());
535
536 EXPECT_EQ(kDone, r2.finalResult());
537 EXPECT_EQ("hello, world", r2.readString());
538 }
539
540 TEST(DataConsumerTeeTest, Error)
541 {
542 OwnPtr<Handle> src(adoptPtr(new Handle));
543 OwnPtr<WebDataConsumerHandle> dest1, dest2;
544
545 src->add(Command(Command::Data, "hello, "));
546 src->add(Command(Command::Data, "world"));
547 src->add(Command(Command::Error));
548
549 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
550 t->run(src.release(), &dest1, &dest2);
551
552 ASSERT_TRUE(dest1);
553 ASSERT_TRUE(dest2);
554
555 HandleReader r1, r2;
556 r1.start(dest1.release());
557 r2.start(dest2.release());
558
559 r1.waitableEvent()->wait();
560 r2.waitableEvent()->wait();
561
562 EXPECT_EQ(kUnexpectedError, r1.finalResult());
563 EXPECT_EQ(kUnexpectedError, r2.finalResult());
564 }
565
566 void postStop(TestingThread* thread)
567 {
568 thread->executionContext()->stopActiveDOMObjects();
569 }
570
571 TEST(DataConsumerTeeTest, StopSource)
572 {
573 OwnPtr<Handle> src(adoptPtr(new Handle));
574 OwnPtr<WebDataConsumerHandle> dest1, dest2;
575
576 src->add(Command(Command::Data, "hello, "));
577 src->add(Command(Command::Data, "world"));
578
579 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
580 t->run(src.release(), &dest1, &dest2);
581
582 ASSERT_TRUE(dest1);
583 ASSERT_TRUE(dest2);
584
585 HandleReader r1, r2;
586 r1.start(dest1.release());
587 r2.start(dest2.release());
588
589 // We can pass a raw pointer because the subsequent |wait| calls ensure
590 // t->thread() is alive.
591 t->thread()->thread()->postTask(FROM_HERE, new Task(threadSafeBind(postStop, AllowCrossThreadAccess(t->thread()))));
592
593 r1.waitableEvent()->wait();
594 r2.waitableEvent()->wait();
595
596 EXPECT_EQ(kUnexpectedError, r1.finalResult());
597 EXPECT_EQ(kUnexpectedError, r2.finalResult());
598 }
599
600 TEST(DataConsumerTeeTest, DetachSource)
601 {
602 OwnPtr<Handle> src(adoptPtr(new Handle));
603 OwnPtr<WebDataConsumerHandle> dest1, dest2;
604
605 src->add(Command(Command::Data, "hello, "));
606 src->add(Command(Command::Data, "world"));
607
608 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
609 t->run(src.release(), &dest1, &dest2);
610
611 ASSERT_TRUE(dest1);
612 ASSERT_TRUE(dest2);
613
614 HandleReader r1, r2;
615 r1.start(dest1.release());
616 r2.start(dest2.release());
617
618 t = nullptr;
619
620 r1.waitableEvent()->wait();
621 r2.waitableEvent()->wait();
622
623 EXPECT_EQ(kUnexpectedError, r1.finalResult());
624 EXPECT_EQ(kUnexpectedError, r2.finalResult());
625 }
626
627 TEST(DataConsumerTeeTest, DetachSourceAfterReadingDone)
628 {
629 OwnPtr<Handle> src(adoptPtr(new Handle));
630 OwnPtr<WebDataConsumerHandle> dest1, dest2;
631
632 src->add(Command(Command::Data, "hello, "));
633 src->add(Command(Command::Data, "world"));
634 src->add(Command(Command::Done));
635
636 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
637 t->run(src.release(), &dest1, &dest2);
638
639 ASSERT_TRUE(dest1);
640 ASSERT_TRUE(dest2);
641
642 HandleReader r1, r2;
643 r1.start(dest1.release());
644 r1.waitableEvent()->wait();
645
646 EXPECT_EQ(kDone, r1.finalResult());
647 EXPECT_EQ("hello, world", r1.readString());
648
649 t = nullptr;
650
651 r2.start(dest2.release());
652 r2.waitableEvent()->wait();
653
654 EXPECT_EQ(kDone, r2.finalResult());
655 EXPECT_EQ("hello, world", r2.readString());
656 }
657
658 TEST(DataConsumerTeeTest, DetachOneDestination)
659 {
660 OwnPtr<Handle> src(adoptPtr(new Handle));
661 OwnPtr<WebDataConsumerHandle> dest1, dest2;
662
663 src->add(Command(Command::Data, "hello, "));
664 src->add(Command(Command::Data, "world"));
665 src->add(Command(Command::Done));
666
667 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
668 t->run(src.release(), &dest1, &dest2);
669
670 ASSERT_TRUE(dest1);
671 ASSERT_TRUE(dest2);
672
673 dest1 = nullptr;
674
675 HandleReader r2;
676 r2.start(dest2.release());
677 r2.waitableEvent()->wait();
678
679 EXPECT_EQ(kDone, r2.finalResult());
680 EXPECT_EQ("hello, world", r2.readString());
681 }
682
683 TEST(DataConsumerTeeTest, DetachBothDestinationsShouldStopSourceReader)
684 {
685 OwnPtr<Handle> src(adoptPtr(new Handle));
686 RefPtr<Handle::Context> context(src->context());
687 OwnPtr<WebDataConsumerHandle> dest1, dest2;
688
689 src->add(Command(Command::Data, "hello, "));
690 src->add(Command(Command::Data, "world"));
691
692 OwnPtr<TeeCreationThread> t = adoptPtr(new TeeCreationThread());
693 t->run(src.release(), &dest1, &dest2);
694
695 ASSERT_TRUE(dest1);
696 ASSERT_TRUE(dest2);
697
698 dest1 = nullptr;
699 dest2 = nullptr;
700
701 // Collect garbage to finalize the source reader.
702 Heap::collectAllGarbage();
703 context->detached()->wait();
704 }
705
706 } // namespace
707 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698