[server] Fix remote access buffer handling when output not bound

Summary:
If a client has not bound a certain output do not directly return
but try to deliver the buffer to other clients.

If none of them has requested it, send bufferReleased signal
immediately to let compositor know that the buffer should
be cleaned up.

Test Plan: Manually. Autotest planned.

Reviewers: #kwin, #frameworks, davidedmundson

Reviewed By: #kwin, davidedmundson

Subscribers: jgrulich, davidedmundson, kde-frameworks-devel

Tags: #frameworks

Differential Revision: https://phabricator.kde.org/D15422
This commit is contained in:
Roman Gilg 2018-09-11 17:32:20 +02:00
parent 44f0d3ced0
commit 88fde0b4ce
2 changed files with 222 additions and 99 deletions

View file

@ -1,5 +1,6 @@
/********************************************************************
Copyright 2016 Oleg Chernovskiy <kanedias@xaker.ru>
Copyright 2018 Roman Gilg <subdiff@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@ -48,22 +49,108 @@ private Q_SLOTS:
void testSendReleaseSingle();
void testSendReleaseMultiple();
void testSendReleaseCrossScreen();
void testSendClientGone();
void testSendReceiveClientGone();
private:
Display *m_display = nullptr;
OutputInterface *m_outputInterface = nullptr;
OutputInterface *m_outputInterface[2] = {nullptr};
RemoteAccessManagerInterface *m_remoteAccessInterface = nullptr;
ConnectionThread *m_connection = nullptr;
QThread *m_thread = nullptr;
EventQueue *m_queue = nullptr;
Registry *m_registry = nullptr;
Output *m_output = nullptr;
};
class MockupClient : public QObject
{
Q_OBJECT
public:
MockupClient(QObject *parent = nullptr);
~MockupClient();
void bindOutput(int index);
ConnectionThread *connection = nullptr;
QThread *thread = nullptr;
EventQueue *queue = nullptr;
Registry *registry = nullptr;
RemoteAccessManager *remoteAccess = nullptr;
Output *outputs[2] = {nullptr};
};
static const QString s_socketName = QStringLiteral("kwayland-test-remote-access-0");
MockupClient::MockupClient(QObject *parent)
: QObject(parent)
{
// setup connection
connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(connection, &ConnectionThread::connected);
QVERIFY(connectedSpy.isValid());
connection->setSocketName(s_socketName);
thread = new QThread(this);
connection->moveToThread(thread);
thread->start();
connection->initConnection();
QVERIFY(connectedSpy.wait());
queue = new EventQueue(this);
queue->setup(connection);
registry = new Registry(this);
QSignalSpy interfacesAnnouncedSpy(registry, &Registry::interfacesAnnounced);
QVERIFY(interfacesAnnouncedSpy.isValid());
registry->setEventQueue(queue);
registry->create(connection);
QVERIFY(registry->isValid());
registry->setup();
QVERIFY(interfacesAnnouncedSpy.wait());
remoteAccess = registry->createRemoteAccessManager(
registry->interface(Registry::Interface::RemoteAccessManager).name,
registry->interface(Registry::Interface::RemoteAccessManager).version,
this);
QVERIFY(remoteAccess->isValid());
connection->flush();
}
MockupClient::~MockupClient()
{
#define CLEANUP(variable) \
if (variable) { \
delete variable; \
variable = nullptr; \
}
CLEANUP(outputs[0])
CLEANUP(outputs[1])
CLEANUP(remoteAccess)
CLEANUP(queue)
CLEANUP(registry)
if (thread) {
if (connection) {
connection->flush();
connection->deleteLater();
connection = nullptr;
}
thread->quit();
thread->wait();
delete thread;
thread = nullptr;
}
#undef CLEANUP
}
void MockupClient::bindOutput(int index)
{
// client-bound output
outputs[index] = registry->createOutput(registry->interfaces(Registry::Interface::Output)[index].name,
registry->interfaces(Registry::Interface::Output)[index].version,
this);
QVERIFY(outputs[index]->isValid());
connection->flush();
}
void RemoteAccessTest::init()
{
qRegisterMetaType<const BufferHandle *>();
@ -76,42 +163,18 @@ void RemoteAccessTest::init()
m_display->start();
QVERIFY(m_display->isRunning());
m_display->createShm();
m_outputInterface = m_display->createOutput();
m_outputInterface->create();
auto initOutputIface = [this](int i) {
m_outputInterface[i] = m_display->createOutput();
m_outputInterface[i]->create();
};
initOutputIface(0);
initOutputIface(1);
m_remoteAccessInterface = m_display->createRemoteAccessManager();
m_remoteAccessInterface->create();
QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased);
QVERIFY(bufferReleasedSpy.isValid());
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected);
QVERIFY(connectedSpy.isValid());
m_connection->setSocketName(s_socketName);
m_thread = new QThread(this);
m_connection->moveToThread(m_thread);
m_thread->start();
m_connection->initConnection();
QVERIFY(connectedSpy.wait());
m_queue = new EventQueue(this);
m_queue->setup(m_connection);
m_registry = new Registry(this);
QSignalSpy interfacesAnnouncedSpy(m_registry, &Registry::interfacesAnnounced);
QVERIFY(interfacesAnnouncedSpy.isValid());
m_registry->setEventQueue(m_queue);
m_registry->create(m_connection);
QVERIFY(m_registry->isValid());
m_registry->setup();
QVERIFY(interfacesAnnouncedSpy.wait());
// client-bound output
m_output = m_registry->createOutput(m_registry->interface(Registry::Interface::Output).name,
m_registry->interface(Registry::Interface::Output).version,
this);
}
void RemoteAccessTest::cleanup()
@ -121,22 +184,8 @@ void RemoteAccessTest::cleanup()
delete variable; \
variable = nullptr; \
}
CLEANUP(m_output)
CLEANUP(m_queue)
CLEANUP(m_registry)
if (m_thread) {
if (m_connection) {
m_connection->flush();
m_connection->deleteLater();
m_connection = nullptr;
}
m_thread->quit();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
CLEANUP(m_outputInterface[0])
CLEANUP(m_outputInterface[1])
CLEANUP(m_remoteAccessInterface)
CLEANUP(m_display)
#undef CLEANUP
@ -148,16 +197,13 @@ void RemoteAccessTest::testSendReleaseSingle()
// setup
QVERIFY(!m_remoteAccessInterface->isBound());
auto client = m_registry->createRemoteAccessManager(
m_registry->interface(Registry::Interface::RemoteAccessManager).name,
m_registry->interface(Registry::Interface::RemoteAccessManager).version,
this);
QVERIFY(client->isValid());
m_connection->flush();
auto *client = new MockupClient(this);
client->bindOutput(0);
m_display->dispatchEvents();
QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now
QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady);
QSignalSpy bufferReadySpy(client->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy.isValid());
BufferHandle *buf = new BufferHandle();
@ -168,7 +214,7 @@ void RemoteAccessTest::testSendReleaseSingle()
buf->setSize(50, 50);
buf->setFormat(100500);
buf->setStride(7800);
m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf);
m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf);
// receive buffer
QVERIFY(bufferReadySpy.wait());
@ -193,7 +239,6 @@ void RemoteAccessTest::testSendReleaseSingle()
// cleanup
delete buf;
delete client;
m_connection->flush();
m_display->dispatchEvents();
QVERIFY(!m_remoteAccessInterface->isBound());
}
@ -204,23 +249,16 @@ void RemoteAccessTest::testSendReleaseMultiple()
// setup
QVERIFY(!m_remoteAccessInterface->isBound());
auto client1 = m_registry->createRemoteAccessManager(
m_registry->interface(Registry::Interface::RemoteAccessManager).name,
m_registry->interface(Registry::Interface::RemoteAccessManager).version,
this);
QVERIFY(client1->isValid());
auto client2 = m_registry->createRemoteAccessManager(
m_registry->interface(Registry::Interface::RemoteAccessManager).name,
m_registry->interface(Registry::Interface::RemoteAccessManager).version,
this);
QVERIFY(client2->isValid());
m_connection->flush();
auto *client1 = new MockupClient(this);
auto *client2 = new MockupClient(this);
client1->bindOutput(0);
client2->bindOutput(0);
m_display->dispatchEvents();
QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients
QSignalSpy bufferReadySpy1(client1, &RemoteAccessManager::bufferReady);
QSignalSpy bufferReadySpy1(client1->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy1.isValid());
QSignalSpy bufferReadySpy2(client2, &RemoteAccessManager::bufferReady);
QSignalSpy bufferReadySpy2(client2->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy2.isValid());
BufferHandle *buf = new BufferHandle();
@ -231,10 +269,13 @@ void RemoteAccessTest::testSendReleaseMultiple()
buf->setSize(50, 50);
buf->setFormat(100500);
buf->setStride(7800);
m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf);
m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf);
// wait for event loop
QVERIFY(bufferReadySpy1.wait());
if (bufferReadySpy2.size() == 0) {
QVERIFY(bufferReadySpy2.wait());
}
// receive buffer at client 1
QCOMPARE(bufferReadySpy1.size(), 1);
@ -251,6 +292,9 @@ void RemoteAccessTest::testSendReleaseMultiple()
// wait for event loop
QVERIFY(paramsObtainedSpy1.wait());
QCOMPARE(paramsObtainedSpy1.size(), 1);
if (paramsObtainedSpy2.size() == 0) {
QVERIFY(paramsObtainedSpy2.wait());
}
QCOMPARE(paramsObtainedSpy2.size(), 1);
// release
@ -266,7 +310,92 @@ void RemoteAccessTest::testSendReleaseMultiple()
delete buf;
delete client1;
delete client2;
m_connection->flush();
m_display->dispatchEvents();
QVERIFY(!m_remoteAccessInterface->isBound());
}
void RemoteAccessTest::testSendReleaseCrossScreen()
{
// this test verifies that multiple buffers for multiple screens are sent to
// multiple clients and returned back
// setup
QVERIFY(!m_remoteAccessInterface->isBound());
auto *client1 = new MockupClient(this);
auto *client2 = new MockupClient(this);
client1->bindOutput(1);
client2->bindOutput(0);
m_display->dispatchEvents();
QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients
QSignalSpy bufferReadySpy1(client1->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy1.isValid());
QSignalSpy bufferReadySpy2(client2->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy2.isValid());
BufferHandle *buf1 = new BufferHandle();
QTemporaryFile *tmpFile1 = new QTemporaryFile(this);
tmpFile1->open();
BufferHandle *buf2 = new BufferHandle();
QTemporaryFile *tmpFile2 = new QTemporaryFile(this);
tmpFile2->open();
buf1->setFd(tmpFile1->handle());
buf1->setSize(50, 50);
buf1->setFormat(100500);
buf1->setStride(7800);
buf2->setFd(tmpFile2->handle());
buf2->setSize(100, 100);
buf2->setFormat(100500);
buf2->setStride(7800);
m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf1);
m_remoteAccessInterface->sendBufferReady(m_outputInterface[1], buf2);
// wait for event loop
QVERIFY(bufferReadySpy1.wait());
if (bufferReadySpy2.size() == 0) {
QVERIFY(bufferReadySpy2.wait());
}
// receive buffer at client 1
QCOMPARE(bufferReadySpy1.size(), 1);
auto rbuf1 = bufferReadySpy1.takeFirst()[1].value<const RemoteBuffer *>();
QSignalSpy paramsObtainedSpy1(rbuf1, &RemoteBuffer::parametersObtained);
QVERIFY(paramsObtainedSpy1.isValid());
// receive buffer at client 2
QCOMPARE(bufferReadySpy2.size(), 1);
auto rbuf2 = bufferReadySpy2.takeFirst()[1].value<const RemoteBuffer *>();
QSignalSpy paramsObtainedSpy2(rbuf2, &RemoteBuffer::parametersObtained);
QVERIFY(paramsObtainedSpy2.isValid());
// wait for event loop
QVERIFY(paramsObtainedSpy1.wait());
if (paramsObtainedSpy2.size() == 0) {
QVERIFY(paramsObtainedSpy2.wait());
}
QCOMPARE(paramsObtainedSpy1.size(), 1);
QCOMPARE(paramsObtainedSpy2.size(), 1);
// release
QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased);
QVERIFY(bufferReleasedSpy.isValid());
delete rbuf1;
QVERIFY(bufferReleasedSpy.wait());
delete rbuf2;
QVERIFY(bufferReleasedSpy.wait());
QCOMPARE(bufferReleasedSpy.size(), 2);
// cleanup
delete buf1;
delete buf2;
delete client1;
delete client2;
m_display->dispatchEvents();
QVERIFY(!m_remoteAccessInterface->isBound());
}
@ -275,16 +404,12 @@ void RemoteAccessTest::testSendClientGone()
{
// this test verifies that when buffer is sent and client is gone, server will release buffer correctly
QVERIFY(!m_remoteAccessInterface->isBound());
auto client = m_registry->createRemoteAccessManager(
m_registry->interface(Registry::Interface::RemoteAccessManager).name,
m_registry->interface(Registry::Interface::RemoteAccessManager).version,
this);
QVERIFY(client->isValid());
m_connection->flush();
auto *client = new MockupClient(this);
client->bindOutput(0);
m_display->dispatchEvents();
QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now
QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady);
QSignalSpy bufferReadySpy(client->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy.isValid());
BufferHandle *buf = new BufferHandle();
@ -295,7 +420,7 @@ void RemoteAccessTest::testSendClientGone()
buf->setSize(50, 50);
buf->setFormat(100500);
buf->setStride(7800);
m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf);
m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf);
// release forcefully
QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased);
@ -305,7 +430,6 @@ void RemoteAccessTest::testSendClientGone()
// cleanup
delete buf;
m_connection->flush();
m_display->dispatchEvents();
QVERIFY(!m_remoteAccessInterface->isBound());
}
@ -315,16 +439,12 @@ void RemoteAccessTest::testSendReceiveClientGone()
// this test verifies that when buffer is sent, received and client is gone,
// both client and server will release buffer correctly
QVERIFY(!m_remoteAccessInterface->isBound());
auto client = m_registry->createRemoteAccessManager(
m_registry->interface(Registry::Interface::RemoteAccessManager).name,
m_registry->interface(Registry::Interface::RemoteAccessManager).version,
this);
QVERIFY(client->isValid());
m_connection->flush();
auto *client = new MockupClient(this);
client->bindOutput(0);
m_display->dispatchEvents();
QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now
QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady);
QSignalSpy bufferReadySpy(client->remoteAccess, &RemoteAccessManager::bufferReady);
QVERIFY(bufferReadySpy.isValid());
BufferHandle *buf = new BufferHandle();
@ -335,7 +455,7 @@ void RemoteAccessTest::testSendReceiveClientGone()
buf->setSize(50, 50);
buf->setFormat(100500);
buf->setStride(7800);
m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf);
m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf);
// receive buffer
QVERIFY(bufferReadySpy.wait());
@ -359,11 +479,9 @@ void RemoteAccessTest::testSendReceiveClientGone()
// cleanup
delete buf;
m_connection->flush();
m_display->dispatchEvents();
QVERIFY(!m_remoteAccessInterface->isBound());
}
QTEST_GUILESS_MAIN(RemoteAccessTest)
#include "test_remote_access.moc"

View file

@ -204,13 +204,18 @@ void RemoteAccessManagerInterface::Private::sendBufferReady(const OutputInterfac
// clients don't necessarily bind outputs
if (boundScreens.isEmpty()) {
return;
continue;
}
// no reason for client to bind wl_output multiple times, send only to first one
org_kde_kwin_remote_access_manager_send_buffer_ready(res, buf->fd(), boundScreens[0]);
holder.counter++;
}
if (holder.counter == 0) {
// buffer was not requested by any client
emit q->bufferReleased(buf);
return;
}
// store buffer locally, clients will ask it later
sentBuffers[buf->fd()] = holder;
}