Skip to content

Commit

Permalink
Document session
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog committed Feb 20, 2024
1 parent e17e829 commit ce3c662
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 49 deletions.
6 changes: 3 additions & 3 deletions src/display_device/parsed_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* const std::shared_ptr< rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
*
* parsed_config_t parsed_config;
Expand Down Expand Up @@ -100,7 +100,7 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* const std::shared_ptr< rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
*
* parsed_config_t parsed_config;
Expand Down Expand Up @@ -192,7 +192,7 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* const std::shared_ptr< rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
* const auto hdr_option = parse_hdr_option(video_config, *launch_session);
* ```
Expand Down
2 changes: 1 addition & 1 deletion src/display_device/parsed_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ namespace display_device {
*
* EXAMPLES:
* ```cpp
* const std::shared_ptr< rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
* const auto parsed_config = make_parsed_config(video_config, *launch_session);
* ```
Expand Down
56 changes: 40 additions & 16 deletions src/display_device/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,35 @@ namespace display_device {

class session_t::StateRestoreRetryTimer {
public:
/**
* @brief A constructor for the timer.
* @param mutex A shared mutex for synchronization.
* @param settings A shared settings instance for reverting settings.
* @param timeout_duration An amount of time to wait until retrying.
* @warning Because we are keeping references to shared parameters, we MUST ensure they outlive this object!
*/
StateRestoreRetryTimer(std::mutex &mutex, settings_t &settings, std::chrono::milliseconds timeout_duration):
mutex { mutex }, settings { settings }, timeout_duration { timeout_duration }, timer_thread {
std::thread { [this]() {
std::unique_lock<std::mutex> lock { this->mutex };
while (keep_alive) {
can_wake_up = false;
if (next_wake_up_time) {
// We're going to sleep forever until manually woken up or the time elapses
sleep_cv.wait_until(lock, *next_wake_up_time, [this]() { return can_wake_up; });
}
else {
// We're going to sleep forever until manually woken up
sleep_cv.wait(lock, [this]() { return can_wake_up; });
}

if (next_wake_up_time) {
// Timer has been started freshly or we have waited for the required amount of time.
// We can check this by comparing time points.
// Timer has just been started, or we have waited for the required amount of time.
// We can check which case it is by comparing time points.

const auto now { std::chrono::steady_clock::now() };
if (now < *next_wake_up_time) {
// Thread has been waken up by `start_unlocked` to synchronize the time point.
// Thread has been woken up manually to synchronize the time points.
// We do nothing and just go back to waiting with a new time point.
}
else {
Expand All @@ -50,6 +60,9 @@ namespace display_device {
} {
}

/**
* @brief A destructor for the timer that gracefully shuts down the thread.
*/
~StateRestoreRetryTimer() {
{
std::lock_guard lock { mutex };
Expand All @@ -61,8 +74,16 @@ namespace display_device {
timer_thread.join();
}

/**
* @brief Start or stop the timer thread.
* @param start Indicate whether to start or stop the timer.
* True - start or restart the timer to be executed after the specified duration from now.
* False - stop the timer and put the thread to sleep.
* @warning This method does NOT acquire the mutex! It is intended to be used from places
* where the mutex has already been locked.
*/
void
setup_timer_unlocked(bool start) {
setup_timer(bool start) {
if (start) {
next_wake_up_time = std::chrono::steady_clock::now() + timeout_duration;
}
Expand All @@ -78,22 +99,25 @@ namespace display_device {
}

private:
/**
* @brief Manually wake up the thread.
*/
void
wake_up_thread() {
can_wake_up = true;
sleep_cv.notify_one();
}

std::mutex &mutex;
settings_t &settings;
std::chrono::milliseconds timeout_duration;
std::mutex &mutex; /**< A reference to a shared mutex. */
settings_t &settings; /**< A reference to a shared settings instance. */
std::chrono::milliseconds timeout_duration; /**< A retry time for the timer. */

std::thread timer_thread;
std::condition_variable sleep_cv;
std::thread timer_thread; /**< A timer thread. */
std::condition_variable sleep_cv; /**< Condition variable for waking up thread. */

bool can_wake_up { false };
bool keep_alive { true };
boost::optional<std::chrono::steady_clock::time_point> next_wake_up_time;
bool can_wake_up { false }; /**< Safeguard for the condition variable to prevent sporadic thread wake ups. */
bool keep_alive { true }; /**< A kill switch for the thread when it has been woken up. */
boost::optional<std::chrono::steady_clock::time_point> next_wake_up_time; /**< Next time point for thread to wake up. */
};

session_t::deinit_t::~deinit_t() {
Expand Down Expand Up @@ -123,7 +147,7 @@ namespace display_device {
std::lock_guard lock { mutex };

const auto result { settings.apply_config(config, session) };
timer->setup_timer_unlocked(!result);
timer->setup_timer(!result);
return result;
}

Expand All @@ -132,19 +156,19 @@ namespace display_device {
std::lock_guard lock { mutex };

const auto result { settings.revert_settings() };
timer->setup_timer_unlocked(!result);
timer->setup_timer(!result);
}

void
session_t::reset_persistence() {
std::lock_guard lock { mutex };

settings.reset_persistence();
timer->setup_timer_unlocked(false);
timer->setup_timer(false);
}

session_t::session_t():
timer { std::make_unique<StateRestoreRetryTimer>(mutex, settings, std::chrono::milliseconds { 30 * 1000 }) } {
timer { std::make_unique<StateRestoreRetryTimer>(mutex, settings, std::chrono::seconds { 30 }) } {
}

} // namespace display_device
148 changes: 132 additions & 16 deletions src/display_device/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,172 @@

namespace display_device {

//! A singleton for managing the display state for the current Sunshine session
/**
* @brief A singleton class for managing the display device configuration for the whole Sunshine session.
*
* This class is meant to be an entry point for applying the configuration and reverting it later
* from within the various places in the Sunshine's source code.
*
* It is similar to settings_t and is more or less a wrapper around it.
* However, this class ensures thread-safe usage for the methods and additionally
* performs automatic cleanups.
*
* @note A lazy-evaluated, correctly-destroyed, thread-safe singleton pattern is used here (https://stackoverflow.com/a/1008289).
*/
class session_t {
public:
/**
* @brief A class that uses RAII to perform cleanup when it's destroyed.
* @note The deinit_t usage pattern is used here instead of the session_t destructor
* to expedite the cleanup process in case of Sunshine termination.
* @see session_t::init()
*/
class deinit_t {
public:
/**
* @brief A destructor that restores (or tries to) the initial state.
*/
virtual ~deinit_t();
};

/*!
* Gets the current session instance.
/**
* @brief Get the singleton instance.
* @returns Singleton instance for the class.
*
* EXAMPLES:
* ```cpp
* session_t& session { session_t::get() };
* ```
*/
static session_t &
get();

/*!
* Initializes the session which will perform recovery and cleanup in case we crashed
* or did an unexpected shutdown.
/**
* @brief Initialize the singleton and perform the initial state recovery (if needed).
* @returns A deinit_t instance that performs cleanup when destroyed.
* @see deinit_t
*
* EXAMPLES:
* ```cpp
* const auto session_guard { session_t::init() };
* ```
*/
static std::unique_ptr<deinit_t>
init();

/*!
* Prepares the display device based on the session and the configuration.
/**
* @brief Configure the display device based on the user configuration and the session information.
* @param config User's video related configuration.
* @param session Session information.
* @returns The apply result value.
* @note Upon failing to completely apply configuration, the applied settings will be reverted.
* In case the settings cannot be reverted immediately, it will be retried again in 30 seconds
* (repeating indefinitely until success or until persistence is reset).
* @see settings_t::apply_result_t
*
* @returns result structure indicating whether we can continue with the streaming session creation or not.
* EXAMPLES:
* ```cpp
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
*
* const auto result = session_t::get().configure_display(video_config, *launch_session);
* ```
*/
settings_t::apply_result_t
configure_display(const config::video_t &config, const rtsp_stream::launch_session_t &session);

/*!
* Try to restore the previous display state.
/**
* @brief Revert the display configuration and restore the previous state.
* @note This method automatically loads the persistence (if any) from the previous Sunshine session.
* @note In case the state could not be restored, it will be retried again in 30 seconds
* (repeating indefinitely until success or until persistence is reset).
*
* EXAMPLES:
* ```cpp
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
*
* @note Not everything can be restored if the display was unplugged, etc.
* const auto result = session_t::get().configure_display(video_config, *launch_session);
* if (result) {
* // Wait for some time
* session_t::get().restore_state();
* }
* ```
*/
void
restore_state();

/**
* @brief Reset the persistence and currently held initial display state.
*
* This is normally used to get out of the "broken" state where the algorithm wants
* to restore the initial display state and refuses start the stream in most cases.
*
* This could happen if the display is no longer available or the hardware was changed
* and the device ids no longer match.
*
* The user then accepts that Sunshine is not able to restore the state and "agrees" to
* do it manually.
*
* @note This also stops the 30 seconds retry timer.
*
* EXAMPLES:
* ```cpp
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session; // Assuming ptr is properly initialized
* const config::video_t &video_config { config::video };
*
* const auto result = session_t::get().configure_display(video_config, *launch_session);
* if (!result) {
* // Wait for user to decide what to do
* const bool user_wants_reset { true };
* if (user_wants_reset) {
* session_t::get().reset_persistence();
* }
* }
* ```
*/
void
reset_persistence();

/**
* @brief A deleted copy constructor for singleton pattern.
* @note Public to ensure better error message.
*/
session_t(session_t const &) = delete;

/**
* @brief A deleted assignment operator for singleton pattern.
* @note Public to ensure better error message.
*/
void
operator=(session_t const &) = delete;

private:
// Forward declaration for a timer that is started whenever we fail to restore the state.
/**
* @brief A class for retrying to restore the original state.
*
* This timer class spins a thread which is mostly sleeping all the time, but can be
* configured to wake up every 30 seconds to try and restore the previous state.
*
* It is tightly synchronized with the session_t class via a shared mutex to ensure
* that stupid race conditions do not happen where we successfully apply settings
* for them to be reset by the timer thread immediately.
*/
class StateRestoreRetryTimer;

/**
* @brief A private constructor to ensure the singleton pattern.
* @note Cannot be defaulted in declaration because of forward declared StateRestoreRetryTimer.
*/
explicit session_t();

settings_t settings;
std::mutex mutex;
settings_t settings; /**< A class for managing display device settings. */
std::mutex mutex; /**< A mutex for ensuring thread-safety. */

// Warning! Must be declared after settings and mutex members!
/**
* @brief An instance of StateRestoreRetryTimer.
* @warning MUST BE declared after the settings and mutex members to ensure proper destruction order!.
*/
std::unique_ptr<StateRestoreRetryTimer> timer;
};

Expand Down
Loading

0 comments on commit ce3c662

Please sign in to comment.