From: Hannu Niemistö Date: Wed, 18 Jan 2017 15:55:14 +0000 (+0200) Subject: Fixed memory leaks of SCL module listening systems X-Git-Tag: v1.27.0~18 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=8561e498009a25473db94b0e667866aa79de90b1;p=simantics%2Fplatform.git Fixed memory leaks of SCL module listening systems The main addition in this change are backlinks from UpdateListeners to Observables. They make it possible to implement UpdateListener.stopListening that removes the listener from all Observables. This method is called automatically before the listener is notified about the change, but client code can also call it when it becames disinterested about changes. Because now the client code is reposible for signaling when the listening stops, the listeners are no more store to WeakHashMap in ModuleEntry. This change also contains a change in the default imports of some graph based modules (Prelude -> StandardLibrary). refs #6943 Change-Id: I7cf8d12b610ab22a70300d9491b645fa34dce620 --- diff --git a/bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/RuntimeEnvironmentRequest.java b/bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/RuntimeEnvironmentRequest.java index 6fbe36854..cde4e0239 100644 --- a/bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/RuntimeEnvironmentRequest.java +++ b/bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/util/RuntimeEnvironmentRequest.java @@ -1,8 +1,5 @@ package org.simantics.db.layer0.util; -import java.util.HashMap; -import java.util.Map; - import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.ParametrizedPrimitiveRead; @@ -33,7 +30,7 @@ public class RuntimeEnvironmentRequest extends UnaryRead callback; @@ -46,68 +43,64 @@ public class RuntimeEnvironmentRequest extends UnaryRead callback, UpdateListenerImpl listener) { - - try { - - SCLContext context = SCLContext.getCurrent(); - - RuntimeEnvironment env; - Object graph = context.get("graph"); - if(graph == null) - try { - env = SimanticsInternal.getSession().syncRequest(new Read() { - @Override - public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException { - - SCLContext sclContext = SCLContext.getCurrent(); - Object oldGraph = sclContext.get("graph"); - try { - sclContext.put("graph", graph); - return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( - environmentSpecification, - callback.getClass().getClassLoader(), listener); - } catch (ImportFailureException e) { - throw new DatabaseException(e); - } catch (Throwable t) { - throw new DatabaseException(t); - } finally { - sclContext.put("graph", oldGraph); - } + public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener callback, UpdateListenerImpl listener) { + + try { + + SCLContext context = SCLContext.getCurrent(); + + RuntimeEnvironment env; + Object graph = context.get("graph"); + if(graph == null) + try { + env = SimanticsInternal.getSession().syncRequest(new Read() { + @Override + public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException { + + SCLContext sclContext = SCLContext.getCurrent(); + Object oldGraph = sclContext.get("graph"); + try { + sclContext.put("graph", graph); + return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( + environmentSpecification, + callback.getClass().getClassLoader(), listener); + } catch (ImportFailureException e) { + throw new DatabaseException(e); + } catch (Throwable t) { + throw new DatabaseException(t); + } finally { + sclContext.put("graph", oldGraph); } - }); - } catch (DatabaseException e) { - callback.exception(e); - return; - } - else - env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( - environmentSpecification, - callback.getClass().getClassLoader(), listener); - callback.execute(env); - } catch (ImportFailureException e) { - callback.exception(new DatabaseException(e)); - } - - } - - }; + } + }); + } catch (DatabaseException e) { + callback.exception(e); + return; + } + else + env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( + environmentSpecification, + callback.getClass().getClassLoader(), listener); + callback.execute(env); + } catch (ImportFailureException e) { + callback.exception(new DatabaseException(e)); + } - // This is needed to prevent garbage collection from collecting UpdateListenerImpls - // -ModuleRepository only makes a weak reference to the listener - final static Map map = new HashMap(); + } @Override public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException { final EnvironmentSpecification environmentSpecification = EnvironmentSpecification.of( "Builtin", "", - "Prelude", "", + "StandardLibrary", "", "Simantics/All", ""); fillEnvironmentSpecification(environmentSpecification); Resource mainModule = Layer0Utils.getPossibleChild(graph, parameter, "SCLMain"); @@ -120,7 +113,9 @@ public class RuntimeEnvironmentRequest extends UnaryRead(mainModuleUri) { - + + UpdateListenerImpl sclListener; + @Override public void register(ReadGraph graph, Listener procedure) { @@ -129,12 +124,11 @@ public class RuntimeEnvironmentRequest extends UnaryRead callback; - - UpdateListenerImpl(EnvironmentSpecification environmentSpecification, Listener callback) { - this.environmentSpecification = environmentSpecification; - this.callback = callback; - } + static class UpdateListenerImpl extends UpdateListener { + + final EnvironmentSpecification environmentSpecification; + final Listener callback; + + UpdateListenerImpl(EnvironmentSpecification environmentSpecification, Listener callback) { + this.environmentSpecification = environmentSpecification; + this.callback = callback; + } @Override public void notifyAboutUpdate() { - if(callback.isDisposed()) { - return; - } - getRuntimeEnvironment(environmentSpecification, callback, this); + if(callback.isDisposed()) { + stopListening(); + return; + } + getRuntimeEnvironment(environmentSpecification, callback, this); + } + }; + + final public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener callback, UpdateListenerImpl listener) { + + try { + + SCLContext context = SCLContext.getCurrent(); + + RuntimeEnvironment env; + Object graph = context.get("graph"); + if(graph == null) + try { + env = SimanticsInternal.getSession().syncRequest(new Read() { + @Override + public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException { + + SCLContext sclContext = SCLContext.getCurrent(); + Object oldGraph = sclContext.get("graph"); + try { + sclContext.put("graph", graph); + return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( + environmentSpecification, + callback.getClass().getClassLoader(), listener); + } catch (ImportFailureException e) { + throw new DatabaseException(e); + } catch (Throwable t) { + throw new DatabaseException(t); + } finally { + sclContext.put("graph", oldGraph); + } + } + }); + } catch (DatabaseException e) { + callback.exception(e); + return; + } + else + env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( + environmentSpecification, + callback.getClass().getClassLoader(), listener); + callback.execute(env); + } catch (ImportFailureException e) { + callback.exception(new DatabaseException(e)); } - final public static void getRuntimeEnvironment(EnvironmentSpecification environmentSpecification, Listener callback, UpdateListenerImpl listener) { + } - try { - - SCLContext context = SCLContext.getCurrent(); - - RuntimeEnvironment env; - Object graph = context.get("graph"); - if(graph == null) - try { - env = SimanticsInternal.getSession().syncRequest(new Read() { - @Override - public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException { - - SCLContext sclContext = SCLContext.getCurrent(); - Object oldGraph = sclContext.get("graph"); - try { - sclContext.put("graph", graph); - return SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( - environmentSpecification, - callback.getClass().getClassLoader(), listener); - } catch (ImportFailureException e) { - throw new DatabaseException(e); - } catch (Throwable t) { - throw new DatabaseException(t); - } finally { - sclContext.put("graph", oldGraph); - } - } - }); - } catch (DatabaseException e) { - callback.exception(e); - return; - } - else - env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( - environmentSpecification, - callback.getClass().getClassLoader(), listener); - callback.execute(env); - } catch (ImportFailureException e) { - callback.exception(new DatabaseException(e)); - } - - } - - }; - - // This is needed to prevent garbage collection from collecting UpdateListenerImpls - // -ModuleRepository only makes a weak reference to the listener - final static Map map = new HashMap<>(); - @Override public RuntimeEnvironment perform(ReadGraph graph) throws DatabaseException { final EnvironmentSpecification environmentSpecification = EnvironmentSpecification.of( "Builtin", "", - "Prelude", "", + "StandardLibrary", "", "Simantics/All", ""); fillEnvironmentSpecification(environmentSpecification); - + Layer0 L0 = Layer0.getInstance(graph); Collection sclModules = graph.syncRequest(new ObjectsWithType(parameter, L0.ConsistsOf, L0.SCLModule)); for (Resource sclModule : sclModules) environmentSpecification.importModule(graph.getURI(sclModule), ""); - + Resource mainModule = Layer0Utils.getPossibleChild(graph, parameter2, "SCLMain"); if(mainModule != null) environmentSpecification.importModule(graph.getURI(mainModule), ""); - + return graph.syncRequest(new ParametrizedPrimitiveRead(environmentSpecification) { - - @Override - public void register(ReadGraph graph, Listener procedure) { - - SCLContext context = SCLContext.getCurrent(); - Object oldGraph = context.put("graph", graph); - try { - - if(procedure.isDisposed()) { - UpdateListenerImpl.getRuntimeEnvironment(parameter, procedure, null); - } else { - UpdateListenerImpl impl = new UpdateListenerImpl(parameter, procedure); - impl.notifyAboutUpdate(); - map.put(parameter, impl); - } - - } finally { - context.put("graph", oldGraph); - } - - } - + UpdateListenerImpl sclListener; + @Override + public void register(ReadGraph graph, Listener procedure) { + + SCLContext context = SCLContext.getCurrent(); + Object oldGraph = context.put("graph", graph); + try { + + if(procedure.isDisposed()) { + getRuntimeEnvironment(parameter, procedure, null); + } else { + sclListener = new UpdateListenerImpl(parameter, procedure); + sclListener.notifyAboutUpdate(); + } + + } finally { + context.put("graph", oldGraph); + } + + } + @Override public void unregistered() { - map.remove(parameter); + if(sclListener != null) + sclListener.stopListening(); } - + }); } - + @Override public int hashCode() { return 31*getClass().hashCode() + super.hashCode(); } - public static void flush() { - map.clear(); - } - } \ No newline at end of file diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/ComponentTypeScriptRuntimeEnvironmentRequest.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/ComponentTypeScriptRuntimeEnvironmentRequest.java index 7f2b54559..89385e5be 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/ComponentTypeScriptRuntimeEnvironmentRequest.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/ComponentTypeScriptRuntimeEnvironmentRequest.java @@ -1,7 +1,5 @@ package org.simantics.modeling; -import java.util.ArrayList; - import org.simantics.db.ReadGraph; import org.simantics.db.common.request.ParametrizedPrimitiveRead; import org.simantics.db.procedure.Listener; @@ -13,9 +11,7 @@ import org.simantics.scl.osgi.SCLOsgi; import org.simantics.scl.runtime.SCLContext; public class ComponentTypeScriptRuntimeEnvironmentRequest extends ParametrizedPrimitiveRead { - - // This array list is only needed to keep strong references to update listeners preventing their garbage collection. - ArrayList listeners; + UpdateListener listener; public ComponentTypeScriptRuntimeEnvironmentRequest(EnvironmentSpecification parameter) { super(parameter); @@ -23,20 +19,22 @@ public class ComponentTypeScriptRuntimeEnvironmentRequest extends ParametrizedPr @Override public void register(ReadGraph graph, Listener procedure) { - UpdateListener listener = null; - if(!procedure.isDisposed()) { + if(!procedure.isDisposed() && listener == null) { listener = new UpdateListener() { @Override public void notifyAboutUpdate() { createRuntimeEnvironment(graph, procedure, this); } }; - if(listeners == null) - listeners = new ArrayList(2); - listeners.add(listener); } createRuntimeEnvironment(graph, procedure, listener); } + + @Override + public void unregistered() { + if(listener != null) + listener.stopListening(); + } private void createRuntimeEnvironment(ReadGraph graph, Listener procedure, UpdateListener listener) { SCLContext context = SCLContext.getCurrent(); diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java index 10a217b9d..07af09e55 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java @@ -16,12 +16,13 @@ import org.simantics.db.request.Read; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingUtils; import org.simantics.scl.compiler.module.repository.UpdateListener; +import org.simantics.scl.compiler.module.repository.UpdateListener.Observable; import org.simantics.scl.compiler.source.ModuleSource; import org.simantics.scl.compiler.source.StringModuleSource; import org.simantics.scl.compiler.source.repository.ModuleSourceRepository; import org.simantics.scl.runtime.SCLContext; -import org.simantics.structural2.utils.StructuralUtils; import org.simantics.scl.runtime.tuple.Tuple0; +import org.simantics.structural2.utils.StructuralUtils; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; @@ -54,15 +55,20 @@ public enum GraphModuleSourceRepository implements ModuleSourceRepository { } } - static class ModuleListener implements SyncListener { + static class ModuleListener implements SyncListener, Observable { UpdateListener listener; boolean alreadyExecutedOnce; final String moduleName; public ModuleListener(UpdateListener listener, String moduleName) { this.listener = listener; + this.listener.addObservable(this); this.moduleName = moduleName; } @Override + public void removeListener(UpdateListener listener) { + listener = null; + } + @Override public boolean isDisposed() { return listener == null; } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java index b851810e3..7170b992b 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import org.simantics.scl.compiler.common.exceptions.InternalCompilerError; @@ -24,6 +23,7 @@ import org.simantics.scl.compiler.errors.Success; import org.simantics.scl.compiler.module.ImportDeclaration; import org.simantics.scl.compiler.module.Module; import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor; +import org.simantics.scl.compiler.module.repository.UpdateListener.Observable; import org.simantics.scl.compiler.runtime.RuntimeEnvironment; import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl; import org.simantics.scl.compiler.runtime.RuntimeModule; @@ -67,9 +67,9 @@ public class ModuleRepository { PENDING_MODULES.get().remove(moduleName); } - private class ModuleEntry implements UpdateListener { + private class ModuleEntry extends UpdateListener implements Observable { final String moduleName; - WeakHashMap listeners = new WeakHashMap(); + THashSet listeners = new THashSet(); ModuleSource source; Failable compilationResult; @@ -80,14 +80,20 @@ public class ModuleRepository { } synchronized void addListener(UpdateListener listener) { - if(listener != null) - listeners.put(listener, null); + if(listener == null || listeners == null) + return; + listeners.add(listener); + listener.addObservable(this); + } + + public synchronized void removeListener(UpdateListener listener) { + if (listeners == null) + return; + listeners.remove(listener); } @Override public void notifyAboutUpdate() { - if (listeners == null) - return; ArrayList externalListeners = new ArrayList(); notifyAboutUpdate(externalListeners); for(UpdateListener listener : externalListeners) @@ -95,18 +101,26 @@ public class ModuleRepository { } synchronized void notifyAboutUpdate(ArrayList externalListeners) { + stopListening(); + if (listeners == null) + return; if(moduleCache.get(moduleName) == this) { moduleCache.remove(moduleName); if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) { System.out.println("Invalidate " + moduleName); - for(UpdateListener l : listeners.keySet()) + for(UpdateListener l : listeners) System.out.println(" " + l); } - for(UpdateListener l : listeners.keySet()) + THashSet listenersCopy = listeners; + listeners = null; + for(UpdateListener l : listenersCopy) + l.stopListening(); + for(UpdateListener l : listenersCopy) if(l instanceof ModuleEntry) ((ModuleEntry)l).notifyAboutUpdate(externalListeners); - else + else { externalListeners.add(l); + } } } @@ -176,10 +190,11 @@ public class ModuleRepository { return runtimeModule; } - public void dispose() { + public synchronized void dispose() { if (listeners != null) listeners.clear(); listeners = null; + stopListening(); source = null; compilationResult = null; if (runtimeModule != null) { @@ -219,7 +234,7 @@ public class ModuleRepository { public Failable getRuntimeModule(String moduleName) { return getRuntimeModule(moduleName, null); } - + private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) { /* It is deliberate that the following code does not try to prevent * simultaneous compilation of the same module. This is because in @@ -244,13 +259,16 @@ public class ModuleRepository { THashMap result = new THashMap(); Collection failures = null; + THashSet originalImports = new THashSet(); ArrayList stack = new ArrayList(imports.length); - for(ImportDeclaration import_ : imports) + for(ImportDeclaration import_ : imports) { stack.add(import_); + originalImports.add(import_.moduleName); + } while(!stack.isEmpty()) { ImportDeclaration import_ = stack.remove(stack.size()-1); if(!result.containsKey(import_.moduleName)) { - ModuleEntry entry = getModuleEntry(import_.moduleName, listener); + ModuleEntry entry = getModuleEntry(import_.moduleName, originalImports.contains(import_.moduleName) ? listener : null); Failable compilationResult = entry.compilationResult; if(compilationResult.didSucceed()) { result.put(import_.moduleName, entry); diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/UpdateListener.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/UpdateListener.java index 7320758e4..4e3ad119d 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/UpdateListener.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/UpdateListener.java @@ -1,5 +1,41 @@ package org.simantics.scl.compiler.module.repository; -public interface UpdateListener { - void notifyAboutUpdate(); +import gnu.trove.set.hash.THashSet; + +/** + * Listener that is notified about changes in modules and their dependencies. It is possible + * to listen multiple different modules with one listener. When a change happens, the listener + * automatically stops listening any other changes. The idea is that the client then asks all modules + * again using the listener as a parameter. + */ +public abstract class UpdateListener { + private final THashSet observables = new THashSet(); + + public interface Observable { + void removeListener(UpdateListener listener); + } + + public abstract void notifyAboutUpdate(); + + /** + * Registers an observable to the listener. The client code should never + * call this method. It is needed so that it is possible to implement + * {@link #stopListening}. + */ + public void addObservable(Observable observable) { + synchronized(observables) { + observables.add(observable); + } + } + + /** + * Stops listening changes. + */ + public void stopListening() { + synchronized(observables) { + for(Observable observable : observables) + observable.removeListener(this); + observables.clear(); + } + } } diff --git a/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java b/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java index 1f60de086..244c7463f 100644 --- a/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java +++ b/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java @@ -10,7 +10,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.Arrays; import org.eclipse.core.runtime.FileLocator; @@ -21,7 +20,9 @@ import org.simantics.scl.compiler.module.repository.UpdateListener; import org.simantics.scl.compiler.source.EncodedTextualModuleSource; import org.simantics.scl.compiler.types.Type; -public class BundleModuleSource extends EncodedTextualModuleSource { +import gnu.trove.set.hash.THashSet; + +public class BundleModuleSource extends EncodedTextualModuleSource implements UpdateListener.Observable { public static final ImportDeclaration[] DEFAULT_IMPORTS = new ImportDeclaration[] { new ImportDeclaration("Builtin", ""), @@ -36,13 +37,21 @@ public class BundleModuleSource extends EncodedTextualModuleSource { public final URL url; private byte[] digest; - private ArrayList listeners; + private THashSet listeners; public BundleModuleSource(String moduleName, Bundle bundle, URL url) { super(moduleName); this.bundle = bundle; this.url = url; } + + @Override + public void removeListener(UpdateListener listener) { + if(listeners != null) + synchronized(listeners) { + listeners.remove(listener); + } + } @Override protected ImportDeclaration[] getBuiltinImports(UpdateListener listener) { @@ -84,8 +93,9 @@ public class BundleModuleSource extends EncodedTextualModuleSource { digest = computeDigest(); if(listener != null) { if(listeners == null) - listeners = new ArrayList(2); + listeners = new THashSet(4); listeners.add(listener); + listener.addObservable(this); } return url.openStream(); } @@ -108,7 +118,7 @@ public class BundleModuleSource extends EncodedTextualModuleSource { byte[] newDigest = computeDigest(); if(!Arrays.equals(digest, newDigest)) { digest = newDigest; - ArrayList oldListeners = listeners; + THashSet oldListeners = listeners; listeners = null; for(UpdateListener listener : oldListeners) listener.notifyAboutUpdate(); diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLAnnotationModel.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLAnnotationModel.java index 47b203d61..e432a179a 100644 --- a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLAnnotationModel.java +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/editor2/SCLAnnotationModel.java @@ -70,6 +70,7 @@ public class SCLAnnotationModel extends AnnotationModel { @Override public void disconnect(IDocument document) { connected = false; + updateListener.stopListening(); super.disconnect(document); } } diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesContentProvider.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesContentProvider.java index eb5c25e4a..7a5daffdb 100644 --- a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesContentProvider.java +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesContentProvider.java @@ -16,6 +16,7 @@ import org.simantics.scl.compiler.module.repository.ModuleRepository; import org.simantics.scl.compiler.module.repository.UpdateListener; import gnu.trove.map.hash.THashMap; +import gnu.trove.procedure.TObjectObjectProcedure; import gnu.trove.procedure.TObjectProcedure; public class SCLIssuesContentProvider implements IStructuredContentProvider { @@ -39,16 +40,19 @@ public class SCLIssuesContentProvider implements IStructuredContentProvider { } private UpdateListener getUpdateListener(String moduleName) { - UpdateListener listener = updateListeners.get(moduleName); - if(listener == null) { - listener = new UpdateListener() { - @Override - public void notifyAboutUpdate() { - if(!disposed) - listenModule(moduleName); - } - }; - updateListeners.put(moduleName, listener); + UpdateListener listener; + synchronized(updateListeners) { + listener = updateListeners.get(moduleName); + if(listener == null) { + listener = new UpdateListener() { + @Override + public void notifyAboutUpdate() { + if(!disposed) + listenModule(moduleName); + } + }; + updateListeners.put(moduleName, listener); + } } return listener; } @@ -106,7 +110,20 @@ public class SCLIssuesContentProvider implements IStructuredContentProvider { @Override public void dispose() { + if(this.disposed) + return; this.disposed = true; + if(repository != null) + synchronized(updateListeners) { + updateListeners.forEachEntry(new TObjectObjectProcedure() { + @Override + public boolean execute(String moduleName, UpdateListener listener) { + listener.stopListening(); + return true; + } + }); + updateListeners.clear(); + } } @Override diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesView.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesView.java index adc7def79..112a5f471 100644 --- a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesView.java +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/issues/SCLIssuesView.java @@ -40,6 +40,8 @@ public class SCLIssuesView extends ViewPart { ImageRegistry imageRegistry; + SCLIssuesContentProvider issuesContentProvider = new SCLIssuesContentProvider(); + public SCLIssuesView() { super(); imageRegistry = Activator.getInstance().getImageRegistry(); @@ -66,7 +68,7 @@ public class SCLIssuesView extends ViewPart { tableViewer = new TableViewer(parent, SWT.FULL_SELECTION | SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL); ColumnViewerToolTipSupport.enableFor(tableViewer); - tableViewer.setContentProvider(new SCLIssuesContentProvider()); + tableViewer.setContentProvider(issuesContentProvider); Table table = tableViewer.getTable(); table.setHeaderVisible(true); @@ -165,5 +167,11 @@ public class SCLIssuesView extends ViewPart { public void setFocus() { tableViewer.getControl().setFocus(); } + + @Override + public void dispose() { + super.dispose(); + issuesContentProvider.dispose(); + } }