]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.impl/src/org/simantics/db/impl/query/CacheEntryBase.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.db.impl / src / org / simantics / db / impl / query / CacheEntryBase.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2018 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.db.impl.query;
13
14 import java.util.ArrayList;
15 import java.util.Iterator;
16
17 import org.simantics.databoard.Bindings;
18 import org.simantics.db.DevelopmentKeys;
19 import org.simantics.db.exception.DatabaseException;
20 import org.simantics.db.impl.graph.ReadGraphImpl;
21 import org.simantics.db.impl.procedure.InternalProcedure;
22 import org.simantics.utils.Development;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 public abstract class CacheEntryBase<Procedure> extends CacheEntry<Procedure> {
27
28     private static final Logger LOGGER = LoggerFactory.getLogger(CacheEntryBase.class);
29     
30         // Default level is something that is not quite a prospect but still allows for ordering within CacheCollectionResult 
31         public static final short UNDEFINED_LEVEL = 5;
32         
33         public short level = UNDEFINED_LEVEL;
34         public short age = 0;
35         public int GCStatus = 0;
36         
37     final public static CacheEntryBase[] NONE = new CacheEntryBase[0];
38
39         static Object NO_RESULT = new Object() { public String toString() { return "NO_RESULT"; }};
40         static protected Object INVALID_RESULT = new Object() { public String toString() { return "INVALID_RESULT"; }};
41         
42 //      // Just created
43 //    static protected Object FRESH = new Object() { public String toString() { return "CREATED"; }};
44     // Result is computed - no exception
45     static protected Object READY = new Object() { public String toString() { return "READY"; }};
46     // Computation is under way
47     static protected Object PENDING = new Object() { public String toString() { return "PENDING"; }};
48     // Entry is discarded and is waiting for garbage collect
49     static protected Object DISCARDED = new Object() { public String toString() { return "DISCARDED"; }};
50     // The result has been invalidated
51     static protected Object REQUIRES_COMPUTATION = new Object() { public String toString() { return "REFUTED"; }};
52     // The computation has excepted - the exception is in the result
53     static protected Object EXCEPTED = new Object() { public String toString() { return "EXCEPTED"; }};
54
55     // This indicates the status of the entry
56     public Object statusOrException = REQUIRES_COMPUTATION;
57     
58     private CacheEntry p1 = null;
59     private Object p2OrParents = null;
60     
61     private int hash = 0;
62     
63     @Override
64     final public int hashCode() {
65         if(hash == 0) hash = makeHash();
66         return hash;
67     }
68     
69     abstract int makeHash();
70     
71     // This can be tested to see if the result is finished
72     Object result = NO_RESULT;
73     
74     final public boolean isFresh() {
75         return REQUIRES_COMPUTATION == statusOrException;
76     }
77
78     public void setReady() {
79         assert(result != NO_RESULT);
80         statusOrException = READY;
81     }
82
83     @Deprecated
84     final public boolean isReady() {
85         return READY == statusOrException || EXCEPTED == statusOrException;
86     }
87     
88     @Override
89     public void discard() {
90                 if (Development.DEVELOPMENT) {
91                         if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
92                                 System.err.println("[QUERY STATE]: discarded " + this);
93                         }
94                 }
95         statusOrException = DISCARDED;
96     }
97     
98     @Override
99     final public boolean isDiscarded() {
100         return DISCARDED == statusOrException;
101     }
102     
103     @Override
104     final public void refute() {
105                 if (Development.DEVELOPMENT) {
106                         if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
107                                 System.err.println("[QUERY STATE]: refuted " + this);
108                         }
109                 }
110         statusOrException = REQUIRES_COMPUTATION;
111     }
112     
113     @Override
114     final public boolean isRefuted() {
115         return REQUIRES_COMPUTATION == statusOrException;
116     }
117
118     @Override
119     public void except(Throwable throwable) {
120                 if (Development.DEVELOPMENT) {
121                         if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
122                                 System.err.println("[QUERY STATE]: excepted " + this);
123                         }
124                 }
125         if(statusOrException != DISCARDED) {
126                 statusOrException = EXCEPTED;
127                 result = throwable;
128         } else {
129                 LOGGER.warn("Cache entry got excepted status after being discarded: " + getClass().getSimpleName(), throwable);
130                 result = throwable;
131         }
132     }
133     
134     final public void checkAndThrow() throws DatabaseException {
135         if(isExcepted()) {
136             Throwable throwable = (Throwable)result;
137             if(throwable instanceof DatabaseException) throw (DatabaseException)throwable;
138             else throw new DatabaseException(throwable);
139         }
140     }
141     
142     @Override
143     final public boolean isExcepted() {
144         return EXCEPTED == statusOrException;
145     }
146
147     @Override
148     public void setPending(QuerySupport querySupport) {
149         statusOrException = PENDING;
150         clearResult(querySupport);
151     }
152     
153     @Override
154     final public boolean isPending() {
155         return PENDING == statusOrException;
156     }
157     
158     final public boolean requiresComputation() {
159         return REQUIRES_COMPUTATION == statusOrException;
160     }
161     
162     final public boolean assertPending() {
163         boolean result = isPending();
164         if(!result) {
165                 LOGGER.warn("Assertion failed, expected pending, got " + statusOrException);
166         }
167         return result;
168     }
169
170     final public boolean assertNotPending() {
171         boolean result = !isPending();
172         if(!result) {
173                 new Exception(this +  ": Assertion failed, expected not pending, got " + statusOrException).printStackTrace();
174         }
175         return result;
176     }
177
178     final public boolean assertNotDiscarded() {
179         boolean result = !isDiscarded();
180         if(!result) {
181                 new Exception(this +  ": Assertion failed, expected not discarded, got " + statusOrException).printStackTrace();
182         }
183         return result;
184     }
185
186     @Override
187     public void setResult(Object result) {
188         this.result = result;
189     }
190     
191     @SuppressWarnings("unchecked")
192     @Override
193     final public <T> T getResult() {
194         assert(statusOrException != DISCARDED);
195         return (T)result;
196     }
197     
198     @Override
199     public void clearResult(QuerySupport support) {
200         setResult(NO_RESULT);
201     }
202     
203     @Override
204     final public void addParent(CacheEntry entry) {
205          
206         assert(entry != null);
207         
208         if(p1 == entry) {
209                 return;
210         }
211         if(p2OrParents == entry) {
212                 return;
213         }
214         if(p1 == null) {
215                 p1 = entry;
216         } else if(p2OrParents == null) {
217                 p2OrParents = entry;
218         } else if(p2OrParents instanceof QueryIdentityHashSet) {
219             ((QueryIdentityHashSet)p2OrParents).add(entry);
220             ((QueryIdentityHashSet)p2OrParents).purge();
221         } else {
222             CacheEntry tmp = (CacheEntry)p2OrParents;
223             p2OrParents = new QueryIdentityHashSet(2);
224             ((QueryIdentityHashSet)p2OrParents).add(tmp);
225             ((QueryIdentityHashSet)p2OrParents).add(entry);
226         }
227         
228     }
229
230     @Override
231     CacheEntry pruneFirstParents() {
232
233         if(p1 == null) {
234                 // No parents
235                 return null;
236         }
237         
238         if(!p1.isDiscarded()) {
239                 
240                 // First parent is still active
241                 return p1;
242                 
243         } else {
244                 
245                 // Clear p1
246                 p1 = null;
247                 
248                 // First parent is discarded => look for more parents
249                 if(p2OrParents instanceof QueryIdentityHashSet) {
250
251                         QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
252                         CacheEntry entry = set.removeDiscarded();
253                         if(entry == null) p2OrParents = null;
254                         p1 = entry;
255                         return p1;
256
257                 } else if(p2OrParents instanceof CacheEntry) {
258                         
259                         CacheEntry entry = (CacheEntry)p2OrParents;
260                         if(entry.isDiscarded()) {
261                                 // Second entry is also discarded => all empty
262                                 p2OrParents = null;
263                                 return null;
264                         } else {
265                                 p1 = entry;
266                                 p2OrParents = null;
267                                 return p1;
268                         }
269                         
270                 } else {
271                 
272                         // Nothing left
273                         return null;
274                         
275                 }
276                 
277         }
278         
279     }
280
281     @Override
282     void pruneParentSet() {
283         // First parent is discarded => look for more parents
284         if(p2OrParents instanceof QueryIdentityHashSet) {
285
286             QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
287             set.removeDiscardedReally();
288             if(set.isEmpty()) p2OrParents = null;
289
290         } else if(p2OrParents instanceof CacheEntry) {
291
292             CacheEntry entry = (CacheEntry)p2OrParents;
293             if(entry.isDiscarded()) {
294                 // Second entry is also discarded => all empty
295                 p2OrParents = null;
296             }
297
298         } else {
299
300             // Nothing left
301
302         }
303     }
304
305     @Override
306     final public void removeParent(CacheEntry entry) {
307        
308         if(p1 == null) {
309             if(p2OrParents != null) throw new Error("CacheEntryBase.removeParent: corrupted parents (p1 == null, while p2OrParents != null).");
310             else throw new Error("CacheEntryBase.removeParent: no parents.");
311         }
312         if(p1 == entry) {
313             if(p2OrParents == null) {
314                 p1 = null;
315             } else if(p2OrParents instanceof QueryIdentityHashSet) {
316                 QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
317                 int size = set.size();
318                 if(size == 0) {
319                     p1 = null;
320                     p2OrParents = null;
321                 } else if (size == 1) {
322                     CacheEntry next = set.iterator().next();
323                     p1 = next;
324                     set = null;
325                 } else if(set.size() == 2) {
326                     Iterator<CacheEntry> iterator = set.iterator();
327                     p1 = iterator.next();
328                     p2OrParents = iterator.next();
329                 } else {
330                     p1 = set.iterator().next();
331                     set.remove(p1);
332                 }
333             } else {
334                 p1 = (CacheEntry)p2OrParents;
335                 p2OrParents = null;
336             }
337             
338         } else if(p2OrParents.getClass() == QueryIdentityHashSet.class) {
339             
340             QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
341             boolean success = set.remove(entry);
342             if(!success) {
343                 throw new Error("CacheEntryBase.removeParent: parent was not found.");
344             }
345             assert(set.size() >= 1);
346             if(set.size() == 1) {
347                 p2OrParents = set.iterator().next();
348             }
349             
350         } else {
351             if(p2OrParents == entry) {
352                 p2OrParents = null;
353             } else {
354                 throw new Error("CacheEntryBase.removeParent: had 2 parents but neither was removed.");
355             }
356         }
357     }
358
359     @Override
360     final public boolean hasParents() {
361         assert(statusOrException != DISCARDED);
362         return p1 != null;
363     }
364     
365     @Override
366         final public Iterable<CacheEntry<?>> getParents(QueryProcessor processor) {
367
368                 ArrayList<CacheEntry<?>> result = new ArrayList<CacheEntry<?>>();
369                 if(p1 != null) result.add(p1);
370                 if(p2OrParents != null) {
371                 if(p2OrParents instanceof QueryIdentityHashSet) {
372                         for(CacheEntry entry : (QueryIdentityHashSet)p2OrParents) {
373                                 result.add(entry);
374                         }
375                 } else {
376                         result.add((CacheEntry)p2OrParents);
377                 }
378                 }
379                 fillImpliedParents(processor, result);
380                 return result;
381                 
382         }
383     
384     @Override
385     CacheEntry getFirstParent(QueryProcessor processor) {
386         return p1;
387     }
388     
389     @Override
390     boolean moreThanOneParent(QueryProcessor processor) {
391         return p2OrParents != null;
392     }
393     
394     @Override
395     int parentCount(QueryProcessor processor) {
396         if(p2OrParents != null) {
397                 if(p2OrParents instanceof QueryIdentityHashSet) {
398                         return ((QueryIdentityHashSet)p2OrParents).size()+1;
399                 } else {
400                         return 2;
401                 }
402         } else {
403                 return p1 != null ? 1 : 0;
404         }
405         
406     }
407     
408     protected void fillImpliedParents(QueryProcessor processor, ArrayList<CacheEntry<?>> result) {
409     }
410     
411     protected String internalError() {
412         return toString() + " " + statusOrException + " " + result;
413     }
414
415     
416     protected boolean handleException(ReadGraphImpl graph, IntProcedure procedure) throws DatabaseException {
417         if(isExcepted()) {
418                 procedure.exception(graph, (Throwable)getResult());
419                 return true;
420         } else {
421                 return false;
422         }
423     }
424     
425     protected boolean handleException(ReadGraphImpl graph, TripleIntProcedure procedure) throws DatabaseException {
426         if(isExcepted()) {
427                 procedure.exception(graph, (Throwable)getResult());
428                 return true;
429         } else {
430                 return false;
431         }
432     }
433
434     protected <T> boolean handleException(ReadGraphImpl graph, InternalProcedure<T> procedure) throws DatabaseException {
435         if(isExcepted()) {
436                 procedure.exception(graph, (Throwable)getResult());
437                 return true;
438         } else {
439                 return false;
440         }
441     }
442     
443     @Override
444     boolean isImmutable(ReadGraphImpl graph) throws DatabaseException {
445         return false;
446     }
447     
448     @Override
449     boolean shouldBeCollected() {
450         return true;
451     }
452     
453     @Override
454     short getLevel() {
455         return level;
456     }
457     
458     @Override
459     short setLevel(short level) {
460         short existing = this.level;
461         this.level = level;
462         return existing;
463     }
464     
465     @Override
466     void prepareRecompute(QuerySupport querySupport) {
467         setPending(querySupport);
468     }
469     
470     @Override
471     int getGCStatus() {
472         return GCStatus;
473     }
474     
475     @Override
476     int setGCStatus(int status) {
477         GCStatus = status;
478         return GCStatus;
479     }
480     
481     @Override
482     void setGCStatusFlag(int flag, boolean value) {
483         if(value) {
484                 GCStatus |= flag;
485         } else {
486                 GCStatus &= ~flag;
487         }
488     }
489     
490     @Override
491     public Object getOriginalRequest() {
492         // This is the original request for all built-in queries
493         return getQuery();
494     }
495
496     public CacheEntryBase() {
497     }
498
499 }