Skip to content

Commit 97528db

Browse files
author
Christian Fremerey
committed
[Video Capture Service] Allow buffer retired while still in use in BroadcastingReceiver
In the recently added class BroadcastingReceiver for enabling multi-client access to video capture devices, see design doc [1], an assumption was made that producers never retire buffers while they are still in use. Even though this assumption may have held at some point during development, a test failure at a CL [2] that moves the service to the browser process on ChromeOS revealed that it no longer holds. This CL removes this assumption and adds correct handling of the case. [1] https://docs.google.com/document/d/1mYnsZfLBRmbsDpUtfb6C7dzhfw2Kcxg_-uiG_6MnWVQ/edit?usp=sharing [2] https://chromium-review.googlesource.com/c/chromium/src/+/1506604/1 test: services_unittests --gtest_filter=BroadcastingReceiverTest.* test: services_unittests --gtest_filter=MockVideoCaptureDeviceSharedAccessTest.* test: content_browsertests --gtest_filter=WebRtcVideoCaptureServiceBrowserTest.* Bug: 783442, 939587 Change-Id: I9d27c69386e76c2ea472f8549721fa5cf890ae61 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1509902 Reviewed-by: Robert Sesek <[email protected]> Reviewed-by: Emircan Uysaler <[email protected]> Commit-Queue: Christian Fremerey <[email protected]> Cr-Original-Commit-Position: refs/heads/master@{#639747}(cherry picked from commit ce0b05d2aa49a6b22c1033c4d1081a3d923248a7) Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1520348 Reviewed-by: Christian Fremerey <[email protected]> Cr-Commit-Position: refs/branch-heads/3729@{#85} Cr-Branched-From: d4a8972-refs/heads/master@{#638880}
1 parent f45c768 commit 97528db

File tree

6 files changed

+206
-30
lines changed

6 files changed

+206
-30
lines changed

