forked from qt-creator/qt-creator
Clang: Take over jobs if document's project changes
On registerProjectPartsForEditor() we recreated the affected DocumentProcessors, but did not take care of the jobs that were in the queue, which effectively could led to lost jobs. Catch up on this. Task-number: QTCREATORBUG-18856 Change-Id: I4184e5dc6480667953f2d2081ccf39a28c092186 Reviewed-by: Ivan Donchevskii <ivan.donchevskii@qt.io>
This commit is contained in:
@@ -28,7 +28,6 @@
|
||||
#include "clangdocuments.h"
|
||||
#include "clangdocumentsuspenderresumer.h"
|
||||
#include "clangfilesystemwatcher.h"
|
||||
#include "clangtranslationunits.h"
|
||||
#include "codecompleter.h"
|
||||
#include "diagnosticset.h"
|
||||
#include "tokeninfos.h"
|
||||
@@ -158,12 +157,17 @@ void ClangCodeModelServer::registerProjectPartsForEditor(const RegisterProjectPa
|
||||
std::vector<Document> affectedDocuments = documents.setDocumentsDirtyIfProjectPartChanged();
|
||||
|
||||
for (Document &document : affectedDocuments) {
|
||||
document.setResponsivenessIncreaseNeeded(document.isResponsivenessIncreased());
|
||||
documents.remove({document.fileContainer()});
|
||||
|
||||
documentProcessors().remove(document);
|
||||
document.translationUnits().removeAll();
|
||||
document.translationUnits().createAndAppend();
|
||||
documentProcessors().create(document);
|
||||
Document newDocument = *documents.create({document.fileContainer()}).begin();
|
||||
newDocument.setDirtyIfDependencyIsMet(document.filePath());
|
||||
newDocument.setIsUsedByCurrentEditor(document.isUsedByCurrentEditor());
|
||||
newDocument.setIsVisibleInEditor(document.isVisibleInEditor(), document.visibleTimePoint());
|
||||
newDocument.setResponsivenessIncreaseNeeded(document.isResponsivenessIncreased());
|
||||
|
||||
documentProcessors().reset(document, newDocument);
|
||||
|
||||
QTC_CHECK(document.useCount() == 1);
|
||||
}
|
||||
|
||||
processJobsForDirtyAndVisibleDocuments();
|
||||
|
@@ -155,6 +155,11 @@ bool Document::isParsed() const
|
||||
return d->translationUnits.areAllTranslationUnitsParsed();
|
||||
}
|
||||
|
||||
long Document::useCount() const
|
||||
{
|
||||
return d.use_count();
|
||||
}
|
||||
|
||||
Utf8String Document::filePath() const
|
||||
{
|
||||
checkIfNull();
|
||||
@@ -175,6 +180,7 @@ FileContainer Document::fileContainer() const
|
||||
|
||||
return FileContainer(d->filePath,
|
||||
d->projectPart.id(),
|
||||
d->fileArguments,
|
||||
Utf8String(),
|
||||
false,
|
||||
d->documentRevision);
|
||||
|
@@ -78,6 +78,7 @@ public:
|
||||
bool isNull() const;
|
||||
bool isIntact() const;
|
||||
bool isParsed() const;
|
||||
long useCount() const;
|
||||
|
||||
Utf8String filePath() const;
|
||||
Utf8StringVector fileArguments() const;
|
||||
|
@@ -102,6 +102,12 @@ JobRequests DocumentProcessor::process()
|
||||
return d->jobs.process();
|
||||
}
|
||||
|
||||
JobRequests DocumentProcessor::stop()
|
||||
{
|
||||
d->supportiveTranslationUnitInitializer.abort();
|
||||
return d->jobs.stop();
|
||||
}
|
||||
|
||||
Document DocumentProcessor::document() const
|
||||
{
|
||||
return d->document;
|
||||
|
@@ -59,6 +59,7 @@ public:
|
||||
= PreferredTranslationUnit::RecentlyParsed);
|
||||
|
||||
JobRequests process();
|
||||
JobRequests stop();
|
||||
|
||||
Document document() const;
|
||||
|
||||
|
@@ -28,6 +28,8 @@
|
||||
#include "clangexceptions.h"
|
||||
#include "projectpart.h"
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
namespace ClangBackEnd {
|
||||
|
||||
DocumentProcessors::DocumentProcessors(Documents &documents,
|
||||
@@ -84,6 +86,25 @@ void DocumentProcessors::remove(const Document &document)
|
||||
throw DocumentProcessorDoesNotExist(document.filePath(), document.projectPart().id());
|
||||
}
|
||||
|
||||
void DocumentProcessors::reset(const Document &oldDocument, const Document &newDocument)
|
||||
{
|
||||
// Wait until the currently running jobs finish and remember the not yet
|
||||
// processed job requests for the new processor...
|
||||
JobRequests jobsToTakeOver = processor(oldDocument).stop();
|
||||
// ...but do not take over irrelevant ones.
|
||||
jobsToTakeOver = Utils::filtered(jobsToTakeOver, [](const JobRequest &job){
|
||||
return job.isTakeOverable();
|
||||
});
|
||||
|
||||
// Remove current processor
|
||||
remove(oldDocument);
|
||||
|
||||
// Create new processor and take over not yet processed jobs.
|
||||
DocumentProcessor newProcessor = create(newDocument);
|
||||
for (const JobRequest &job : jobsToTakeOver)
|
||||
newProcessor.addJob(job);
|
||||
}
|
||||
|
||||
JobRequests DocumentProcessors::process()
|
||||
{
|
||||
JobRequests jobsStarted;
|
||||
|
@@ -54,6 +54,7 @@ public:
|
||||
DocumentProcessor create(const Document &document);
|
||||
DocumentProcessor processor(const Document &document);
|
||||
void remove(const Document &document);
|
||||
void reset(const Document &oldDocument, const Document &newDocument);
|
||||
|
||||
JobRequests process();
|
||||
|
||||
|
@@ -168,6 +168,42 @@ static JobRequest::RunConditions conditionsForType(JobRequest::Type type)
|
||||
return conditions;
|
||||
}
|
||||
|
||||
bool JobRequest::isTakeOverable() const
|
||||
{
|
||||
// When new project information comes in and there are unprocessed jobs
|
||||
// in the queue, we need to decide what to do with them.
|
||||
|
||||
switch (type) {
|
||||
// Never discard these as the client side might wait for a response.
|
||||
case Type::CompleteCode:
|
||||
case Type::RequestReferences:
|
||||
case Type::FollowSymbol:
|
||||
return true;
|
||||
|
||||
// Discard this one as UpdateDocumentAnnotations will have the same effect.
|
||||
case Type::RequestDocumentAnnotations:
|
||||
|
||||
// Discard Suspend because the document will be cleared anyway.
|
||||
// Discard Resume because a (re)parse will happen on demand.
|
||||
case Type::SuspendDocument:
|
||||
case Type::ResumeDocument:
|
||||
|
||||
// Discard these as they are initial jobs that will be recreated on demand
|
||||
// anyway.
|
||||
case Type::UpdateDocumentAnnotations:
|
||||
case Type::CreateInitialDocumentPreamble:
|
||||
|
||||
// Discard these as they only make sense in a row. Avoid splitting them up.
|
||||
case Type::ParseSupportiveTranslationUnit:
|
||||
case Type::ReparseSupportiveTranslationUnit:
|
||||
|
||||
case Type::Invalid:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JobRequest::JobRequest(Type type)
|
||||
{
|
||||
static quint64 idCounter = 0;
|
||||
|
@@ -96,6 +96,7 @@ public:
|
||||
|
||||
IAsyncJob *createJob() const;
|
||||
void cancelJob(ClangCodeModelClientInterface &client) const;
|
||||
bool isTakeOverable() const;
|
||||
|
||||
bool operator==(const JobRequest &other) const;
|
||||
|
||||
|
@@ -113,6 +113,20 @@ JobRequests Jobs::process()
|
||||
return jobsStarted;
|
||||
}
|
||||
|
||||
JobRequests Jobs::stop()
|
||||
{
|
||||
// Take the queued jobs to prevent processing them.
|
||||
const JobRequests queuedJobs = queue();
|
||||
queue().clear();
|
||||
|
||||
// Wait until currently running jobs finish.
|
||||
QFutureSynchronizer<void> waitForFinishedJobs;
|
||||
foreach (const RunningJob &runningJob, m_running.values())
|
||||
waitForFinishedJobs.addFuture(runningJob.future);
|
||||
|
||||
return queuedJobs;
|
||||
}
|
||||
|
||||
JobRequests Jobs::runJobs(const JobRequests &jobsRequests)
|
||||
{
|
||||
JobRequests jobsStarted;
|
||||
@@ -166,6 +180,11 @@ void Jobs::onJobFinished(IAsyncJob *asyncJob)
|
||||
process();
|
||||
}
|
||||
|
||||
Jobs::JobFinishedCallback Jobs::jobFinishedCallback() const
|
||||
{
|
||||
return m_jobFinishedCallback;
|
||||
}
|
||||
|
||||
void Jobs::setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback)
|
||||
{
|
||||
m_jobFinishedCallback = jobFinishedCallback;
|
||||
|
@@ -71,6 +71,7 @@ public:
|
||||
= PreferredTranslationUnit::RecentlyParsed);
|
||||
|
||||
JobRequests process();
|
||||
JobRequests stop();
|
||||
|
||||
void setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback);
|
||||
|
||||
@@ -80,6 +81,7 @@ public /*for tests*/:
|
||||
const JobRequests &queue() const;
|
||||
bool isJobRunningForTranslationUnit(const Utf8String &translationUnitId) const;
|
||||
bool isJobRunningForJobRequest(const JobRequest &jobRequest) const;
|
||||
JobFinishedCallback jobFinishedCallback() const;
|
||||
|
||||
private:
|
||||
JobRequests runJobs(const JobRequests &jobRequest);
|
||||
|
@@ -56,8 +56,7 @@ SupportiveTranslationUnitInitializer::State SupportiveTranslationUnitInitializer
|
||||
|
||||
void SupportiveTranslationUnitInitializer::startInitializing()
|
||||
{
|
||||
QTC_CHECK(m_state == State::NotInitialized);
|
||||
if (abortIfDocumentIsClosed())
|
||||
if (!checkStateAndDocument(State::NotInitialized))
|
||||
return;
|
||||
|
||||
m_document.translationUnits().createAndAppend();
|
||||
@@ -71,10 +70,15 @@ void SupportiveTranslationUnitInitializer::startInitializing()
|
||||
m_state = State::WaitingForParseJob;
|
||||
}
|
||||
|
||||
void SupportiveTranslationUnitInitializer::abort()
|
||||
{
|
||||
m_jobs.setJobFinishedCallback(Jobs::JobFinishedCallback());
|
||||
m_state = State::Aborted;
|
||||
}
|
||||
|
||||
void SupportiveTranslationUnitInitializer::checkIfParseJobFinished(const Jobs::RunningJob &job)
|
||||
{
|
||||
QTC_CHECK(m_state == State::WaitingForParseJob);
|
||||
if (abortIfDocumentIsClosed())
|
||||
if (!checkStateAndDocument(State::WaitingForParseJob))
|
||||
return;
|
||||
|
||||
if (job.jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit) {
|
||||
@@ -90,8 +94,7 @@ void SupportiveTranslationUnitInitializer::checkIfParseJobFinished(const Jobs::R
|
||||
|
||||
void SupportiveTranslationUnitInitializer::checkIfReparseJobFinished(const Jobs::RunningJob &job)
|
||||
{
|
||||
QTC_CHECK(m_state == State::WaitingForReparseJob);
|
||||
if (abortIfDocumentIsClosed())
|
||||
if (!checkStateAndDocument(State::WaitingForReparseJob))
|
||||
return;
|
||||
|
||||
if (job.jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit) {
|
||||
@@ -106,18 +109,22 @@ void SupportiveTranslationUnitInitializer::checkIfReparseJobFinished(const Jobs:
|
||||
}
|
||||
}
|
||||
|
||||
bool SupportiveTranslationUnitInitializer::abortIfDocumentIsClosed()
|
||||
bool SupportiveTranslationUnitInitializer::checkStateAndDocument(State currentExpectedState)
|
||||
{
|
||||
QTC_CHECK(m_isDocumentClosedChecker);
|
||||
|
||||
if (m_isDocumentClosedChecker(m_document.filePath(), m_document.projectPart().id())) {
|
||||
if (m_state != currentExpectedState) {
|
||||
m_state = State::Aborted;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
QTC_CHECK(m_isDocumentClosedChecker);
|
||||
if (m_isDocumentClosedChecker(m_document.filePath(), m_document.projectPart().id())) {
|
||||
m_state = State::Aborted;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SupportiveTranslationUnitInitializer::addJob(JobRequest::Type jobRequestType)
|
||||
{
|
||||
const JobRequest jobRequest = m_jobs.createJobRequest(
|
||||
|
@@ -52,6 +52,7 @@ public:
|
||||
|
||||
State state() const;
|
||||
void startInitializing();
|
||||
void abort();
|
||||
|
||||
public: // for tests
|
||||
void setState(const State &state);
|
||||
@@ -59,7 +60,7 @@ public: // for tests
|
||||
void checkIfReparseJobFinished(const Jobs::RunningJob &job);
|
||||
|
||||
private:
|
||||
bool abortIfDocumentIsClosed();
|
||||
bool checkStateAndDocument(State currentExpectedState);
|
||||
void addJob(JobRequest::Type jobRequestType);
|
||||
|
||||
private:
|
||||
|
@@ -406,6 +406,18 @@ TEST_F(ClangCodeModelServerSlowTest, TranslationUnitAfterUpdateNeedsReparse)
|
||||
ASSERT_THAT(clangServer, HasDirtyDocument(filePathA, projectPartId, 1U, true, true));
|
||||
}
|
||||
|
||||
TEST_F(ClangCodeModelServerSlowTest, TakeOverJobsOnProjectPartChange)
|
||||
{
|
||||
registerProjectAndFileAndWaitForFinished(filePathC, 2);
|
||||
updateVisibilty(filePathB, filePathB); // Disable processing jobs
|
||||
requestReferences();
|
||||
|
||||
expectReferences();
|
||||
|
||||
changeProjectPartArguments(); // Here we do not want to loose the RequestReferences job
|
||||
updateVisibilty(filePathC, filePathC); // Enable processing jobs
|
||||
}
|
||||
|
||||
void ClangCodeModelServer::SetUp()
|
||||
{
|
||||
clangServer.setClient(&mockClangCodeModelClient);
|
||||
|
@@ -126,6 +126,19 @@ TEST_F(DocumentProcessors, Remove)
|
||||
ASSERT_TRUE(documentProcessors.processors().empty());
|
||||
}
|
||||
|
||||
TEST_F(DocumentProcessors, ResetTakesOverJobsInQueue)
|
||||
{
|
||||
documentProcessors.create(document);
|
||||
documentProcessors.processor(document).addJob(JobRequest::Type::RequestReferences);
|
||||
documents.remove({document.fileContainer()});
|
||||
const auto newDocument = *documents.create({document.fileContainer()}).begin();
|
||||
|
||||
documentProcessors.reset(document, newDocument);
|
||||
|
||||
ASSERT_THAT(documentProcessors.processor(document).queue().first().type,
|
||||
JobRequest::Type::RequestReferences);
|
||||
}
|
||||
|
||||
TEST_F(DocumentProcessors, RemoveThrowsForNotExisting)
|
||||
{
|
||||
ASSERT_THROW(documentProcessors.remove(document),
|
||||
|
@@ -111,6 +111,18 @@ TEST_F(SupportiveTranslationUnitInitializerSlowTest, StartInitializingStartsJob)
|
||||
ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ParseSupportiveTranslationUnit);
|
||||
}
|
||||
|
||||
TEST_F(SupportiveTranslationUnitInitializerSlowTest, Abort)
|
||||
{
|
||||
initializer.startInitializing();
|
||||
assertSingleJobRunningAndEmptyQueue();
|
||||
|
||||
initializer.abort();
|
||||
|
||||
ASSERT_THAT(initializer.state(),
|
||||
Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted));
|
||||
ASSERT_FALSE(jobs.jobFinishedCallback());
|
||||
}
|
||||
|
||||
TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedAbortsIfDocumentIsClosed)
|
||||
{
|
||||
documents.remove({FileContainer(filePath, projectPartId)});
|
||||
|
Reference in New Issue
Block a user