HaikuDepot: Shutdown Handling

Improvements to the shutdown handling mechanics so that
if there is an install etc... happening when the
application is quit that it will wait until the process
is complete before actually terminating.

Change-Id: I8d3c4fbd9de0abc9382d55f0a6955b7f63a36637
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4322
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: Adrien Destugues <pulkomandy@gmail.com>
This commit is contained in:
Andrew Lindesay 2021-07-23 17:56:02 +12:00
parent 6a41334ced
commit 95c7b01864
16 changed files with 205 additions and 173 deletions

View File

@ -149,6 +149,7 @@ local applicationSources =
ScrollableGroupView.cpp
SettingsWindow.cpp
SharedBitmap.cpp
ShuttingDownWindow.cpp
ToLatestUserUsageConditionsWindow.cpp
UserCredentials.cpp
UserDetail.cpp
@ -174,7 +175,6 @@ local applicationSources =
AbstractSingleFileServerProcess.cpp
LocalPkgDataLoadProcess.cpp
LocalRepositoryUpdateProcess.cpp
NonBlockingProcessNode.cpp
ProcessCoordinator.cpp
ProcessCoordinatorFactory.cpp
ServerHelper.cpp

View File

@ -12,8 +12,8 @@
#include "Logger.h"
#define SPIN_UNTIL_STARTED_DELAY_MI 250 * 1000
// quarter of a second
#define SPIN_DELAY_MI 500 * 1000
// half of a second
#define TIMEOUT_UNTIL_STARTED_SECS 10
#define TIMEOUT_UNTIL_STOPPED_SECS 10
@ -45,20 +45,21 @@ AbstractProcessNode::Process() const
status_t
AbstractProcessNode::_SpinUntilProcessState(
uint32 desiredStatesMask, uint32 timeoutSeconds)
uint32 desiredStatesMask, int32 timeoutSeconds)
{
bigtime_t start = system_time();
bigtime_t timeoutMicroSeconds = timeoutSeconds * 1000 * 1000;
while (true) {
if ((Process()->ProcessState() & desiredStatesMask) != 0)
return B_OK;
usleep(SPIN_UNTIL_STARTED_DELAY_MI);
usleep(SPIN_DELAY_MI);
if (system_time() - start > timeoutMicroSeconds) {
HDERROR("[Node<%s>] timeout waiting for process state",
Process()->Name());
int32 secondElapsed = static_cast<int32>(
(system_time() - start) / (1000 * 1000));
if (timeoutSeconds > 0 && secondElapsed > timeoutSeconds) {
HDERROR("[Node<%s>] timeout waiting for process state after %"
B_PRIi32 " seconds", Process()->Name(), secondElapsed);
return B_ERROR;
}
}

View File

@ -24,8 +24,8 @@ public:
virtual ~AbstractProcessNode();
AbstractProcess* Process() const;
virtual status_t StartProcess() = 0;
virtual status_t StopProcess() = 0;
virtual status_t Start() = 0;
virtual status_t RequestStop() = 0;
void AddPredecessor(AbstractProcessNode* node);
int32 CountPredecessors() const;
@ -40,7 +40,7 @@ public:
protected:
status_t _SpinUntilProcessState(
uint32 desiredStatesMask,
uint32 timeoutSeconds);
int32 timeoutSeconds);
private:
void _AddSuccessor(AbstractProcessNode* node);

View File

@ -1,52 +0,0 @@
/*
* Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "NonBlockingProcessNode.h"
#include <unistd.h>
#include "AbstractProcess.h"
#include "Logger.h"
#define TIMEOUT_UNTIL_STARTED_SECS 10
#define TIMEOUT_UNTIL_STOPPED_SECS 10
NonBlockingProcessNode::NonBlockingProcessNode(AbstractProcess* process)
:
AbstractProcessNode(process)
{
}
NonBlockingProcessNode::~NonBlockingProcessNode()
{
}
/*! Considered to be protected from concurrent access by the ProcessCoordinator
*/
status_t
NonBlockingProcessNode::StartProcess()
{
HDINFO("[Node<%s>] initiating non-blocking", Process()->Name());
Process()->Run();
return B_OK;
}
/*! Considered to be protected from concurrent access by the ProcessCoordinator
*/
status_t
NonBlockingProcessNode::StopProcess()
{
Process()->SetListener(NULL);
status_t stopResult = Process()->Stop();
return stopResult;
}

View File

@ -1,26 +0,0 @@
/*
* Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef NON_BLOCKING_PROCESS_NODE_H
#define NON_BLOCKING_PROCESS_NODE_H
#include "AbstractProcessNode.h"
class AbstractProcess;
class NonBlockingProcessNode : public AbstractProcessNode {
public:
NonBlockingProcessNode(
AbstractProcess* process);
virtual ~NonBlockingProcessNode();
virtual status_t StartProcess();
virtual status_t StopProcess();
};
#endif // NON_BLOCKING_PROCESS_NODE_H

View File

@ -142,7 +142,7 @@ ProcessCoordinator::Start()
void
ProcessCoordinator::Stop()
ProcessCoordinator::RequestStop()
{
AutoLocker<BLocker> locker(&fLock);
if (!fWasStopped) {
@ -150,20 +150,11 @@ ProcessCoordinator::Stop()
HDINFO("[Coordinator] will stop process coordinator");
for (int32 i = 0; i < fNodes.CountItems(); i++) {
AbstractProcessNode* node = fNodes.ItemAt(i);
if (node->Process()->ErrorStatus() != B_OK) {
HDINFO("[Coordinator] stopping process [%s] (owing to error)",
node->Process()->Name());
} else {
HDINFO("[Coordinator] stopping process [%s]",
node->Process()->Name());
}
node->StopProcess();
HDINFO("[Coordinator] stopping process [%s]",
node->Process()->Name());
node->RequestStop();
}
}
if (fListener != NULL) {
ProcessCoordinatorState state = _CreateStatus();
fListener->CoordinatorChanged(state);
}
}
@ -307,7 +298,7 @@ ProcessCoordinator::_Coordinate()
if (node->Process()->ProcessState() == PROCESS_INITIAL) {
if (node->AllPredecessorsComplete())
node->StartProcess();
node->Start();
else {
HDTRACE("[Coordinator] all predecessors not complete -> "
"[%s] not started", node->Process()->Name());
@ -349,7 +340,7 @@ ProcessCoordinator::_StopSuccessorNodes(AbstractProcessNode* predecessorNode)
if (process->ProcessState() == PROCESS_INITIAL) {
HDDEBUG("[Coordinator] [%s] (failed) --> [%s] (stopping)",
predecessorNode->Process()->Name(), process->Name());
node->StopProcess();
node->RequestStop();
_StopSuccessorNodes(node);
}
}

View File

@ -98,7 +98,7 @@ public:
bool IsRunning();
void Start();
void Stop();
void RequestStop();
status_t ErrorStatus();

View File

@ -173,7 +173,8 @@ ProcessCoordinatorFactory::_CreateInstallPackageActionCoordinator(
PackageInfoRef package = _ExtractPackageFromMessage(model, message);
if (package.IsSet()) {
AbstractProcessNode *processNode =
new ThreadedProcessNode(new InstallPackageProcess(package, model));
new ThreadedProcessNode(
new InstallPackageProcess(package, model), 10);
processCoordinator->AddNode(processNode);
} else {
HDERROR("unable to find the package");
@ -191,8 +192,8 @@ ProcessCoordinatorFactory::_CreateUninstallPackageActionCoordinator(
PackageInfoRef package = _ExtractPackageFromMessage(model, message);
if (package.IsSet()) {
AbstractProcessNode *processNode =
new ThreadedProcessNode(new UninstallPackageProcess(
package, model));
new ThreadedProcessNode(
new UninstallPackageProcess(package, model), 10);
processCoordinator->AddNode(processNode);
} else {
HDERROR("unable to find the package");

View File

@ -12,15 +12,25 @@
#include "Logger.h"
#define TIMEOUT_UNTIL_STARTED_SECS 10
#define TIMEOUT_UNTIL_STOPPED_SECS 10
#define TIMEOUT_UNTIL_STARTED_SECS_DEFAULT 10
#define TIMEOUT_UNTIL_STOPPED_SECS_DEFAULT 10
ThreadedProcessNode::ThreadedProcessNode(AbstractProcess* process,
int32 startTimeoutSeconds)
:
AbstractProcessNode(process),
fWorker(B_BAD_THREAD_ID),
fStartTimeoutSeconds(startTimeoutSeconds)
{
}
ThreadedProcessNode::ThreadedProcessNode(AbstractProcess* process)
:
AbstractProcessNode(process),
fWorker(B_BAD_THREAD_ID)
fWorker(B_BAD_THREAD_ID),
fStartTimeoutSeconds(TIMEOUT_UNTIL_STARTED_SECS_DEFAULT)
{
}
@ -34,7 +44,7 @@ ThreadedProcessNode::~ThreadedProcessNode()
*/
status_t
ThreadedProcessNode::StartProcess()
ThreadedProcessNode::Start()
{
if (fWorker != B_BAD_THREAD_ID)
return B_BUSY;
@ -47,40 +57,17 @@ ThreadedProcessNode::StartProcess()
if (fWorker >= 0) {
resume_thread(fWorker);
return _SpinUntilProcessState(PROCESS_RUNNING | PROCESS_COMPLETE,
TIMEOUT_UNTIL_STARTED_SECS);
fStartTimeoutSeconds);
}
return B_ERROR;
}
/*! Considered to be protected from concurrent access by the ProcessCoordinator
*/
status_t
ThreadedProcessNode::StopProcess()
ThreadedProcessNode::RequestStop()
{
Process()->SetListener(NULL);
status_t stopResult = Process()->Stop();
status_t waitResult = _SpinUntilProcessState(PROCESS_COMPLETE,
TIMEOUT_UNTIL_STOPPED_SECS);
// if the thread is still running then it will be necessary to tear it
// down.
if (waitResult != B_OK) {
HDINFO("[%s] process did not stop within timeout - will be stopped "
"uncleanly", Process()->Name());
kill_thread(fWorker);
}
if (stopResult != B_OK)
return stopResult;
if (waitResult != B_OK)
return waitResult;
return B_OK;
return Process()->Stop();
}