services/video_capture/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ source_set("tests") {
7676
testonly = true
7777

7878
sources = [
79+
"broadcasting_receiver_unittest.cc",
7980
"device_media_to_mojo_adapter_unittest.cc",
8081
"test/device_factory_provider_connectortest.cc",
8182
"test/device_factory_provider_test.cc",

services/video_capture/broadcasting_receiver.cc

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ BroadcastingReceiver::BufferContext::BufferContext(
9595
media::mojom::VideoBufferHandlePtr buffer_handle)
9696
: buffer_id_(buffer_id),
9797
buffer_handle_(std::move(buffer_handle)),
98-
consumer_hold_count_(0) {}
98+
consumer_hold_count_(0),
99+
is_retired_(false) {
100+
static int next_buffer_context_id = 0;
101+
buffer_context_id_ = next_buffer_context_id++;
102+
}
99103

100104
BroadcastingReceiver::BufferContext::~BufferContext() = default;
101105

@@ -233,8 +237,10 @@ int32_t BroadcastingReceiver::AddClient(
233237
}
234238

235239
for (auto& buffer_context : buffer_contexts_) {
240+
if (buffer_context.is_retired())
241+
continue;
236242
added_client_context.client()->OnNewBuffer(
237-
buffer_context.buffer_id(),
243+
buffer_context.buffer_context_id(),
238244
buffer_context.CloneBufferHandle(
239245
added_client_context.target_buffer_type()));
240246
}
@@ -260,11 +266,13 @@ void BroadcastingReceiver::OnNewBuffer(
260266
int32_t buffer_id,
261267
media::mojom::VideoBufferHandlePtr buffer_handle) {
262268
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
269+
CHECK(FindUnretiredBufferContextFromBufferId(buffer_id) ==
270+
buffer_contexts_.end());
263271
buffer_contexts_.emplace_back(buffer_id, std::move(buffer_handle));
264272
auto& buffer_context = buffer_contexts_.back();
265273
for (auto& client : clients_) {
266274
client.second.client()->OnNewBuffer(
267-
buffer_context.buffer_id(),
275+
buffer_context.buffer_context_id(),
268276
buffer_context.CloneBufferHandle(client.second.target_buffer_type()));
269277
}
270278
}
@@ -277,7 +285,9 @@ void BroadcastingReceiver::OnFrameReadyInBuffer(
277285
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
278286
if (clients_.empty())
279287
return;
280-
auto& buffer_context = LookupBufferContextFromBufferId(buffer_id);
288+
auto buffer_context_iter = FindUnretiredBufferContextFromBufferId(buffer_id);
289+
CHECK(buffer_context_iter != buffer_contexts_.end());
290+
auto& buffer_context = *buffer_context_iter;
281291
buffer_context.set_access_permission(std::move(access_permission));
282292
for (auto& client : clients_) {
283293
if (client.second.is_suspended())
@@ -286,27 +296,29 @@ void BroadcastingReceiver::OnFrameReadyInBuffer(
286296
mojo::MakeStrongBinding(
287297
std::make_unique<ConsumerAccessPermission>(base::BindOnce(
288298
&BroadcastingReceiver::OnClientFinishedConsumingFrame,
289-
weak_factory_.GetWeakPtr(), buffer_context.buffer_id())),
299+
weak_factory_.GetWeakPtr(), buffer_context.buffer_context_id())),
290300
mojo::MakeRequest(&consumer_access_permission));
291301
client.second.client()->OnFrameReadyInBuffer(
292-
buffer_context.buffer_id(), frame_feedback_id,
302+
buffer_context.buffer_context_id(), frame_feedback_id,
293303
std::move(consumer_access_permission), frame_info.Clone());
294304
buffer_context.IncreaseConsumerCount();
295305
}
296306
}
297307

298308
void BroadcastingReceiver::OnBufferRetired(int32_t buffer_id) {
299309
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
300-
auto context_iter =
301-
std::find_if(buffer_contexts_.begin(), buffer_contexts_.end(),
302-
[buffer_id](const BufferContext& entry) {
303-
return entry.buffer_id() == buffer_id;
304-
});
305-
auto& buffer_context = *context_iter;
306-
CHECK(!buffer_context.IsStillBeingConsumed());
307-
buffer_contexts_.erase(context_iter);
310+
auto buffer_context_iter = FindUnretiredBufferContextFromBufferId(buffer_id);
311+
CHECK(buffer_context_iter != buffer_contexts_.end());
312+
const auto context_id = buffer_context_iter->buffer_context_id();
313+
if (buffer_context_iter->IsStillBeingConsumed())
314+
// Mark the buffer context as retired but keep holding on to it until the
315+
// last client finished consuming it, because it contains the
316+
// |access_permission| required during consumption.
317+
buffer_context_iter->set_retired();
318+
else
319+
buffer_contexts_.erase(buffer_context_iter);
308320
for (auto& client : clients_) {
309-
client.second.client()->OnBufferRetired(buffer_id);
321+
client.second.client()->OnBufferRetired(context_id);
310322
}
311323
}
312324

@@ -367,26 +379,36 @@ void BroadcastingReceiver::OnStopped() {
367379
}
368380
}
369381

