diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java index cab51e8f07461209a7a377a4387f81f869f1d279..68d3f57a2e99340e4b1dbb8c4188a023a8f1aa3d 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java @@ -20,8 +20,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; import javax.annotation.PostConstruct; @@ -40,6 +38,7 @@ import kieker.webgui.domain.ComponentListContainer; import kieker.webgui.domain.DisplayType; import kieker.webgui.persistence.IProjectDAO; import kieker.webgui.service.IProjectService; +import kieker.webgui.service.impl.utility.LockManager; import org.primefaces.model.UploadedFile; @@ -57,8 +56,8 @@ public final class ProjectServiceImpl implements IProjectService { private final List<String> projects = Collections.synchronizedList(new ArrayList<String>()); - private final ConcurrentHashMap<String, Semaphore> fileSystemLocks = new ConcurrentHashMap<String, Semaphore>(); - private final ConcurrentHashMap<String, Semaphore> analysesLocks = new ConcurrentHashMap<String, Semaphore>(); + private final LockManager fileSystemLockManager = new LockManager(); + private final LockManager analysesLockManager = new LockManager(); @Autowired private IProjectDAO projectDAO; @@ -83,22 +82,21 @@ public final class ProjectServiceImpl implements IProjectService { @Override public void addProject(final String projectName, final String username) throws ProjectAlreadyExistingException, IOException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { this.projectDAO.addProject(projectName, username); this.projects.add(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public void deleteProject(final String projectName) throws ProjectNotExistingException, IOException, ProjectStillRunningException, LockProjectException { - ProjectServiceImpl.tryLockProjects(projectName, this.fileSystemLocks, projectName, this.analysesLocks); + this.fileSystemLockManager.lock(projectName); + this.analysesLockManager.lock(projectName); - final Semaphore oldLockObjectA = this.analysesLocks.get(projectName); - final Semaphore oldLockObjectB = this.fileSystemLocks.get(projectName); try { final STATE state = this.acManager.getCurrentState(projectName); if ((state == STATE.RUNNING) || (state == STATE.TERMINATING)) { @@ -106,14 +104,11 @@ public final class ProjectServiceImpl implements IProjectService { } this.projectDAO.removeProject(projectName); this.projects.remove(projectName); - - // Remove the old lock objects - this.analysesLocks.remove(projectName); - this.fileSystemLocks.remove(projectName); } finally { - oldLockObjectA.release(); - oldLockObjectB.release(); + this.fileSystemLockManager.unlock(projectName); + this.analysesLockManager.unlock(projectName); } + } @Override @@ -122,14 +117,16 @@ public final class ProjectServiceImpl implements IProjectService { if (originalProjectName.equals(newProjectName)) { throw new ProjectAlreadyExistingException("Source and target are identical"); } - ProjectServiceImpl.tryLockProjects(originalProjectName, this.fileSystemLocks, newProjectName, this.fileSystemLocks); + + this.fileSystemLockManager.lock(originalProjectName); + this.fileSystemLockManager.lock(newProjectName); try { this.projectDAO.copyProject(originalProjectName, newProjectName); this.projects.add(newProjectName); } finally { - ProjectServiceImpl.unlockProject(originalProjectName, this.fileSystemLocks); - ProjectServiceImpl.unlockProject(newProjectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(originalProjectName); + this.fileSystemLockManager.unlock(newProjectName); } } @@ -139,7 +136,9 @@ public final class ProjectServiceImpl implements IProjectService { if (originalProjectName.equals(newProjectName)) { throw new ProjectAlreadyExistingException("Source and target are identical"); } - ProjectServiceImpl.tryLockProjects(originalProjectName, this.fileSystemLocks, newProjectName, this.fileSystemLocks); + + this.fileSystemLockManager.lock(originalProjectName); + this.fileSystemLockManager.lock(newProjectName); try { this.projectDAO.copyProject(originalProjectName, newProjectName); @@ -149,111 +148,111 @@ public final class ProjectServiceImpl implements IProjectService { this.projects.remove(originalProjectName); } } finally { - ProjectServiceImpl.unlockProject(originalProjectName, this.fileSystemLocks); - ProjectServiceImpl.unlockProject(newProjectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(originalProjectName); + this.fileSystemLockManager.unlock(newProjectName); } } @Override public void importProject(final String projectName, final String username, final UploadedFile file) throws ProjectAlreadyExistingException, IOException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { this.projectDAO.importProject(projectName, username, file); this.projects.add(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public MIProject loadProject(final String projectName) throws ProjectNotExistingException, IOException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.loadProject(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public Object loadProject(final String projectName, final ClassContainer classAndMethodContainer) throws ProjectNotExistingException, IOException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.loadProject(projectName, classAndMethodContainer); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public void saveProject(final String projectName, final MIProject project, final long timeStamp, final boolean overwriteNewerProject, final String username, final String analysisLayout, final String cockpitLayout) throws ProjectNotExistingException, IOException, NewerProjectException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { this.projectDAO.saveProject(projectName, project, timeStamp, overwriteNewerProject, username, analysisLayout, cockpitLayout); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public long getCurrTimeStamp(final String projectName) throws ProjectNotExistingException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getCurrTimeStamp(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public void uploadLibrary(final UploadedFile file, final String projectName) throws ProjectNotExistingException, IOException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { this.projectDAO.uploadLibrary(file, projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public ClassLoader getClassLoader(final String projectName, final Object requester) throws ProjectNotExistingException, IOException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getClassLoader(projectName, requester); // NOPMD (ClassLoader) } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public ComponentListContainer getAvailableComponents(final String projectName) throws LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getAvailableComponents(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public List<String> listAllLibraries(final String projectName) throws ProjectNotExistingException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.listAllLibraries(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @@ -261,157 +260,158 @@ public final class ProjectServiceImpl implements IProjectService { public void initializeAnalysis(final String projectName, final ClassLoader classLoader) throws ProjectNotExistingException, InvalidAnalysisStateException, AnalysisInitializationException, LockProjectException { // We have to lock both - the project and the analysis, as a file has to be loaded - ProjectServiceImpl.tryLockProjects(projectName, this.fileSystemLocks, projectName, this.analysesLocks); + this.fileSystemLockManager.lock(projectName); + this.analysesLockManager.lock(projectName); try { this.acManager.initializeAnalysis(projectName, classLoader); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); + this.analysesLockManager.unlock(projectName); } } @Override public void cleanAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { this.acManager.cleanAnalysis(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public void startAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { this.acManager.startAnalysis(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public void stopAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { this.acManager.stopAnalysis(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public void emergencyShutdownOfAnalysis(final String projectName) throws ProjectNotExistingException, LockProjectException, InvalidAnalysisStateException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { this.acManager.emergencyShutdown(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public Object getDisplay(final String projectName, final String viewName, final String displayName) throws InvalidAnalysisStateException, DisplayNotFoundException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { return this.acManager.getDisplay(projectName, viewName, displayName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public STATE getCurrentState(final String projectName) throws LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { return this.acManager.getCurrentState(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public Object[] getLogEntries(final String projectName) throws InvalidAnalysisStateException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { return this.acManager.getLogEntries(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @Override public boolean deleteLibrary(final String projectName, final String libName) throws IOException, ProjectNotExistingException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.deleteLibrary(projectName, libName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public String getOwner(final String projectName) throws ProjectNotExistingException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getOwner(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public String getLastUser(final String projectName) throws ProjectNotExistingException, LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getLastUser(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public String getAnalysisLayout(final String projectName) throws LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getAnalysisLayout(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public String getCockpitLayout(final String projectName) throws LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.lock(projectName); try { return this.projectDAO.getCockpitLayout(projectName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.fileSystemLocks); + this.fileSystemLockManager.unlock(projectName); } } @Override public DisplayType getDisplayType(final String projectName, final String viewName, final String displayConnectorName) throws LockProjectException { - ProjectServiceImpl.tryLockProject(projectName, this.analysesLocks); + this.analysesLockManager.lock(projectName); try { return this.acManager.getDisplayType(projectName, viewName, displayConnectorName); } finally { - ProjectServiceImpl.unlockProject(projectName, this.analysesLocks); + this.analysesLockManager.unlock(projectName); } } @@ -422,126 +422,4 @@ public final class ProjectServiceImpl implements IProjectService { } } - /** - * This method tries to lock the given projects using the given maps. The method makes sure that the projects are locked in a valid order (For this both strings - * are compared. If both are equal, the first map will be used for the first lock). - * - * @param projectNameA - * The first project to lock. - * @param lockMapA - * The map containing the locks. - * * @param projectNameB - * The second project to lock. - * @param lockMapB - * The map containing the locks. - * - * @throws LockProjectException - * If the project could not be locked. - */ - public static void tryLockProjects(final String projectNameA, final ConcurrentHashMap<String, Semaphore> lockMapA, final String projectNameB, - final ConcurrentHashMap<String, Semaphore> lockMapB) throws LockProjectException { - final String firstProject; - final String secondProject; - final ConcurrentHashMap<String, Semaphore> firstLockMap; - final ConcurrentHashMap<String, Semaphore> secondLockMap; - - // Find the correct order - final int compareValue = projectNameA.compareTo(projectNameB); - if (compareValue >= 0) { - firstProject = projectNameA; - secondProject = projectNameB; - firstLockMap = lockMapA; - secondLockMap = lockMapB; - } else { - firstProject = projectNameB; - secondProject = projectNameA; - firstLockMap = lockMapB; - secondLockMap = lockMapA; - } - - // Now try to lock both projects. If the locking of the second project fails, we have to unlock the first one though - ProjectServiceImpl.tryLockProject(firstProject, firstLockMap); - - try { - ProjectServiceImpl.tryLockProject(secondProject, secondLockMap); - } catch (final LockProjectException ex) { - ProjectServiceImpl.unlockProject(firstProject, firstLockMap); - throw ex; - } - } - - /** - * This method tries to lock the given project using the given map. - * - * @param projectName - * The project to lock. - * @param lockMap - * The map containing the locks. - * - * @throws LockProjectException - * If the project could not be locked. - */ - private static void tryLockProject(final String projectName, final ConcurrentHashMap<String, Semaphore> lockMap) throws LockProjectException { - // As we also remove locks from time to time, we have to check whether the acquired lock is valid. If we fail to acquire the correct lock too often, the - // locking failed. - int failCounter = 0; - - while (true) { - final Semaphore projectLock = ProjectServiceImpl.getLock(projectName, lockMap); - projectLock.acquireUninterruptibly(); - - if (lockMap.get(projectName) == projectLock) { - // Everything went well - return; - } else { - // The lock is invalid - projectLock.release(); - failCounter++; - - if (failCounter > 10) { - // Abort - throw new LockProjectException("The project could not be locked."); - } - } - } - } - - /** - * Unlocks the given project. - * - * @param projectName - * The project to unlock. - * @param lockMap - * The map containing the locks. - */ - private static void unlockProject(final String projectName, final ConcurrentHashMap<String, Semaphore> lockMap) { - lockMap.get(projectName).release(); - } - - /** - * This method can be used to get the lock for a given project. If the lock doesn't exist already, a new one will be created. - * - * @param projectName - * The project whose lock should be delivered. - * @param lockMap - * The map containing the locks. - * - * @return A lock object for the project. - */ - private static Semaphore getLock(final String projectName, final ConcurrentHashMap<String, Semaphore> lockMap) { - // Create a new lock and put it into the map - if it doesn't already contain the key - final Semaphore newLock = new Semaphore(1); - final Semaphore existLock = lockMap.putIfAbsent(projectName, newLock); - - // Find out which of the both locks is the correct one - final Semaphore lock; - if (existLock != null) { - lock = existLock; - } else { - lock = newLock; - } - - return lock; - } - } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/LockManager.java b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/LockManager.java new file mode 100644 index 0000000000000000000000000000000000000000..e3180584018e43f2f72a493554e2a4b91505142f --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/LockManager.java @@ -0,0 +1,134 @@ +/*************************************************************************** + * Copyright 2013 Kieker Project (http://kieker-monitoring.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package kieker.webgui.service.impl.utility; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; + +/** + * This manager is responsible for locking critical code using keys. Internally we use {@link ReentrantLock}s, but make sure that they are removed once they are no + * longer needed. The manager has been developed with a simple design. If necessary, the manager can be modified in order to provide a faster access to the locks. + * + * @author Nils Christian Ehmke + */ +public final class LockManager { + + private final Map<String, Lock> locks = new ConcurrentHashMap<String, Lock>(); + + /** + * Creates a new instance of this class. + */ + public LockManager() { + // No code necessary + } + + /** + * Locks the objects for the given keys in a definite order. + * + * @param keys + * The keys used for the locking. + */ + public void lock(final String[] keys) { + // Make sure that the keys are in a definite order + Arrays.sort(keys); + + // Lock all keys + for (final String key : keys) { + this.lock(key); + } + } + + /** + * Locks the object for the given key. + * + * @param key + * The key used for the locking. + */ + public void lock(final String key) { + final Lock lock; + + synchronized (this) { + // Get the existing lock object or create it if necessary + if (this.locks.containsKey(key)) { + lock = this.locks.get(key); + } else { + lock = new Lock(); + this.locks.put(key, lock); + } + + // Prepare the locking + lock.preLock(); + } + + lock.lock(); + } + + /** + * Unlocks the object for the given key. + * + * @param key + * The key used for the unlocking. + */ + public void unlock(final String key) { + synchronized (this) { + // Get the lock object and unlock it + final Lock lock = this.locks.get(key); + lock.unlock(); + + // Remove the lock object if possible + if (lock.isFree()) { + this.locks.remove(key); + } + } + } + + /** + * This is a helper class containing a lock object and a lock counter. + * + * @author Nils Christian Ehmke + */ + private static final class Lock { + + private final ReentrantLock reentrantLock = new ReentrantLock(); + private final AtomicLong lockCounter = new AtomicLong(); + + public Lock() { + // No code necessary + } + + public boolean isFree() { + return this.lockCounter.get() == 0; + } + + public void preLock() { + this.lockCounter.incrementAndGet(); + } + + public void lock() { + this.reentrantLock.lock(); + } + + public void unlock() { + this.reentrantLock.unlock(); + this.lockCounter.decrementAndGet(); + } + + } +}