client/BackgroundCommand: infrastructure for commands running in background
This commit is contained in:
parent
28fc1d555f
commit
9f1c23e217
|
@ -220,6 +220,7 @@ sources = [
|
|||
'src/client/Subscribe.cxx',
|
||||
'src/client/File.cxx',
|
||||
'src/client/Response.cxx',
|
||||
'src/client/ThreadBackgroundCommand.cxx',
|
||||
'src/Listen.cxx',
|
||||
'src/LogInit.cxx',
|
||||
'src/LogBackend.cxx',
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_BACKGROUND_COMMAND_HXX
|
||||
#define MPD_BACKGROUND_COMMAND_HXX
|
||||
|
||||
/**
|
||||
* A command running in background. It can take some time to finish,
|
||||
* and will then call Client::OnBackgroundCommandFinished() from
|
||||
* inside the client's #EventLoop thread. The important point is that
|
||||
* sucha long-running command does not block MPD's main loop, and
|
||||
* other clients can still be handled meanwhile.
|
||||
*
|
||||
* (Note: "idle" is not a "background command" by this definition; it
|
||||
* is a special case.)
|
||||
*
|
||||
* @see ThreadBackgroundCommand
|
||||
*/
|
||||
class BackgroundCommand {
|
||||
public:
|
||||
virtual ~BackgroundCommand() = default;
|
||||
|
||||
/**
|
||||
* Cancel command execution. After this method returns, the
|
||||
* object will be deleted. It will be called from the
|
||||
* #Client's #EventLoop thread.
|
||||
*/
|
||||
virtual void Cancel() noexcept = 0;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -18,8 +18,10 @@
|
|||
*/
|
||||
|
||||
#include "Client.hxx"
|
||||
#include "Config.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "BackgroundCommand.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "config.h"
|
||||
|
||||
|
@ -27,6 +29,37 @@ Client::~Client() noexcept
|
|||
{
|
||||
if (FullyBufferedSocket::IsDefined())
|
||||
FullyBufferedSocket::Close();
|
||||
|
||||
if (background_command) {
|
||||
background_command->Cancel();
|
||||
background_command.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Client::SetBackgroundCommand(std::unique_ptr<BackgroundCommand> _bc) noexcept
|
||||
{
|
||||
assert(!background_command);
|
||||
assert(_bc);
|
||||
|
||||
background_command = std::move(_bc);
|
||||
|
||||
/* disable timeouts while in "idle" */
|
||||
timeout_event.Cancel();
|
||||
}
|
||||
|
||||
void
|
||||
Client::OnBackgroundCommandFinished() noexcept
|
||||
{
|
||||
assert(background_command);
|
||||
|
||||
background_command.reset();
|
||||
|
||||
/* just in case OnSocketInput() has returned
|
||||
InputResult::PAUSE meanwhile */
|
||||
ResumeInput();
|
||||
|
||||
timeout_event.Schedule(client_timeout);
|
||||
}
|
||||
|
||||
Instance &
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <set>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
@ -47,6 +48,7 @@ class PlayerControl;
|
|||
struct playlist;
|
||||
class Database;
|
||||
class Storage;
|
||||
class BackgroundCommand;
|
||||
|
||||
class Client final
|
||||
: FullyBufferedSocket,
|
||||
|
@ -102,6 +104,14 @@ private:
|
|||
*/
|
||||
std::list<ClientMessage> messages;
|
||||
|
||||
/**
|
||||
* The command currently running in background. If this is
|
||||
* set, then the client is occupied and will not process any
|
||||
* new input. If the connection gets closed, the
|
||||
* #BackgroundCommand will be cancelled.
|
||||
*/
|
||||
std::unique_ptr<BackgroundCommand> background_command;
|
||||
|
||||
public:
|
||||
Client(EventLoop &loop, Partition &partition,
|
||||
UniqueSocketDescriptor fd, int uid,
|
||||
|
@ -152,6 +162,19 @@ public:
|
|||
void IdleAdd(unsigned flags) noexcept;
|
||||
bool IdleWait(unsigned flags) noexcept;
|
||||
|
||||
/**
|
||||
* Called by a command handler to defer execution to a
|
||||
* #BackgroundCommand.
|
||||
*/
|
||||
void SetBackgroundCommand(std::unique_ptr<BackgroundCommand> _bc) noexcept;
|
||||
|
||||
/**
|
||||
* Called by the current #BackgroundCommand when it has
|
||||
* finished, after sending the response. This method then
|
||||
* deletes the #BackgroundCommand.
|
||||
*/
|
||||
void OnBackgroundCommandFinished() noexcept;
|
||||
|
||||
enum class SubscribeResult {
|
||||
/** success */
|
||||
OK,
|
||||
|
|
|
@ -25,6 +25,7 @@ void
|
|||
Client::OnTimeout() noexcept
|
||||
{
|
||||
assert(!idle_waiting);
|
||||
assert(!background_command);
|
||||
|
||||
FormatDebug(client_domain, "[%u] timeout", num);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "Config.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "List.hxx"
|
||||
#include "BackgroundCommand.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "net/UniqueSocketDescriptor.hxx"
|
||||
|
|
|
@ -54,6 +54,8 @@ Client::ProcessCommandList(bool list_ok,
|
|||
CommandResult
|
||||
Client::ProcessLine(char *line) noexcept
|
||||
{
|
||||
assert(!background_command);
|
||||
|
||||
if (!IsLowerAlphaASCII(*line)) {
|
||||
/* all valid MPD commands begin with a lower case
|
||||
letter; this could be a badly routed HTTP
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
BufferedSocket::InputResult
|
||||
Client::OnSocketInput(void *data, size_t length) noexcept
|
||||
{
|
||||
if (background_command)
|
||||
return InputResult::PAUSE;
|
||||
|
||||
char *p = (char *)data;
|
||||
char *newline = (char *)memchr(p, '\n', length);
|
||||
if (newline == nullptr)
|
||||
|
@ -48,6 +51,7 @@ Client::OnSocketInput(void *data, size_t length) noexcept
|
|||
switch (result) {
|
||||
case CommandResult::OK:
|
||||
case CommandResult::IDLE:
|
||||
case CommandResult::BACKGROUND:
|
||||
case CommandResult::ERROR:
|
||||
break;
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "ThreadBackgroundCommand.hxx"
|
||||
#include "Client.hxx"
|
||||
#include "Response.hxx"
|
||||
#include "command/CommandError.hxx"
|
||||
#include "protocol/Result.hxx"
|
||||
|
||||
ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept
|
||||
:thread(BIND_THIS_METHOD(_Run)),
|
||||
defer_finish(_client.GetEventLoop(), BIND_THIS_METHOD(DeferredFinish)),
|
||||
client(_client)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ThreadBackgroundCommand::_Run() noexcept
|
||||
{
|
||||
assert(!error);
|
||||
|
||||
try {
|
||||
Run();
|
||||
} catch (...) {
|
||||
error = std::current_exception();
|
||||
}
|
||||
|
||||
defer_finish.Schedule();
|
||||
}
|
||||
|
||||
void
|
||||
ThreadBackgroundCommand::DeferredFinish() noexcept
|
||||
{
|
||||
/* free the Thread */
|
||||
thread.Join();
|
||||
|
||||
/* send the response */
|
||||
Response response(client, 0);
|
||||
|
||||
if (!error) {
|
||||
PrintError(response, std::move(error));
|
||||
} else {
|
||||
SendResponse(response);
|
||||
command_success(client);
|
||||
}
|
||||
|
||||
/* delete this object */
|
||||
client.OnBackgroundCommandFinished();
|
||||
}
|
||||
|
||||
void
|
||||
ThreadBackgroundCommand::Cancel() noexcept
|
||||
{
|
||||
CancelThread();
|
||||
thread.Join();
|
||||
|
||||
/* cancel the DeferEvent, just in case the Thread has
|
||||
meanwhile finished execution */
|
||||
defer_finish.Cancel();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_THREAD_BACKGROUND_COMMAND_HXX
|
||||
#define MPD_THREAD_BACKGROUND_COMMAND_HXX
|
||||
|
||||
#include "BackgroundCommand.hxx"
|
||||
#include "event/DeferEvent.hxx"
|
||||
#include "thread/Thread.hxx"
|
||||
|
||||
#include <exception>
|
||||
|
||||
class Client;
|
||||
class Response;
|
||||
|
||||
/**
|
||||
* A #BackgroundCommand which defers execution into a new thread.
|
||||
*/
|
||||
class ThreadBackgroundCommand : public BackgroundCommand {
|
||||
Thread thread;
|
||||
DeferEvent defer_finish;
|
||||
Client &client;
|
||||
|
||||
/**
|
||||
* The error thrown by Run().
|
||||
*/
|
||||
std::exception_ptr error;
|
||||
|
||||
public:
|
||||
explicit ThreadBackgroundCommand(Client &_client) noexcept;
|
||||
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return defer_finish.GetEventLoop();
|
||||
}
|
||||
|
||||
void Start() {
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
void Cancel() noexcept final;
|
||||
|
||||
private:
|
||||
void _Run() noexcept;
|
||||
void DeferredFinish() noexcept;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* If this method throws, the exception will be converted to a
|
||||
* MPD response, and SendResponse() will not be called.
|
||||
*/
|
||||
virtual void Run() = 0;
|
||||
|
||||
/**
|
||||
* Send the response after Run() has finished. Note that you
|
||||
* must not send errors here; if an error occurs, Run() should
|
||||
* throw an exception instead.
|
||||
*/
|
||||
virtual void SendResponse(Response &response) noexcept = 0;
|
||||
|
||||
virtual void CancelThread() noexcept = 0;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -41,6 +41,11 @@ enum class CommandResult {
|
|||
*/
|
||||
IDLE,
|
||||
|
||||
/**
|
||||
* A #BackgroundCommand has been installed.
|
||||
*/
|
||||
BACKGROUND,
|
||||
|
||||
/**
|
||||
* There was an error. The "ACK" response was sent to the
|
||||
* client.
|
||||
|
|
Loading…
Reference in New Issue