370-
void BroadcastingReceiver::OnClientFinishedConsumingFrame(int32_t buffer_id) {
382+
void BroadcastingReceiver::OnClientFinishedConsumingFrame(
383+
int32_t buffer_context_id) {
371384
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
372-
auto& buffer_context = LookupBufferContextFromBufferId(buffer_id);
373-
buffer_context.DecreaseConsumerCount();
385+
auto buffer_context_iter =
386+
std::find_if(buffer_contexts_.begin(), buffer_contexts_.end(),
387+
[buffer_context_id](const BufferContext& entry) {
388+
return entry.buffer_context_id() == buffer_context_id;
389+
});
390+
CHECK(buffer_context_iter != buffer_contexts_.end());
391+
buffer_context_iter->DecreaseConsumerCount();
392+
if (buffer_context_iter->is_retired() &&
393+
!buffer_context_iter->IsStillBeingConsumed()) {
394+
buffer_contexts_.erase(buffer_context_iter);
395+
}
374396
}
375397

376398
void BroadcastingReceiver::OnClientDisconnected(int32_t client_id) {
377399
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
378400
clients_.erase(client_id);
379401
}
380402

381-
BroadcastingReceiver::BufferContext&
382-
BroadcastingReceiver::LookupBufferContextFromBufferId(int32_t buffer_id) {
403+
std::vector<BroadcastingReceiver::BufferContext>::iterator
404+
BroadcastingReceiver::FindUnretiredBufferContextFromBufferId(
405+
int32_t buffer_id) {
383406
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
384-
auto context_iter =
385-
std::find_if(buffer_contexts_.begin(), buffer_contexts_.end(),
386-
[buffer_id](const BufferContext& entry) {
387-
return entry.buffer_id() == buffer_id;
388-
});
389-
return *context_iter;
407+
return std::find_if(buffer_contexts_.begin(), buffer_contexts_.end(),
408+
[buffer_id](const BufferContext& entry) {
409+
return !entry.is_retired() &&
410+
entry.buffer_id() == buffer_id;
411+
});
390412
}
391413

392414
} // namespace video_capture

services/video_capture/broadcasting_receiver.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class BroadcastingReceiver : public mojom::Receiver {
103103
~BufferContext();
104104
BufferContext(BufferContext&& other);
105105
BufferContext& operator=(BufferContext&& other);
106+
int32_t buffer_context_id() const { return buffer_context_id_; }
106107
int32_t buffer_id() const { return buffer_id_; }
107108
void set_access_permission(
108109
mojom::ScopedAccessPermissionPtr access_permission) {
@@ -111,6 +112,8 @@ class BroadcastingReceiver : public mojom::Receiver {
111112
void IncreaseConsumerCount();
112113
void DecreaseConsumerCount();
113114
bool IsStillBeingConsumed() const;
115+
bool is_retired() const { return is_retired_; }
116+
void set_retired() { is_retired_ = true; }
114117
media::mojom::VideoBufferHandlePtr CloneBufferHandle(
115118
media::VideoCaptureBufferType target_buffer_type);
116119

@@ -121,17 +124,20 @@ class BroadcastingReceiver : public mojom::Receiver {
121124
// a regular shared memory and keep it in this form.
122125
void ConvertRawFileDescriptorToSharedBuffer();
123126

127+
int32_t buffer_context_id_;
124128
int32_t buffer_id_;
125129
media::mojom::VideoBufferHandlePtr buffer_handle_;
126130
// Indicates how many consumers are currently relying on
127131
// |access_permission_|.
128132
int32_t consumer_hold_count_;
133+
bool is_retired_;
129134
mojom::ScopedAccessPermissionPtr access_permission_;
130135
};
131136

132-
void OnClientFinishedConsumingFrame(int32_t buffer_id);
137+
void OnClientFinishedConsumingFrame(int32_t buffer_context_id);
133138
void OnClientDisconnected(int32_t client_id);
134-
BufferContext& LookupBufferContextFromBufferId(int32_t buffer_id);
139+
std::vector<BufferContext>::iterator FindUnretiredBufferContextFromBufferId(
140+
int32_t buffer_id);
135141

136142
SEQUENCE_CHECKER(sequence_checker_);
137143
std::map<int32_t /*client_id*/, ClientContext> clients_;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2019 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 "services/video_capture/broadcasting_receiver.h"
6+
7+
#include "base/run_loop.h"
8+
#include "base/test/scoped_task_environment.h"
9+
#include "media/capture/video/shared_memory_handle_provider.h"
10+
#include "mojo/public/cpp/bindings/strong_binding.h"
11+
#include "services/video_capture/public/cpp/mock_receiver.h"
12+
#include "testing/gmock/include/gmock/gmock.h"
13+
#include "testing/gtest/include/gtest/gtest.h"
14+
15+
using testing::_;
16+
using testing::InvokeWithoutArgs;
17+
18+
namespace video_capture {
19+
20+
class FakeAccessPermission : public mojom::ScopedAccessPermission {
21+
public:
22+
FakeAccessPermission(base::OnceClosure destruction_cb)
23+
: destruction_cb_(std::move(destruction_cb)) {}
24+
~FakeAccessPermission() override { std::move(destruction_cb_).Run(); }
25+
26+
private:
27+
base::OnceClosure destruction_cb_;
28+
};
29+
30+
class BroadcastingReceiverTest : public ::testing::Test {
31+
public:
32+
void SetUp() override {
33+
mojom::ReceiverPtr receiver_1;
34+
mojom::ReceiverPtr receiver_2;
35+
mock_receiver_1_ =
36+
std::make_unique<MockReceiver>(mojo::MakeRequest(&receiver_1));
37+
mock_receiver_2_ =
38+
std::make_unique<MockReceiver>(mojo::MakeRequest(&receiver_2));
39+
broadcaster_.AddClient(std::move(receiver_1),
40+
media::VideoCaptureBufferType::kSharedMemory);
41+
broadcaster_.AddClient(std::move(receiver_2),
42+
media::VideoCaptureBufferType::kSharedMemory);
43+
}
44+
45+
protected:
46+
BroadcastingReceiver broadcaster_;
47+
std::unique_ptr<MockReceiver> mock_receiver_1_;
48+
std::unique_ptr<MockReceiver> mock_receiver_2_;
49+
base::test::ScopedTaskEnvironment task_environment_;
50+
};
51+
52+
TEST_F(
53+
BroadcastingReceiverTest,
54+
HoldsOnToAccessPermissionForRetiredBufferUntilLastClientFinishedConsuming) {
55+
media::SharedMemoryHandleProvider shm_provider;
56+
const size_t kArbitraryDummyBufferSize = 8u;
57+
ASSERT_TRUE(shm_provider.InitForSize(kArbitraryDummyBufferSize));
58+
media::mojom::VideoBufferHandlePtr buffer_handle =
59+
media::mojom::VideoBufferHandle::New();
60+
buffer_handle->set_shared_buffer_handle(
61+
shm_provider.GetHandleForInterProcessTransit(true /*read_only*/));
62+
static const int kArbiraryBufferId = 123;
63+
static const int kArbiraryFrameFeedbackId = 456;
64+
broadcaster_.OnNewBuffer(kArbiraryBufferId, std::move(buffer_handle));
65+
66+
base::RunLoop frame_arrived_at_receiver_1;
67+
base::RunLoop frame_arrived_at_receiver_2;
68+
EXPECT_CALL(*mock_receiver_1_, DoOnFrameReadyInBuffer(_, _, _, _))
69+
.WillOnce(InvokeWithoutArgs([&frame_arrived_at_receiver_1]() {
70+
frame_arrived_at_receiver_1.Quit();
71+
}));
72+
EXPECT_CALL(*mock_receiver_2_, DoOnFrameReadyInBuffer(_, _, _, _))
73+
.WillOnce(InvokeWithoutArgs([&frame_arrived_at_receiver_2]() {
74+
frame_arrived_at_receiver_2.Quit();
75+
}));
76+
mock_receiver_2_->HoldAccessPermissions();
77+
78+
mojom::ScopedAccessPermissionPtr access_permission;
79+
bool access_permission_has_been_released = false;
80+
mojo::MakeStrongBinding(std::make_unique<FakeAccessPermission>(base::BindOnce(
81+
[](bool* access_permission_has_been_released) {
82+
*access_permission_has_been_released = true;
83+
},
84+
&access_permission_has_been_released)),
85+
mojo::MakeRequest(&access_permission));
86+
media::mojom::VideoFrameInfoPtr frame_info =
87+
media::mojom::VideoFrameInfo::New();
88+
media::VideoFrameMetadata frame_metadata;
89+
frame_info->metadata = frame_metadata.GetInternalValues().Clone();
90+
broadcaster_.OnFrameReadyInBuffer(kArbiraryBufferId, kArbiraryFrameFeedbackId,
91+
std::move(access_permission),
92+
std::move(frame_info));
93+
94+
// mock_receiver_1_ finishes consuming immediately.
95+
// mock_receiver_2_ continues consuming.
96+
frame_arrived_at_receiver_1.Run();
97+
frame_arrived_at_receiver_2.Run();
98+
99+
base::RunLoop buffer_retired_arrived_at_receiver_1;
100+
base::RunLoop buffer_retired_arrived_at_receiver_2;
101+
EXPECT_CALL(*mock_receiver_1_, DoOnBufferRetired(_))
102+
.WillOnce(InvokeWithoutArgs([&buffer_retired_arrived_at_receiver_1]() {
103+
buffer_retired_arrived_at_receiver_1.Quit();
104+
}));
105+
EXPECT_CALL(*mock_receiver_2_, DoOnBufferRetired(_))
106+
.WillOnce(InvokeWithoutArgs([&buffer_retired_arrived_at_receiver_2]() {
107+
buffer_retired_arrived_at_receiver_2.Quit();
108+
}));
109+
110+
// retire the buffer
111+
broadcaster_.OnBufferRetired(kArbiraryBufferId);
112+
113+
// expect that both receivers get the retired event
114+
buffer_retired_arrived_at_receiver_1.Run();
115+
buffer_retired_arrived_at_receiver_2.Run();
116+
117+
// expect that |access_permission| is still being held
118+
base::RunLoop().RunUntilIdle();
119+
EXPECT_FALSE(access_permission_has_been_released);
120+
121+
// mock_receiver_2_ finishes consuming
122+
mock_receiver_2_->ReleaseAccessPermissions();
123+
124+
// expect that |access_permission| is released
125+
base::RunLoop().RunUntilIdle();
126+
EXPECT_TRUE(access_permission_has_been_released);
127+
}
128+
129+
} // namespace video_capture

services/video_capture/public/cpp/mock_receiver.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,24 @@
99

1010
namespace video_capture {
1111

12-
MockReceiver::MockReceiver() : binding_(this) {}
12+
MockReceiver::MockReceiver()
13+
: binding_(this), should_store_access_permissions_(false) {}
1314

1415
MockReceiver::MockReceiver(mojom::ReceiverRequest request)
15-
: binding_(this, std::move(request)) {}
16+
: binding_(this, std::move(request)),
17+
should_store_access_permissions_(false) {}
1618

1719
MockReceiver::~MockReceiver() = default;
1820

21+
void MockReceiver::HoldAccessPermissions() {
22+
should_store_access_permissions_ = true;
23+
}
24+
25+
void MockReceiver::ReleaseAccessPermissions() {
26+
should_store_access_permissions_ = false;
27+
access_permissions_.clear();
28+
}
29+
1930
void MockReceiver::OnNewBuffer(
2031
int32_t buffer_id,
2132
media::mojom::VideoBufferHandlePtr buffer_handle) {
@@ -31,6 +42,8 @@ void MockReceiver::OnFrameReadyInBuffer(
3142
media::mojom::VideoFrameInfoPtr frame_info) {
3243
DoOnFrameReadyInBuffer(buffer_id, frame_feedback_id, &access_permission,
3344
&frame_info);
45+
if (should_store_access_permissions_)
46+
access_permissions_.emplace_back(std::move(access_permission));
3447
}
3548

3649
void MockReceiver::OnBufferRetired(int32_t buffer_id) {

services/video_capture/public/cpp/mock_receiver.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class MockReceiver : public mojom::Receiver {
2222
explicit MockReceiver(mojom::ReceiverRequest request);
2323
~MockReceiver() override;
2424

25+
void HoldAccessPermissions();
26+
void ReleaseAccessPermissions();
27+
2528
// Use forwarding method to work around gmock not supporting move-only types.
2629
void OnNewBuffer(int32_t buffer_id,
2730
media::mojom::VideoBufferHandlePtr buffer_handle) override;
@@ -50,6 +53,8 @@ class MockReceiver : public mojom::Receiver {
5053
private:
5154
const mojo::Binding<mojom::Receiver> binding_;
5255
std::vector<int32_t> known_buffer_ids_;
56+
bool should_store_access_permissions_;
57+
std::vector<mojom::ScopedAccessPermissionPtr> access_permissions_;
5358
};
5459

5560
} // namespace video_capture

0 commit comments

Comments
 (0)