View File

@ -14,16 +14,19 @@ class AbstractProcess;
class ThreadedProcessNode : public AbstractProcessNode {
public:
ThreadedProcessNode(AbstractProcess* process,
int32 startTimeoutSeconds);
ThreadedProcessNode(AbstractProcess* process);
virtual ~ThreadedProcessNode();
virtual status_t StartProcess();
virtual status_t StopProcess();
virtual status_t Start();
virtual status_t RequestStop();
private:
static status_t _StartProcess(void* cookie);
thread_id fWorker;
int32 fStartTimeoutSeconds;
};

View File

@ -48,6 +48,7 @@
#include "support.h"
#include "ScreenshotWindow.h"
#include "SettingsWindow.h"
#include "ShuttingDownWindow.h"
#include "ToLatestUserUsageConditionsWindow.h"
#include "UserLoginWindow.h"
#include "UserUsageConditionsWindow.h"
@ -133,12 +134,14 @@ MainWindow::MainWindow(const BMessage& settings)
B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
fScreenshotWindow(NULL),
fShuttingDownWindow(NULL),
fUserMenu(NULL),
fLogInItem(NULL),
fLogOutItem(NULL),
fUsersUserUsageConditionsMenuItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fCoordinator(NULL),
fShouldCloseWhenNoProcessesToCoordinate(false),
fSinglePackageMode(false)
{
if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
@ -226,12 +229,14 @@ MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
fPackageListView(NULL),
fWorkStatusView(NULL),
fScreenshotWindow(NULL),
fShuttingDownWindow(NULL),
fUserMenu(NULL),
fLogInItem(NULL),
fLogOutItem(NULL),
fUsersUserUsageConditionsMenuItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fCoordinator(NULL),
fShouldCloseWhenNoProcessesToCoordinate(false),
fSinglePackageMode(true)
{
if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
@ -268,24 +273,57 @@ MainWindow::~MainWindow()
BPackageRoster().StopWatching(this);
if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
fScreenshotWindow->Quit();
if (fScreenshotWindow != NULL) {
if (fScreenshotWindow->Lock())
fScreenshotWindow->Quit();
}
if (fShuttingDownWindow != NULL) {
if (fShuttingDownWindow->Lock())
fShuttingDownWindow->Quit();
}
}
bool
MainWindow::QuitRequested()
{
BMessage settings;
StoreSettings(settings);
BMessage message(MSG_MAIN_WINDOW_CLOSED);
message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
be_app->PostMessage(&message);
_StopProcessCoordinators();
// If there are any processes in flight we need to be careful to make
// sure that they are cleanly completed before actually quitting. By
// turning on the `fShouldCloseWhenNoProcessesToCoordinate` flag, when
// the process coordination has completed then it will detect this and
// quit again.
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
fShouldCloseWhenNoProcessesToCoordinate = true;
if (fCoordinator.IsSet()) {
HDINFO("a coordinator is running --> will wait before quitting...");
if (fShuttingDownWindow == NULL)
fShuttingDownWindow = new ShuttingDownWindow(this);
fShuttingDownWindow->Show();
return false;
}
}
BMessage settings;
StoreSettings(settings);
BMessage message(MSG_MAIN_WINDOW_CLOSED);
message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
be_app->PostMessage(&message);
if (fShuttingDownWindow != NULL) {
if (fShuttingDownWindow->Lock())
fShuttingDownWindow->Quit();
fShuttingDownWindow = NULL;
}
return true;
}
@ -1367,6 +1405,13 @@ MainWindow::_AddProcessCoordinator(ProcessCoordinator* item)
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
if (fShouldCloseWhenNoProcessesToCoordinate) {
HDINFO("system shutting down --> new process coordinator [%s] rejected",
item->Name().String());
delete item;
return;
}
item->SetListener(this);
if (!fCoordinator.IsSet()) {
@ -1405,29 +1450,19 @@ MainWindow::_SpinUntilProcessCoordinatorComplete()
void
MainWindow::_StopProcessCoordinators()
{
HDINFO("will stop all process coordinators");
HDINFO("will stop all queued process coordinators");
AutoLocker<BLocker> lock(&fCoordinatorLock);
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
while (!fCoordinatorQueue.empty()) {
BReference<ProcessCoordinator> processCoordinator
= fCoordinatorQueue.front();
HDINFO("will drop queued process coordinator [%s]",
processCoordinator->Name().String());
fCoordinatorQueue.pop();
}
if (fCoordinator.IsSet()) {
fCoordinator->Stop();
}
while (!fCoordinatorQueue.empty()) {
BReference<ProcessCoordinator> processCoordinator
= fCoordinatorQueue.front();
HDINFO("will drop queued process coordinator [%s]",
processCoordinator->Name().String());
fCoordinatorQueue.pop();
}
HDINFO("will wait until the process coordinator has stopped");
_SpinUntilProcessCoordinatorComplete();
HDINFO("did stop all process coordinators");
if (fCoordinator.IsSet())
fCoordinator->RequestStop();
}
@ -1474,6 +1509,11 @@ MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
}
else {
_NotifyWorkStatusClear();
if (fShouldCloseWhenNoProcessesToCoordinate) {
HDINFO("no more processes to coord --> will quit");
BMessage message(B_QUIT_REQUESTED);
PostMessage(&message);
}
}
}
else {
@ -1481,8 +1521,10 @@ MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
coordinatorState.Progress());
// show the progress to the user.
}
} else
} else {
_NotifyWorkStatusClear();
HDINFO("! unknown process coordinator changed");
}
}

