]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/repository/ModuleRepository.java
Fixes to thread safety problems in SCL compiler
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / simantics / scl / compiler / module / repository / ModuleRepository.java
1 package org.simantics.scl.compiler.module.repository;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.concurrent.ConcurrentHashMap;
8
9 import org.simantics.scl.compiler.common.exceptions.InternalCompilerError;
10 import org.simantics.scl.compiler.compilation.CompilationContext;
11 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
12 import org.simantics.scl.compiler.environment.ConcreteEnvironment;
13 import org.simantics.scl.compiler.environment.EmptyEnvironment;
14 import org.simantics.scl.compiler.environment.Environment;
15 import org.simantics.scl.compiler.environment.NamespaceImpl.ModuleImport;
16 import org.simantics.scl.compiler.environment.NamespaceSpec;
17 import org.simantics.scl.compiler.environment.filter.NamespaceFilter;
18 import org.simantics.scl.compiler.environment.filter.NamespaceFilters;
19 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
20 import org.simantics.scl.compiler.errors.CompilationError;
21 import org.simantics.scl.compiler.errors.DoesNotExist;
22 import org.simantics.scl.compiler.errors.Failable;
23 import org.simantics.scl.compiler.errors.Failure;
24 import org.simantics.scl.compiler.errors.Locations;
25 import org.simantics.scl.compiler.errors.Success;
26 import org.simantics.scl.compiler.module.ImportDeclaration;
27 import org.simantics.scl.compiler.module.Module;
28 import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor;
29 import org.simantics.scl.compiler.module.repository.UpdateListener.Observable;
30 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
31 import org.simantics.scl.compiler.runtime.RuntimeEnvironmentImpl;
32 import org.simantics.scl.compiler.runtime.RuntimeModule;
33 import org.simantics.scl.compiler.runtime.RuntimeModuleMap;
34 import org.simantics.scl.compiler.source.ModuleSource;
35 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;
36 import org.simantics.scl.compiler.top.ModuleInitializer;
37 import org.simantics.scl.compiler.top.SCLCompilerConfiguration;
38 import org.simantics.scl.compiler.top.ValueNotFound;
39 import org.simantics.scl.compiler.types.Types;
40
41 import gnu.trove.map.hash.THashMap;
42 import gnu.trove.map.hash.TObjectLongHashMap;
43 import gnu.trove.procedure.TObjectObjectProcedure;
44 import gnu.trove.set.hash.THashSet;
45
46 /**
47  * Manages compilation and caching of SCL modules.
48  * 
49  * @author Hannu Niemistö
50  */
51 public class ModuleRepository {
52     private final ModuleRepository parentRepository;
53     private final ModuleSourceRepository sourceRepository;
54     private ConcurrentHashMap<String, ModuleEntry> moduleCache = new ConcurrentHashMap<String, ModuleEntry>();
55     
56     private static final ThreadLocal<THashSet<String>> PENDING_MODULES = new ThreadLocal<THashSet<String>>();
57     
58     private ModuleCompilationOptionsAdvisor advisor = null;
59     
60     private static void beginModuleCompilation(String moduleName) {
61         THashSet<String> set = PENDING_MODULES.get();
62         if(set == null) {
63             set = new THashSet<String>();
64             PENDING_MODULES.set(set);
65         }
66         if(!set.add(moduleName))
67             throw new IllegalArgumentException("Cyclic module dependency detected at " + moduleName + ".");
68     }
69     
70     private static void finishModuleCompilation(String moduleName) {
71         PENDING_MODULES.get().remove(moduleName);
72     }
73             
74     private class ModuleEntry extends UpdateListener implements Observable {
75         final String moduleName;
76         THashSet<UpdateListener> listeners = new THashSet<UpdateListener>(); // listeners == null is used as a marker that this entry is disposed
77                                                                              // should be handled only inside synchronized code
78         
79         ModuleSource source;
80         Failable<Module> compilationResult;
81         Failable<RuntimeModule> runtimeModule; // created lazily
82
83         public ModuleEntry(String moduleName) {
84             this.moduleName = moduleName;
85         }
86         
87         synchronized void addListener(UpdateListener listener) {
88             if(listener == null || listeners == null)
89                 return;
90             listeners.add(listener);
91             listener.addObservable(this);
92         }
93
94         public synchronized void removeListener(UpdateListener listener) {
95             if (listeners == null)
96                 return;
97             listeners.remove(listener);
98         }
99         
100         @Override
101         public void notifyAboutUpdate() {
102             // There is a chance that another observable calls notifyAboutUpdate() before stopListening has been completed,
103             // but notifyAboutUpdate(ArrayList<UpdateListener>) lets only one thread to do the notification of dependencies
104             // by clearing listeners field.
105             stopListening();
106             ArrayList<UpdateListener> externalListeners = new ArrayList<UpdateListener>();
107             notifyAboutUpdate(externalListeners);
108             for(UpdateListener listener : externalListeners)
109                 listener.notifyAboutUpdate();
110         }
111
112         void notifyAboutUpdate(ArrayList<UpdateListener> externalListeners) {
113             THashSet<UpdateListener> listenersCopy;
114             synchronized(this) {
115                 listenersCopy = listeners;
116                 if (listenersCopy == null)
117                     return;
118                 listeners = null;
119             }
120             if(moduleCache.get(moduleName) == this) {
121                 moduleCache.remove(moduleName);
122                 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE) {
123                     System.out.println("Invalidate " + moduleName);
124                     for(UpdateListener l : listenersCopy)
125                         System.out.println("    " + l);
126                 }
127                 for(UpdateListener l : listenersCopy)
128                     if(!l.stopListening())
129                         ;
130                     else if(l instanceof ModuleEntry)
131                         ((ModuleEntry)l).notifyAboutUpdate(externalListeners);
132                     else
133                         externalListeners.add(l);
134             }
135         }
136
137         private ModuleEntry initModuleEntryAndAddListener(UpdateListener listener) {
138             source = sourceRepository.getModuleSource(moduleName, this);
139             
140             if(source == null)
141                 compilationResult = DoesNotExist.getInstance();
142             else {
143                 if(SCLCompilerConfiguration.TRACE_MODULE_UPDATE)
144                     System.out.println("Compile " + source);
145                 beginModuleCompilation(moduleName);
146                 compilationResult = source.compileModule(ModuleRepository.this, this, advisor == null ? null : advisor.getOptions(moduleName));
147                 finishModuleCompilation(moduleName);
148             }
149         
150             ModuleEntry oldEntry = moduleCache.putIfAbsent(moduleName, this);
151             if(oldEntry != null) {
152                 oldEntry.addListener(listener);
153                 return oldEntry;
154             }
155             
156             addListener(listener);
157             return this;
158         }
159         
160         @SuppressWarnings({ "rawtypes", "unchecked" })
161         public synchronized Failable<RuntimeModule> getRuntimeModule() {
162             if(runtimeModule == null) {
163                 if(compilationResult.didSucceed()) {
164                     Module module = compilationResult.getResult();
165                     RuntimeModuleMap parentModules = new RuntimeModuleMap();
166                     if(!moduleName.equals(Types.BUILTIN)) {
167                         parentModules.add(ModuleRepository.this.getRuntimeModule(Types.BUILTIN)
168                                 .getResult());
169                         Collection<ImportDeclaration> dependencies = module.getDependencies();
170                         THashMap<String, ModuleEntry> moduleEntries;
171                         try {
172                             moduleEntries = getModuleEntries(null, dependencies.toArray(new ImportDeclaration[dependencies.size()]), null, false);
173                         } catch (ImportFailureException e) {
174                             throw new InternalCompilerError(e);
175                         }
176                         for(RuntimeModule m : mapEntriesToRuntimeModules(moduleEntries).values())
177                             parentModules.add(m);
178                     }
179                     /*for(ImportDeclaration importAst : module.getDependencies()) {
180                         RuntimeModule parentModule =
181                                 ModuleRepository.this.getRuntimeModule(importAst.moduleName)
182                                 .getResult();
183                         if(parentModule != null)
184                             parentModules.add(parentModule);
185                     }*/
186                     RuntimeModule rm = new RuntimeModule(module, parentModules, module.getParentClassLoader());
187                     ModuleInitializer initializer = module.getModuleInitializer();
188                     if(initializer != null)
189                         try {
190                             initializer.initializeModule(rm.getMutableClassLoader().getClassLoader());
191                         } catch (Exception e) {
192                             compilationResult = new Failure(new CompilationError[] {new CompilationError("Initialization of module " + moduleName + " failed: " + e.getMessage())});
193                             e.printStackTrace();
194                         }
195                     runtimeModule = new Success<RuntimeModule>(rm); 
196                 }
197                 else
198                     runtimeModule = (Failable<RuntimeModule>)(Failable)compilationResult;
199             }
200             return runtimeModule;
201         }
202
203         public synchronized void dispose() {
204             listeners = null;
205             stopListening();
206             source = null;
207             compilationResult = null;
208             if (runtimeModule != null) {
209                 if (runtimeModule.didSucceed())
210                     runtimeModule.getResult().dispose();
211             }
212             runtimeModule = null;
213         }
214         
215         @Override
216         public String toString() {
217             return "ModuleEntry@" + moduleName + "@" + hashCode();
218         }
219     }
220     
221     public ModuleRepository(ModuleRepository parentRepository, ModuleSourceRepository sourceRepository) {
222         this.parentRepository = parentRepository;
223         this.sourceRepository = sourceRepository;
224     }
225
226     public ModuleRepository(ModuleSourceRepository sourceRepository) {
227         this(null, sourceRepository);
228     }
229     
230     public Failable<Module> getModule(String moduleName, UpdateListener listener) {
231         return getModuleEntry(moduleName, listener).compilationResult;
232     }
233     
234     public Failable<Module> getModule(String moduleName) {
235         return getModule(moduleName, null);
236     }
237     
238     public Failable<RuntimeModule> getRuntimeModule(String moduleName, UpdateListener listener) {
239         return getModuleEntry(moduleName, listener).getRuntimeModule();
240     }
241     
242     public Failable<RuntimeModule> getRuntimeModule(String moduleName) {
243         return getRuntimeModule(moduleName, null);
244     }
245
246     private ModuleEntry getModuleEntry(String moduleName, UpdateListener listener) {
247         /* It is deliberate that the following code does not try to prevent
248          * simultaneous compilation of the same module. This is because in
249          * some situations only certain thread trying compilation can succeed
250          * in it.
251          */
252         ModuleEntry entry = moduleCache.get(moduleName);
253         if(entry == null)
254             entry = new ModuleEntry(moduleName).initModuleEntryAndAddListener(listener);
255         else
256             entry.addListener(listener);
257
258         if(entry.compilationResult == DoesNotExist.INSTANCE && parentRepository != null)
259             return parentRepository.getModuleEntry(moduleName, listener);
260         else
261             return entry;
262     }
263     
264     private THashMap<String, ModuleEntry> getModuleEntries(
265             CompilationContext compilationContext,
266             ImportDeclaration[] imports,
267             UpdateListener listener,
268             boolean robustly) throws ImportFailureException {
269         THashMap<String, ModuleEntry> result = new THashMap<String, ModuleEntry>();
270         Collection<ImportFailure> failures = null;
271         
272         TObjectLongHashMap<String> originalImports = new TObjectLongHashMap<String>(); 
273         ArrayList<ImportDeclaration> stack = new ArrayList<ImportDeclaration>(imports.length);
274         for(ImportDeclaration import_ : imports) {
275             stack.add(import_);
276             originalImports.put(import_.moduleName, import_.location);
277         }
278         while(!stack.isEmpty()) {
279             ImportDeclaration import_ = stack.remove(stack.size()-1);
280             if(!result.containsKey(import_.moduleName)) {
281                 boolean originalImport = originalImports.contains(import_.moduleName);
282                 ModuleEntry entry = getModuleEntry(import_.moduleName, originalImport ? listener : null);
283                 Failable<Module> compilationResult = entry.compilationResult;
284                 if(compilationResult.didSucceed()) {
285                     result.put(import_.moduleName, entry);
286                     stack.addAll(compilationResult.getResult().getDependencies());
287                     if(originalImport) {
288                         String deprecation = compilationResult.getResult().getDeprecation();
289                         if(deprecation != null && compilationContext != null) {
290                             long location = originalImport ? originalImports.get(import_.moduleName) : Locations.NO_LOCATION;
291                             compilationContext.errorLog.logWarning(location, "Deprecated module " + import_.moduleName   + (deprecation.isEmpty() ? "." : ": " + deprecation));
292                         }
293                     }
294                 }
295                 else {
296                     if(failures == null)
297                         failures = new ArrayList<ImportFailure>(2);
298                     failures.add(new ImportFailure(import_.location, import_.moduleName,
299                             compilationResult == DoesNotExist.INSTANCE
300                                     ? ImportFailure.MODULE_DOES_NOT_EXIST_REASON
301                                     : ((Failure)compilationResult).errors));
302                 }
303             }
304         }
305         
306         if(failures != null && !robustly)
307             throw new ImportFailureException(failures);
308         
309         return result;
310     }
311
312     private static THashMap<String, Module> mapEntriesToModules(THashMap<String, ModuleEntry> entries) {
313         final THashMap<String, Module> result = new THashMap<String, Module>(entries.size());
314         entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
315             @Override
316             public boolean execute(String a, ModuleEntry b) {
317                 result.put(a, b.compilationResult.getResult());
318                 return true;
319             }
320         });
321         return result;
322     }
323     
324     private static THashMap<String, RuntimeModule> mapEntriesToRuntimeModules(THashMap<String, ModuleEntry> entries) {
325         final THashMap<String, RuntimeModule> result = new THashMap<String, RuntimeModule>(entries.size());
326         entries.forEachEntry(new TObjectObjectProcedure<String, ModuleEntry>() {
327             @Override
328             public boolean execute(String a, ModuleEntry b) {
329                 result.put(a, b.getRuntimeModule().getResult());
330                 return true;
331             }
332         });
333         return result;
334     }
335     
336     public Environment createEnvironment(
337             ImportDeclaration[] imports,
338             UpdateListener listener) throws ImportFailureException {
339         return createEnvironment(null, imports, listener);
340     }
341     
342     public Environment createEnvironment(
343             CompilationContext compilationContext,
344             ImportDeclaration[] imports,
345             UpdateListener listener) throws ImportFailureException {
346         THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, false);
347         THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
348         return createEnvironment(moduleMap, imports);
349     }
350     
351     public Environment createEnvironmentRobustly(
352             CompilationContext compilationContext,
353             ImportDeclaration[] imports,
354             UpdateListener listener) {
355         try {
356             THashMap<String, ModuleEntry> entries = getModuleEntries(compilationContext, imports, listener, true);
357             THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
358             return createEnvironment(moduleMap, imports);
359         } catch(ImportFailureException e) {
360             // Should not happen because of robust flag
361             return EmptyEnvironment.INSTANCE;
362         }
363     }
364     
365     public Environment createEnvironment(
366             EnvironmentSpecification specification,
367             UpdateListener listener) throws ImportFailureException {
368         return createEnvironment(specification.imports.toArray(new ImportDeclaration[specification.imports.size()]), listener);
369     }
370     
371     public RuntimeEnvironment createRuntimeEnvironment(
372             EnvironmentSpecification environmentSpecification, ClassLoader parentClassLoader) throws ImportFailureException {
373         return createRuntimeEnvironment(environmentSpecification, parentClassLoader, null);
374     }
375     
376     public RuntimeEnvironment createRuntimeEnvironment(EnvironmentSpecification environmentSpecification) throws ImportFailureException {
377         return createRuntimeEnvironment(environmentSpecification, getClass().getClassLoader());
378     }
379     
380     public RuntimeEnvironment createRuntimeEnvironment(
381             EnvironmentSpecification environmentSpecification,
382             ClassLoader parentClassLoader,
383             UpdateListener listener) throws ImportFailureException {
384         return createRuntimeEnvironment(
385                 environmentSpecification.imports.toArray(new ImportDeclaration[environmentSpecification.imports.size()]),
386                 parentClassLoader,
387                 listener);
388     }
389     
390     public RuntimeEnvironment createRuntimeEnvironment(
391             ImportDeclaration[] imports,
392             ClassLoader parentClassLoader,
393             UpdateListener listener) throws ImportFailureException {
394         THashMap<String, ModuleEntry> entries = getModuleEntries(null, imports, listener, false);
395         THashMap<String, Module> moduleMap = mapEntriesToModules(entries);
396         Environment environment = createEnvironment(moduleMap, imports);
397         THashMap<String, RuntimeModule> runtimeModuleMap = mapEntriesToRuntimeModules(entries);
398         return new RuntimeEnvironmentImpl(environment, parentClassLoader, runtimeModuleMap);
399     }
400     
401     private static Environment createEnvironment(
402             THashMap<String, Module> moduleMap, 
403             ImportDeclaration[] imports) {
404         NamespaceSpec spec = new NamespaceSpec();
405         for(ImportDeclaration import_ : imports)
406             if(import_.localName != null)
407                 addToNamespace(moduleMap, spec, import_.moduleName, import_.localName,
408                         NamespaceFilters.createFromSpec(import_.spec));
409         
410         return new ConcreteEnvironment(moduleMap, spec.toNamespace());
411     }
412     
413     private static void addToNamespace(THashMap<String, Module> moduleMap, 
414             NamespaceSpec namespace, String moduleName, String localName,
415             NamespaceFilter filter) {
416         if(localName.isEmpty())
417             addToNamespace(moduleMap, namespace, moduleName, filter);
418         else
419             addToNamespace(moduleMap, namespace.getNamespace(localName), moduleName, filter);
420     }
421     
422     private static void addToNamespace(THashMap<String, Module> moduleMap, 
423             NamespaceSpec namespace, String moduleName, NamespaceFilter filter) {
424         ModuleImport moduleImport = namespace.moduleMap.get(moduleName);
425         if(moduleImport == null) {
426             Module module = moduleMap.get(moduleName);
427             namespace.moduleMap.put(moduleName, new ModuleImport(module, filter));
428             for(ImportDeclaration import_ : module.getDependencies())
429                 if(import_.localName != null) {
430                     NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
431                     if(import_.localName.equals(""))
432                         localFilter = NamespaceFilters.intersection(filter, localFilter);
433                     addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
434                 }
435         }
436         else if(!filter.isSubsetOf(moduleImport.filter)) {
437             moduleImport.filter = NamespaceFilters.union(moduleImport.filter, filter);
438             for(ImportDeclaration import_ : moduleImport.module.getDependencies())
439                 // We have to recheck only modules imported to this namespace
440                 if("".equals(import_.localName)) {
441                     NamespaceFilter localFilter = NamespaceFilters.createFromSpec(import_.spec);
442                     localFilter = NamespaceFilters.intersection(filter, localFilter);
443                     addToNamespace(moduleMap, namespace, import_.moduleName, import_.localName, localFilter);
444                 }
445         }
446     }
447
448     public Object getValue(String moduleName, String valueName) throws ValueNotFound {
449         Failable<RuntimeModule> module = getRuntimeModule(moduleName);
450         if(module.didSucceed())
451             return module.getResult().getValue(valueName);
452         else if(module == DoesNotExist.INSTANCE)
453             throw new ValueNotFound("Didn't find module " + moduleName);
454         else
455             throw new ValueNotFound(((Failure)module).toString());
456     }
457
458     public Object getValue(String fullValueName) throws ValueNotFound {
459         int p = fullValueName.lastIndexOf('/');
460         if(p < 0)
461             throw new ValueNotFound(fullValueName + " is not a valid full value name.");
462         return getValue(fullValueName.substring(0, p), fullValueName.substring(p+1));
463     }
464
465     public SCLValue getValueRef(String moduleName, String valueName) throws ValueNotFound {
466         Failable<Module> module = getModule(moduleName);
467         if(module.didSucceed()) {
468             SCLValue value = module.getResult().getValue(valueName);
469             if(value == null)
470                 throw new ValueNotFound("Module " + moduleName + " does not contain value " + valueName + ".");
471             return value;
472         }
473         else if(module == DoesNotExist.INSTANCE)
474             throw new ValueNotFound("Didn't find module " + moduleName);
475         else
476             throw new ValueNotFound(((Failure)module).toString());
477     }
478     
479     public SCLValue getValueRef(String fullValueName) throws ValueNotFound {
480         int p = fullValueName.lastIndexOf('/');
481         if(p < 0)
482             throw new ValueNotFound(fullValueName + " is not a valid full value name.");
483         return getValueRef(fullValueName.substring(0, p), fullValueName.substring(p+1));
484     }
485     
486     public ModuleSourceRepository getSourceRepository() {
487         return sourceRepository;
488     }
489     
490     public String getDocumentation(String documentationName) {
491         String documentation = sourceRepository.getDocumentation(documentationName);
492         if(documentation == null && parentRepository != null)
493             return parentRepository.getDocumentation(documentationName);
494         return documentation;
495     }
496     
497     /**
498      * Flush clears module repository cache completely. It should not be called in 
499      * normal operation, but may be useful during testing for clearing repositories
500      * that are statically defined.
501      */
502     public void flush() {
503         if (parentRepository != null)
504             parentRepository.flush();
505         if (moduleCache != null)
506             for (ModuleEntry entry : moduleCache.values())
507                 entry.dispose();
508         moduleCache = null;
509     }
510
511     /**
512      * Gets the map of all modules that have been currently compiled successfully.
513      * Not that the method does not return all possible modules in the source repository.
514      */
515     public Map<String, Module> getModules() {
516         Map<String, Module> result = new HashMap<>(moduleCache.size()); 
517         for (Map.Entry<String, ModuleEntry> entry : moduleCache.entrySet()) {
518             ModuleEntry moduleEntry = entry.getValue();
519             if (moduleEntry.compilationResult.didSucceed()) {
520                 result.put(entry.getKey(), moduleEntry.compilationResult.getResult());
521             }
522         }
523         return result;
524     }
525
526     public ModuleCompilationOptionsAdvisor getAdvisor() {
527         return advisor;
528     }
529
530     public void setAdvisor(ModuleCompilationOptionsAdvisor advisor) {
531         this.advisor = advisor;
532     }
533
534 }
535