rhbz#1425304 sdext: join TimerScheduler thread before shutdown

Use a osl::Condition to interrupt the waiting thread, and join it.

Change-Id: I638a2495afd082446c39faf9362578bd0f758d04
(cherry picked from commit 15b033d1dd09cd1898a994a0f12e38ed5cf847cb)
Reviewed-on: https://gerrit.libreoffice.org/37992
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
diff --git a/sdext/source/presenter/PresenterScrollBar.cxx b/sdext/source/presenter/PresenterScrollBar.cxx
index 8cba1bc..831c0ec 100644
--- a/sdext/source/presenter/PresenterScrollBar.cxx
+++ b/sdext/source/presenter/PresenterScrollBar.cxx
@@ -802,6 +802,7 @@ void PresenterScrollBar::MousePressRepeater::Start (const PresenterScrollBar::Ar
        // Schedule repeated executions.
        auto pThis(shared_from_this());
        mnMousePressRepeaterTaskId = PresenterTimer::ScheduleRepeatedTask (
            mpScrollBar->GetComponentContext(),
            [pThis] (TimeValue const& rTime) { return pThis->Callback(rTime); },
            500000000,
            250000000);
diff --git a/sdext/source/presenter/PresenterScrollBar.hxx b/sdext/source/presenter/PresenterScrollBar.hxx
index 7706ce4..1c51194 100644
--- a/sdext/source/presenter/PresenterScrollBar.hxx
+++ b/sdext/source/presenter/PresenterScrollBar.hxx
@@ -60,6 +60,9 @@ public:

    virtual void SAL_CALL disposing() override;

    css::uno::Reference<css::uno::XComponentContext> const&
        GetComponentContext() { return mxComponentContext; }

    void SetVisible (const bool bIsVisible);

    /** Set the bounding box of the scroll bar.
diff --git a/sdext/source/presenter/PresenterTextView.cxx b/sdext/source/presenter/PresenterTextView.cxx
index 8e3800b..1946696 100644
--- a/sdext/source/presenter/PresenterTextView.cxx
+++ b/sdext/source/presenter/PresenterTextView.cxx
@@ -73,6 +73,7 @@ PresenterTextView::PresenterTextView (
      mpFont(),
      maParagraphs(),
      mpCaret(new PresenterTextCaret(
          rxContext,
          [this] (sal_Int32 const nParagraphIndex, sal_Int32 const nCharacterIndex)
              { return this->GetCaretBounds(nParagraphIndex, nCharacterIndex); },
          rInvalidator)),
@@ -1077,9 +1078,11 @@ void PresenterTextParagraph::SetupCellArray (
//===== PresenterTextCaret ================================================----

PresenterTextCaret::PresenterTextCaret (
        uno::Reference<uno::XComponentContext> const& xContext,
    const ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)>& rCharacterBoundsAccess,
    const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator)
    : mnParagraphIndex(-1),
    : m_xContext(xContext)
    , mnParagraphIndex(-1),
      mnCharacterIndex(-1),
      mnCaretBlinkTaskId(0),
      mbIsCaretVisible(false),
@@ -1100,6 +1103,7 @@ void PresenterTextCaret::ShowCaret()
    if (mnCaretBlinkTaskId == 0)
    {
        mnCaretBlinkTaskId = PresenterTimer::ScheduleRepeatedTask (
            m_xContext,
            [this] (TimeValue const&) { return this->InvertCaret(); },
            CaretBlinkIntervall,
            CaretBlinkIntervall);
diff --git a/sdext/source/presenter/PresenterTextView.hxx b/sdext/source/presenter/PresenterTextView.hxx
index b56bd5a..9fc7fd6 100644
--- a/sdext/source/presenter/PresenterTextView.hxx
+++ b/sdext/source/presenter/PresenterTextView.hxx
@@ -41,6 +41,7 @@ class PresenterTextCaret
{
public:
    PresenterTextCaret (
        css::uno::Reference<css::uno::XComponentContext> const& xContext,
        const ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)>&
            rCharacterBoundsAccess,
        const ::std::function<void (const css::awt::Rectangle&)>&
@@ -69,6 +70,7 @@ public:
    const css::awt::Rectangle& GetBounds() const;

private:
    css::uno::Reference<css::uno::XComponentContext> const& m_xContext;
    sal_Int32 mnParagraphIndex;
    sal_Int32 mnCharacterIndex;
    sal_Int32 mnCaretBlinkTaskId;
diff --git a/sdext/source/presenter/PresenterTimer.cxx b/sdext/source/presenter/PresenterTimer.cxx
index e9d3117..6d48976 100644
--- a/sdext/source/presenter/PresenterTimer.cxx
+++ b/sdext/source/presenter/PresenterTimer.cxx
@@ -18,10 +18,15 @@
 */

#include "PresenterTimer.hxx"

#include <com/sun/star/lang/XMultiComponentFactory.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/frame/XTerminateListener.hpp>

#include <osl/doublecheckedlocking.h>
#include <osl/thread.hxx>
#include <osl/conditn.hxx>

#include <algorithm>
#include <iterator>
@@ -71,7 +76,8 @@ class TimerScheduler
      public ::osl::Thread
{
public:
    static std::shared_ptr<TimerScheduler> Instance();
    static std::shared_ptr<TimerScheduler> Instance(
        uno::Reference<uno::XComponentContext> const& xContext);
    static SharedTimerTask CreateTimerTask (
        const PresenterTimer::Task& rTask,
        const TimeValue& rDueTime,
@@ -90,6 +96,9 @@ public:
    static sal_Int64 ConvertFromTimeValue (
        const TimeValue& rTimeValue);

    static void NotifyTermination();
    static bool HasInstance() { return mpInstance != nullptr; }

private:
    static std::shared_ptr<TimerScheduler> mpInstance;
    static ::osl::Mutex maInstanceMutex;
@@ -101,8 +110,10 @@ private:
    TaskContainer maScheduledTasks;
    ::osl::Mutex maCurrentTaskMutex;
    SharedTimerTask mpCurrentTask;
    ::osl::Condition m_Shutdown;

    TimerScheduler();
    TimerScheduler(
        uno::Reference<uno::XComponentContext> const& xContext);
    virtual ~TimerScheduler() override;
    class Deleter {public: void operator () (TimerScheduler* pScheduler) { delete pScheduler; } };
    friend class Deleter;
@@ -111,15 +122,42 @@ private:
    virtual void SAL_CALL onTerminated() override { mpLateDestroy.reset(); }
};

class TerminateListener
    : public ::cppu::WeakImplHelper<frame::XTerminateListener>
{
    virtual ~TerminateListener() override
    {
        assert(!TimerScheduler::HasInstance());
    }

    virtual void SAL_CALL disposing(lang::EventObject const&)
        throw (uno::RuntimeException, std::exception) override
    {
    }

    virtual void SAL_CALL queryTermination(lang::EventObject const&)
        throw (uno::RuntimeException, std::exception) override
    {
    }

    virtual void SAL_CALL notifyTermination(lang::EventObject const&)
        throw (uno::RuntimeException, std::exception) override
    {
        TimerScheduler::NotifyTermination();
    }
};

} // end of anonymous namespace

//===== PresenterTimer ========================================================

sal_Int32 PresenterTimer::ScheduleRepeatedTask (
    const uno::Reference<uno::XComponentContext>& xContext,
    const Task& rTask,
    const sal_Int64 nDelay,
    const sal_Int64 nIntervall)
{
    assert(xContext.is());
    TimeValue aCurrentTime;
    if (TimerScheduler::GetCurrentTime(aCurrentTime))
    {
@@ -128,7 +166,7 @@ sal_Int32 PresenterTimer::ScheduleRepeatedTask (
            aDueTime,
            TimerScheduler::ConvertFromTimeValue (aCurrentTime) + nDelay);
        SharedTimerTask pTask (TimerScheduler::CreateTimerTask(rTask, aDueTime, nIntervall));
        TimerScheduler::Instance()->ScheduleTask(pTask);
        TimerScheduler::Instance(xContext)->ScheduleTask(pTask);
        return pTask->mnTaskId;
    }

@@ -137,7 +175,11 @@ sal_Int32 PresenterTimer::ScheduleRepeatedTask (

void PresenterTimer::CancelTask (const sal_Int32 nTaskId)
{
    return TimerScheduler::Instance()->CancelTask(nTaskId);
    auto const pInstance(TimerScheduler::Instance(nullptr));
    if (pInstance)
    {
        pInstance->CancelTask(nTaskId);
    }
}

//===== TimerScheduler ========================================================
@@ -146,23 +188,33 @@ std::shared_ptr<TimerScheduler> TimerScheduler::mpInstance;
::osl::Mutex TimerScheduler::maInstanceMutex;
sal_Int32 TimerScheduler::mnTaskId = PresenterTimer::NotAValidTaskId;

std::shared_ptr<TimerScheduler> TimerScheduler::Instance()
std::shared_ptr<TimerScheduler> TimerScheduler::Instance(
    uno::Reference<uno::XComponentContext> const& xContext)
{
    ::osl::MutexGuard aGuard (maInstanceMutex);
    if (mpInstance.get() == nullptr)
    {
        mpInstance.reset(new TimerScheduler(), TimerScheduler::Deleter());
        if (!xContext.is())
            return nullptr;
        mpInstance.reset(new TimerScheduler(xContext), TimerScheduler::Deleter());
        mpInstance->create();
    }
    return mpInstance;
}

TimerScheduler::TimerScheduler()
TimerScheduler::TimerScheduler(
        uno::Reference<uno::XComponentContext> const& xContext)
    : maTaskContainerMutex(),
      maScheduledTasks(),
      maCurrentTaskMutex(),
      mpCurrentTask()
{
    uno::Reference<frame::XDesktop> const xDesktop(
            frame::Desktop::create(xContext));
    uno::Reference<frame::XTerminateListener> const xListener(
            new TerminateListener);
    // assuming the desktop can take ownership
    xDesktop->addTerminateListener(xListener);
}

TimerScheduler::~TimerScheduler()
@@ -222,6 +274,33 @@ void TimerScheduler::CancelTask (const sal_Int32 nTaskId)
    // Let the main-loop cleanup in its own time
}

void TimerScheduler::NotifyTermination()
{
    std::shared_ptr<TimerScheduler> const pInstance(TimerScheduler::mpInstance);
    if (!pInstance)
    {
        return;
    }

    {
        ::osl::MutexGuard aGuard(pInstance->maTaskContainerMutex);
        pInstance->maScheduledTasks.clear();
    }

    {
        ::osl::MutexGuard aGuard(pInstance->maCurrentTaskMutex);
        if (pInstance->mpCurrentTask)
        {
            pInstance->mpCurrentTask->mbIsCanceled = true;
        }
    }

    pInstance->m_Shutdown.set();

    // rhbz#1425304 join thread before shutdown
    pInstance->join();
}

void SAL_CALL TimerScheduler::run()
{
    osl_setThreadName("sdext::presenter::TimerScheduler");
@@ -269,7 +348,8 @@ void SAL_CALL TimerScheduler::run()
            // Wait until the first task becomes due.
            TimeValue aTimeValue;
            ConvertToTimeValue(aTimeValue, nDifference);
            wait(aTimeValue);
            // wait on condition variable, so the thread can be stopped
            m_Shutdown.wait(&aTimeValue);
        }
        else
        {
@@ -381,7 +461,9 @@ PresenterClockTimer::PresenterClockTimer (const Reference<XComponentContext>& rx
      mnTimerTaskId(PresenterTimer::NotAValidTaskId),
      mbIsCallbackPending(false),
      mxRequestCallback()
    , m_xContext(rxContext)
{
    assert(m_xContext.is());
    Reference<lang::XMultiComponentFactory> xFactory (
        rxContext->getServiceManager(), UNO_QUERY);
    if (xFactory.is())
@@ -416,6 +498,7 @@ void PresenterClockTimer::AddListener (const SharedListener& rListener)
    if (mnTimerTaskId==PresenterTimer::NotAValidTaskId)
    {
        mnTimerTaskId = PresenterTimer::ScheduleRepeatedTask(
            m_xContext,
            [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); },
            0,
            250000000 /*ns*/);
diff --git a/sdext/source/presenter/PresenterTimer.hxx b/sdext/source/presenter/PresenterTimer.hxx
index 6b6ce0e..4878497 100644
--- a/sdext/source/presenter/PresenterTimer.hxx
+++ b/sdext/source/presenter/PresenterTimer.hxx
@@ -57,6 +57,7 @@ public:
        nIntervall ns long until CancelTask is called.
    */
    static sal_Int32 ScheduleRepeatedTask (
        const css::uno::Reference<css::uno::XComponentContext>& xContext,
        const Task& rTask,
        const sal_Int64 nFirst,
        const sal_Int64 nIntervall);
@@ -108,6 +109,7 @@ private:
    sal_Int32 mnTimerTaskId;
    bool mbIsCallbackPending;
    css::uno::Reference<css::awt::XRequestCallback> mxRequestCallback;
    const css::uno::Reference<css::uno::XComponentContext> m_xContext;

    PresenterClockTimer (
        const css::uno::Reference<css::uno::XComponentContext>& rxContext);