|
6 | 6 | #include "DeviceInputTrack.h" |
7 | 7 | #include "MediaTrackGraphImpl.h" |
8 | 8 | #include "StaticComponents.h" |
| 9 | +#include "VideoUtils.h" |
9 | 10 | #include "gmock/gmock.h" |
10 | 11 | #include "gtest/gtest-printers.h" |
11 | 12 | #include "gtest/gtest.h" |
@@ -3642,7 +3643,6 @@ TEST(TestAudioTrackGraph, MessageOrdering) |
3642 | 3643 | InSequence s; |
3643 | 3644 | EXPECT_CALL(checkpoint, Call(StrEq("Prior to tail dispatch"))); |
3644 | 3645 | EXPECT_CALL(*processedTrack, AddListenerImpl); |
3645 | | - EXPECT_CALL(*processedTrack, ProcessInput).Times(AtLeast(0)); |
3646 | 3646 | EXPECT_CALL(checkpoint, Call(StrEq("1-main->graph"))); |
3647 | 3647 | EXPECT_CALL(*processedTrack, ProcessInput).Times(AtLeast(1)); |
3648 | 3648 | EXPECT_CALL(checkpoint, Call(StrEq("processed task on main"))) |
@@ -3861,6 +3861,119 @@ TEST(TestAudioTrackGraph, MessageOrdering) |
3861 | 3861 | (void)WaitFor(destroyPromise).unwrap()[0]; |
3862 | 3862 | } |
3863 | 3863 |
|
| 3864 | +TEST(TestAudioTrackGraph, MessageAtomicity) |
| 3865 | +{ |
| 3866 | + MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual); |
| 3867 | + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); |
| 3868 | + |
| 3869 | + MediaTrackGraphImpl* graph = MediaTrackGraphImpl::GetInstance( |
| 3870 | + MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1, |
| 3871 | + CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false), |
| 3872 | + nullptr, AbstractThread::MainThread()); |
| 3873 | + |
| 3874 | + // Mocks and expectations. |
| 3875 | + RefPtr processedTrack = new MockProcessedMediaTrack(graph->GraphRate()); |
| 3876 | + |
| 3877 | + MockFunction<void(const char* name)> checkpoint; |
| 3878 | + { |
| 3879 | + InSequence s; |
| 3880 | + EXPECT_CALL(*processedTrack, AddListenerImpl); |
| 3881 | + EXPECT_CALL(*processedTrack, ProcessInput).Times(AtLeast(0)); |
| 3882 | + // All "Main" graph tasks dispatch in one group and run atomically. |
| 3883 | + EXPECT_CALL(checkpoint, Call(StrEq("Main"))).Times(500); |
| 3884 | + // All "Other" tq tasks dispatch in one group, then dispatch in one group to |
| 3885 | + // the graph, and run atomically. |
| 3886 | + EXPECT_CALL(checkpoint, Call(StrEq("Other"))).Times(500); |
| 3887 | + EXPECT_CALL(*processedTrack, ProcessInput); |
| 3888 | + EXPECT_CALL(*processedTrack, RemoveListenerImpl); |
| 3889 | + } |
| 3890 | + |
| 3891 | + // Add a track to maintain an output-only audio driver. |
| 3892 | + RefPtr<OnFallbackListener> fallbackListener; |
| 3893 | + DispatchFunction([&] { |
| 3894 | + graph->AddTrack(processedTrack); |
| 3895 | + processedTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr); |
| 3896 | + fallbackListener = new OnFallbackListener(processedTrack); |
| 3897 | + processedTrack->AddListener(fallbackListener); |
| 3898 | + }); |
| 3899 | + |
| 3900 | + RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); |
| 3901 | + while (stream->State().isNothing()) { |
| 3902 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 3903 | + } |
| 3904 | + EXPECT_EQ(*stream->State(), CUBEB_STATE_STARTED); |
| 3905 | + // Wait for the AudioCallbackDriver to come into effect. |
| 3906 | + DispatchFunction([&] { |
| 3907 | + while (fallbackListener->OnFallback()) { |
| 3908 | + EXPECT_EQ(stream->ManualDataCallback(WEBAUDIO_BLOCK_SIZE), |
| 3909 | + MockCubebStream::KeepProcessing::Yes); |
| 3910 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 3911 | + } |
| 3912 | + }); |
| 3913 | + |
| 3914 | + // The graph is now run by ManualDataCallback(). |
| 3915 | + |
| 3916 | + // Run the setup tasks in the graph. They run prior to processing tracks. |
| 3917 | + DispatchFunction([&] { |
| 3918 | + EXPECT_EQ(stream->ManualDataCallback(2 * WEBAUDIO_BLOCK_SIZE), |
| 3919 | + MockCubebStream::KeepProcessing::Yes); |
| 3920 | + }); |
| 3921 | + |
| 3922 | + auto tq = |
| 3923 | + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::WEBRTC_WORKER), |
| 3924 | + __func__, /*aSupportsTailDispatch=*/true); |
| 3925 | + |
| 3926 | + // Dispatch task A to the graph, then task B to another tail-dispatchable |
| 3927 | + // target, then task C to the graph again. Tail dispatch preserves target |
| 3928 | + // dispatch ordering by default, guaranteeing that a task dispatched from B to |
| 3929 | + // the graph cannot run before A. |
| 3930 | + // The graph requires task atomicity however, meaning that A and C must run in |
| 3931 | + // the same graph iteration. |
| 3932 | + // This is hard to test because the graph drains all direct tasks at the end |
| 3933 | + // of the iteration, rather than in between tasks. Do *many* dispatches and |
| 3934 | + // rely on racing with the other target dispatching to the graph, instead. |
| 3935 | + DispatchFunction([&] { |
| 3936 | + for (size_t i = 0; i < 500; ++i) { |
| 3937 | + EXPECT_TRUE(NS_SUCCEEDED(graph->Dispatch( |
| 3938 | + NS_NewRunnableFunction(__func__, [&] { checkpoint.Call("Main"); })))); |
| 3939 | + EXPECT_TRUE( |
| 3940 | + NS_SUCCEEDED(tq->Dispatch(NS_NewRunnableFunction(__func__, [&] { |
| 3941 | + EXPECT_TRUE(NS_SUCCEEDED(graph->Dispatch(NS_NewRunnableFunction( |
| 3942 | + "TaskQueue.Task", [&] { checkpoint.Call("Other"); })))); |
| 3943 | + })))); |
| 3944 | + } |
| 3945 | + }); |
| 3946 | + |
| 3947 | + // Run the dispatched functions with tail dispatch after each. |
| 3948 | + ProcessEventQueue(); |
| 3949 | + |
| 3950 | + // Wait for tq to do all its dispatches. |
| 3951 | + WaitForMirrors(tq); |
| 3952 | + |
| 3953 | + // Run graph runnables, at beginning of iteration. |
| 3954 | + DispatchFunction([&] { |
| 3955 | + EXPECT_EQ(stream->ManualDataCallback(WEBAUDIO_BLOCK_SIZE), |
| 3956 | + MockCubebStream::KeepProcessing::Yes); |
| 3957 | + }); |
| 3958 | + |
| 3959 | + DispatchFunction([&] { |
| 3960 | + processedTrack->RemoveListener(fallbackListener); |
| 3961 | + processedTrack->Destroy(); |
| 3962 | + }); |
| 3963 | + |
| 3964 | + // Process the destroy message and drain the stream. |
| 3965 | + auto destroyPromise = TakeN(cubeb->StreamDestroyEvent(), 1); |
| 3966 | + DispatchFunction([&] { |
| 3967 | + while (stream->ManualDataCallback(0) == |
| 3968 | + MockCubebStream::KeepProcessing::Yes) { |
| 3969 | + } |
| 3970 | + }); |
| 3971 | + // Ensure the stream is no longer used by its MockCubeb before releasing our |
| 3972 | + // reference, and before the next test might ForceSetCubebContext() to |
| 3973 | + // destroy our cubeb. |
| 3974 | + (void)WaitFor(destroyPromise).unwrap()[0]; |
| 3975 | +} |
| 3976 | + |
3864 | 3977 | #undef InvokeAsync |
3865 | 3978 | #undef TEST_WithTailDispatch |
3866 | 3979 | #undef DispatchFunction |
|
0 commit comments