Use async locks when invoking custom command

This commit is contained in:
Martchus 2023-01-17 22:58:03 +01:00
parent 80b5094659
commit 599185d3ae
2 changed files with 72 additions and 32 deletions

View File

@ -381,7 +381,16 @@ struct LIBREPOMGR_EXPORT CustomCommand : public InternalBuildAction {
void run();
private:
void acquireNextSharedLock();
void acquireNextExclusiveLock();
std::string m_workingDirectory;
const std::string *m_command;
std::shared_ptr<BuildProcessSession> m_process;
std::set<std::string_view> m_sharedLockNames;
std::set<std::string_view> m_exclusiveLockNames;
std::set<std::string_view>::iterator m_sharedLockNamesIterator;
std::set<std::string_view>::iterator m_exclusiveLockNamesIterator;
};
struct LIBREPOMGR_EXPORT ReloadDatabase : public InternalBuildAction {

View File

@ -16,6 +16,7 @@ namespace LibRepoMgr {
CustomCommand::CustomCommand(ServiceSetup &setup, const std::shared_ptr<BuildAction> &buildAction)
: InternalBuildAction(setup, buildAction)
, m_command(nullptr)
{
}
@ -57,43 +58,73 @@ void CustomCommand::run()
m_buildAction->appendOutput(Phrases::InfoMessage, "Running custom command: ", command, '\n');
// prepare process and finish handler
auto process
= m_buildAction->makeBuildProcess("command", m_workingDirectory + "/the.log", [this](boost::process::child &&, ProcessResult &&result) {
if (result.errorCode) {
m_buildAction->appendOutput(Phrases::InfoMessage, "Unable to invoke command: ", result.errorCode.message());
reportError(result.errorCode.message());
return;
}
m_buildAction->appendOutput(
result.exitCode == 0 ? Phrases::InfoMessage : Phrases::ErrorMessage, "Command exited with return code ", result.exitCode);
if (result.exitCode != 0) {
reportError(argsToString("non-zero exit code ", result.exitCode));
return;
}
const auto buildLock = m_setup.building.lockToWrite();
reportSuccess();
});
m_command = &command;
m_process = m_buildAction->makeBuildProcess("command", m_workingDirectory + "/the.log", [this](boost::process::child &&, ProcessResult &&result) {
if (result.errorCode) {
m_buildAction->appendOutput(Phrases::InfoMessage, "Unable to invoke command: ", result.errorCode.message());
reportError(result.errorCode.message());
return;
}
m_buildAction->appendOutput(
result.exitCode == 0 ? Phrases::InfoMessage : Phrases::ErrorMessage, "Command exited with return code ", result.exitCode);
if (result.exitCode != 0) {
reportError(argsToString("non-zero exit code ", result.exitCode));
return;
}
const auto buildLock = m_setup.building.lockToWrite();
reportSuccess();
});
// acquire locks
// note: Using an std::set here (instead of a std::vector) to ensure we don't attempt to acquire the same lock twice and to ensure
// locks are always acquired in the same order (to prevent deadlocks).
const auto sharedLockNames = splitStringSimple<std::set<std::string_view>>(findSetting(sharedLocksSetting), ",");
const auto exclusiveLockNames = splitStringSimple<std::set<std::string_view>>(findSetting(exclusiveLocksSetting), ",");
auto &locks = process->locks();
auto &log = m_buildAction->log();
locks.reserve(sharedLockNames.size() + exclusiveLockNames.size());
for (const auto &lockName : sharedLockNames) {
if (!lockName.empty()) {
locks.emplace_back(m_setup.locks.acquireToRead(log, std::string(lockName)));
}
}
for (const auto &lockName : exclusiveLockNames) {
if (!lockName.empty()) {
locks.emplace_back(m_setup.locks.acquireToWrite(log, std::string(lockName)));
}
}
m_sharedLockNames = splitStringSimple<std::set<std::string_view>>(findSetting(sharedLocksSetting), ",");
m_sharedLockNamesIterator = m_sharedLockNames.begin();
m_exclusiveLockNames = splitStringSimple<std::set<std::string_view>>(findSetting(exclusiveLocksSetting), ",");
m_exclusiveLockNamesIterator = m_exclusiveLockNames.begin();
m_process->locks().reserve(m_sharedLockNames.size() + m_exclusiveLockNames.size());
acquireNextSharedLock();
}
process->launch(boost::process::start_dir(m_workingDirectory), boost::process::search_path("bash"), "-ec", command);
void LibRepoMgr::CustomCommand::acquireNextSharedLock()
{
if (m_sharedLockNamesIterator == m_sharedLockNames.end()) {
// continue acquiring exclusive locks once all shared locks are acquired
acquireNextExclusiveLock();
return;
}
if (m_sharedLockNamesIterator->empty()) {
++m_sharedLockNamesIterator;
acquireNextSharedLock();
return;
}
m_setup.locks.acquireToRead(
m_buildAction->log(), std::string(*m_sharedLockNamesIterator), [this, buildAction = m_buildAction](SharedLoggingLock &&lock) {
m_process->locks().emplace_back(std::move(lock));
++m_sharedLockNamesIterator;
acquireNextSharedLock();
});
}
void LibRepoMgr::CustomCommand::acquireNextExclusiveLock()
{
if (m_exclusiveLockNamesIterator == m_exclusiveLockNames.end()) {
// execute process once all locks are acquired
m_process->launch(boost::process::start_dir(m_workingDirectory), boost::process::search_path("bash"), "-ec", *m_command);
m_process.reset();
return;
}
if (m_exclusiveLockNamesIterator->empty()) {
++m_exclusiveLockNamesIterator;
acquireNextExclusiveLock();
return;
}
m_setup.locks.acquireToWrite(
m_buildAction->log(), std::string(*m_exclusiveLockNamesIterator), [this, buildAction = m_buildAction](UniqueLoggingLock &&lock) {
m_process->locks().emplace_back(std::move(lock));
++m_exclusiveLockNamesIterator;
acquireNextExclusiveLock();
});
}
} // namespace LibRepoMgr