Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/confighttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "confighttp.h"
#include "crypto.h"
#include "display_device.h"
#include "entry_handler.h"
#include "file_handler.h"
#include "globals.h"
#include "httpcommon.h"
Expand Down Expand Up @@ -1833,6 +1834,9 @@ namespace confighttp {
platf::set_thread_name("confighttp::tcp");
server->start([&display_addr](const unsigned short port) {
BOOST_LOG(info) << "Configuration UI available at [https://"sv << display_addr << ":" << port << "]";
#ifdef _WIN32
service_ctrl::signal_ready();
#endif
});
} catch (boost::system::system_error &err) {
// It's possible the exception gets thrown after calling server->stop() from a different thread
Expand Down
34 changes: 34 additions & 0 deletions src/entry_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
* @brief Definitions for entry handling functions.
*/
// standard includes
#include <bit>
#include <charconv>
#include <csignal>
#include <cstdint>
#include <format>
#include <iostream>
#include <string>
#include <thread>

// lib includes
#include <lizardbyte/common/env.h>

// local includes
#include "config.h"
#include "confighttp.h"
Expand Down Expand Up @@ -121,6 +128,33 @@ bool is_gamestream_enabled() {
}

namespace service_ctrl {
/// Environment variable used to pass the inherited service readiness event handle.
constexpr auto SERVICE_READY_EVENT_ENV = "SUNSHINE_SERVICE_READY_EVENT";

void signal_ready() {
std::string event_handle_text;
if (!lizardbyte::common::get_env(SERVICE_READY_EVENT_ENV, event_handle_text)) {
return;
}

std::uintptr_t ready_event_value {};
const auto [end, error] = std::from_chars(event_handle_text.data(), event_handle_text.data() + event_handle_text.size(), ready_event_value);
if (error != std::errc {} || end != event_handle_text.data() + event_handle_text.size() || ready_event_value == 0) {
BOOST_LOG(warning) << "Ignoring invalid service ready event handle";
static_cast<void>(lizardbyte::common::unset_env(SERVICE_READY_EVENT_ENV));
return;
}

auto ready_event = std::bit_cast<HANDLE>(ready_event_value);
if (!SetEvent(ready_event)) {
auto winerr = GetLastError();
BOOST_LOG(warning) << "Failed to signal service ready event: "sv << winerr;
}

CloseHandle(ready_event);
static_cast<void>(lizardbyte::common::unset_env(SERVICE_READY_EVENT_ENV));
}

/**
* @brief Owns Windows service-manager handles for the Sunshine service.
*/
Expand Down
8 changes: 8 additions & 0 deletions src/entry_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ bool is_gamestream_enabled();
* @brief Namespace for controlling the Sunshine service model on Windows.
*/
namespace service_ctrl {
/**
* @brief Signal the Windows service wrapper that Sunshine is ready.
* @examples
* signal_ready();
* @examples_end
*/
void signal_ready();

/**
* @brief Check if the service is running.
* @examples
Expand Down
84 changes: 84 additions & 0 deletions tests/unit/test_entry_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,42 @@
#include "../tests_common.h"
#include "../tests_log_checker.h"

#ifdef _WIN32
#include <bit>
#include <cstdint>
#include <lizardbyte/common/env.h>
#include <string>
#include <Windows.h>
#endif

#include <src/entry_handler.h>

#ifdef _WIN32
namespace {
constexpr auto SERVICE_READY_EVENT_ENV = "SUNSHINE_SERVICE_READY_EVENT";

class ServiceReadySignalTest: public testing::Test {
protected:
void SetUp() override {
static_cast<void>(lizardbyte::common::unset_env(SERVICE_READY_EVENT_ENV));
}

void TearDown() override {
static_cast<void>(lizardbyte::common::unset_env(SERVICE_READY_EVENT_ENV));
}
};

std::string handle_to_string(HANDLE handle) {
return std::to_string(std::bit_cast<std::uintptr_t>(handle));
}

bool is_handle_open(HANDLE handle) {
DWORD flags {};
return GetHandleInformation(handle, &flags) != FALSE;
}
} // namespace
#endif

TEST(EntryHandlerTests, LogPublisherDataTest) {
// call log_publisher_data
log_publisher_data();
Expand All @@ -16,3 +50,53 @@ TEST(EntryHandlerTests, LogPublisherDataTest) {
ASSERT_TRUE(log_checker::line_starts_with("test_sunshine.log", "Info: Publisher Website: "));
ASSERT_TRUE(log_checker::line_starts_with("test_sunshine.log", "Info: Get support: "));
}

#ifdef _WIN32
TEST_F(ServiceReadySignalTest, SignalsAndClosesEventHandle) {
const auto source_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
ASSERT_NE(source_event, nullptr);

HANDLE inherited_event {};
ASSERT_TRUE(DuplicateHandle(GetCurrentProcess(), source_event, GetCurrentProcess(), &inherited_event, 0, TRUE, DUPLICATE_SAME_ACCESS));
ASSERT_EQ(lizardbyte::common::set_env(SERVICE_READY_EVENT_ENV, handle_to_string(inherited_event)), 0);

service_ctrl::signal_ready();

EXPECT_EQ(WaitForSingleObject(source_event, 0), WAIT_OBJECT_0);
const auto inherited_event_is_open = is_handle_open(inherited_event);
EXPECT_FALSE(inherited_event_is_open);
if (inherited_event_is_open) {
CloseHandle(inherited_event);
}

std::string env_value;
EXPECT_FALSE(lizardbyte::common::get_env(SERVICE_READY_EVENT_ENV, env_value));
EXPECT_TRUE(CloseHandle(source_event));
}

TEST_F(ServiceReadySignalTest, ClearsInvalidHandleText) {
ASSERT_EQ(lizardbyte::common::set_env(SERVICE_READY_EVENT_ENV, "not-a-handle"), 0);

service_ctrl::signal_ready();

std::string env_value;
EXPECT_FALSE(lizardbyte::common::get_env(SERVICE_READY_EVENT_ENV, env_value));
}

TEST_F(ServiceReadySignalTest, ClosesHandleWhenSetEventFails) {
const auto mutex = CreateMutexW(nullptr, FALSE, nullptr);
ASSERT_NE(mutex, nullptr);
ASSERT_EQ(lizardbyte::common::set_env(SERVICE_READY_EVENT_ENV, handle_to_string(mutex)), 0);

service_ctrl::signal_ready();

const auto mutex_is_open = is_handle_open(mutex);
EXPECT_FALSE(mutex_is_open);
if (mutex_is_open) {
CloseHandle(mutex);
}

std::string env_value;
EXPECT_FALSE(lizardbyte::common::get_env(SERVICE_READY_EVENT_ENV, env_value));
}
#endif
1 change: 1 addition & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ target_compile_options(audio-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
add_executable(sunshinesvc sunshinesvc.cpp)
target_link_libraries(sunshinesvc
${CMAKE_THREAD_LIBS_INIT}
lizardbyte::common
wtsapi32
${PLATFORM_LIBRARIES})
target_compile_options(sunshinesvc PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
Loading