Merge pull request #7302 from VPeruS/check-deadlock
[input_common] Fixed thread hang
This commit is contained in:
commit
212b497d5c
4 changed files with 188 additions and 42 deletions
|
@ -536,42 +536,46 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
|
||||||
std::function<void(u16, u16, u16, u16)> data_callback) {
|
std::function<void(u16, u16, u16, u16)> data_callback) {
|
||||||
|
|
||||||
std::thread([=, this] {
|
std::thread([=, this] {
|
||||||
|
u16 min_x{UINT16_MAX};
|
||||||
|
u16 min_y{UINT16_MAX};
|
||||||
|
u16 max_x{};
|
||||||
|
u16 max_y{};
|
||||||
|
|
||||||
Status current_status{Status::Initialized};
|
Status current_status{Status::Initialized};
|
||||||
SocketCallback callback{
|
SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
|
||||||
[](Response::Version) {}, [](Response::PortInfo) {},
|
[&](Response::PadData data) {
|
||||||
[&](Response::PadData data) {
|
constexpr u16 CALIBRATION_THRESHOLD = 100;
|
||||||
static constexpr u16 CALIBRATION_THRESHOLD = 100;
|
|
||||||
static constexpr u16 MAX_VALUE = UINT16_MAX;
|
|
||||||
|
|
||||||
if (current_status == Status::Initialized) {
|
if (current_status == Status::Initialized) {
|
||||||
// Receiving data means the communication is ready now
|
// Receiving data means the communication is ready now
|
||||||
current_status = Status::Ready;
|
current_status = Status::Ready;
|
||||||
status_callback(current_status);
|
status_callback(current_status);
|
||||||
}
|
}
|
||||||
const auto& touchpad_0 = data.touch[0];
|
if (data.touch[0].is_active == 0) {
|
||||||
if (touchpad_0.is_active == 0) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x,
|
||||||
LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
|
data.touch[0].y);
|
||||||
const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
|
min_x = std::min(min_x, static_cast<u16>(data.touch[0].x));
|
||||||
const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
|
min_y = std::min(min_y, static_cast<u16>(data.touch[0].y));
|
||||||
if (current_status == Status::Ready) {
|
if (current_status == Status::Ready) {
|
||||||
// First touch - min data (min_x/min_y)
|
// First touch - min data (min_x/min_y)
|
||||||
current_status = Status::Stage1Completed;
|
current_status = Status::Stage1Completed;
|
||||||
status_callback(current_status);
|
status_callback(current_status);
|
||||||
}
|
}
|
||||||
if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
|
if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD &&
|
||||||
touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
|
data.touch[0].y - min_y > CALIBRATION_THRESHOLD) {
|
||||||
// Set the current position as max value and finishes configuration
|
// Set the current position as max value and finishes
|
||||||
const u16 max_x = touchpad_0.x;
|
// configuration
|
||||||
const u16 max_y = touchpad_0.y;
|
max_x = data.touch[0].x;
|
||||||
current_status = Status::Completed;
|
max_y = data.touch[0].y;
|
||||||
data_callback(min_x, min_y, max_x, max_y);
|
current_status = Status::Completed;
|
||||||
status_callback(current_status);
|
data_callback(min_x, min_y, max_x, max_y);
|
||||||
|
status_callback(current_status);
|
||||||
|
|
||||||
complete_event.Set();
|
complete_event.Set();
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
Socket socket{host, port, std::move(callback)};
|
Socket socket{host, port, std::move(callback)};
|
||||||
std::thread worker_thread{SocketLoop, &socket};
|
std::thread worker_thread{SocketLoop, &socket};
|
||||||
complete_event.Wait();
|
complete_event.Wait();
|
||||||
|
|
|
@ -54,6 +54,18 @@ struct Message {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr Type GetMessageType();
|
constexpr Type GetMessageType();
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Message<T> CreateMessage(const u32 magic, const T data, const u32 sender_id) {
|
||||||
|
boost::crc_32_type crc;
|
||||||
|
Header header{
|
||||||
|
magic, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, sender_id, GetMessageType<T>(),
|
||||||
|
};
|
||||||
|
Message<T> message{header, data};
|
||||||
|
crc.process_bytes(&message, sizeof(Message<T>));
|
||||||
|
message.header.crc = crc.checksum();
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Request {
|
namespace Request {
|
||||||
|
|
||||||
enum RegisterFlags : u8 {
|
enum RegisterFlags : u8 {
|
||||||
|
@ -101,14 +113,7 @@ static_assert(std::is_trivially_copyable_v<PadData>,
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Message<T> Create(const T data, const u32 client_id = 0) {
|
Message<T> Create(const T data, const u32 client_id = 0) {
|
||||||
boost::crc_32_type crc;
|
return CreateMessage(CLIENT_MAGIC, data, client_id);
|
||||||
Header header{
|
|
||||||
CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
|
|
||||||
};
|
|
||||||
Message<T> message{header, data};
|
|
||||||
crc.process_bytes(&message, sizeof(Message<T>));
|
|
||||||
message.header.crc = crc.checksum();
|
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
} // namespace Request
|
} // namespace Request
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,12 @@ add_executable(tests
|
||||||
core/network/network.cpp
|
core/network/network.cpp
|
||||||
tests.cpp
|
tests.cpp
|
||||||
video_core/buffer_base.cpp
|
video_core/buffer_base.cpp
|
||||||
|
input_common/calibration_configuration_job.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
create_target_directory_groups(tests)
|
create_target_directory_groups(tests)
|
||||||
|
|
||||||
target_link_libraries(tests PRIVATE common core)
|
target_link_libraries(tests PRIVATE common core input_common)
|
||||||
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads)
|
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads)
|
||||||
|
|
||||||
add_test(NAME tests COMMAND tests)
|
add_test(NAME tests COMMAND tests)
|
||||||
|
|
136
src/tests/input_common/calibration_configuration_job.cpp
Normal file
136
src/tests/input_common/calibration_configuration_job.cpp
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/crc.hpp>
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
|
#include "input_common/drivers/udp_client.h"
|
||||||
|
#include "input_common/helpers/udp_protocol.h"
|
||||||
|
|
||||||
|
class FakeCemuhookServer {
|
||||||
|
public:
|
||||||
|
FakeCemuhookServer()
|
||||||
|
: socket(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) {}
|
||||||
|
|
||||||
|
~FakeCemuhookServer() {
|
||||||
|
is_running = false;
|
||||||
|
boost::system::error_code error_code;
|
||||||
|
socket.shutdown(boost::asio::socket_base::shutdown_both, error_code);
|
||||||
|
socket.close();
|
||||||
|
if (handler.joinable()) {
|
||||||
|
handler.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 GetPort() {
|
||||||
|
return socket.local_endpoint().port();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetHost() {
|
||||||
|
return socket.local_endpoint().address().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Run(const std::vector<InputCommon::CemuhookUDP::Response::TouchPad> touch_movement_path) {
|
||||||
|
constexpr size_t HeaderSize = sizeof(InputCommon::CemuhookUDP::Header);
|
||||||
|
constexpr size_t PadDataSize =
|
||||||
|
sizeof(InputCommon::CemuhookUDP::Message<InputCommon::CemuhookUDP::Response::PadData>);
|
||||||
|
|
||||||
|
REQUIRE(touch_movement_path.size() > 0);
|
||||||
|
is_running = true;
|
||||||
|
handler = std::thread([touch_movement_path, this]() {
|
||||||
|
auto current_touch_position = touch_movement_path.begin();
|
||||||
|
while (is_running) {
|
||||||
|
boost::asio::ip::udp::endpoint sender_endpoint;
|
||||||
|
boost::system::error_code error_code;
|
||||||
|
auto received_size = socket.receive_from(boost::asio::buffer(receive_buffer),
|
||||||
|
sender_endpoint, 0, error_code);
|
||||||
|
|
||||||
|
if (received_size < HeaderSize) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputCommon::CemuhookUDP::Header header{};
|
||||||
|
std::memcpy(&header, receive_buffer.data(), HeaderSize);
|
||||||
|
switch (header.type) {
|
||||||
|
case InputCommon::CemuhookUDP::Type::PadData: {
|
||||||
|
InputCommon::CemuhookUDP::Response::PadData pad_data{};
|
||||||
|
pad_data.touch[0] = *current_touch_position;
|
||||||
|
const auto pad_message = InputCommon::CemuhookUDP::CreateMessage(
|
||||||
|
InputCommon::CemuhookUDP::SERVER_MAGIC, pad_data, 0);
|
||||||
|
std::memcpy(send_buffer.data(), &pad_message, PadDataSize);
|
||||||
|
socket.send_to(boost::asio::buffer(send_buffer, PadDataSize), sender_endpoint,
|
||||||
|
0, error_code);
|
||||||
|
|
||||||
|
bool can_advance =
|
||||||
|
std::next(current_touch_position) != touch_movement_path.end();
|
||||||
|
if (can_advance) {
|
||||||
|
std::advance(current_touch_position, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InputCommon::CemuhookUDP::Type::PortInfo:
|
||||||
|
case InputCommon::CemuhookUDP::Type::Version:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
boost::asio::io_service io_service;
|
||||||
|
boost::asio::ip::udp::socket socket;
|
||||||
|
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> send_buffer;
|
||||||
|
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> receive_buffer;
|
||||||
|
bool is_running = false;
|
||||||
|
std::thread handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") {
|
||||||
|
Common::Event complete_event;
|
||||||
|
FakeCemuhookServer server;
|
||||||
|
server.Run({{
|
||||||
|
.is_active = 1,
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.is_active = 1,
|
||||||
|
.x = 200,
|
||||||
|
.y = 200,
|
||||||
|
}});
|
||||||
|
|
||||||
|
InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status{};
|
||||||
|
u16 min_x{};
|
||||||
|
u16 min_y{};
|
||||||
|
u16 max_x{};
|
||||||
|
u16 max_y{};
|
||||||
|
InputCommon::CemuhookUDP::CalibrationConfigurationJob job(
|
||||||
|
server.GetHost(), server.GetPort(),
|
||||||
|
[&status,
|
||||||
|
&complete_event](InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status_) {
|
||||||
|
status = status_;
|
||||||
|
if (status ==
|
||||||
|
InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed) {
|
||||||
|
complete_event.Set();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
|
||||||
|
min_x = min_x_;
|
||||||
|
min_y = min_y_;
|
||||||
|
max_x = max_x_;
|
||||||
|
max_y = max_y_;
|
||||||
|
});
|
||||||
|
|
||||||
|
complete_event.WaitUntil(std::chrono::system_clock::now() + std::chrono::seconds(10));
|
||||||
|
REQUIRE(status == InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed);
|
||||||
|
REQUIRE(min_x == 0);
|
||||||
|
REQUIRE(min_y == 0);
|
||||||
|
REQUIRE(max_x == 200);
|
||||||
|
REQUIRE(max_y == 200);
|
||||||
|
}
|
Loading…
Reference in a new issue