]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/ClusterTable.java
Dispose ClientChangesImpl ChangeSets to minimize memory footprint
[simantics/platform.git] / bundles / org.simantics.db.procore / src / fi / vtt / simantics / procore / internal / ClusterTable.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 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 fi.vtt.simantics.procore.internal;
13
14 import java.io.File;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.TreeMap;
20 import java.util.concurrent.Semaphore;
21 import java.util.function.Consumer;
22
23 import org.simantics.databoard.Bindings;
24 import org.simantics.db.ClusterCreator;
25 import org.simantics.db.Database;
26 import org.simantics.db.Database.Session.ClusterChanges;
27 import org.simantics.db.DevelopmentKeys;
28 import org.simantics.db.SessionVariables;
29 import org.simantics.db.common.utils.Logger;
30 import org.simantics.db.exception.ClusterDoesNotExistException;
31 import org.simantics.db.exception.DatabaseException;
32 import org.simantics.db.exception.ResourceNotFoundException;
33 import org.simantics.db.exception.RuntimeDatabaseException;
34 import org.simantics.db.impl.ClusterBase;
35 import org.simantics.db.impl.ClusterI;
36 import org.simantics.db.impl.ClusterSupport;
37 import org.simantics.db.impl.ClusterTraitsBase;
38 import org.simantics.db.impl.IClusterTable;
39 import org.simantics.db.impl.graph.WriteGraphImpl;
40 import org.simantics.db.impl.query.QueryProcessor;
41 import org.simantics.db.procore.cluster.ClusterBig;
42 import org.simantics.db.procore.cluster.ClusterImpl;
43 import org.simantics.db.procore.cluster.ClusterSmall;
44 import org.simantics.db.procore.cluster.ClusterTraits;
45 import org.simantics.db.procore.protocol.Constants;
46 import org.simantics.db.service.ClusterCollectorPolicy;
47 import org.simantics.db.service.ClusterCollectorPolicy.CollectorCluster;
48 import org.simantics.db.service.ClusterUID;
49 import org.simantics.utils.Development;
50
51 import fi.vtt.simantics.procore.DebugPolicy;
52 import fi.vtt.simantics.procore.internal.ClusterControlImpl.ClusterStateImpl;
53 import fi.vtt.simantics.procore.internal.SessionImplSocket.TaskHelper;
54 import gnu.trove.map.hash.TLongIntHashMap;
55 import gnu.trove.map.hash.TLongObjectHashMap;
56 import gnu.trove.procedure.TIntProcedure;
57 import gnu.trove.procedure.TLongObjectProcedure;
58 import gnu.trove.procedure.TObjectProcedure;
59 import gnu.trove.set.hash.TIntHashSet;
60
61 public final class ClusterTable implements IClusterTable {
62
63     private static final boolean VALIDATE_SIZE = false;
64
65     int maximumBytes = 128 * 1024 * 1024;
66     int limit = (int)(0.8*(double)maximumBytes);
67
68     long timeCounter = 0;
69
70     final private SessionImplSocket sessionImpl;
71     final private ArrayList<ClusterI> writeOnlyClusters = new ArrayList<ClusterI>();
72     final private TIntHashSet writeOnlyInvalidates = new TIntHashSet();
73     final private static int ARRAY_SIZE = ClusterTraits.getClusterArraySize();
74     final private ClusterImpl[] clusterArray = new ClusterImpl[ARRAY_SIZE];
75     final private Boolean[] immutables = new Boolean[ARRAY_SIZE];
76     final private boolean[] virtuals = new boolean[ARRAY_SIZE];
77     static class ImportanceEntry implements CollectorCluster {
78         final public long importance;
79         final public long clusterId;
80         public ImportanceEntry(ClusterImpl impl) {
81             this(impl.getImportance(), impl.getClusterId());
82         }
83
84         public ImportanceEntry(long importance, long clusterId) {
85             this.importance = importance;
86             this.clusterId = clusterId;
87         }
88         @Override
89         public long getImportance() {
90             return importance;
91         }
92         @Override
93         public long getClusterId() {
94             return clusterId;
95         }
96     }
97     private class Clusters {
98         // This makes sure that non-null values from hashMap.get can be trusted (no unsynchronized rehashes occur)
99         final public TLongObjectHashMap<ClusterImpl> hashMap = new TLongObjectHashMap<ClusterImpl>(2 * ARRAY_SIZE);
100         //private final HashMap<ClusterUID, ClusterImpl> clusterU2I = new HashMap<ClusterUID, ClusterImpl>(); // Maps cluster UID to cluster.
101         private Clusters() {
102             clear();
103         }
104         private void clear() {
105             hashMap.clear();
106           //  clusterU2I.clear();
107             hashMap.put(0, null); // reserved for null value
108           //  clusterU2I.put(ClusterUID.make(0, 0), null);
109         }
110         private int size() {
111             return hashMap.size();
112         }
113         private ClusterImpl getClusterByClusterId(long clusterId) {
114             return hashMap.get(clusterId);
115         }
116         private ClusterImpl getClusterByClusterUID(ClusterUID clusterUID) {
117                 return getClusterByClusterId(clusterUID.second);
118             //return clusterU2I.get(clusterUID);
119         }
120         private ClusterImpl getClusterByClusterUID(long id1, long id2) {
121                 return getClusterByClusterId(id2);
122         }
123         private ClusterImpl makeProxy(long clusterId) {
124             return makeProxy(ClusterUID.make(0, clusterId));
125         }
126         private ClusterImpl makeProxy(ClusterUID clusterUID) {
127             ClusterImpl proxy = hashMap.get(clusterUID.second);
128             if (null != proxy)
129                 return proxy;
130             int clusterKey = hashMap.size();
131             ClusterSmall sentinel = new ClusterSmall(clusterUID, clusterKey, ClusterTable.this, sessionImpl.clusterTranslator);
132             if (sentinel.clusterId != sentinel.clusterUID.second)
133                 throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
134             create(sentinel);
135             return sentinel;
136         }
137         private void replace(ClusterImpl proxy) {
138             ClusterImpl old = clusterArray[proxy.clusterKey];
139             create(proxy);
140             if (null != old) {
141                 if (old.clusterKey != proxy.clusterKey)
142                     throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
143                 if (old.clusterId != proxy.clusterId)
144                     throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
145                 if (!old.clusterUID.equals(proxy.clusterUID))
146                     throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
147             }
148         }
149         private ClusterSmall freeProxy(ClusterImpl proxy) {
150             ClusterImpl clusterImpl = hashMap.get(proxy.clusterId);
151             if (null == clusterImpl)
152                 throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
153 //            ClusterUID clusterUID = ClusterUID.make(0, proxy.clusterId);
154 //            ClusterImpl clusterImpl2 = clusterU2I.get(clusterUID );
155 //            if (clusterImpl != clusterImpl2)
156 //                throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
157             if (proxy.clusterId != clusterImpl.clusterId)
158                 throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
159             if (proxy.clusterKey != clusterImpl.clusterKey)
160                 throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
161             ClusterSmall sentinel = new ClusterSmall(makeClusterUID(proxy.clusterId) , proxy.clusterKey, ClusterTable.this, sessionImpl.clusterTranslator);
162             return (ClusterSmall)create(sentinel);
163         }
164         private ClusterImpl create(ClusterImpl clusterImpl) {
165             hashMap.put(clusterImpl.clusterId, clusterImpl);
166 //            clusterU2I.put(clusterImpl.clusterUID, clusterImpl);
167             clusterArray[clusterImpl.clusterKey] = clusterImpl;
168             return clusterImpl;
169         }
170         private void forEachValue(TObjectProcedure<ClusterImpl> procedure) {
171             hashMap.forEachValue(procedure);
172         }
173         private void forEachEntry(TLongObjectProcedure<ClusterImpl> procedure) {
174             hashMap.forEachEntry(procedure);
175         }
176         private ClusterUID makeClusterUID(long clusterId) {
177             return ClusterUID.make(0, clusterId);
178         }
179         private void removeProxy(ClusterImpl proxy) {
180             hashMap.remove(proxy.clusterId);
181             clusterArray[proxy.clusterKey] = null;
182         }
183     }
184     final public TreeMap<Long, CollectorCluster> importanceMap = new TreeMap<Long, CollectorCluster>();
185
186     private ClusterCollectorPolicy collectorPolicy;
187     private boolean dirtySizeInBytes = true;
188     private long sizeInBytes = 0;
189     private final Clusters clusters;
190     ClusterUID makeClusterUID(long clusterId) {
191         return clusters.makeClusterUID(clusterId);
192     }
193     public ClusterUID getClusterUIDByResourceKey(int resourceKey) throws DatabaseException {
194         int clusterKey = ClusterTraits.getClusterKeyFromResourceKey(resourceKey);
195         return clusterArray[clusterKey].clusterUID;
196     }
197     ClusterTable(SessionImplSocket sessionImpl, File folderPath) {
198         this.sessionImpl = sessionImpl;
199         clusters = new Clusters();
200     }
201
202     void dispose() {
203         clusters.clear();
204         importanceMap.clear();
205     }
206
207     public void setCollectorPolicy(ClusterCollectorPolicy policy) {
208         this.collectorPolicy = policy;
209     }
210
211     /**
212      * Sets Cluster Table allocation size by percentage of maximum usable
213      * memory. Allocation is limited between 5% and 50% of maximum memory.
214      *
215      * @param precentage
216      */
217     private void setAllocateSize(double percentage) {
218
219         percentage = Math.max(percentage, 0.05);
220         percentage = Math.min(percentage, 0.5);
221
222         maximumBytes = (int) Math.floor(percentage * Runtime.getRuntime().maxMemory());
223     }
224
225     private double estimateProperAllocation(ClusterCollectorSupport support) {
226         long max = Runtime.getRuntime().maxMemory();
227         long free = Runtime.getRuntime().freeMemory();
228         long total = Runtime.getRuntime().totalMemory();
229         long realFree = max - total + free; // amount of free memory
230         long current = support.getCurrentSize(); // currently allocated cache
231         long inUseTotal = max - realFree; // currently used memory
232         long inUseOther = inUseTotal - current; // memory that the application uses (without the cache)
233         double otherUsePercentage = (double) inUseOther / (double) max; // percentage of memory that the application uses
234         double aimFree = 0.2; // percentage of maximum heap that we try to keep free
235         double estimate = 1.0 - otherUsePercentage - aimFree;
236         estimate = Math.min(estimate, 0.5);
237         estimate = Math.max(estimate, 0.05);
238         // System.out.println("Estimated allocation percentage " + estimate +
239 // " memory stats: max: " + max + ", free: " + realFree + ", inUse: " +
240 // inUseTotal + ", cached: " + current + ", cacheSize: " + maximumBytes);
241         return estimate;
242     }
243
244     void checkCollect() {
245         collector.collect();
246     }
247
248     synchronized ClusterImpl getClusterByClusterId(long clusterId) {
249         return clusters.getClusterByClusterId(clusterId);
250     }
251
252     ClusterBase getClusterByClusterKey(int clusterKey) {
253         return clusterArray[clusterKey];
254     }
255
256     synchronized ClusterImpl makeProxy(ClusterUID clusterUID, long clusterId) {
257         if (clusterUID.second != clusterId)
258             throw new RuntimeDatabaseException("Illegal id for cluster=" + clusterUID + " id=" + clusterId);
259         return clusters.makeProxy(clusterUID);
260     }
261
262     synchronized ClusterImpl makeCluster(long clusterId, boolean writeOnly) {
263         checkCollect();
264         ClusterImpl proxy = clusters.getClusterByClusterId(clusterId);
265         if (null != proxy)
266             return proxy;
267         ClusterUID clusterUID = ClusterUID.make(0, clusterId);
268         int clusterKey = clusters.size(); // new key
269         if (writeOnly) {
270             proxy = new ClusterWriteOnly(clusterUID, clusterKey, sessionImpl);
271             writeOnlyClusters.add(proxy);
272             return clusters.create(proxy);
273         } else {
274             ClusterImpl cluster = ClusterImpl.make(clusterUID, clusterKey, sessionImpl.clusterTranslator);
275             clusters.create(cluster);
276             if (!cluster.isLoaded())
277                 Logger.defaultLogError(new Exception("Bug in ClusterTable.makeCluster(long, boolean), cluster not loaded"));
278             importanceMap.put(cluster.getImportance(), new ImportanceEntry(cluster));
279             if(collectorPolicy != null) collectorPolicy.added(cluster);
280             return cluster;
281         }
282     }
283
284     synchronized void replaceCluster(ClusterI cluster) {
285         checkCollect();
286         int clusterKey = cluster.getClusterKey();
287         ClusterI existing = clusterArray[clusterKey];
288         if (existing.hasVirtual())
289             cluster.markVirtual();
290
291         importanceMap.remove(existing.getImportance());
292         if (collectorPolicy != null)
293             collectorPolicy.removed((ClusterImpl)existing);
294
295         clusters.replace((ClusterImpl)cluster);
296         if (!cluster.isLoaded())
297             Logger.defaultLogError(new Exception("Bug in ClusterTable.replaceCluster(ClusterI), cluster not loaded"));
298
299         importanceMap.put(cluster.getImportance(), new ImportanceEntry((ClusterImpl)cluster));
300         if(collectorPolicy != null) collectorPolicy.added((ClusterImpl)cluster);
301
302         if (!dirtySizeInBytes) {
303             if (existing != cluster) {
304                 adjustCachedSize(-existing.getCachedSize(), existing);
305             }
306             // This will update sizeInBytes through adjustCachedSize
307             cluster.getCachedSize();
308         }
309         validateSize();
310     }
311
312     synchronized void release(CollectorCluster cluster) {
313         importanceMap.remove(cluster.getImportance());
314         release(cluster.getClusterId());
315     }
316
317     synchronized void release(long clusterId) {
318         //System.out.println("ClusterTable.release(" + clusterId + "): " + sizeInBytes);
319         //validateSize();
320         ClusterImpl clusterImpl = clusters.getClusterByClusterId(clusterId);
321         if (null == clusterImpl)
322             return;
323         if(!clusterImpl.isLoaded() || clusterImpl.isEmpty())
324             return;
325         clusters.freeProxy(clusterImpl);
326         importanceMap.remove(clusterImpl.getImportance());
327         if (collectorPolicy != null)
328             collectorPolicy.removed(clusterImpl);
329         if (sessionImpl.writeState != null)
330             sessionImpl.clusterStream.flush(clusterImpl.clusterUID);
331         if (!dirtySizeInBytes) {
332             adjustCachedSize(-clusterImpl.getCachedSize(), clusterImpl);
333         }
334     }
335
336     synchronized void compact(long id) {
337         ClusterI impl = clusters.getClusterByClusterId(id);
338         if (impl != null)
339             impl.compact();
340     }
341
342     void updateSize() {
343 // cachedSize = getSizeInBytes();
344     }
345
346     double getLoadProbability() {
347         // This can currently cause stack overflow
348         return 1.0;
349 // if(cachedSize < SLOW_LIMIT) return 1.0;
350 // if(cachedSize > HIGH_LIMIT) return 1e-2;
351 //
352 // double pos = (double)(cachedSize - SLOW_LIMIT) / (double)(HIGH_LIMIT -
353 // SLOW_LIMIT);
354 // double val = 0.1 * ((pos-1) * (pos-1)) + 1e-2;
355 // return val;
356     }
357
358     class SizeProcedure implements TObjectProcedure<ClusterImpl> {
359         public long result = 0;
360
361         @Override
362         public boolean execute(ClusterImpl cluster) {
363             if (cluster != null) {
364                 try {
365                     if (cluster.isLoaded() && !cluster.isEmpty()) {
366                         result += cluster.getCachedSize();
367                     }
368                 } catch (Throwable t) {
369                     Logger.defaultLogError(t);
370                 }
371             }
372             return true;
373         }
374
375         public void clear() {
376             result = 0;
377         }
378
379     };
380
381     private SizeProcedure sizeProcedure = new SizeProcedure();
382     long getSizeInBytes() {
383         if (dirtySizeInBytes) {
384             sizeProcedure.clear();
385             clusters.forEachValue(sizeProcedure);
386             sizeInBytes = sizeProcedure.result;
387             // System.err.println("recomputed size of clusterTable => " + sizeInBytes);
388             setDirtySizeInBytes(false);
389         }
390         return sizeInBytes;
391     }
392
393     public void setDirtySizeInBytes(boolean value) {
394         dirtySizeInBytes = value;
395     }
396
397     ClusterStateImpl getState() {
398         final ClusterStateImpl result = new ClusterStateImpl();
399         clusters.forEachEntry(new TLongObjectProcedure<ClusterImpl>() {
400             @Override
401             public boolean execute(long arg0, ClusterImpl arg1) {
402                 if (arg1 == null)
403                     return true;
404                 if (arg1.isLoaded() && !arg1.isEmpty()) {
405                     result.ids.add(new ImportanceEntry(arg1));
406                 }
407                 return true;
408             }
409         });
410         return result;
411     }
412
413     void restoreState(ClusterStateImpl state) {
414         ClusterStateImpl current = getState();
415         for (CollectorCluster id : current.ids)
416             if (!state.ids.contains(id))
417                 collectorSupport.release(id);
418     }
419
420     class ClusterCollectorImpl implements ClusterCollector {
421
422         final private ClusterCollectorSupport support;
423         private ClusterCollectorPolicy policy;
424         ClusterCollectorImpl(ClusterCollectorSupport support) {
425             this.support = support;
426         }
427
428         ClusterCollectorPolicy setPolicy(ClusterCollectorPolicy newPolicy) {
429
430             ClusterCollectorPolicy oldPolicy = policy;
431             policy = newPolicy;
432
433             if(policy != null) {
434                 for (CollectorCluster id : support.getResidentClusters()) {
435                     policy.added(getClusterByClusterId(id.getClusterId()));
436                 }
437             }
438
439             support.setPolicy(policy);
440
441             return oldPolicy;
442
443         }
444
445         @Override
446         public void collect() {
447
448             if(policy != null) {
449
450                 release(policy.select());
451
452             } else {
453
454                 int size = support.getCurrentSize();
455                 boolean dynamicAllocation = useDynamicAllocation();
456                 if (dynamicAllocation)
457                     setAllocateSize(estimateProperAllocation(support));
458                 if (DebugPolicy.CLUSTER_COLLECTION) {
459                     System.out.println("Cluster collector activated, current size = " + size + " limit = " + maximumBytes);
460                 }
461                 if (dynamicAllocation) {
462                     int collectSize = maximumBytes / 2;
463                     collectSize = Math.min(collectSize, 32 * 1024 * 1024);
464                     // try to keep allocated clusters below the maximum
465                     if (maximumBytes - size > collectSize)
466                         return;
467                     collectSize += size - maximumBytes;
468                     collect(collectSize);
469                 } else {
470                     // try to keep allocated clusters below the maximum
471                     if (size < maximumBytes)
472                         return;
473                     // shave off 20%
474                     collect(size-limit);
475                 }
476
477             }
478
479         }
480
481         private boolean useDynamicAllocation() {
482             return "true".equalsIgnoreCase(System.getProperty("org.simantics.db.cluster.dynamicAlloc"));
483         }
484
485         @Override
486         public void collect(int target) {
487
488             if(policy != null) {
489
490                 release(policy.select(target));
491
492             } else {
493
494                 ArrayList<CollectorCluster> toRelease = new ArrayList<CollectorCluster>();
495
496                 for (CollectorCluster cluster : support.getResidentClusters()) {
497                     target -= support.getClusterSize(cluster);
498                     if (target > 0) {
499                         toRelease.add(cluster);
500                     } else {
501                         break;
502                     }
503                 }
504
505                 release(toRelease);
506
507                 if (DebugPolicy.CLUSTER_COLLECTION) {
508                     System.out.println("Cluster collector finished, current size = " + support.getCurrentSize());
509                 }
510
511             }
512
513         }
514
515         void release(Collection<CollectorCluster> toRelease) {
516             for (CollectorCluster id : toRelease) {
517                 support.release(id);
518             }
519         }
520
521     }
522
523     private ClusterCollectorSupport collectorSupport = new ClusterCollectorSupportImpl(this);
524     ClusterCollectorImpl collector = new ClusterCollectorImpl(collectorSupport);
525
526     void gc() {
527         collector.collect();
528     }
529
530     private long newResourceClusterId = Constants.NewClusterId;
531     public static final int CLUSTER_FILL_SIZE = ClusterTraitsBase.getMaxNumberOfResources();
532
533     /*
534      * Uusi id varataan vasta, kun lis�t��n resurssi => reservedIds sis�lt��
535      * vain jo k�yt�ss� olevia klustereita
536      */
537
538     ClusterImpl getNewResourceCluster(ClusterSupport cs, GraphSession graphSession, boolean writeOnly)
539     throws DatabaseException {
540         ClusterImpl result = null;
541         if (Constants.NewClusterId == newResourceClusterId) {
542             newResourceClusterId = graphSession.newClusterId();
543             result = getClusterByClusterIdOrMake(newResourceClusterId, writeOnly);
544         } else {
545             ClusterImpl cluster = getClusterByClusterIdOrThrow(newResourceClusterId);
546             if (cluster.getNumberOfResources(cs) >= CLUSTER_FILL_SIZE) {
547                 newResourceClusterId = graphSession.newClusterId();
548                 cluster = getClusterByClusterIdOrMake(newResourceClusterId, writeOnly);
549             }
550             result = cluster;
551         }
552         return ensureLoaded(result);
553     }
554
555     void flushCluster(GraphSession graphSession) {
556 // We seem to disagree about this.
557 // graphSession.newClusterId();
558         newResourceClusterId = Constants.NewClusterId;
559     }
560
561     void writeOnlyInvalidate(ClusterI impl) {
562         writeOnlyInvalidates.add(impl.getClusterKey());
563     }
564
565     void removeWriteOnlyClusters() {
566         for (ClusterI proxy : writeOnlyClusters) {
567             if (!(proxy instanceof ClusterImpl))
568                 throw new RuntimeDatabaseException("ClusterTable corrupted. Contact application support.");
569             clusters.freeProxy((ClusterImpl)proxy);
570         }
571         writeOnlyClusters.clear();
572         writeOnlyInvalidates.forEach(new TIntProcedure() {
573             @Override
574             public boolean execute(int clusterKey) {
575                 ClusterImpl proxy = clusterArray[clusterKey];
576                 ClusterUID clusterUID = proxy.getClusterUID();
577                 System.err.println("writeOnlyInvalidate " + clusterUID);
578                 clusters.freeProxy(proxy);
579                 return true;
580             }
581         });
582         writeOnlyInvalidates.clear();
583     }
584
585     public ClusterImpl getClusterByClusterUID(ClusterUID clusterUID) {
586         synchronized (this) {
587             return clusters.getClusterByClusterUID(clusterUID);
588         }
589     }
590     public int getClusterKeyByClusterUIDOrMakeProxy(ClusterUID clusterUID) {
591         return getClusterKeyByClusterUIDOrMakeProxy(0/*clusterUID.first*/, clusterUID.second);
592     }
593     public int getClusterKeyByClusterUIDOrMakeProxy(long id1, long id2) {
594         return getClusterByClusterUIDOrMakeProxy(id1, id2).clusterKey;
595     }
596     public ClusterImpl getClusterByClusterUIDOrMakeProxy(ClusterUID clusterUID) {
597         synchronized (this) {
598             ClusterImpl clusterImpl = clusters.getClusterByClusterUID(clusterUID);
599             if (null == clusterImpl)
600                 clusterImpl = clusters.makeProxy(clusterUID);
601             return clusterImpl;
602         }
603     }
604     public ClusterImpl getClusterByClusterUIDOrMakeProxy(long id1, long id2) {
605         synchronized (this) {
606             ClusterImpl clusterImpl = clusters.getClusterByClusterUID(id1, id2);
607             if (null == clusterImpl)
608                 clusterImpl = clusters.makeProxy(id2);
609             return clusterImpl;
610         }
611     }
612     ClusterImpl getLoadOrThrow(long clusterId) throws DatabaseException {
613         synchronized (this) {
614             ClusterImpl proxy = clusters.getClusterByClusterId(clusterId);
615             int clusterKey = 0;
616             if (proxy != null)
617                 if (proxy.isLoaded())
618                     return proxy;
619                 else
620                     clusterKey = proxy.getClusterKey();
621             try {
622                 if (clusterKey == 0) {
623                     proxy = clusters.makeProxy(clusterId);
624                     clusterKey = proxy.getClusterKey();
625                 }
626                 ClusterImpl ci = tryLoad(clusterId, clusterKey);
627                 if (null == ci)
628                     throw new ResourceNotFoundException(clusterId);
629                 return ci;
630             } catch (ClusterDoesNotExistException t) {
631                 clusters.removeProxy(proxy);
632                 throw t;
633             }
634         }
635     }
636
637     ClusterImpl getClusterByClusterIdOrMake(long clusterId, boolean writeOnly) {
638         synchronized (this) {
639             ClusterImpl proxy = clusters.getClusterByClusterId(clusterId);
640             if (proxy != null)
641                 return proxy;
642             else
643                 return makeCluster(clusterId, writeOnly);
644         }
645     }
646     ClusterImpl getClusterByClusterIdOrThrow(long clusterId) {
647         synchronized (this) {
648             ClusterImpl proxy = clusters.getClusterByClusterId(clusterId);
649             if (null == proxy)
650                 throw new IllegalArgumentException("Cluster id=" + clusterId + " is not created.");
651             return proxy;
652         }
653     }
654     long getClusterIdOrCreate(ClusterUID clusterUID) {
655         return clusterUID.second;
656     }
657     final long getClusterIdByResourceKey(final int resourceKey)
658     throws DatabaseException {
659         int clusterKey = ClusterTraitsBase.getClusterKeyFromResourceKey(resourceKey);
660         if (ClusterTraitsBase.isVirtualClusterKey(clusterKey))
661             throw new RuntimeException("Tried to get a persistent cluster for a virtual resource.");
662         ClusterI c = clusterArray[clusterKey];
663         if (c == null)
664             throw new RuntimeException("No cluster for key " + resourceKey);
665         return c.getClusterId();
666     }
667     final long getClusterIdByResourceKeyNoThrow(final int resourceKey) {
668         int clusterKey = ClusterTraitsBase.getClusterKeyFromResourceKeyNoThrow(resourceKey);
669         if (ClusterTraitsBase.isVirtualClusterKey(clusterKey)) {
670             Logger.defaultLogError("Tried to get a persistent cluster for a virtual resource. key=" + resourceKey);
671             return 0;
672         }
673         ClusterI c = clusterArray[clusterKey];
674         if (c == null) {
675             Logger.defaultLogError("No cluster for key " + resourceKey);
676             return 0;
677         }
678         return c.getClusterId();
679     }
680
681     int counter = 0;
682
683     void refresh(long csid, SessionImplSocket session, ClusterUID[] clusterUID) {
684         synchronized (this) {
685             session.flushCounter = 0;
686             session.clusterStream.reallyFlush();
687             ClientChangesImpl cs = new ClientChangesImpl(session);
688             if (session.clientChanges == null)
689                 session.clientChanges = cs;
690             for (int i=0; i<clusterUID.length; ++i) {
691                 try {
692                     if (DebugPolicy.REPORT_CLUSTER_EVENTS)
693                         System.err.println("cluster=" + clusterUID[i] + " has changed. length=" + clusterUID.length);
694                     ClusterImpl oldCluster = clusters.getClusterByClusterUID(clusterUID[i]);
695                     if (null == oldCluster)
696                         continue;
697                     if (!oldCluster.isLoaded())
698                         continue;
699                     int clusterKey = oldCluster.getClusterKey();
700                     if (ClusterTraitsBase.isVirtualClusterKey(clusterKey))
701                         continue;
702                     boolean big = oldCluster instanceof ClusterBig;
703                     boolean small = oldCluster instanceof ClusterSmall;
704                     if (!big && !small)
705                         continue;
706 //                    ClusterImpl newCluster = (ClusterImpl) sessionImpl.graphSession.getClusterImpl(clusterUID[i], clusterKey);
707                     Database.Session dbSession = sessionImpl.graphSession.dbSession;
708                     
709                     ClusterImpl newCluster = dbSession.clone(clusterUID[i], new ClusterCreator() {
710                         
711                         @Override
712                         public <T> T create(ClusterUID uid, byte[] bytes, int[] ints, long[] longs) {
713                             ClusterSupport support = sessionImpl.clusterTranslator;
714                             try {
715                                 return (T)ClusterImpl.make(longs, ints, bytes, support, support.getClusterKeyByClusterUIDOrMake(uid));
716                             } catch (DatabaseException e) {
717                                 e.printStackTrace();
718                                 return null;
719                             }
720                         }
721                         
722                     });
723                     if (null == newCluster)
724                         continue;
725                     if (DebugPolicy.REPORT_CLUSTER_EVENTS)
726                         System.err.println("cluster=" + newCluster + "  updated.");
727                     importanceMap.remove(oldCluster.getImportance());
728                     if (collectorPolicy != null)
729                         collectorPolicy.removed(oldCluster);
730                     clusters.replace(newCluster);
731                     if (!newCluster.isLoaded())
732                         Logger.defaultLogError(new Exception("Bug in ClusterTable.refresh, cluster not loaded"));
733                     importanceMap.put(newCluster.getImportance(), new ImportanceEntry(newCluster));
734                     if (collectorPolicy != null)
735                         collectorPolicy.added(newCluster);
736                     // Now we have fetched the new cluster but to emulate effects of the changes in it we fetch the cluster changes from server.
737                     refreshCluster(csid, session, oldCluster, clusterUID[i], newCluster.clusterId, clusterKey);
738                 } catch (Throwable t) {
739                     Logger.defaultLogError("Failed to load cluster in refresh.", t);
740                 }
741             }
742             // Fake update of cluster changes.
743             QueryProcessor queryProcessor = session.getQueryProvider2();
744             WriteGraphImpl writer = WriteGraphImpl.create(queryProcessor, session.writeSupport, null);
745             TaskHelper th = null;
746             if (null == session.writeState) {
747                 th = new TaskHelper("Refresh");
748                 session.writeState = new WriteState<Object>(writer, th.writeTraits, th.sema, th.proc);
749                 try {
750                     session.getQueryProvider2().performDirtyUpdates(writer);
751                     session.fireMetadataListeners(writer, cs);
752                     session.getQueryProvider2().performScheduledUpdates(writer);
753                     session.fireReactionsToSynchronize(cs);
754                     session.fireSessionVariableChange(SessionVariables.QUEUED_WRITES);
755                     session.printDiagnostics();
756                 } finally {
757                     if (null != th)
758                         session.writeState = null;
759                     cs.dispose();
760                 }
761             }
762         }
763     }
764     final void refreshCluster(long csid, SessionImplSocket session, ClusterImpl cluster, ClusterUID clusterUID, long clusterId, int clusterKey)
765     throws DatabaseException {
766         if (DebugPolicy.REPORT_CLUSTER_EVENTS)
767             System.err.println("cluster=" + clusterUID + " id=" + clusterId + " key=" + clusterKey + " resources will be updated.");
768         // get cluster change sets
769         QueryProcessor queryProcessor = session.getQueryProvider2();
770         ClusterChanges cc;
771         try {
772             cc = session.graphSession.getClusterChanges(clusterUID, csid);
773         } catch (Exception e) {
774             Logger.defaultLogError("Could not get cluster changes. cluster=" + clusterUID, e);
775             release(clusterId);
776             return;
777         }
778         for (int i=0; i<cc.getResourceIndex().length; ++i) {
779             int resource = ClusterTraits.createResourceKey(clusterKey, cc.getResourceIndex()[i]);
780             ClusterUID pClusterUID = new ClusterUID(cc.getPredicateFirst()[i], cc.getPredicateSecond()[i]);
781             ClusterImpl pCluster = clusters.getClusterByClusterUID(pClusterUID);
782             if (null == pCluster)
783                 continue;
784             int pClusterKey = pCluster.getClusterKey();
785             int predicate = ClusterTraits.createResourceKey(pClusterKey, cc.getPredicateIndex()[i]);
786             queryProcessor.updateStatements(resource, predicate);
787             if (DebugPolicy.REPORT_CLUSTER_EVENTS)
788                 System.err.println("resource " + cc.getResourceIndex()[i] + " relation " + cc.getPredicateIndex()[i] + " changed.");
789         }
790         for (int i=0; i<cc.getValueIndex().length; ++i) {
791             int resource = ClusterTraits.createResourceKey(clusterKey, cc.getValueIndex()[i]);
792             queryProcessor.updateValue(resource);
793             if (DebugPolicy.REPORT_CLUSTER_EVENTS)
794                 System.err.println("value " + cc.getValueIndex()[i] + " changed.");
795         }
796     }
797     final void refreshImportance(ClusterImpl c) {
798
799         if (c.isWriteOnly())
800             return;
801
802         importanceMap.remove(c.getImportance());
803         if(collectorPolicy != null) collectorPolicy.removed(c);
804
805         long newImportance = timeCounter();
806 // System.err.println("refreshImportance " + c.getClusterId() + " => " + newImportance);
807         c.setImportance(newImportance);
808         if (!c.isLoaded())
809             Logger.defaultLogError(new Exception("Bug in ClusterTable.refreshImportance(ClusterImpl), cluster not loaded"));
810
811         importanceMap.put(c.getImportance(), new ImportanceEntry(c));
812         if(collectorPolicy != null) collectorPolicy.added(c);
813
814     }
815
816     static long loadTime = 0;
817
818     @SuppressWarnings("unchecked")
819     public final <T extends ClusterI> T getClusterProxyByResourceKey(final int resourceKey) {
820         int clusterKey = ClusterTraitsBase.getClusterKeyFromResourceKeyNoThrow(resourceKey);
821         if (ClusterTraitsBase.isVirtualClusterKey(clusterKey))
822             throw new RuntimeException("Tried to get a persistent cluster for a virtual resource.");
823         ClusterI cluster = clusterArray[clusterKey];
824         if (cluster == null)
825             throw new RuntimeException("No proxy for existing cluster. Resource key = " + resourceKey);
826         return (T)cluster;
827     }
828
829     TLongIntHashMap clusterLoadHistogram = new TLongIntHashMap();
830     int clusterLoadCounter = 0;
831
832     private <T extends ClusterI> T ensureLoaded(T c) {
833         ClusterI cluster;
834         ClusterImpl cs = (ClusterImpl) c;
835         try {
836             if(DebugPolicy.REPORT_CLUSTER_LOADING) {
837                 long start = System.nanoTime();
838                 cluster = load2(cs.getClusterId(), cs.getClusterKey());
839                 long load = System.nanoTime()-start;
840                 loadTime +=  load;
841                 if(DebugPolicy.REPORT_CLUSTER_LOADING) {
842                     int was = clusterLoadHistogram.get(cluster.getClusterId());
843                     clusterLoadHistogram.put(cluster.getClusterId(), was+1);
844                     clusterLoadCounter++;
845                     String text = "Load2 " + cluster + " " + 1e-9*loadTime + "s. " + 1e-6*load + "ms. " + clusterLoadCounter + " " + was + " " + cluster.getUsedSpace() + " " + cluster.getImportance();
846                     if(DebugPolicy.REPORT_CLUSTER_LOADING_STACKS) {
847                         new Exception(text).printStackTrace();
848                     } else {
849                         System.err.println(text);
850                     }
851                 }
852             } else {
853                 cluster = load2(cs.getClusterId(), cs.getClusterKey());
854             }
855         } catch (DatabaseException e) {
856             Logger.defaultLogError(e);
857             if (DebugPolicy.REPORT_CLUSTER_EVENTS)
858                 e.printStackTrace();
859             String msg = "Failed to load cluster " + cs.getClusterUID();// + " resourceId=" + (((cs.getClusterId() << 16 + (resourceKey & 65535))));
860             // TODO: this jams the system => needs refactoring.
861             throw new RuntimeDatabaseException(msg, e);
862         }
863         return (T) cluster;
864     }
865     
866     @SuppressWarnings("unchecked")
867     public final <T extends ClusterI> T getClusterByResourceKey(final int resourceKey) {
868         int clusterKey = ClusterTraitsBase.getClusterKeyFromResourceKeyNoThrow(resourceKey);
869         if (ClusterTraitsBase.isVirtualClusterKey(clusterKey))
870             throw new RuntimeException("Tried to get a persistent cluster for a virtual resource.");
871         ClusterI c = clusterArray[clusterKey];
872         if (c == null)
873             return null;
874         if (c.isLoaded()) {
875             if ((counter++ & 4095) == 0)
876                 refreshImportance((ClusterImpl) c);
877             return (T) c;
878         }
879         if (!(c instanceof ClusterSmall)) {
880             Logger.defaultLogError("Proxy must be instance of ClusterSmall");
881             return null;
882         }
883         return ensureLoaded((T)c);
884     }
885
886     @SuppressWarnings("unchecked")
887     final <T extends ClusterI> T checkedGetClusterByResourceKey(final int resourceKey) {
888         int clusterKey = ClusterTraitsBase.getClusterKeyFromResourceKeyNoThrow(resourceKey);
889         if (ClusterTraitsBase.isVirtualClusterKey(clusterKey))
890             throw new RuntimeException("Tried to get a persistent cluster for a virtual resource.");
891         ClusterI c = clusterArray[clusterKey];
892         if (c == null)
893             throw new RuntimeException("No cluster for resource key " + resourceKey);
894         if (c.isLoaded()) {
895             if ((counter++ & 4095) == 0)
896                 refreshImportance((ClusterImpl) c);
897             return (T) c;
898         }
899         if (!(c instanceof ClusterSmall)) {
900             Logger.defaultLogError("Proxy must be instance of ClusterSmall");
901             return null;
902         }
903         ClusterI cluster;
904         ClusterSmall cs = (ClusterSmall) c;
905         try {
906 //          System.err.println("Load2 " + resourceKey);
907             cluster = load2(cs.getClusterId(), cs.getClusterKey());
908         } catch (DatabaseException e) {
909             if (DebugPolicy.REPORT_CLUSTER_EVENTS)
910                 e.printStackTrace();
911             int resourceIndex = resourceKey & ClusterTraitsBase.getResourceIndexFromResourceKeyNoThrow(resourceKey);
912             long resourceId = ClusterTraitsBase.createResourceIdNoThrow(cs.getClusterId(), resourceIndex);
913             String msg = "Failed to load cluster " + cs.getClusterUID() + " for resource key " + resourceKey + " resourceIndex=" + resourceIndex + " resourceId=" + resourceId;
914             Logger.defaultLogError(msg, e);
915             c.setDeleted(true, null);
916             return (T)c;
917         }
918         return (T) cluster;
919     }
920
921     void printDebugInfo(ClusterSupport support)
922             throws DatabaseException {
923         final int SIZE = clusters.size();
924         long sum = 0;
925         for (int i = 1; i < SIZE; ++i) {
926             ClusterI c = clusterArray[i];
927             c.getNumberOfResources(support);
928             long size = c.getUsedSpace();
929             System.out.println("cluster=" + c.getClusterId() + " size=" + size);
930             c.printDebugInfo("koss: ", support);
931             sum += size;
932         }
933         System.out.println("Total number of clusters " + SIZE);
934         System.out.println("Total cluster size " + sum);
935     }
936
937     Map<Long,ClusterImpl> prefetch = new HashMap<Long,ClusterImpl>();
938
939     public synchronized ClusterImpl load2(long clusterId, int clusterKey) throws DatabaseException {
940         ClusterImpl curr = (ClusterImpl) clusterArray[clusterKey];
941         if (curr.isLoaded())
942             return curr;
943
944 //        getPrefetched();
945 //
946 //        curr = (ClusterImpl) clusterArray[clusterKey];
947 //        if (curr.isLoaded())
948 //            return curr;
949
950         final Semaphore s = new Semaphore(0);
951         final DatabaseException[] ex = new DatabaseException[1];
952
953         load2(clusterId, clusterKey, e -> {
954             ex[0] = e;
955             s.release();
956         });
957         try {
958             s.acquire();
959         } catch (InterruptedException e) {
960             Logger.defaultLogError(e);
961         }
962         if (null != ex[0])
963             throw ex[0];
964         if (Development.DEVELOPMENT) {
965             if (Development.<Boolean>getProperty(DevelopmentKeys.CLUSTERTABLE_VALIDATE_ON_LOAD, Bindings.BOOLEAN)) {
966                 try {
967                     ClusterImpl loaded = (ClusterImpl) clusterArray[clusterKey];
968                     if (loaded instanceof ClusterSmall)
969                         ((ClusterSmall) loaded).check();
970                     else if (loaded instanceof ClusterBig)
971                         ((ClusterBig) loaded).check();
972                 } catch (Throwable t) {
973                     t.printStackTrace();
974                 }
975             }
976         }
977         
978         validate(clusterKey);
979         
980         return (ClusterImpl) clusterArray[clusterKey];
981     }
982     
983     void validate(int clusterKey) {
984         
985 //      if(sessionImpl.graphSession.graphClient instanceof GraphClientImpl2) {
986 //          
987 //        ClusterImpl server = (ClusterImpl)clusterArray[clusterKey]; 
988 //        String sd = server.dump(sessionImpl.clusterTranslator);
989 //        GraphClientImpl2 gci = ((GraphClientImpl2)sessionImpl.graphSession.graphClient);
990 //        ClusterImpl memory = gci.clusters.get(server.clusterUID);
991 //        if(memory == null)
992 //            System.err.println("ad");
993 //        String md = memory.dump(gci.support);
994 //        
995 //        if(!sd.equals(md)) {
996 //            
997 //            int diffPos = 0;
998 //            int minLength = Math.min(sd.length(), md.length());
999 //            for (int i = 0; i < minLength; i++) {
1000 //                if (sd.charAt(i) != md.charAt(i)) {
1001 //                    diffPos = i;
1002 //                    break;
1003 //                }
1004 //            }
1005 //            
1006 //            int start = Math.max(diffPos-200, 0);
1007 //            int end = Math.min(minLength-1, diffPos+200);
1008 //
1009 //            System.err.println("== CLUSTER DIFFERENCE " + clusterArray[clusterKey].getClusterUID() +  " == ");
1010 //            System.err.println("== SESSION == ");
1011 //            System.err.println(sd.substring(start, end));
1012 //            System.err.println("== MEM == ");
1013 //            System.err.println(md.substring(start, end));
1014 //            System.err.println("== CLUSTER DIFFERENCE ENDS == ");
1015 //            
1016 //            throw new IllegalStateException();
1017 //        }
1018 //        
1019 //      }
1020     }
1021
1022     public synchronized ClusterImpl getPrefetched() {
1023         synchronized(prefetch) {
1024             return null;
1025         }
1026     }
1027
1028     public synchronized void load2(long clusterId, int clusterKey, final Consumer<DatabaseException> runnable) {
1029
1030         assert (Constants.ReservedClusterId != clusterId);
1031
1032         ClusterImpl cluster = null;
1033         DatabaseException e = null;
1034
1035         try {
1036
1037             ClusterUID clusterUID = clusters.makeClusterUID(clusterId);
1038 //            cluster = (ClusterImpl) sessionImpl.getGraphSession().getClusterImpl(clusterUID, clusterKey);
1039             Database.Session session = sessionImpl.graphSession.dbSession;
1040 //            cluster = (ClusterImpl)gci.getClusterByClusterKey(clusterKey).clone(sessionImpl.clusterTranslator);
1041 //            cluster = (ClusterImpl)((ClusterImpl)gci.getClusterByClusterUIDOrMakeProxy(clusterUID)).clone(sessionImpl.clusterTranslator);
1042             
1043 //            cluster = gci.clone(clusterUID, sessionImpl.clusterTranslator);
1044             
1045             cluster = session.clone(clusterUID, new ClusterCreator() {
1046                 
1047                 @Override
1048                 public <T> T create(ClusterUID uid, byte[] bytes, int[] ints, long[] longs) {
1049                     ClusterSupport support = sessionImpl.clusterTranslator;
1050                     try {
1051                         return (T)ClusterImpl.make(longs, ints, bytes, support, support.getClusterKeyByClusterUIDOrMake(uid));
1052                     } catch (DatabaseException e) {
1053                         e.printStackTrace();
1054                         return null;
1055                     }
1056                 }
1057                 
1058             });
1059             
1060         } catch (Throwable t) {
1061             // It's totally legal to call for non-existing cluster.
1062             // Sometimes this indicates an error, though.
1063             Logger.getDefault().logInfo("Load cluster failed.", t);
1064             if (t instanceof DatabaseException)
1065                 e = (DatabaseException) t;
1066             else
1067                 e = new DatabaseException("Load cluster failed.", t);
1068         }
1069         if (null == cluster) {
1070             runnable.accept(e);
1071             return;
1072         }
1073
1074 //      new Exception("Load cluster " + cluster.getClusterId() + " " + cluster.getCachedSize() + " " + cluster.getImmutable()).printStackTrace();
1075
1076         // Can not be called with null argument.
1077         replaceCluster(cluster);
1078         sessionImpl.onClusterLoaded(clusterId);
1079         runnable.accept(null);
1080
1081     }
1082
1083     public synchronized ClusterImpl tryLoad(long clusterId, int clusterKey) throws DatabaseException {
1084
1085         assert (Constants.ReservedClusterId != clusterId);
1086
1087         ClusterUID clusterUID = clusters.makeClusterUID(clusterId);
1088         
1089         Database.Session session = sessionImpl.graphSession.dbSession;
1090         
1091         ClusterImpl cluster = session.clone(clusterUID, new ClusterCreator() {
1092             
1093             @Override
1094             public <T> T create(ClusterUID uid, byte[] bytes, int[] ints, long[] longs) {
1095                 ClusterSupport support = sessionImpl.clusterTranslator;
1096                 try {
1097                     return (T)ClusterImpl.make(longs, ints, bytes, support, support.getClusterKeyByClusterUIDOrMake(uid));
1098                 } catch (DatabaseException e) {
1099                     e.printStackTrace();
1100                     return null;
1101                 }
1102             }
1103             
1104         });
1105         if (null == clusterArray[clusterKey])
1106             clusterArray[clusterKey] = cluster;
1107         else
1108             replaceCluster(cluster);
1109         sessionImpl.onClusterLoaded(clusterId);
1110         
1111         validate(clusterKey);
1112         
1113         return cluster;
1114     }
1115
1116     public Collection<ClusterI> getClusters() {
1117         ArrayList<ClusterI> result = new ArrayList<ClusterI>();
1118         for (int i = 0; i < clusterArray.length; i++) {
1119             ClusterI cluster = clusterArray[i];
1120             if (cluster != null)
1121                 result.add(cluster);
1122         }
1123         return result;
1124     }
1125
1126     public int size() {
1127         return clusters.size();
1128     }
1129
1130     public long timeCounter() {
1131         return timeCounter++;
1132     }
1133
1134     public boolean hasVirtual(int index) {
1135         return virtuals[index];
1136     }
1137
1138     public void markVirtual(int index) {
1139         virtuals[index] = true;
1140     }
1141
1142     public ClusterImpl[] getClusterArray() {
1143         return clusterArray;
1144     }
1145
1146     public boolean isImmutable(int id) {
1147
1148         int clusterKey = ClusterTraitsBase.getClusterKeyFromResourceKeyNoThrow(id);
1149         Boolean exist = immutables[clusterKey];
1150         if(exist != null) return exist;
1151
1152         ClusterI cluster = getClusterByResourceKey(id);
1153         if(cluster == null) {
1154                 return false;
1155         } else {
1156                 boolean result = cluster.getImmutable();
1157                 markImmutable(cluster, result);
1158                 return result;
1159         }
1160         
1161     }
1162
1163     public void markImmutable(ClusterI cluster, boolean value) {
1164         immutables[cluster.getClusterKey()] = value;
1165     }
1166     @Override
1167     public int getClusterKeyByUID(long first, long second) throws DatabaseException {
1168         return getClusterKeyByClusterUIDOrMakeProxy(ClusterUID.make(first, second));
1169     }
1170
1171     public void adjustCachedSize(long l, ClusterI cluster) {
1172         if (l != 0) {
1173             //System.out.println("ClusterTable: adjusting cluster table cached size by " + l + ": " + sizeInBytes + " -> "
1174             //        + (sizeInBytes + l) + ", for cluster " + cluster.getClusterId() + " (" + cluster + ")");
1175             sizeInBytes += l;
1176         }
1177     }
1178
1179     private void validateSize() {
1180         if (!VALIDATE_SIZE)
1181             return;
1182
1183 //        System.out.println("validating cached cluster sizes: " + sizeInBytes + ", hashMap.size="
1184 //                + clusters.hashMap.size() + ", importanceMap.size=" + importanceMap.size());
1185
1186         int i = clusterArray.length;
1187         long size = 0;
1188         for (int j = 0; j < i; ++j) {
1189             ClusterI c = clusterArray[j];
1190             if (c == null)
1191                 continue;
1192             size += c.getCachedSize();
1193         }
1194         if (sizeInBytes != size) {
1195             if (!dirtySizeInBytes)
1196                 System.out.println("BUG: CACHED CLUSTER SIZE DIFFERS FROM CALCULATED: " + sizeInBytes + " != " + size + ", delta = " + (sizeInBytes - size));
1197             //else System.out.println("\"BUG?\": SIZES DIFFER: " + sizeInBytes + " != " + size + ", delta = " + (sizeInBytes - size));
1198         }
1199
1200         int ims = importanceMap.size();
1201         int[] hms = {0};
1202         clusters.hashMap.forEachEntry((cid, c) -> {
1203             if (c != null && !c.isWriteOnly() && !c.isEmpty() && c.isLoaded()) {
1204                 //System.out.println(cid + ": " + c);
1205                 hms[0]++;
1206             }
1207             return true;
1208         });
1209         if (Math.abs(ims-hms[0]) > 0) {
1210             System.out.println("BUG2: hashmap and importanceMap sizes differ: " + hms[0] + " != " + ims + ", delta=" + (hms[0] - ims));
1211         }
1212     }
1213
1214 }