1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-23 17:05:26 +02:00

Bug 429488: Fix for deadlock in QMakeProjectInfo.updateActiveConfiguration

To prevent a deadlock between two the workspace and QMakeProjectInfo.sync
locks, QMakeProjectInfo class has been rewritten to not call any method
under sync-lock except for IQMakeEnv.init/destroy method.

All methods should allow calling under workspace lock.

Added a new IQMakeEnv2 interface to provide an explicit init method.

Original QMakeProjectInfo has been split to
QMakeProjectInfo and QMakeProjectInfoManager.

This change is fully backward compatible.

Change-Id: I880f22dd9bd999af1d3f47e4a3c4c0ab216b4ad2
Signed-off-by: David Kaspar <dkaspar@blackberry.com>
Reviewed-on: https://git.eclipse.org/r/23270
Tested-by: Hudson CI
Reviewed-by: Andrew Eidsness <eclipse@jfront.com>
Reviewed-by: Doug Schaefer <dschaefer@qnx.com>
IP-Clean: Doug Schaefer <dschaefer@qnx.com>
This commit is contained in:
David Kaspar 2014-03-12 17:59:12 +01:00 committed by Doug Schaefer
parent e62ed2c8b7
commit 73a912f95b
9 changed files with 430 additions and 328 deletions

View file

