Commit 4324e61d authored by scottmg@chromium.org's avatar scottmg@chromium.org

Renderer reading side of gamepad

The is the glue code that reads from the shared memory buffer on the renderer
side. The writer is a background thread the browser. The data here is returned
back up into WebKit code via the Chromium port.

The writer on the browser side was in this patch:
http://codereview.chromium.org/8568029/

And the rest of the related gamepad changes are here:
http://codereview.chromium.org/8345027/

BUG=79050


Review URL: http://codereview.chromium.org/8689011

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@112345 0039d316-1c4b-4281-b951-d872f2087c98
parent 9052ccad
...@@ -9,15 +9,15 @@ namespace WebKit { ...@@ -9,15 +9,15 @@ namespace WebKit {
class WebGamepads; class WebGamepads;
} }
namespace gamepad { namespace content {
class DataFetcher { class GamepadDataFetcher {
public: public:
virtual ~DataFetcher() {} virtual ~GamepadDataFetcher() {}
virtual void GetGamepadData(WebKit::WebGamepads* pads, virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) = 0; bool devices_changed_hint) = 0;
}; };
} // namespace gamepad } // namespace content
#endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_H_ #endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_H_
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#pragma comment(lib, "delayimp.lib") #pragma comment(lib, "delayimp.lib")
#pragma comment(lib, "xinput.lib") #pragma comment(lib, "xinput.lib")
namespace gamepad { namespace content {
using namespace WebKit; using namespace WebKit;
...@@ -78,13 +78,13 @@ bool EnableXInput() { ...@@ -78,13 +78,13 @@ bool EnableXInput() {
} }
DataFetcherWindows::DataFetcherWindows() GamepadDataFetcherWindows::GamepadDataFetcherWindows()
: xinput_available_(EnableXInput()) { : xinput_available_(EnableXInput()) {
} }
void DataFetcherWindows::GetGamepadData(WebGamepads* pads, void GamepadDataFetcherWindows::GetGamepadData(WebGamepads* pads,
bool devices_changed_hint) { bool devices_changed_hint) {
TRACE_EVENT0("GAMEPAD", "DataFetcherWindows::GetGamepadData"); TRACE_EVENT0("GAMEPAD", "GetGamepadData");
// If there's no XInput DLL on the system, early out so that we don't // If there's no XInput DLL on the system, early out so that we don't
// call any other XInput functions. // call any other XInput functions.
...@@ -104,7 +104,7 @@ void DataFetcherWindows::GetGamepadData(WebGamepads* pads, ...@@ -104,7 +104,7 @@ void DataFetcherWindows::GetGamepadData(WebGamepads* pads,
if (devices_changed_hint) { if (devices_changed_hint) {
for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
WebGamepad& pad = pads->items[i]; WebGamepad& pad = pads->items[i];
TRACE_EVENT1("GAMEPAD", "DataFetcherWindows::GetCapabilities", "id", i); TRACE_EVENT1("GAMEPAD", "GetCapabilities", "id", i);
XINPUT_CAPABILITIES caps; XINPUT_CAPABILITIES caps;
DWORD res = XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps); DWORD res = XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps);
if (res == ERROR_DEVICE_NOT_CONNECTED) { if (res == ERROR_DEVICE_NOT_CONNECTED) {
...@@ -170,4 +170,4 @@ void DataFetcherWindows::GetGamepadData(WebGamepads* pads, ...@@ -170,4 +170,4 @@ void DataFetcherWindows::GetGamepadData(WebGamepads* pads,
} }
} }
} // namespace gamepad } // namespace content
...@@ -16,17 +16,17 @@ ...@@ -16,17 +16,17 @@
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "content/browser/gamepad/data_fetcher.h" #include "content/browser/gamepad/data_fetcher.h"
namespace gamepad { namespace content {
class DataFetcherWindows : public DataFetcher { class GamepadDataFetcherWindows : public GamepadDataFetcher {
public: public:
DataFetcherWindows(); GamepadDataFetcherWindows();
virtual void GetGamepadData(WebKit::WebGamepads* pads, virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) OVERRIDE; bool devices_changed_hint) OVERRIDE;
private: private:
bool xinput_available_; bool xinput_available_;
}; };
} // namespace gamepad } // namespace content
#endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_WIN_H_ #endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_WIN_H_
...@@ -21,31 +21,29 @@ ...@@ -21,31 +21,29 @@
#include "content/browser/gamepad/data_fetcher_win.h" #include "content/browser/gamepad/data_fetcher_win.h"
#endif #endif
namespace gamepad { namespace content {
Provider* Provider::instance_ = NULL; GamepadProvider* GamepadProvider::instance_ = NULL;
using namespace content; // Define the default data fetcher that GamepadProvider will use if none is
// supplied. (GamepadPlatformDataFetcher).
// Define the default data fetcher that Provider will use if none is supplied.
// (PlatformDataFetcher).
#if defined(OS_WIN) #if defined(OS_WIN)
typedef DataFetcherWindows PlatformDataFetcher; typedef GamepadDataFetcherWindows GamepadPlatformDataFetcher;
#else #else
class EmptyDataFetcher : public DataFetcher { class GamepadEmptyDataFetcher : public GamepadDataFetcher {
public: public:
void GetGamepadData(WebKit::WebGamepads* pads, bool) { void GetGamepadData(WebKit::WebGamepads* pads, bool) {
pads->length = 0; pads->length = 0;
} }
}; };
typedef EmptyDataFetcher PlatformDataFetcher; typedef GamepadEmptyDataFetcher GamepadPlatformDataFetcher;
#endif #endif
Provider::Provider(DataFetcher* fetcher) GamepadProvider::GamepadProvider(GamepadDataFetcher* fetcher)
: creator_loop_(MessageLoop::current()->message_loop_proxy()), : creator_loop_(MessageLoop::current()->message_loop_proxy()),
provided_fetcher_(fetcher), provided_fetcher_(fetcher),
devices_changed_(true), devices_changed_(true),
...@@ -59,25 +57,25 @@ Provider::Provider(DataFetcher* fetcher) ...@@ -59,25 +57,25 @@ Provider::Provider(DataFetcher* fetcher)
memset(hwbuf, 0, sizeof(GamepadHardwareBuffer)); memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));
} }
Provider::~Provider() { GamepadProvider::~GamepadProvider() {
base::SystemMonitor* monitor = base::SystemMonitor::Get(); base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor) if (monitor)
monitor->RemoveDevicesChangedObserver(this); monitor->RemoveDevicesChangedObserver(this);
Stop(); Stop();
} }
base::SharedMemoryHandle Provider::GetRendererSharedMemoryHandle( base::SharedMemoryHandle GamepadProvider::GetRendererSharedMemoryHandle(
base::ProcessHandle process) { base::ProcessHandle process) {
base::SharedMemoryHandle renderer_handle; base::SharedMemoryHandle renderer_handle;
gamepad_shared_memory_.ShareToProcess(process, &renderer_handle); gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
return renderer_handle; return renderer_handle;
} }
void Provider::OnDevicesChanged() { void GamepadProvider::OnDevicesChanged() {
devices_changed_ = true; devices_changed_ = true;
} }
void Provider::Start() { void GamepadProvider::Start() {
DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_); DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_);
if (polling_thread_.get()) if (polling_thread_.get())
...@@ -93,21 +91,21 @@ void Provider::Start() { ...@@ -93,21 +91,21 @@ void Provider::Start() {
MessageLoop* polling_loop = polling_thread_->message_loop(); MessageLoop* polling_loop = polling_thread_->message_loop();
polling_loop->PostTask( polling_loop->PostTask(
FROM_HERE, FROM_HERE,
base::Bind(&Provider::DoInitializePollingThread, this)); base::Bind(&GamepadProvider::DoInitializePollingThread, this));
} }
void Provider::Stop() { void GamepadProvider::Stop() {
DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_); DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_);
polling_thread_.reset(); polling_thread_.reset();
data_fetcher_.reset(); data_fetcher_.reset();
} }
void Provider::DoInitializePollingThread() { void GamepadProvider::DoInitializePollingThread() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop()); DCHECK(MessageLoop::current() == polling_thread_->message_loop());
if (!provided_fetcher_.get()) if (!provided_fetcher_.get())
provided_fetcher_.reset(new PlatformDataFetcher); provided_fetcher_.reset(new GamepadPlatformDataFetcher);
// Pass ownership of fetcher to provider_. // Pass ownership of fetcher to provider_.
data_fetcher_.swap(provided_fetcher_); data_fetcher_.swap(provided_fetcher_);
...@@ -116,30 +114,38 @@ void Provider::DoInitializePollingThread() { ...@@ -116,30 +114,38 @@ void Provider::DoInitializePollingThread() {
ScheduleDoPoll(); ScheduleDoPoll();
} }
void Provider::DoPoll() { void GamepadProvider::DoPoll() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop()); DCHECK(MessageLoop::current() == polling_thread_->message_loop());
GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
base::subtle::Barrier_AtomicIncrement(&hwbuf->start_marker, 1);
ANNOTATE_BENIGN_RACE_SIZED(
&hwbuf->buffer,
sizeof(WebKit::WebGamepads),
"Racey reads are discarded");
// Acquire the SeqLock. There is only ever one writer to this data.
// See gamepad_hardware_buffer.h.
hwbuf->sequence.WriteBegin();
data_fetcher_->GetGamepadData(&hwbuf->buffer, devices_changed_); data_fetcher_->GetGamepadData(&hwbuf->buffer, devices_changed_);
base::subtle::Barrier_AtomicIncrement(&hwbuf->end_marker, 1); hwbuf->sequence.WriteEnd();
devices_changed_ = false; devices_changed_ = false;
// Schedule our next interval of polling. // Schedule our next interval of polling.
ScheduleDoPoll(); ScheduleDoPoll();
} }
void Provider::ScheduleDoPoll() { void GamepadProvider::ScheduleDoPoll() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop()); DCHECK(MessageLoop::current() == polling_thread_->message_loop());
MessageLoop::current()->PostDelayedTask( MessageLoop::current()->PostDelayedTask(
FROM_HERE, FROM_HERE,
base::Bind(&Provider::DoPoll, weak_factory_.GetWeakPtr()), base::Bind(&GamepadProvider::DoPoll, weak_factory_.GetWeakPtr()),
kDesiredSamplingIntervalMs); kDesiredSamplingIntervalMs);
} }
GamepadHardwareBuffer* Provider::SharedMemoryAsHardwareBuffer() { GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() {
void* mem = gamepad_shared_memory_.memory(); void* mem = gamepad_shared_memory_.memory();
DCHECK(mem); DCHECK(mem);
return static_cast<GamepadHardwareBuffer*>(mem); return static_cast<GamepadHardwareBuffer*>(mem);
} }
} // namespace gamepad } // namespace content
...@@ -21,13 +21,13 @@ class Thread; ...@@ -21,13 +21,13 @@ class Thread;
struct GamepadMsg_Updated_Params; struct GamepadMsg_Updated_Params;
namespace gamepad { namespace content {
class CONTENT_EXPORT Provider : class CONTENT_EXPORT GamepadProvider :
public base::RefCountedThreadSafe<Provider>, public base::RefCountedThreadSafe<GamepadProvider>,
public base::SystemMonitor::DevicesChangedObserver { public base::SystemMonitor::DevicesChangedObserver {
public: public:
explicit Provider(DataFetcher* fetcher); explicit GamepadProvider(GamepadDataFetcher* fetcher);
// Starts or Stops the provider. Called from creator_loop_. // Starts or Stops the provider. Called from creator_loop_.
void Start(); void Start();
...@@ -36,14 +36,14 @@ class CONTENT_EXPORT Provider : ...@@ -36,14 +36,14 @@ class CONTENT_EXPORT Provider :
base::ProcessHandle renderer_process); base::ProcessHandle renderer_process);
private: private:
friend class base::RefCountedThreadSafe<Provider>; friend class base::RefCountedThreadSafe<GamepadProvider>;
virtual ~Provider(); virtual ~GamepadProvider();
// Method for starting the polling, runs on polling_thread_. // Method for starting the polling, runs on polling_thread_.
void DoInitializePollingThread(); void DoInitializePollingThread();
// Method for polling a DataFetcher. Runs on the polling_thread_. // Method for polling a GamepadDataFetcher. Runs on the polling_thread_.
void DoPoll(); void DoPoll();
void ScheduleDoPoll(); void ScheduleDoPoll();
...@@ -56,23 +56,23 @@ class CONTENT_EXPORT Provider : ...@@ -56,23 +56,23 @@ class CONTENT_EXPORT Provider :
// The Message Loop on which this object was created. // The Message Loop on which this object was created.
// Typically the I/O loop, but may be something else during testing. // Typically the I/O loop, but may be something else during testing.
scoped_refptr<base::MessageLoopProxy> creator_loop_; scoped_refptr<base::MessageLoopProxy> creator_loop_;
scoped_ptr<DataFetcher> provided_fetcher_; scoped_ptr<GamepadDataFetcher> provided_fetcher_;
// When polling_thread_ is running, members below are only to be used // When polling_thread_ is running, members below are only to be used
// from that thread. // from that thread.
scoped_ptr<DataFetcher> data_fetcher_; scoped_ptr<GamepadDataFetcher> data_fetcher_;
base::SharedMemory gamepad_shared_memory_; base::SharedMemory gamepad_shared_memory_;
bool devices_changed_; bool devices_changed_;
// Polling is done on this background thread. // Polling is done on this background thread.
scoped_ptr<base::Thread> polling_thread_; scoped_ptr<base::Thread> polling_thread_;
static Provider* instance_; static GamepadProvider* instance_;
base::WeakPtrFactory<Provider> weak_factory_; base::WeakPtrFactory<GamepadProvider> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Provider); DISALLOW_COPY_AND_ASSIGN(GamepadProvider);
}; };
} // namespace gamepad } // namespace content
#endif // CONTENT_BROWSER_GAMEPAD_PROVIDER_H_ #endif // CONTENT_BROWSER_GAMEPAD_PROVIDER_H_
...@@ -10,39 +10,39 @@ ...@@ -10,39 +10,39 @@
#include "content/common/gamepad_messages.h" #include "content/common/gamepad_messages.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace { namespace {
class MockDataFetcher : public gamepad::DataFetcher { using WebKit::WebGamepads;
class MockDataFetcher : public GamepadDataFetcher {
public: public:
MockDataFetcher() : read_data_(false, false) { MockDataFetcher(WebGamepads& test_data) : read_data_(false, false) {
memset(&test_data, 0, sizeof(test_data)); test_data_ = test_data;
} }
virtual void GetGamepadData(WebKit::WebGamepads* pads, virtual void GetGamepadData(WebGamepads* pads,
bool devices_changed_hint) OVERRIDE { bool devices_changed_hint) OVERRIDE {
*pads = test_data; *pads = test_data_;
read_data_.Signal(); read_data_.Signal();
} }
void SetData(WebKit::WebGamepads& data) {
test_data = data;
}
void WaitForDataRead() { return read_data_.Wait(); } void WaitForDataRead() { return read_data_.Wait(); }
WebKit::WebGamepads test_data; WebGamepads test_data_;
base::WaitableEvent read_data_; base::WaitableEvent read_data_;
}; };
// Main test fixture // Main test fixture
class GamepadProviderTest : public testing::Test { class GamepadProviderTest : public testing::Test {
public: public:
gamepad::Provider* CreateProvider() { GamepadProvider* CreateProvider(WebGamepads& test_data) {
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
base::SystemMonitor::AllocateSystemIOPorts(); base::SystemMonitor::AllocateSystemIOPorts();
#endif #endif
system_monitor_.reset(new base::SystemMonitor); system_monitor_.reset(new base::SystemMonitor);
mock_data_fetcher_ = new MockDataFetcher; mock_data_fetcher_ = new MockDataFetcher(test_data);
provider_ = new gamepad::Provider(mock_data_fetcher_); provider_ = new GamepadProvider(mock_data_fetcher_);
return provider_.get(); return provider_.get();
} }
...@@ -53,24 +53,20 @@ class GamepadProviderTest : public testing::Test { ...@@ -53,24 +53,20 @@ class GamepadProviderTest : public testing::Test {
MessageLoop main_message_loop_; MessageLoop main_message_loop_;
scoped_ptr<base::SystemMonitor> system_monitor_; scoped_ptr<base::SystemMonitor> system_monitor_;
MockDataFetcher* mock_data_fetcher_; MockDataFetcher* mock_data_fetcher_;
scoped_refptr<gamepad::Provider> provider_; scoped_refptr<GamepadProvider> provider_;
}; };
TEST_F(GamepadProviderTest, BasicStartStop) { TEST_F(GamepadProviderTest, BasicStartStop) {
gamepad::Provider* provider = CreateProvider(); WebGamepads test_data;
memset(&test_data, 0, sizeof(test_data));
GamepadProvider* provider = CreateProvider(test_data);
provider->Start(); provider->Start();
provider->Stop(); provider->Stop();
// Just ensure that there's no asserts on startup, shutdown, or destroy. // Just ensure that there's no asserts on startup, shutdown, or destroy.
} }
// http://crbug.com/105348 TEST_F(GamepadProviderTest, PollingAccess) {
TEST_F(GamepadProviderTest, FLAKY_PollingAccess) { WebGamepads test_data;
using namespace gamepad;
Provider* provider = CreateProvider();
provider->Start();
WebKit::WebGamepads test_data;
test_data.length = 1; test_data.length = 1;
test_data.items[0].connected = true; test_data.items[0].connected = true;
test_data.items[0].timestamp = 0; test_data.items[0].timestamp = 0;
...@@ -79,7 +75,9 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) { ...@@ -79,7 +75,9 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
test_data.items[0].buttons[0] = 1.f; test_data.items[0].buttons[0] = 1.f;
test_data.items[0].axes[0] = -1.f; test_data.items[0].axes[0] = -1.f;
test_data.items[0].axes[1] = .5f; test_data.items[0].axes[1] = .5f;
mock_data_fetcher_->SetData(test_data);
GamepadProvider* provider = CreateProvider(test_data);
provider->Start();
main_message_loop_.RunAllPending(); main_message_loop_.RunAllPending();
...@@ -95,22 +93,14 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) { ...@@ -95,22 +93,14 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
GamepadHardwareBuffer* hwbuf = static_cast<GamepadHardwareBuffer*>(mem); GamepadHardwareBuffer* hwbuf = static_cast<GamepadHardwareBuffer*>(mem);
// See gamepad_hardware_buffer.h for details on the read discipline. // See gamepad_hardware_buffer.h for details on the read discipline.
base::subtle::Atomic32 start, end; WebGamepads output;
WebKit::WebGamepads output;
int contention_count; base::subtle::Atomic32 version;
do {
// Here we're attempting to test the read discipline during contention. If version = hwbuf->sequence.ReadBegin();
// we fail to read this many times, then the read thread is starving, and we
// should fail the test.
for (contention_count = 0; contention_count < 1000; ++contention_count) {
end = base::subtle::Acquire_Load(&hwbuf->end_marker);
memcpy(&output, &hwbuf->buffer, sizeof(output)); memcpy(&output, &hwbuf->buffer, sizeof(output));
start = base::subtle::Acquire_Load(&hwbuf->start_marker); } while (hwbuf->sequence.ReadRetry(version));
if (start == end)
break;
base::PlatformThread::YieldCurrentThread();
}
EXPECT_GT(1000, contention_count);
EXPECT_EQ(1u, output.length); EXPECT_EQ(1u, output.length);
EXPECT_EQ(1u, output.items[0].buttonsLength); EXPECT_EQ(1u, output.items[0].buttonsLength);
EXPECT_EQ(1.f, output.items[0].buttons[0]); EXPECT_EQ(1.f, output.items[0].buttons[0]);
...@@ -122,3 +112,5 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) { ...@@ -122,3 +112,5 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
} }
} // namespace } // namespace
} // namespace content
...@@ -29,7 +29,7 @@ bool GamepadBrowserMessageFilter::OnMessageReceived( ...@@ -29,7 +29,7 @@ bool GamepadBrowserMessageFilter::OnMessageReceived(
void GamepadBrowserMessageFilter::OnGamepadStartPolling( void GamepadBrowserMessageFilter::OnGamepadStartPolling(
base::SharedMemoryHandle* renderer_handle) { base::SharedMemoryHandle* renderer_handle) {
if (!provider_) { if (!provider_) {
provider_ = new gamepad::Provider(NULL); provider_ = new content::GamepadProvider(NULL);
provider_->Start(); provider_->Start();
} }
*renderer_handle = provider_->GetRendererSharedMemoryHandle(peer_handle()); *renderer_handle = provider_->GetRendererSharedMemoryHandle(peer_handle());
......
...@@ -23,7 +23,7 @@ class GamepadBrowserMessageFilter : public BrowserMessageFilter { ...@@ -23,7 +23,7 @@ class GamepadBrowserMessageFilter : public BrowserMessageFilter {
void OnGamepadStartPolling(base::SharedMemoryHandle* renderer_handle); void OnGamepadStartPolling(base::SharedMemoryHandle* renderer_handle);
void OnGamepadStopPolling(); void OnGamepadStopPolling();
scoped_refptr<gamepad::Provider> provider_; scoped_refptr<content::GamepadProvider> provider_;
DISALLOW_COPY_AND_ASSIGN(GamepadBrowserMessageFilter); DISALLOW_COPY_AND_ASSIGN(GamepadBrowserMessageFilter);
}; };
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
#ifndef CONTENT_COMMON_GAMEPAD_HARDWARE_BUFFER_H_ #ifndef CONTENT_COMMON_GAMEPAD_HARDWARE_BUFFER_H_
#define CONTENT_COMMON_GAMEPAD_HARDWARE_BUFFER_H_ #define CONTENT_COMMON_GAMEPAD_HARDWARE_BUFFER_H_
#include "base/atomicops.h" #include "content/common/gamepad_seqlock.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h"
namespace gamepad { namespace content {
/* /*
...@@ -19,25 +19,13 @@ we want low latency (so would like to avoid explicit communication via IPC ...@@ -19,25 +19,13 @@ we want low latency (so would like to avoid explicit communication via IPC
between producer and consumer) and relatively large data size. between producer and consumer) and relatively large data size.
Writer and reader operate on the same buffer assuming contention is low, and Writer and reader operate on the same buffer assuming contention is low, and
start_marker and end_marker are used to detect inconsistent reads. contention is detected by using the associated SeqLock.
The writer atomically increments the start_marker counter before starting,
then fills in the gamepad data, then increments end_marker to the same value
as start_marker. The readers first reads end_marker, then the the data and
then start_marker, and if the reader finds that the start and end markers were
different, then it must retry as the buffer was updated while being read.
There is a requirement for memory barriers between the accesses to the markers
and the main data to ensure that both the reader and write see a consistent
view of those values. In the current implementation, the writer uses an
Barrier_AtomicIncrement for the counter, and the reader uses an Acquire_Load.
*/ */
struct GamepadHardwareBuffer { struct GamepadHardwareBuffer {
base::subtle::Atomic32 start_marker; GamepadSeqLock sequence;
WebKit::WebGamepads buffer; WebKit::WebGamepads buffer;
base::subtle::Atomic32 end_marker;
}; };
} }
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/gamepad_seqlock.h"
namespace content {
GamepadSeqLock::GamepadSeqLock()
: sequence_(0) {
}
base::subtle::Atomic32 GamepadSeqLock::ReadBegin() {
base::subtle::Atomic32 version;
for (;;) {
version = base::subtle::NoBarrier_Load(&sequence_);
// If the counter is even, then the associated data might be in a
// consistent state, so we can try to read.
if ((version & 1) == 0)
break;
// Otherwise, the writer is in the middle of an update. Retry the read.
base::PlatformThread::YieldCurrentThread();
}
return version;
}
bool GamepadSeqLock::ReadRetry(base::subtle::Atomic32 version) {
// If the sequence number was updated then a read should be re-attempted.
// -- Load fence, read membarrier
return base::subtle::Release_Load(&sequence_) != version;
}
void GamepadSeqLock::WriteBegin() {
// Increment the sequence number to odd to indicate the beginning of a write
// update.
base::subtle::Barrier_AtomicIncrement(&sequence_, 1);
// -- Store fence, write membarrier
}
void GamepadSeqLock::WriteEnd() {
// Increment the sequence to an even number to indicate the completion of
// a write update.
// -- Store fence, write membarrier
base::subtle::Barrier_AtomicIncrement(&sequence_, 1);
}
} // namespace content
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_COMMON_GAMEPAD_SEQLOCK_H_
#define CONTENT_COMMON_GAMEPAD_SEQLOCK_H_
#include "base/atomicops.h"
#include "base/threading/platform_thread.h"
namespace content {
// This SeqLock handles only *one* writer and multiple readers. It may be
// suitable for low-contention with relatively infrequent writes, and many
// readers. See:
// http://en.wikipedia.org/wiki/Seqlock
// http://www.concurrencykit.org/doc/ck_sequence.html
// This implementation is based on ck_sequence.h from http://concurrencykit.org.
//
// Currently, this is used in only one location. It may make sense to
// generalize with a higher-level construct that owns both the lock and the
// data buffer, if it is to be used more widely.
//
// You must be very careful not to operate on potentially inconsistent read
// buffers. If the read must be retry'd, the data in the read buffer could
// contain any random garbage. e.g., contained pointers might be
// garbage, or indices could be out of range. Probably the only suitable thing
// to do during the read loop is to make a copy of the data, and operate on it
// only after the read was found to be consistent.
class GamepadSeqLock {
public:
GamepadSeqLock();
base::subtle::Atomic32 ReadBegin();
bool ReadRetry(base::subtle::Atomic32 version);
void WriteBegin();
void WriteEnd();
private:
base::subtle::Atomic32 sequence_;
DISALLOW_COPY_AND_ASSIGN(GamepadSeqLock);
};
} // namespace content
#endif // CONTENT_COMMON_GAMEPAD_SEQLOCK_H_
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/gamepad_seqlock.h"
#include <stdlib.h>
#include "base/atomic_ref_count.h"
#include "base/threading/platform_thread.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
// Basic test to make sure that basic operation works correctly.
struct TestData {
unsigned a, b, c;
};
class BasicSeqLockTestThread : public PlatformThread::Delegate {
public:
BasicSeqLockTestThread() {}
void Init(
content::GamepadSeqLock* seqlock,
TestData* data,
base::subtle::Atomic32* ready) {
seqlock_ = seqlock;
data_ = data;
ready_ = ready;
}
virtual void ThreadMain() {
while (AtomicRefCountIsZero(ready_)) {
PlatformThread::YieldCurrentThread();
}
for (unsigned i = 0; i < 10000; ++i) {
TestData copy;
base::subtle::Atomic32 version;
do {
version = seqlock_->ReadBegin();
copy = *data_;
} while (seqlock_->ReadRetry(version));
EXPECT_EQ(copy.a + 100, copy.b);
EXPECT_EQ(copy.c, copy.b + copy.a);
}
AtomicRefCountDec(ready_);
}
private:
content::GamepadSeqLock* seqlock_;
TestData* data_;
base::AtomicRefCount* ready_;
DISALLOW_COPY_AND_ASSIGN(BasicSeqLockTestThread);
};
TEST(GamepadSeqLockTest, ManyThreads) {
content::GamepadSeqLock seqlock;
TestData data = { 0, 0, 0 };
base::AtomicRefCount ready = 0;
ANNOTATE_BENIGN_RACE_SIZED(&data, sizeof(data), "Racey reads are discarded");
static const unsigned kNumReaderThreads = 100;
BasicSeqLockTestThread threads[kNumReaderThreads];
PlatformThreadHandle handles[kNumReaderThreads];
for (unsigned i = 0; i < kNumReaderThreads; ++i)
threads[i].Init(&seqlock, &data, &ready);
for (unsigned i = 0; i < kNumReaderThreads; ++i)
ASSERT_TRUE(PlatformThread::Create(0, &threads[i], &handles[i]));
// The main thread is the writer, and the spawned are readers.
unsigned counter = 0;
for (;;) {
seqlock.WriteBegin();
data.a = counter++;
data.b = data.a + 100;
data.c = data.b + data.a;
seqlock.WriteEnd();
if (counter == 1)
base::AtomicRefCountIncN(&ready, kNumReaderThreads);
if (AtomicRefCountIsZero(&ready))
break;
}
for (unsigned i = 0; i < kNumReaderThreads; ++i)
PlatformThread::Join(handles[i]);
}
} // namespace base
...@@ -134,6 +134,8 @@ ...@@ -134,6 +134,8 @@
'common/font_list_x11.cc', 'common/font_list_x11.cc',
'common/gamepad_hardware_buffer.h', 'common/gamepad_hardware_buffer.h',
'common/gamepad_messages.h', 'common/gamepad_messages.h',
'common/gamepad_seqlock.cc',
'common/gamepad_seqlock.h',
'common/geolocation_messages.h', 'common/geolocation_messages.h',
'common/geoposition.cc', 'common/geoposition.cc',
'common/geoposition.h', 'common/geoposition.h',
......
...@@ -52,6 +52,8 @@ ...@@ -52,6 +52,8 @@
'renderer/devtools_client.h', 'renderer/devtools_client.h',
'renderer/external_popup_menu.cc', 'renderer/external_popup_menu.cc',
'renderer/external_popup_menu.h', 'renderer/external_popup_menu.h',
'renderer/gamepad_shared_memory_reader.cc',
'renderer/gamepad_shared_memory_reader.h',
'renderer/geolocation_dispatcher.cc', 'renderer/geolocation_dispatcher.cc',
'renderer/geolocation_dispatcher.h', 'renderer/geolocation_dispatcher.h',
'renderer/gpu/compositor_thread.cc', 'renderer/gpu/compositor_thread.cc',
......
...@@ -226,6 +226,7 @@ ...@@ -226,6 +226,7 @@
'browser/trace_subscriber_stdio_unittest.cc', 'browser/trace_subscriber_stdio_unittest.cc',
'common/mac/attributed_string_coder_unittest.mm', 'common/mac/attributed_string_coder_unittest.mm',
'common/mac/font_descriptor_unittest.mm', 'common/mac/font_descriptor_unittest.mm',
'common/gamepad_seqlock_unittest.cc',
'common/gpu/gpu_feature_flags_unittest.cc', 'common/gpu/gpu_feature_flags_unittest.cc',
'common/gpu/gpu_info_unittest.cc', 'common/gpu/gpu_info_unittest.cc',
'common/hi_res_timer_manager_unittest.cc', 'common/hi_res_timer_manager_unittest.cc',
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/gamepad_shared_memory_reader.h"
#include "base/debug/trace_event.h"
#include "base/metrics/histogram.h"
#include "content/common/gamepad_messages.h"
#include "content/public/renderer/render_thread.h"
#include "content/common/gamepad_hardware_buffer.h"
#include "ipc/ipc_sync_message_filter.h"
namespace content {
GamepadSharedMemoryReader::GamepadSharedMemoryReader() {
memset(ever_interacted_with_, 0, sizeof(ever_interacted_with_));
CHECK(RenderThread::Get()->Send(new GamepadHostMsg_StartPolling(
&renderer_shared_memory_handle_)));
renderer_shared_memory_.reset(
new base::SharedMemory(renderer_shared_memory_handle_, true));
CHECK(renderer_shared_memory_->Map(sizeof(GamepadHardwareBuffer)));
void *memory = renderer_shared_memory_->memory();
CHECK(memory);
gamepad_hardware_buffer_ =
static_cast<GamepadHardwareBuffer*>(memory);
}
void GamepadSharedMemoryReader::SampleGamepads(WebKit::WebGamepads& gamepads) {
WebKit::WebGamepads read_into;
TRACE_EVENT0("GAMEPAD", "SampleGamepads");
// Only try to read this many times before failing to avoid waiting here
// very long in case of contention with the writer. TODO(scottmg) Tune this
// number (as low as 1?) if histogram shows distribution as mostly
// 0-and-maximum.
const int kMaximumContentionCount = 10;
int contention_count = -1;
base::subtle::Atomic32 version;
do {
version = gamepad_hardware_buffer_->sequence.ReadBegin();
memcpy(&read_into, &gamepad_hardware_buffer_->buffer, sizeof(read_into));
++contention_count;
if (contention_count == kMaximumContentionCount)
break;
} while (gamepad_hardware_buffer_->sequence.ReadRetry(version));
HISTOGRAM_COUNTS("Gamepad.ReadContentionCount", contention_count);
if (contention_count >= kMaximumContentionCount) {
// We failed to successfully read, presumably because the hardware
// thread was taking unusually long. Don't copy the data to the output
// buffer, and simply leave what was there before.
return;
}
// New data was read successfully, copy it into the output buffer.
memcpy(&gamepads, &read_into, sizeof(gamepads));
// Override the "connected" with false until the user has interacted
// with the gamepad. This is to prevent fingerprinting on drive-by pages.
for (unsigned i = 0; i < WebKit::WebGamepads::itemsLengthCap; ++i) {
WebKit::WebGamepad& pad = gamepads.items[i];
// If the device is physically connected, then check if we should
// keep it disabled. We track if any of the primary 4 buttons have been
// pressed to determine a reasonable intentional interaction from the user.
if (pad.connected) {
if (ever_interacted_with_[i])
continue;
const unsigned kPrimaryInteractionButtons = 4;
for (unsigned j = 0; j < kPrimaryInteractionButtons; ++j)
ever_interacted_with_[i] |= pad.buttons[j] > 0.5f;
// If we've not previously set, and the user still hasn't touched
// these buttons, then don't pass the data on to the Chromium port.
if (!ever_interacted_with_[i])
pad.connected = false;
}
}
}
GamepadSharedMemoryReader::~GamepadSharedMemoryReader() {
RenderThread::Get()->Send(new GamepadHostMsg_StopPolling());
}
} // namespace content
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef GAMEPAD_UTIL_H_
#define GAMEPAD_UTIL_H_
#include "base/shared_memory.h"
#include "base/memory/scoped_ptr.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h"
namespace content {
struct GamepadHardwareBuffer;
class GamepadSharedMemoryReader {
public:
GamepadSharedMemoryReader();
virtual ~GamepadSharedMemoryReader();
void SampleGamepads(WebKit::WebGamepads&);
private:
base::SharedMemoryHandle renderer_shared_memory_handle_;
scoped_ptr<base::SharedMemory> renderer_shared_memory_;
GamepadHardwareBuffer* gamepad_hardware_buffer_;
bool ever_interacted_with_[WebKit::WebGamepads::itemsLengthCap];
};
} // namespace content
#endif // GAMEPAD_UTIL_H_
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "content/common/webmessageportchannel_impl.h" #include "content/common/webmessageportchannel_impl.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/public/renderer/content_renderer_client.h" #include "content/public/renderer/content_renderer_client.h"
#include "content/renderer/gamepad_shared_memory_reader.h"
#include "content/renderer/gpu/webgraphicscontext3d_command_buffer_impl.h" #include "content/renderer/gpu/webgraphicscontext3d_command_buffer_impl.h"
#include "content/renderer/media/audio_device.h" #include "content/renderer/media/audio_device.h"
#include "content/renderer/media/audio_hardware.h" #include "content/renderer/media/audio_hardware.h"
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
#include "ipc/ipc_sync_message_filter.h" #include "ipc/ipc_sync_message_filter.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBlobRegistry.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebBlobRegistry.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGraphicsContext3D.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebGraphicsContext3D.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBFactory.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBKey.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBKey.h"
...@@ -76,6 +78,7 @@ using WebKit::WebAudioDevice; ...@@ -76,6 +78,7 @@ using WebKit::WebAudioDevice;
using WebKit::WebBlobRegistry; using WebKit::WebBlobRegistry;
using WebKit::WebFileSystem; using WebKit::WebFileSystem;
using WebKit::WebFrame; using WebKit::WebFrame;
using WebKit::WebGamepads;
using WebKit::WebIDBFactory; using WebKit::WebIDBFactory;
using WebKit::WebIDBKey; using WebKit::WebIDBKey;
using WebKit::WebIDBKeyPath; using WebKit::WebIDBKeyPath;
...@@ -619,6 +622,14 @@ WebBlobRegistry* RendererWebKitPlatformSupportImpl::blobRegistry() { ...@@ -619,6 +622,14 @@ WebBlobRegistry* RendererWebKitPlatformSupportImpl::blobRegistry() {
return blob_registry_.get(); return blob_registry_.get();
} }
//------------------------------------------------------------------------------
void RendererWebKitPlatformSupportImpl::sampleGamepads(WebGamepads& gamepads) {
if (!gamepad_shared_memory_reader_.get())
gamepad_shared_memory_reader_.reset(new content::GamepadSharedMemoryReader);
gamepad_shared_memory_reader_->SampleGamepads(gamepads);
}
WebKit::WebString RendererWebKitPlatformSupportImpl::userAgent( WebKit::WebString RendererWebKitPlatformSupportImpl::userAgent(
const WebKit::WebURL& url) { const WebKit::WebURL& url) {
return WebKitPlatformSupportImpl::userAgent(url); return WebKitPlatformSupportImpl::userAgent(url);
......
...@@ -16,6 +16,10 @@ class RendererClipboardClient; ...@@ -16,6 +16,10 @@ class RendererClipboardClient;
class WebSharedWorkerRepositoryImpl; class WebSharedWorkerRepositoryImpl;
class WebFileSystemImpl; class WebFileSystemImpl;
namespace content {
class GamepadSharedMemoryReader;
}
namespace IPC { namespace IPC {
class SyncMessage; class SyncMessage;
} }
...@@ -84,6 +88,7 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl ...@@ -84,6 +88,7 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl
size_t buffer_size, unsigned channels, double sample_rate, size_t buffer_size, unsigned channels, double sample_rate,
WebKit::WebAudioDevice::RenderCallback* callback) OVERRIDE; WebKit::WebAudioDevice::RenderCallback* callback) OVERRIDE;
virtual WebKit::WebBlobRegistry* blobRegistry() OVERRIDE; virtual WebKit::WebBlobRegistry* blobRegistry() OVERRIDE;
virtual void sampleGamepads(WebKit::WebGamepads&) OVERRIDE;
virtual WebKit::WebString userAgent(const WebKit::WebURL& url) OVERRIDE; virtual WebKit::WebString userAgent(const WebKit::WebURL& url) OVERRIDE;
virtual void GetPlugins(bool refresh, virtual void GetPlugins(bool refresh,
std::vector<webkit::WebPluginInfo>* plugins) OVERRIDE; std::vector<webkit::WebPluginInfo>* plugins) OVERRIDE;
...@@ -121,6 +126,8 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl ...@@ -121,6 +126,8 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl
scoped_ptr<WebFileSystemImpl> web_file_system_; scoped_ptr<WebFileSystemImpl> web_file_system_;
scoped_ptr<WebKit::WebBlobRegistry> blob_registry_; scoped_ptr<WebKit::WebBlobRegistry> blob_registry_;
scoped_ptr<content::GamepadSharedMemoryReader> gamepad_shared_memory_reader_;
}; };
#endif // CONTENT_RENDERER_RENDERER_WEBKITPLATFORMSUPPORT_IMPL_H_ #endif // CONTENT_RENDERER_RENDERER_WEBKITPLATFORMSUPPORT_IMPL_H_
...@@ -392,13 +392,6 @@ ...@@ -392,13 +392,6 @@
fun:base::StatisticsRecorder::FindHistogram fun:base::StatisticsRecorder::FindHistogram
fun:base::*Histogram::FactoryGet fun:base::*Histogram::FactoryGet
} }
{
bug_79050 Test to detect read contention & benign race
ThreadSanitizer:Race
...
fun:::MockDataFetcher::GetGamepadData
fun:gamepad::Provider::DoPoll
}
# 3. Suppressions for real chromium bugs that are not yet fixed. # 3. Suppressions for real chromium bugs that are not yet fixed.
############################ ############################
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment