Restart active download to apply previously not applicable options

Previously, we categorized options that can be used in
aria2.changeOption RPC method into 2 categories.  The options in one
category can be applied on the fly, meaning that download continues to
be active while applying options.  Another category includes options
which are only applicable when downloads are waiting or paused.

In this change, when active download is ordered to change options
which only applicable in waiting or paused state, it is now paused,
and then automatically restarted.  Although we have limited number of
download concurrency, the pause and restart is done atomically, and
the download is inserted at the front of the queue, it is picked up
immediately if the concurrency regulation allows.
dynamic-select-file
Tatsuhiro Tsujikawa 2016-05-06 18:23:44 +09:00
parent e174b90ff2
commit 8897d7ec70
9 changed files with 119 additions and 15 deletions

View File

@ -190,4 +190,10 @@ void Option::setParent(const std::shared_ptr<Option>& parent)
const std::shared_ptr<Option>& Option::getParent() const { return parent_; } const std::shared_ptr<Option>& Option::getParent() const { return parent_; }
bool Option::emptyLocal() const
{
size_t dst;
return !bitfield::getFirstSetBitIndex(dst, use_, use_.size() * 8);
}
} // namespace aria2 } // namespace aria2

View File

@ -92,6 +92,8 @@ public:
// Sets parent Option object for this object. // Sets parent Option object for this object.
void setParent(const std::shared_ptr<Option>& parent); void setParent(const std::shared_ptr<Option>& parent);
const std::shared_ptr<Option>& getParent() const; const std::shared_ptr<Option>& getParent() const;
// Returns true if there is no option stored.
bool emptyLocal() const;
}; };
} // namespace aria2 } // namespace aria2

View File

@ -957,6 +957,8 @@ void RequestGroup::setForceHaltRequested(bool f, HaltReason haltReason)
void RequestGroup::setPauseRequested(bool f) { pauseRequested_ = f; } void RequestGroup::setPauseRequested(bool f) { pauseRequested_ = f; }
void RequestGroup::setRestartRequested(bool f) { restartRequested_ = f; }
void RequestGroup::releaseRuntimeResource(DownloadEngine* e) void RequestGroup::releaseRuntimeResource(DownloadEngine* e)
{ {
#ifdef ENABLE_BITTORRENT #ifdef ENABLE_BITTORRENT
@ -1279,4 +1281,9 @@ bool RequestGroup::isSeeder() const
#endif // !ENABLE_BITTORRENT #endif // !ENABLE_BITTORRENT
} }
void RequestGroup::setPendingOption(std::shared_ptr<Option> option)
{
pendingOption_ = std::move(option);
}
} // namespace aria2 } // namespace aria2

View File

@ -96,6 +96,9 @@ private:
std::shared_ptr<Option> option_; std::shared_ptr<Option> option_;
// options applied on restart
std::shared_ptr<Option> pendingOption_;
std::shared_ptr<SegmentMan> segmentMan_; std::shared_ptr<SegmentMan> segmentMan_;
std::shared_ptr<DownloadContext> downloadContext_; std::shared_ptr<DownloadContext> downloadContext_;
@ -178,6 +181,11 @@ private:
bool pauseRequested_; bool pauseRequested_;
// restartRequested_ indicates that this download should be
// restarted. Usually, it is used with pauseRequested_ to stop
// download first.
bool restartRequested_;
// This flag just indicates that the downloaded file is not saved disk but // This flag just indicates that the downloaded file is not saved disk but
// just sits in memory. // just sits in memory.
bool inMemoryDownload_; bool inMemoryDownload_;
@ -345,6 +353,10 @@ public:
bool isPauseRequested() const { return pauseRequested_; } bool isPauseRequested() const { return pauseRequested_; }
void setRestartRequested(bool f);
bool isRestartRequested() const { return restartRequested_; }
void dependsOn(const std::shared_ptr<Dependency>& dep); void dependsOn(const std::shared_ptr<Dependency>& dep);
bool isDependencyResolved(); bool isDependencyResolved();
@ -500,6 +512,12 @@ public:
// Returns true if this download is now seeding. // Returns true if this download is now seeding.
bool isSeeder() const; bool isSeeder() const;
void setPendingOption(std::shared_ptr<Option> option);
const std::shared_ptr<Option>& getPendingOption() const
{
return pendingOption_;
}
}; };
} // namespace aria2 } // namespace aria2

View File