@ -39,8 +39,6 @@ public final class QMakeEnvProviderDescriptor implements Comparable<QMakeEnvProv
private final AtomicReference<Boolean> evaluation = new AtomicReference<Boolean>();
private final Expression enablementExpression;
private IQMakeEnvProvider provider;
QMakeEnvProviderDescriptor(IConfigurationElement element) {
this.element = element;
@ -99,17 +97,12 @@ public final class QMakeEnvProviderDescriptor implements Comparable<QMakeEnvProv
if (! matches(controller)) {
return null;
}
if (provider == null) {
synchronized (this) {
if (provider == null) {
try {
provider = (IQMakeEnvProvider) element.createExecutableExtension(ATTR_CLASS);
} catch (CoreException e) {
QtPlugin.log("Error in class attribute of " + id, e); //$NON-NLS-1$
return null;
}
}
}
IQMakeEnvProvider provider;
try {
provider = (IQMakeEnvProvider) element.createExecutableExtension(ATTR_CLASS);
} catch (CoreException e) {
QtPlugin.log("Error in class attribute of " + id, e); //$NON-NLS-1$
return null;
}
return provider.createEnv(controller);
}

View file

@ -16,6 +16,7 @@ import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.qt.core.QtPlugin;
import org.eclipse.cdt.qt.core.index.IQMakeEnv;
import org.eclipse.cdt.qt.core.index.IQMakeEnv2;
import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider;
import org.eclipse.cdt.qt.core.index.QMakeEnvInfo;
import org.eclipse.core.resources.IFile;
@ -77,7 +78,7 @@ public final class QMakeEnvProviderManager {
* Represents a fallback IQMakeEnvProvider that is used for a project that has QtNature
* while there is no registered IQMakeEnvProvider that would provide any IQMakeEnv.
*/
private static class ConfigurationQMakeEnv implements IQMakeEnv {
private static class ConfigurationQMakeEnv implements IQMakeEnv2 {
private static final String PRO_FILE_SUFFIX = ".pro"; //$NON-NLS-1$
private static final String ENV_VAR_QMAKE = "QMAKE"; //$NON-NLS-1$
@ -88,6 +89,10 @@ public final class QMakeEnvProviderManager {
this.configuration = configuration;
}
@Override
public void init() {
}
@Override
public void destroy() {
}

View file

@ -10,7 +10,6 @@ package org.eclipse.cdt.internal.qt.core.index;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -19,159 +18,92 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.cdt.qt.core.index.IQMakeEnv;
import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider;
import org.eclipse.cdt.qt.core.index.IQMakeEnv2;
import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider.IController;
import org.eclipse.cdt.qt.core.index.IQMakeInfo;
import org.eclipse.cdt.qt.core.index.IQMakeProjectInfo;
import org.eclipse.cdt.qt.core.index.IQMakeProjectInfoListener;
import org.eclipse.cdt.qt.core.index.QMakeEnvInfo;
import org.eclipse.cdt.qt.core.index.IQMakeInfo;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
/**
* Represents a QMake project information that is based on an activate project configuration of a specified related IProject.
* Allows to resolve actual information and listen its change.
* Manages life-cycle of all QMakeProjectInfo instances.
*/
public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescriptionListener {
public final class QMakeProjectInfo implements IQMakeProjectInfo {
private static final RCListener LISTENER = new RCListener();
// sync object for CACHE field
private static final Object SYNC = new Object();
// a map of all QMakeProjectInfo instances
private static final Map<IProject,QMakeProjectInfo> CACHE = new HashMap<IProject,QMakeProjectInfo>();
private final State STATE_FREEZE = new State();
private final State STATE_INVALID = new State();
// called by QtPlugin activator to setup this class
public static final void start() {
ResourcesPlugin.getWorkspace().addResourceChangeListener(LISTENER, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE);
}
// called by QtPlugin activator to clean up this class
public static final void stop() {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(LISTENER);
List<QMakeProjectInfo> infos;
synchronized (SYNC) {
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
CACHE.clear();
}
for (QMakeProjectInfo info : infos) {
if (info != null) {
info.destroy();
}
}
}
/**
* Returns a QMakeProjectInfo for an active project configuration of a specified project.
*
* @param project the project
* @return the QMakeProjectInfo; or null if the project does not have QtNature
*/
public static QMakeProjectInfo getQMakeProjectInfoFor(IProject project) {
QMakeProjectInfo info;
synchronized (SYNC) {
info = CACHE.get(project);
if (info != null) {
return info;
}
info = new QMakeProjectInfo(project);
CACHE.put(project,info);
}
info.updateActiveConfiguration();
return info;
}
// removes the project from the CACHE
private static void removeProjectFromCache(IResource project) {
QMakeProjectInfo info;
synchronized (SYNC) {
info = CACHE.remove(project);
}
if (info != null) {
info.destroy();
}
}
private final IProject project;
// sync object for all mutable fields
private final Object sync = new Object();
// true if this instance still registered in CACHE
private boolean live = true;
// a set of sensitive files that might affects actual QMake information
private final SensitiveSet sensitiveFilePathSet = new SensitiveSet();
// an active project configuration
private ControllerImpl activeController;
// the last calculated QMake info; null if not calculated
private IQMakeInfo qmakeInfo = null;
// listeners
private final List<IQMakeProjectInfoListener> listeners = new CopyOnWriteArrayList<IQMakeProjectInfoListener>();
private QMakeProjectInfo(IProject project) {
private final IProject project;
private final Object stateSync = new Object();
// represents a current state of QMakeProjectInfo
private State state = STATE_INVALID;
QMakeProjectInfo(IProject project) {
this.project = project;
CoreModel.getDefault().addCProjectDescriptionListener(this, ICDescriptionDelta.ACTIVE_CFG);
}
// called from removeProjectFromCache only
private void destroy() {
synchronized (sync) {
if (! live) {
void destroy() {
setState(STATE_FREEZE);
}
// must not be called under any QMake-related sync-lock, except for workspace lock
private void updateStateFrom(State fromState) {
synchronized (stateSync) {
if (state != fromState) {
return;
}
live = false;
CoreModel.getDefault().removeCProjectDescriptionListener(this);
setActiveConfiguration(null);
qmakeInfo = QMakeInfo.INVALID;
}
updateState();
}
// must not be called under synchronized (SYNC) or synchronized (sync)
private void updateActiveConfiguration() {
synchronized (sync) {
if (! live) {
// must not be called under any QMake-related sync-lock, except for workspace lock
// we are running outside of synchronized block to prevent deadlock involving workspace lock
// this means that theoretically there might be multiple thread calculating the same results but only the last one wins
void updateState() {
// note that getProjectDescription might acquire workspace lock
ICProjectDescription projectDescription = CoreModel.getDefault().getProjectDescriptionManager().getProjectDescription(project);
ICConfigurationDescription configuration = projectDescription != null ? projectDescription.getActiveConfiguration() : null;
setState(configuration != null ? new State(configuration) : STATE_INVALID);
}
private void setState(State newState) {
State oldState = null;
synchronized (stateSync) {
if (newState == null || state == newState) {
return;
}
ICProjectDescription projectDescription = CoreModel.getDefault().getProjectDescriptionManager().getProjectDescription(project);
setActiveConfiguration(projectDescription != null ? projectDescription.getActiveConfiguration() : null);
qmakeInfo = null;
if (state == STATE_FREEZE) {
newState.destroyBeforeInit();
return;
}
oldState = state;
state = newState;
if (oldState != null) {
oldState.destroy();
}
newState.init();
}
notifyListeners();
}
// called under synchronized (sync)
private void setActiveConfiguration(ICConfigurationDescription configuration) {
ControllerImpl previous = activeController;
activeController = configuration != null ? new ControllerImpl(configuration) : null;
if (previous != null) {
previous.destroy();
for (IQMakeProjectInfoListener listener : listeners) {
listener.qmakeInfoChanged();
}
}
// called on active project configuration change
@Override
public void handleEvent(CProjectDescriptionEvent event) {
if (event.getProject() != project) {
return;
}
updateActiveConfiguration();
}
@Override
public void addListener(IQMakeProjectInfoListener listener) {
listeners.add(listener);
@ -182,60 +114,14 @@ public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescr
listeners.remove(listener);
}
private IProject getProject() {
IProject getProject() {
return project;
}
// calculates (if does not exist) and returns actual QMake info
@Override
public IQMakeInfo getActualInfo() {
synchronized (sync) {
if (! live) {
return QMakeInfo.INVALID;
}
if (qmakeInfo == null) {
fetchQMakeInfo();
}
return qmakeInfo;
}
}
// calculates actual QMake info
private void fetchQMakeInfo() {
// retrieves IQMakeEnvInfo from IQMakeEnv
IQMakeEnv qmakeEnv = activeController != null ? activeController.getQMakeEnv() : null;
QMakeEnvInfo qmakeEnvInfo = qmakeEnv != null ? qmakeEnv.getQMakeEnvInfo() : null;
// retrieves .pro file path
String proFilePath = toFilePath(qmakeEnvInfo != null ? qmakeEnvInfo.getProFile() : null);
// retrieves qmake executable path
String qmakeFilePath = qmakeEnvInfo != null ? qmakeEnvInfo.getQMakeFilePath() : null;
// retries environment
List<String> envList = new ArrayList<String>();
Map<String, String> envMap = qmakeEnvInfo != null ? qmakeEnvInfo.getEnvironment() : Collections.<String,String>emptyMap();
for (Map.Entry<String,String> entry : envMap.entrySet()) {
envList.add(entry.getKey() + "=" + entry.getValue());
}
// calculates actual QMake info
qmakeInfo = QMakeInfo.create(proFilePath, qmakeFilePath, envList.toArray(new String[envList.size()]));
// calculates a new set of sensitive file paths
sensitiveFilePathSet.clear();
Set<IFile> envSensFiles = qmakeEnvInfo != null ? qmakeEnvInfo.getSensitiveFiles() : Collections.<IFile>emptySet();
for (IFile sensitiveFile : envSensFiles) {
if (sensitiveFile != null) {
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
}
}
if (proFilePath != null) {
sensitiveFilePathSet.addSensitiveFile(proFilePath);
}
List<String> sensitiveFiles = qmakeInfo.getInvolvedQMakeFiles();
if (sensitiveFiles != null) {
for (String sensitiveFile : sensitiveFiles) {
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
}
synchronized (stateSync) {
return state.getQMakeInfo();
}
}
@ -257,57 +143,82 @@ public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescr
}
// checks if any of the specified files is a sensitive file
private boolean containsAnySensitiveFile(Set<IPath> files) {
synchronized (sync) {
if (live) {
for (Iterator<IPath> iterator = files.iterator(); iterator.hasNext();) {
IPath path = iterator.next();
if (sensitiveFilePathSet.contains(path)) {
return true;
}
}
}
return false;
}
}
// resets actual QMake info and notifies all listeners that QMake information has changes
private void scheduleFetchQMakeInfo() {
synchronized (sync) {
if (! live) {
return;
}
qmakeInfo = null;
}
notifyListeners();
}
private void notifyListeners() {
for (IQMakeProjectInfoListener listener : listeners) {
listener.qmakeInfoChanged();
boolean containsAnySensitiveFile(Set<IPath> files) {
synchronized (stateSync) {
return state.containsAnySensitiveFile(files);
}
}
/**
* Represents a project configuration.
* Represents a state of QMakeInfo for a specific QMakeProjectInfo.
*/
private final class ControllerImpl implements IQMakeEnvProvider.IController {
private final class State implements IController {
// an active project configuration
private final ICConfigurationDescription configuration;
// an active project qmake env
private final IQMakeEnv qmakeEnv;
// the last calculated QMake info; null if not calculated
private final IQMakeInfo qmakeInfo;
// a set of sensitive files that might affects actual QMake information
private final SensitiveSet sensitiveFilePathSet;
public ControllerImpl(ICConfigurationDescription configuration) {
State() {
configuration = null;
qmakeEnv = null;
qmakeInfo = QMakeInfo.INVALID;
sensitiveFilePathSet = new SensitiveSet();
}
State(ICConfigurationDescription configuration) {
this.configuration = configuration;
// qmakeEnv created from registry of qmakeEnvProvider extensions
this.qmakeEnv = QMakeEnvProviderManager.getInstance().createEnv(this);
// retrieves IQMakeEnvInfo from IQMakeEnv
QMakeEnvInfo qmakeEnvInfo = qmakeEnv.getQMakeEnvInfo();
// retrieves .pro file path
String proFilePath = toFilePath(qmakeEnvInfo != null ? qmakeEnvInfo.getProFile() : null);
// retrieves qmake executable path
String qmakeFilePath = qmakeEnvInfo != null ? qmakeEnvInfo.getQMakeFilePath() : null;
// retries environment
List<String> envList = new ArrayList<String>();
Map<String, String> envMap = qmakeEnvInfo != null ? qmakeEnvInfo.getEnvironment() : Collections.<String,String>emptyMap();
for (Map.Entry<String,String> entry : envMap.entrySet()) {
envList.add(entry.getKey() + "=" + entry.getValue());
}
// calculates actual QMake info
qmakeInfo = QMakeInfo.create(proFilePath, qmakeFilePath, envList.toArray(new String[envList.size()]));
// calculates a new set of sensitive file paths
sensitiveFilePathSet = new SensitiveSet();
Set<IFile> envSensFiles = qmakeEnvInfo != null ? qmakeEnvInfo.getSensitiveFiles() : Collections.<IFile>emptySet();
for (IFile sensitiveFile : envSensFiles) {
if (sensitiveFile != null) {
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
}
}
if (proFilePath != null) {
sensitiveFilePathSet.addSensitiveFile(proFilePath);
}
List<String> sensitiveFiles = qmakeInfo.getInvolvedQMakeFiles();
if (sensitiveFiles != null) {
for (String sensitiveFile : sensitiveFiles) {
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
}
}
}
public void destroy() {
qmakeEnv.destroy();
}
public IQMakeEnv getQMakeEnv() {
return qmakeEnv;
private boolean containsAnySensitiveFile(Set<IPath> files) {
for (Iterator<IPath> iterator = files.iterator(); iterator.hasNext();) {
IPath path = iterator.next();
if (sensitiveFilePathSet.contains(path)) {
return true;
}
}
return false;
}
@Override
@ -315,116 +226,32 @@ public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescr
return configuration;
}
public void destroyBeforeInit() {
// see IQMakeEnv JavaDoc for details
if (qmakeEnv != null && ! (qmakeEnv instanceof IQMakeEnv2)) {
qmakeEnv.destroy();
}
}
public void init() {
if (qmakeEnv instanceof IQMakeEnv2) {
((IQMakeEnv2) qmakeEnv).init();
}
}
public void destroy() {
if (qmakeEnv != null) {
qmakeEnv.destroy();
}
}
@Override
public void scheduleUpdate() {
scheduleFetchQMakeInfo();
updateStateFrom(this);
}
}
/**
* Listens on Eclipse file system changes.
*/
private static final class RCListener implements IResourceChangeListener {
@Override
public void resourceChanged(IResourceChangeEvent event) {
RDVisitor visitor = new RDVisitor();
// collect project to delete and changed files
switch (event.getType()) {
case IResourceChangeEvent.PRE_CLOSE:
case IResourceChangeEvent.PRE_DELETE:
IResource project = event.getResource();
if (project != null && project.getType() == IResource.PROJECT) {
visitor.addProjectToDelete(project);
}
break;
case IResourceChangeEvent.POST_CHANGE:
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
delta.accept(visitor);
} catch (CoreException e) {
// empty
}
}
break;
}
// process collected data
visitor.process();
}
}
private static final class RDVisitor implements IResourceDeltaVisitor {
private final Set<IResource> projectsToDelete = new HashSet<IResource>();
private final Set<IResource> projectsToUpdate = new HashSet<IResource>();
private final Set<IPath> changedFiles = new HashSet<IPath>();
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (resource != null) {
switch (resource.getType()) {
case IResource.FILE:
addChangedFile(resource);
return false;
case IResource.PROJECT:
switch (delta.getKind()) {
case IResourceDelta.CHANGED:
if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
addProjectToUpdate(resource);
}
return true;
case IResourceDelta.REMOVED:
addProjectToDelete(resource);
return false;
}
break;
}
}
return true;
}
private void addProjectToUpdate(IResource project) {
projectsToUpdate.add(project);
}
private void addProjectToDelete(IResource project) {
projectsToDelete.add(project);
}
private void addChangedFile(IResource file) {
IPath fullPath = file.getFullPath();
if (fullPath != null) {
changedFiles.add(fullPath);
}
}
public void process() {
// removing projects from CACHE
for(IResource project : projectsToDelete) {
removeProjectFromCache(project);
}
List<QMakeProjectInfo> infos;
synchronized (SYNC) {
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
}
for (QMakeProjectInfo info : infos) {
// checking if any project description change affects QMakeProjectInfo
if (projectsToUpdate.contains(info.getProject())) {
info.updateActiveConfiguration();
}
// checking if any of the changed files affects QMakeProjectInfo
if (info.containsAnySensitiveFile(changedFiles)) {
// if so then scheduling update
info.scheduleFetchQMakeInfo();
}
}
IQMakeInfo getQMakeInfo() {
return qmakeInfo;
}
}

View file

@ -0,0 +1,224 @@
/*
* Copyright (c) 2014 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.cdt.internal.qt.core.index;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
/**
* Represents a management of QMakeProjectInfo instances and manages life-cycle of all QMakeProjectInfo instances.
*/
public class QMakeProjectInfoManager {
private static final PDListener PD_LISTENER = new PDListener();
private static final RCListener RC_LISTENER = new RCListener();
// sync object for CACHE field
private static final Object CACHE_SYNC = new Object();
// a list of all QMakeProjectInfo instances
private static Map<IProject,QMakeProjectInfo> CACHE;
// called by QtPlugin activator to setup this class
public static final void start() {
synchronized (CACHE_SYNC) {
CACHE = new HashMap<IProject,QMakeProjectInfo>();
}
CoreModel.getDefault().addCProjectDescriptionListener(PD_LISTENER, ICDescriptionDelta.ACTIVE_CFG);
ResourcesPlugin.getWorkspace().addResourceChangeListener(RC_LISTENER, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE);
}
// called by QtPlugin activator to clean up this class
public static final void stop() {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(RC_LISTENER);
CoreModel.getDefault().removeCProjectDescriptionListener(PD_LISTENER);
List<QMakeProjectInfo> infos;
synchronized (CACHE_SYNC) {
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
CACHE = null;
}
for (QMakeProjectInfo info : infos) {
if (info != null) {
info.destroy();
}
}
}
/**
* Returns a QMakeProjectInfo for an active project configuration of a specified project.
*
* @param project the project
* @return the QMakeProjectInfo; or null if the project does not have QtNature
*/
public static QMakeProjectInfo getQMakeProjectInfoFor(IProject project) {
return getQMakeProjectInfoFor(project, true);
}
private static QMakeProjectInfo getQMakeProjectInfoFor(IProject project, boolean create) {
QMakeProjectInfo info;
synchronized (CACHE_SYNC) {
info = CACHE.get(project);
if (info != null) {
return info;
}
if (! create) {
// do not create, just return null
return null;
}
info = new QMakeProjectInfo(project);
CACHE.put(project, info);
}
info.updateState();
return info;
}
// removes the project from the CACHE
private static void removeProjectFromCache(IResource project) {
QMakeProjectInfo info;
synchronized (CACHE_SYNC) {
info = CACHE.remove(project);
}
if (info != null) {
info.destroy();
}
}
private static final class PDListener implements ICProjectDescriptionListener {
// called on active project configuration change
@Override
public void handleEvent(CProjectDescriptionEvent event) {
QMakeProjectInfo info = getQMakeProjectInfoFor(event.getProject(), false);
if (info != null) {
info.updateState();
}
}
}
/**
* Listens on Eclipse file system changes.
*/
private static final class RCListener implements IResourceChangeListener {
@Override
public void resourceChanged(IResourceChangeEvent event) {
RDVisitor visitor = new RDVisitor();
// collect project to delete and changed files
switch (event.getType()) {
case IResourceChangeEvent.PRE_CLOSE:
case IResourceChangeEvent.PRE_DELETE:
IResource project = event.getResource();
if (project != null && project.getType() == IResource.PROJECT) {
visitor.addProjectToDelete(project);
}
break;
case IResourceChangeEvent.POST_CHANGE:
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
delta.accept(visitor);
} catch (CoreException e) {
// empty
}
}
break;
}
// process collected data
visitor.process();
}
}
private static final class RDVisitor implements IResourceDeltaVisitor {
private final Set<IResource> projectsToDelete = new HashSet<IResource>();
private final Set<IResource> projectsToUpdate = new HashSet<IResource>();
private final Set<IPath> changedFiles = new HashSet<IPath>();
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (resource != null) {
switch (resource.getType()) {
case IResource.FILE:
addChangedFile(resource);
return false;
case IResource.PROJECT:
switch (delta.getKind()) {
case IResourceDelta.CHANGED:
if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
addProjectToUpdate(resource);
}
return true;
case IResourceDelta.REMOVED:
addProjectToDelete(resource);
return false;
}
break;
}
}
return true;
}
private void addProjectToUpdate(IResource project) {
projectsToUpdate.add(project);
}
private void addProjectToDelete(IResource project) {
projectsToDelete.add(project);
}
private void addChangedFile(IResource file) {
IPath fullPath = file.getFullPath();
if (fullPath != null) {
changedFiles.add(fullPath);
}
}
public void process() {
// removing projects from CACHE
for(IResource project : projectsToDelete) {
removeProjectFromCache(project);
}
List<QMakeProjectInfo> infos;
synchronized (CACHE_SYNC) {
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
}
for (QMakeProjectInfo info : infos) {
// checking if any project description change or any of the changed files affect QMakeProjectInfo
if (projectsToUpdate.contains(info.getProject()) || info.containsAnySensitiveFile(changedFiles)) {
// if so then updating
info.updateState();
}
}
}
}
}

View file

@ -7,14 +7,18 @@
*/
package org.eclipse.cdt.qt.core;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfo;
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfoManager;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
public class QtPlugin extends Plugin {
@ -47,13 +51,27 @@ public class QtPlugin extends Plugin {
@Override
public void start(BundleContext context) throws Exception {
// have to wait for STARTED event because
// cannot access CoreModel.getDefault().addCProjectDescriptionListener()
// since the CoreModel is not completely initialized at this time
BundleListener bundleListener = new BundleListener() {
final AtomicBoolean initStarted = new AtomicBoolean(false);
@Override
public void bundleChanged(BundleEvent bundleEvent) {
if (bundleEvent.getType() == BundleEvent.STARTED) {
if (!initStarted.getAndSet(true)) {
QMakeProjectInfoManager.start();
}
}
}
};
context.addBundleListener(bundleListener);
super.start(context);
QMakeProjectInfo.start();
}
@Override
public void stop(BundleContext context) throws Exception {
QMakeProjectInfo.stop();
QMakeProjectInfoManager.stop();
super.stop(context);
}

View file

@ -9,11 +9,18 @@ package org.eclipse.cdt.qt.core.index;
/**
* Represents a QMake environment. It is usually created by IQMakeEnvProvider.createEnv() method for a specific project configuration.
*
* Note that IQMakeEnv has destroy method only and it is expected that the instance is already initialized in the constructor.
* This means that it may happen that IQMakeEnv instance is created and destroyed immediately.
*
* See IQMakeEnv2 interface if you want to get an explicit notification when IQMakeEnv gets really used. In that case, the instance initialization
* needs to be done in init method completely - not in the constructor.
*/
public interface IQMakeEnv {
/**
* Notifies that this environment is no longer used.
* This method should not use any workspace-lock or sync-locks that might call QMake-related structures.
*/
void destroy();

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2014 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.cdt.qt.core.index;
/**
* Represents a QMake environment similarly to IQMakeEnv but it has an explicit init method which is called to notify
* that the IQMakeEnv is used for active listening on change of QMakeEnvInfo and therefore it should initialize
* its listener for such changes.
*
* Note that it is expected that IQMakeEnv2 does complete initialization in init method - not in the constructor
* i.e. the IQMakeEnv2 instance should start listening on possible changes of its IQMakeEnvInfo.
*/
public interface IQMakeEnv2 extends IQMakeEnv {
/**
* Notifies that this IQMakeEnv is used.
* This method should not use any workspace-lock or sync-locks that might call QMake-related structures.
*/
void init();
}

View file

@ -41,6 +41,8 @@ public interface IQMakeEnvProvider {
/**
* Request the controller to schedule a new qmake run to retrieve new QMake information.
* This method should be called when there is any change in IQMakeEnv that might affect resulting IQMakeEnvInfo.
*
* Note that calculation of new QMakeInfo is done immediately if this controller is still active and used.
*/
void scheduleUpdate();

View file

@ -7,7 +7,7 @@
*/
package org.eclipse.cdt.qt.core.index;
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfo;
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfoManager;
import org.eclipse.core.resources.IProject;
/**
@ -27,7 +27,7 @@ public final class QMakeProjectInfoFactory {
* in the specified project.
*/
public static IQMakeProjectInfo getForActiveConfigurationIn(IProject project) {
return QMakeProjectInfo.getQMakeProjectInfoFor(project);
return QMakeProjectInfoManager.getQMakeProjectInfoFor(project);
}
}