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