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/Subscribe.cxx',
|
||||||
'src/client/File.cxx',
|
'src/client/File.cxx',
|
||||||
'src/client/Response.cxx',
|
'src/client/Response.cxx',
|
||||||
|
'src/client/ThreadBackgroundCommand.cxx',
|
||||||
'src/Listen.cxx',
|
'src/Listen.cxx',
|
||||||
'src/LogInit.cxx',
|
'src/LogInit.cxx',
|
||||||
'src/LogBackend.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 "Client.hxx"
|
||||||
|
#include "Config.hxx"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
|
#include "BackgroundCommand.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
@ -27,6 +29,37 @@ Client::~Client() noexcept
|
||||||
{
|
{
|
||||||
if (FullyBufferedSocket::IsDefined())
|
if (FullyBufferedSocket::IsDefined())
|
||||||
FullyBufferedSocket::Close();
|
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 &
|
Instance &
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ class PlayerControl;
|
||||||
struct playlist;
|
struct playlist;
|
||||||
class Database;
|
class Database;
|
||||||
class Storage;
|
class Storage;
|
||||||
|
class BackgroundCommand;
|
||||||
|
|
||||||
class Client final
|
class Client final
|
||||||
: FullyBufferedSocket,
|
: FullyBufferedSocket,
|
||||||
|
@ -102,6 +104,14 @@ private:
|
||||||
*/
|
*/
|
||||||
std::list<ClientMessage> messages;
|
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:
|
public:
|
||||||
Client(EventLoop &loop, Partition &partition,
|
Client(EventLoop &loop, Partition &partition,
|
||||||
UniqueSocketDescriptor fd, int uid,
|
UniqueSocketDescriptor fd, int uid,
|
||||||
|
@ -152,6 +162,19 @@ public:
|
||||||
void IdleAdd(unsigned flags) noexcept;
|
void IdleAdd(unsigned flags) noexcept;
|
||||||
bool IdleWait(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 {
|
enum class SubscribeResult {
|
||||||
/** success */
|
/** success */
|
||||||
OK,
|
OK,
|
||||||
|
|
|
@ -25,6 +25,7 @@ void
|
||||||
Client::OnTimeout() noexcept
|
Client::OnTimeout() noexcept
|
||||||
{
|
{
|
||||||
assert(!idle_waiting);
|
assert(!idle_waiting);
|
||||||
|
assert(!background_command);
|
||||||
|
|
||||||
FormatDebug(client_domain, "[%u] timeout", num);
|
FormatDebug(client_domain, "[%u] timeout", num);
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "Config.hxx"
|
#include "Config.hxx"
|
||||||
#include "Domain.hxx"
|
#include "Domain.hxx"
|
||||||
#include "List.hxx"
|
#include "List.hxx"
|
||||||
|
#include "BackgroundCommand.hxx"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
#include "net/UniqueSocketDescriptor.hxx"
|
#include "net/UniqueSocketDescriptor.hxx"
|
||||||
|
|
|
@ -54,6 +54,8 @@ Client::ProcessCommandList(bool list_ok,
|
||||||
CommandResult
|
CommandResult
|
||||||
Client::ProcessLine(char *line) noexcept
|
Client::ProcessLine(char *line) noexcept
|
||||||
{
|
{
|
||||||
|
assert(!background_command);
|
||||||
|
|
||||||
if (!IsLowerAlphaASCII(*line)) {
|
if (!IsLowerAlphaASCII(*line)) {
|
||||||
/* all valid MPD commands begin with a lower case
|
/* all valid MPD commands begin with a lower case
|
||||||
letter; this could be a badly routed HTTP
|
letter; this could be a badly routed HTTP
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
BufferedSocket::InputResult
|
BufferedSocket::InputResult
|
||||||
Client::OnSocketInput(void *data, size_t length) noexcept
|
Client::OnSocketInput(void *data, size_t length) noexcept
|
||||||
{
|
{
|
||||||
|
if (background_command)
|
||||||
|
return InputResult::PAUSE;
|
||||||
|
|
||||||
char *p = (char *)data;
|
char *p = (char *)data;
|
||||||
char *newline = (char *)memchr(p, '\n', length);
|
char *newline = (char *)memchr(p, '\n', length);
|
||||||
if (newline == nullptr)
|
if (newline == nullptr)
|
||||||
|
@ -48,6 +51,7 @@ Client::OnSocketInput(void *data, size_t length) noexcept
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case CommandResult::OK:
|
case CommandResult::OK:
|
||||||
case CommandResult::IDLE:
|
case CommandResult::IDLE:
|
||||||
|
case CommandResult::BACKGROUND:
|
||||||
case CommandResult::ERROR:
|
case CommandResult::ERROR:
|
||||||
break;
|
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,
|
IDLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A #BackgroundCommand has been installed.
|
||||||
|
*/
|
||||||
|
BACKGROUND,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There was an error. The "ACK" response was sent to the
|
* There was an error. The "ACK" response was sent to the
|
||||||
* client.
|
* client.
|
||||||
|
|
Loading…
Reference in New Issue