View File

@ -31,6 +31,7 @@ class PackageActionsView;
class PackageInfoView;
class PackageListView;
class ScreenshotWindow;
class ShuttingDownWindow;
class WorkStatusView;
@ -146,6 +147,7 @@ private:
WorkStatusView* fWorkStatusView;
ScreenshotWindow* fScreenshotWindow;
ShuttingDownWindow* fShuttingDownWindow;
BMenu* fUserMenu;
BMenu* fRepositoryMenu;
@ -169,6 +171,7 @@ private:
fCoordinator;
BLocker fCoordinatorLock;
sem_id fCoordinatorRunningSem;
bool fShouldCloseWhenNoProcessesToCoordinate;
bool fSinglePackageMode;

View File

@ -0,0 +1,47 @@
/*
* Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ShuttingDownWindow.h"
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <Locker.h>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ShuttingDownWindow"
#define WINDOW_FRAME BRect(0, 0, 240, 120)
ShuttingDownWindow::ShuttingDownWindow(BWindow* parent)
:
BWindow(WINDOW_FRAME, B_TRANSLATE("Shutting Down"),
B_FLOATING_WINDOW_LOOK, B_MODAL_SUBSET_WINDOW_FEEL,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
| B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_NOT_CLOSABLE )
{
AddToSubset(parent);
BTextView* textView = new BTextView("shutting down message");
textView->AdoptSystemColors();
textView->MakeEditable(false);
textView->MakeSelectable(false);
textView->SetText(B_TRANSLATE("Haiku Depot is stopping or completing "
"running operations before shutting down."));
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
.Add(textView)
.End();
CenterOnScreen();
}
ShuttingDownWindow::~ShuttingDownWindow()
{
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef SHUTTING_DOWN_WINDOW_H
#define SHUTTING_DOWN_WINDOW_H
#include <Locker.h>
#include <Messenger.h>
#include <Window.h>
#include "BarberPole.h"
#include "HaikuDepotConstants.h"
#include "UserDetail.h"
#include "UserUsageConditions.h"
class BButton;
class BCheckBox;
class Model;
class ShuttingDownWindow : public BWindow {
public:
ShuttingDownWindow(BWindow* parent);
virtual ~ShuttingDownWindow();
};
#endif // SHUTTING_DOWN_WINDOW_H

View File

@ -15,6 +15,8 @@
#include <pthread.h>
#include <stdio.h>
#include "Logger.h"
// #pragma mark - MachineRoom
@ -78,6 +80,7 @@ private:
BMessenger* messenger = new BMessenger(pole);
fMessengers.AddItem(messenger);
HDTRACE("did add barber-pole to machine room");
if (wasEmpty)
release_sem(fSpinLoopLock);
@ -91,6 +94,7 @@ private:
BMessenger* messenger = fMessengers.ItemAt(i);
if (messenger->Target(NULL) == pole) {
fMessengers.RemoveItem(messenger, true);
HDTRACE("did remove barber-pole from machine room");
break;
}
}

View File

@ -417,6 +417,7 @@ BHttpRequest::Result() const
status_t
BHttpRequest::Stop()
{
if (fSocket != NULL) {
fSocket->Disconnect();
// Unlock any pending connect, read or write operation.