Fix race condition in unsignaled stream creation when worker!=network

In `WebRtcVoiceReceiveChannel::MaybeCreateDefaultReceiveStream`,
concurrent incoming RTP packets for an unsignaled SSRC could queue up
multiple tasks with `sink = nullptr` on the Worker thread. This was
detected here:

https://ci.chromium.org/ui/p/webrtc/builders/try/win_x86_more_configs/73984/overview

Bug: webrtc:42222117
Change-Id: I17e3f30d8f308ce7ce32bf8c4405d9f1b925fac8
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/476641
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47857}
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 02d0459..7c09cb9 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -2630,11 +2630,16 @@
 bool WebRtcVoiceReceiveChannel::MaybeCreateDefaultReceiveStream(
     const RtpPacketReceived& packet) {
   RTC_DCHECK_RUN_ON(worker_thread_);
+  uint32_t ssrc = packet.Ssrc();
+
+  if (absl::c_linear_search(unsignaled_recv_ssrcs_, ssrc)) {
+    RTC_LOG(LS_INFO) << "Unsignaled stream already exists for SSRC=" << ssrc;
+    return true;
+  }
+
   // Create an unsignaled receive stream for this previously not received
   // ssrc. If there already is N unsignaled receive streams, delete the
   // oldest. See: https://bugs.chromium.org/p/webrtc/issues/detail?id=5208
-  uint32_t ssrc = packet.Ssrc();
-  RTC_DCHECK(!absl::c_linear_search(unsignaled_recv_ssrcs_, ssrc));
 
   // Add new stream.
   StreamParams sp = unsignaled_stream_params_;
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 867ec7f..3622e7e 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -3975,6 +3975,7 @@
       "../rtc_base:net_helper",
       "../rtc_base:random",
       "../rtc_base:rtc_base_tests_utils",
+      "../rtc_base:rtc_event",
       "../rtc_base:socket_address",
       "../rtc_base:ssl",
       "../rtc_base:ssl_adapter",
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index bebbd0c..c64467e 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -77,6 +77,7 @@
 #include "pc/test/fake_periodic_video_source.h"
 #include "pc/test/integration_test_helpers.h"
 #include "pc/test/mock_peer_connection_observers.h"
+#include "rtc_base/event.h"
 #include "rtc_base/fake_mdns_responder.h"
 #include "rtc_base/firewall_socket_server.h"
 #include "rtc_base/logging.h"
@@ -2944,6 +2945,61 @@
   EXPECT_EQ(RtpSourceType::SSRC, sources[0].source_type());
 }
 
+TEST_P(PeerConnectionIntegrationTest, ConcurrentUnsignaledSsrcPackets) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioTrack();
+  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
+              IsRtcOk());
+
+  // Wait until the connection is established.
+  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());
+
+  // Drop all subsequent packets temporarily so we can coordinate the
+  // concurrency.
+  firewall()->AddRule(false);
+
+  // Block the shared worker thread.
+  Event worker_blocked;
+  Event worker_continue;
+  callee()->pc_internal()->worker_thread()->PostTask(
+      [&worker_blocked, &worker_continue] {
+        worker_blocked.Set();
+        worker_continue.Wait(Event::kForever);
+      });
+  worker_blocked.Wait(Event::kForever);
+
+  uint32_t initial_sent = virtual_socket_server()->sent_packets();
+
+  // Clear the firewall rules to allow packets to flow again.
+  // Since the worker thread is blocked, incoming RTP packets on the network
+  // thread will result in tasks posted to the worker thread to handle the
+  // unsignaled SSRC packet (specifically to create the default receive stream).
+  firewall()->ClearRules();
+
+  // Wait until at least 2 packets are sent through the virtual socket server.
+  ASSERT_THAT(WaitUntil(
+                  [&] {
+                    return virtual_socket_server()->sent_packets() -
+                           initial_sent;
+                  },
+                  ::testing::Ge(2u)),
+              IsRtcOk());
+
+  // Let the worker thread resume. It will process the queued tasks.
+  // The first task will create the default stream, and subsequent tasks
+  // for the same SSRC will discover that the stream already exists or is
+  // being created, and safely skip creation.
+  worker_continue.Set();
+
+  // Wait deterministically for the packets to be delivered and processed.
+  MediaExpectations media_expectations;
+  media_expectations.CalleeExpectsSomeAudio(1);
+  ASSERT_TRUE(ExpectNewFrames(media_expectations));
+}
+
 TEST_P(PeerConnectionIntegrationTest, UnsignaledSsrcGetSourcesAudio) {
   ASSERT_TRUE(CreatePeerConnectionWrappers());
   ConnectFakeSignaling();