@ -84,6 +84,7 @@
#include "array_fun.h" #include "array_fun.h"
#include "OpenedFileCounter.h" #include "OpenedFileCounter.h"
#include "wallclock.h" #include "wallclock.h"
#include "RpcMethodImpl.h"
#ifdef ENABLE_BITTORRENT #ifdef ENABLE_BITTORRENT
#include "bittorrent_helper.h" #include "bittorrent_helper.h"
#endif // ENABLE_BITTORRENT #endif // ENABLE_BITTORRENT
@ -369,8 +370,10 @@ public:
try { try {
group->closeFile(); group->closeFile();
if (group->isPauseRequested()) { if (group->isPauseRequested()) {
A2_LOG_NOTICE(fmt(_("Download GID#%s paused"), if (!group->isRestartRequested()) {
GroupId::toHex(group->getGID()).c_str())); A2_LOG_NOTICE(fmt(_("Download GID#%s paused"),
GroupId::toHex(group->getGID()).c_str()));
}
group->saveControlFile(); group->saveControlFile();
} }
else if (group->downloadFinished() && else if (group->downloadFinished() &&
@ -433,9 +436,20 @@ public:
reservedGroups_.push_front(group->getGID(), group); reservedGroups_.push_front(group->getGID(), group);
group->releaseRuntimeResource(e_); group->releaseRuntimeResource(e_);
group->setForceHaltRequested(false); group->setForceHaltRequested(false);
util::executeHookByOptName(group, e_->getOption(),
PREF_ON_DOWNLOAD_PAUSE); auto pendingOption = group->getPendingOption();
notifyDownloadEvent(EVENT_ON_DOWNLOAD_PAUSE, group); if (pendingOption) {
changeOption(group, *pendingOption, e_);
}
if (group->isRestartRequested()) {
group->setPauseRequested(false);
}
else {
util::executeHookByOptName(group, e_->getOption(),
PREF_ON_DOWNLOAD_PAUSE);
notifyDownloadEvent(EVENT_ON_DOWNLOAD_PAUSE, group);
}
// TODO Should we have to prepend spend uris to remaining uris // TODO Should we have to prepend spend uris to remaining uris
// in case PREF_REUSE_URI is disabled? // in case PREF_REUSE_URI is disabled?
} }
@ -445,6 +459,10 @@ public:
executeStopHook(group, e_->getOption(), dr->result); executeStopHook(group, e_->getOption(), dr->result);
group->releaseRuntimeResource(e_); group->releaseRuntimeResource(e_);
} }
group->setRestartRequested(false);
group->setPendingOption(nullptr);
return true; return true;
} }
else { else {

View File

@ -147,12 +147,53 @@ void RpcMethod::gatherRequestOption(Option* option, const Dict* optionsDict)
} }
} }
void RpcMethod::gatherChangeableOption(Option* option, const Dict* optionsDict) void RpcMethod::gatherChangeableOption(Option* option, Option* pendingOption,
const Dict* optionsDict)
{ {
if (optionsDict) { if (!optionsDict) {
gatherOption(optionsDict->begin(), optionsDict->end(), return;
std::mem_fn(&OptionHandler::getChangeOption), option, }
optionParser_);
auto first = optionsDict->begin();
auto last = optionsDict->end();
for (; first != last; ++first) {
const auto& optionName = (*first).first;
auto pref = option::k2p(optionName);
auto handler = optionParser_->find(pref);
if (!handler) {
// Just ignore the unacceptable options in this context.
continue;
}
Option* dst = nullptr;
if (handler->getChangeOption()) {
dst = option;
}
else if (handler->getChangeOptionForReserved()) {
dst = pendingOption;
}
if (!dst) {
continue;
}
const auto opval = downcast<String>((*first).second);
if (opval) {
handler->parse(*dst, opval->s());
}
else if (handler->getCumulative()) {
// header and index-out option can take array as value
const auto oplist = downcast<List>((*first).second);
if (oplist) {
for (auto& elem : *oplist) {
const auto opval = downcast<String>(elem);
if (opval) {
handler->parse(*dst, opval->s());
}
}
}
}
} }
} }

View File

@ -75,7 +75,8 @@ protected:
void gatherRequestOption(Option* option, const Dict* optionsDict); void gatherRequestOption(Option* option, const Dict* optionsDict);
void gatherChangeableOption(Option* option, const Dict* optionDict); void gatherChangeableOption(Option* option, Option* pendingOption,
const Dict* optionDict);
void gatherChangeableOptionForReserved(Option* option, void gatherChangeableOptionForReserved(Option* option,
const Dict* optionsDict); const Dict* optionsDict);

View File

@ -1113,10 +1113,22 @@ std::unique_ptr<ValueBase> ChangeOptionRpcMethod::process(const RpcRequest& req,
a2_gid_t gid = str2Gid(gidParam); a2_gid_t gid = str2Gid(gidParam);
auto group = e->getRequestGroupMan()->findGroup(gid); auto group = e->getRequestGroupMan()->findGroup(gid);
Option option;
if (group) { if (group) {
Option option;
std::shared_ptr<Option> pendingOption;
if (group->getState() == RequestGroup::STATE_ACTIVE) { if (group->getState() == RequestGroup::STATE_ACTIVE) {
gatherChangeableOption(&option, optsParam); pendingOption = std::make_shared<Option>();
gatherChangeableOption(&option, pendingOption.get(), optsParam);
if (!pendingOption->emptyLocal()) {
group->setPendingOption(pendingOption);
// pauseRequestGroup() may fail if group has been told to
// stop/pause already. In that case, we can still apply the
// pending options on pause.
if (pauseRequestGroup(group, false, false)) {
group->setRestartRequested(true);
e->setRefreshInterval(std::chrono::milliseconds(0));
}
}
} }
else { else {
gatherChangeableOptionForReserved(&option, optsParam); gatherChangeableOptionForReserved(&option, optsParam);

View File

@ -143,8 +143,7 @@ size_t countSetBitSlow(const Array& bitfield, size_t nbits)
void flipBit(unsigned char* data, size_t length, size_t bitIndex); void flipBit(unsigned char* data, size_t length, size_t bitIndex);
// Stores first set bit index of bitfield to index. bitfield contains // Stores first set bit index of bitfield to index. bitfield contains
// nbits. Returns true if missing bit index is found. Otherwise // nbits. Returns true if set bit is found. Otherwise returns false.
// returns false.
template <typename Array> template <typename Array>
bool getFirstSetBitIndex(size_t& index, const Array& bitfield, size_t nbits) bool getFirstSetBitIndex(size_t& index, const Array& bitfield, size_t nbits)
{ {