]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.acorn/src/org/simantics/acorn/ClusterManager.java
a2cb2fa864c0cb6b0155a87442cd147e8e527eb6
[simantics/platform.git] / bundles / org.simantics.acorn / src / org / simantics / acorn / ClusterManager.java
1 package org.simantics.acorn;
2
3 import java.io.IOException;
4 import java.math.BigInteger;
5 import java.nio.file.DirectoryStream;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.StandardCopyOption;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.HashMap;
12 import java.util.Map;
13 import java.util.concurrent.atomic.AtomicBoolean;
14
15 import org.simantics.acorn.cluster.ClusterImpl;
16 import org.simantics.acorn.exception.AcornAccessVerificationException;
17 import org.simantics.acorn.exception.IllegalAcornStateException;
18 import org.simantics.acorn.exception.InvalidHeadStateException;
19 import org.simantics.acorn.internal.ClusterSupport2;
20 import org.simantics.acorn.lru.ChangeSetInfo;
21 import org.simantics.acorn.lru.ClusterInfo;
22 import org.simantics.acorn.lru.ClusterLRU;
23 import org.simantics.acorn.lru.ClusterStreamChunk;
24 import org.simantics.acorn.lru.FileInfo;
25 import org.simantics.acorn.lru.LRU;
26 import org.simantics.db.ClusterCreator;
27 import org.simantics.db.Database.Session.ClusterIds;
28 import org.simantics.db.Database.Session.ResourceSegment;
29 import org.simantics.db.ServiceLocator;
30 import org.simantics.db.exception.DatabaseException;
31 import org.simantics.db.impl.ClusterBase;
32 import org.simantics.db.impl.ClusterI;
33 import org.simantics.db.impl.ClusterSupport;
34 import org.simantics.db.procore.cluster.ClusterTraits;
35 import org.simantics.db.service.ClusterSetsSupport;
36 import org.simantics.db.service.ClusterUID;
37 import org.simantics.utils.FileUtils;
38 import org.simantics.utils.threads.logger.ITask;
39 import org.simantics.utils.threads.logger.ThreadLogger;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 public class ClusterManager {
44     
45     final static Logger LOGGER = LoggerFactory.getLogger(ClusterManager.class); 
46
47         private ArrayList<String> currentChanges = new ArrayList<String>();
48
49         public final Path dbFolder;
50         public Path lastSessionDirectory;
51         public Path workingDirectory;
52
53         public LRU<String, ClusterStreamChunk> streamLRU;
54         public LRU<Long, ChangeSetInfo> csLRU;
55         public ClusterLRU clusterLRU;
56         public LRU<String, FileInfo> fileLRU;
57
58         public MainState mainState;
59         public HeadState state;
60
61         private long lastSnapshot = System.nanoTime();
62
63         final public ClusterSupport2 support = new ClusterSupport2(this);
64
65         /*
66          * Public interface
67          * 
68          */
69
70         public ClusterManager(Path dbFolder) {
71                 this.dbFolder = dbFolder;
72         }
73
74         public ArrayList<String> getChanges(long changeSetId) throws AcornAccessVerificationException, IllegalAcornStateException {
75                 ChangeSetInfo info = csLRU.getWithoutMutex(changeSetId);
76                 info.acquireMutex();
77                 try {
78                         info.makeResident();
79                         return info.getCCSIds();
80                 } finally {
81                         info.releaseMutex();
82                 }
83         }
84
85         public ClusterBase getClusterByClusterKey(int clusterKey) throws DatabaseException, AcornAccessVerificationException, IllegalAcornStateException {
86                 return clusterLRU.getClusterByClusterKey(clusterKey);
87         }
88         
89         public ClusterBase getClusterByClusterUIDOrMake(ClusterUID clusterUID) throws DatabaseException, AcornAccessVerificationException, IllegalAcornStateException {
90                 return clusterLRU.getClusterByClusterUIDOrMake(clusterUID);
91         }
92
93         public ClusterImpl getClusterByClusterUIDOrMakeProxy(ClusterUID clusterUID) throws DatabaseException, AcornAccessVerificationException, IllegalAcornStateException {
94                 return clusterLRU.getClusterByClusterUIDOrMakeProxy(clusterUID);
95         }
96
97         public int getClusterKeyByClusterUIDOrMake(ClusterUID clusterUID) throws AcornAccessVerificationException {
98                 return clusterLRU.getClusterKeyByClusterUIDOrMake(clusterUID);
99         }
100
101         public int getClusterKeyByClusterUIDOrMakeWithoutMutex(ClusterUID clusterUID) throws IllegalAcornStateException, AcornAccessVerificationException {
102                 return clusterLRU.getClusterKeyByClusterUIDOrMakeWithoutMutex(clusterUID);
103         }
104
105         public int getClusterKeyByUID(long id1, long id2) throws DatabaseException, IllegalAcornStateException {
106                 return clusterLRU.getClusterKeyByUIDWithoutMutex(id1, id2);
107         }
108         
109         public <T extends ClusterI> T getClusterProxyByResourceKey(int resourceKey) throws DatabaseException, AcornAccessVerificationException, IllegalAcornStateException {
110                 return clusterLRU.getClusterProxyByResourceKey(resourceKey);
111         }
112
113         public ClusterUID getClusterUIDByResourceKey(int resourceKey) throws DatabaseException, AcornAccessVerificationException {
114                 return clusterLRU.getClusterUIDByResourceKey(resourceKey);
115         }
116
117         public ClusterUID getClusterUIDByResourceKeyWithoutMutex(int resourceKey) throws DatabaseException, IllegalAcornStateException, AcornAccessVerificationException {
118                 return clusterLRU.getClusterUIDByResourceKeyWithoutMutex(resourceKey);
119         }
120
121         /*
122          * Private implementation
123          * 
124          */
125
126         private static long countFiles(Path directory) throws IOException {
127                 try (DirectoryStream<Path> ds = Files.newDirectoryStream(directory)) {
128                         int count = 0;
129                         for (@SuppressWarnings("unused") Path p : ds)
130                                 ++count;
131                         return count;
132                 }
133         }
134
135         // Add check to make sure if it safe to make snapshot (used with cancel which is not yet supported and may cause corrupted head.state writing)
136         private AtomicBoolean safeToMakeSnapshot = new AtomicBoolean(true);
137         private IllegalAcornStateException cause;
138         
139         public synchronized void purge(ServiceLocator locator) throws IllegalAcornStateException {
140                 
141             try {
142
143                 // Schedule writing of all data to disk
144                 refreshHeadState();
145                 // Wait for files to be written
146                 synchronizeWorkingDirectory();
147                 
148                 String currentDir = workingDirectory.getFileName().toString();
149                 Path baseline = workingDirectory.resolveSibling(currentDir + "_baseline");
150                 
151                 Files.createDirectories(baseline);
152                 
153                 for(String clusterKey : state.clusters) {
154                         String[] parts1 = clusterKey.split("#");
155                         String[] parts = parts1[0].split("\\.");
156                         String readDirName = parts1[1];
157                         if(!readDirName.equals(currentDir)) {
158                                 String fileName = parts[0] + "." + parts[1] + ".cluster";
159                                 Path from = dbFolder.resolve(readDirName).resolve(fileName);
160                                 Path to = baseline.resolve(fileName);
161                                 LOGGER.info("purge copies " + from + "  => " + to);
162                                 Files.copy(from, to, StandardCopyOption.COPY_ATTRIBUTES);
163                                 long first = new BigInteger(parts[0], 16).longValue();
164                                 long second = new BigInteger(parts[1], 16).longValue();
165                                 ClusterUID uuid = ClusterUID.make(first, second);
166                                 ClusterInfo info = clusterLRU.getWithoutMutex(uuid);
167                                 info.moveTo(baseline);
168                         }
169                 }
170                 
171                 for (String fileKey : state.files) {
172                         String[] parts = fileKey.split("#");
173                         String readDirName = parts[1];
174                         if(!readDirName.equals(currentDir)) {
175                                 String fileName = parts[0] + ".extFile";
176                                 Path from = dbFolder.resolve(readDirName).resolve(fileName);
177                                 Path to = baseline.resolve(fileName);
178                                 LOGGER.info("purge copies " + from + "  => " + to);
179                                 Files.copy(from, to, StandardCopyOption.COPY_ATTRIBUTES);
180                                 FileInfo info = fileLRU.getWithoutMutex(parts[0]);
181                                 info.moveTo(baseline);
182                         }
183                 }
184                 
185                 for (String fileKey : state.stream) {
186                         String[] parts = fileKey.split("#");
187                         String readDirName = parts[1];
188                         if(!readDirName.equals(currentDir)) {
189                                 ClusterStreamChunk chunk = streamLRU.purge(parts[0]);
190                                 LOGGER.info("purge removes " + chunk);
191                         }
192                 }
193                 
194                 // Change sets
195                 for (String fileKey : state.cs) {
196                         String[] parts = fileKey.split("#");
197                         String readDirName = parts[1];
198                         if(!readDirName.equals(currentDir)) {
199                                 Long revisionId = Long.parseLong(parts[0]);
200                                 ChangeSetInfo info = csLRU.purge(revisionId);
201                                 LOGGER.info("purge removes " + info);
202                         }
203 //                      Path readDir = dbFolder.resolve(parts[1]);
204 //                      Long revisionId = Long.parseLong(parts[0]);
205 //                      int offset = Integer.parseInt(parts[2]);
206 //                      int length = Integer.parseInt(parts[3]);
207 //                      ChangeSetInfo info = new ChangeSetInfo(csLRU, readDir, revisionId, offset, length);
208 //                      csLRU.map(info);
209                 }               
210                 
211                 state.tailChangeSetId = state.headChangeSetId;
212                 
213                 makeSnapshot(locator, true);
214                 
215                 Files.walk(dbFolder, 1).filter(Files::isDirectory).forEach(f -> tryPurgeDirectory(f));
216
217             } catch (IllegalAcornStateException e) {
218                 notSafeToMakeSnapshot(e);
219                 throw e;
220             } catch (IOException e) {
221                 IllegalAcornStateException e1 = new IllegalAcornStateException(e);
222                 notSafeToMakeSnapshot(e1);
223                 throw e1;
224             } catch (AcornAccessVerificationException e) {
225                 IllegalAcornStateException e1 = new IllegalAcornStateException(e);
226                 notSafeToMakeSnapshot(e1);
227                 throw e1;
228                 }
229         
230         }
231         
232         void tryPurgeDirectory(Path f) {
233                 
234                 
235                 LOGGER.info("purge deletes " + f);
236
237                 String currentDir = f.getFileName().toString();
238                 if(currentDir.endsWith("db"))
239                         return;
240
241                 if(currentDir.endsWith("_baseline"))
242                         currentDir = currentDir.replace("_baseline", "");
243
244                 int ordinal = Integer.parseInt(currentDir);
245                 if(ordinal < mainState.headDir - 1) {
246                         LOGGER.info("purge deletes " + f);
247                         FileUtils.deleteDir(f.toFile());
248                 }
249                 
250         }
251
252         public synchronized boolean makeSnapshot(ServiceLocator locator, boolean fullSave) throws IllegalAcornStateException {
253             try {
254             if (!safeToMakeSnapshot.get())
255                 throw cause;
256                 // Maximum autosave frequency is per 60s
257                 if(!fullSave && System.nanoTime() - lastSnapshot < 10*1000000000L) {
258     //              LOGGER.info("lastSnapshot too early");
259                     return false;
260                 }
261     
262                 // Cluster files are always there 
263                 // Nothing has been written => no need to do anything
264                 long amountOfFiles = countFiles(workingDirectory);
265                 if(!fullSave && amountOfFiles == 0) {
266                         //LOGGER.info("makeSnapshot: " + amountOfFiles + " files, skipping snapshot");
267                     return false;
268                 }
269     
270                 LOGGER.info("makeSnapshot: start with " + amountOfFiles + " files");
271     
272                 // Schedule writing of all data to disk
273             refreshHeadState();
274     
275                 // Wait for all files to be written
276                 clusterLRU.shutdown();
277                 fileLRU.shutdown();
278                 streamLRU.shutdown();
279                 csLRU.shutdown();
280                 
281                 // Lets check if it is still safe to make a snapshot
282                 if (!safeToMakeSnapshot.get())
283                     throw cause;
284                 
285                 ClusterSetsSupport cssi = locator.getService(ClusterSetsSupport.class); 
286                 cssi.save();
287
288                 persistHeadState();
289
290                 if (LOGGER.isInfoEnabled()) {
291                         amountOfFiles = countFiles(workingDirectory);
292                         LOGGER.info(" -finished: amount of files is {}", amountOfFiles);
293                 }
294
295                 workingDirectory = dbFolder.resolve(Integer.toString(mainState.headDir));
296                 Files.createDirectories(workingDirectory);
297     
298                 cssi.updateWriteDirectory(workingDirectory);
299     
300                 clusterLRU.setWriteDir(workingDirectory);
301                 fileLRU.setWriteDir(workingDirectory);
302                 streamLRU.setWriteDir(workingDirectory);
303                 csLRU.setWriteDir(workingDirectory);
304     
305                 clusterLRU.resume();
306                 fileLRU.resume();
307                 streamLRU.resume();
308                 csLRU.resume();
309     
310                 lastSnapshot = System.nanoTime();
311                 
312                 return true;
313             } catch (IllegalAcornStateException e) {
314                 notSafeToMakeSnapshot(e);
315                 throw e;
316             } catch (IOException e) {
317                 IllegalAcornStateException e1 = new IllegalAcornStateException(e);
318                 notSafeToMakeSnapshot(e1);
319                 throw e1;
320             }
321         }
322         
323         private void refreshHeadState() throws IOException, IllegalAcornStateException {
324                 state.clusters.clear();
325                 state.files.clear();
326                 state.stream.clear();
327                 state.cs.clear();
328
329                 clusterLRU.persist(state.clusters);
330                 fileLRU.persist(state.files);
331                 streamLRU.persist(state.stream);
332                 csLRU.persist(state.cs);
333         }
334         
335         private void synchronizeWorkingDirectory() throws IOException {
336                 // Sync current working directory
337                 Files.walk(workingDirectory, 1).filter(Files::isRegularFile).forEach(FileIO::uncheckedSyncPath);
338         }
339         
340         private void persistHeadState() throws IOException {
341                 synchronizeWorkingDirectory();
342                 state.save(workingDirectory);
343                 mainState.headDir++;
344         }
345
346         
347 //      public void save() throws IOException {
348 //
349 //              refreshHeadState();
350 //              
351 //              clusterLRU.shutdown();
352 //              fileLRU.shutdown();
353 //              streamLRU.shutdown();
354 //              csLRU.shutdown();
355 //
356 //              persistHeadState();
357 //
358 //              mainState.save(getBaseDirectory());
359
360 //              try {
361 //                      ThreadLogVisualizer visualizer = new ThreadLogVisualizer();
362 //                      visualizer.read(new DataInputStream(new FileInputStream(
363 //                                      ThreadLogger.LOG_FILE)));
364 //                      visualizer.visualize3(new PrintStream(ThreadLogger.LOG_FILE
365 //                                      + ".svg"));
366 //              } catch (FileNotFoundException e) {
367 //                      // TODO Auto-generated catch block
368 //                      e.printStackTrace();
369 //              }
370
371                 // LOGGER.info("-- load statistics --");
372                 // for(Pair<ClusterUID, Integer> entry :
373                 // CollectionUtils.valueSortedEntries(histogram)) {
374                 // LOGGER.info(" " + entry.second + " " + entry.first);
375                 // }
376
377 //      }
378         
379         private void acquireAll() throws IllegalAcornStateException {
380                 clusterLRU.acquireMutex();
381                 fileLRU.acquireMutex();
382                 streamLRU.acquireMutex();
383                 csLRU.acquireMutex();
384         }
385         
386         private void releaseAll() {
387                 csLRU.releaseMutex();
388                 streamLRU.releaseMutex();
389                 fileLRU.releaseMutex();
390                 clusterLRU.releaseMutex();
391         }
392
393         private AtomicBoolean rollback = new AtomicBoolean(false);
394         
395         boolean rolledback() {
396             return rollback.get();
397         }
398         
399         public void load() throws IOException {
400
401                 // Main state
402                 mainState = MainState.load(dbFolder, () -> rollback.set(true));
403
404                 lastSessionDirectory = dbFolder.resolve(Integer.toString(mainState.headDir - 1));
405                 
406                 // Head State
407                 if (mainState.isInitial()) {
408                         state = new HeadState();
409                 } else {
410                         try {
411                     state = HeadState.load(lastSessionDirectory);
412                 } catch (InvalidHeadStateException e) {
413                     // For backwards compatibility only!
414                     Throwable cause = e.getCause();
415                     if (cause instanceof Throwable) {
416                         try {
417                             org.simantics.db.javacore.HeadState oldState = org.simantics.db.javacore.HeadState.load(lastSessionDirectory);
418                             
419                             HeadState newState = new HeadState();
420                             newState.clusters = oldState.clusters;
421                             newState.cs = oldState.cs;
422                             newState.files = oldState.files;
423                             newState.stream = oldState.stream;
424                             newState.headChangeSetId = oldState.headChangeSetId;
425                             newState.reservedIds = oldState.reservedIds;
426                             newState.transactionId = oldState.transactionId;
427                             state = newState;
428                         } catch (InvalidHeadStateException e1) {
429                             throw new IOException("Could not load HeadState due to corruption", e1);
430                         }
431                     } else {
432                         // This should never happen as MainState.load() checks the integrity
433                         // of head.state files and rolls back in cases of corruption until a
434                         // consistent state is found (could be case 0 - initial db state)
435                         // IF this does happen something is completely wrong
436                         throw new IOException("Could not load HeadState due to corruption", e);
437                     }
438                 }
439                 }
440                 try {
441                 workingDirectory = dbFolder.resolve(Integer.toString(mainState.headDir));
442                 Files.createDirectories(workingDirectory);
443     
444                 csLRU = new LRU<Long, ChangeSetInfo>(this, "Change Set", workingDirectory);
445                 streamLRU = new LRU<String, ClusterStreamChunk>(this, "Cluster Stream", workingDirectory);
446                 clusterLRU = new ClusterLRU(this, "Cluster", workingDirectory);
447                 fileLRU = new LRU<String, FileInfo>(this, "External Value", workingDirectory);
448     
449                 acquireAll();
450                 
451                 // Clusters
452                 for (String clusterKey : state.clusters) {
453                         String[] parts1 = clusterKey.split("#");
454                         String[] parts = parts1[0].split("\\.");
455                         long first = new BigInteger(parts[0], 16).longValue();
456                         long second = new BigInteger(parts[1], 16).longValue();
457                         ClusterUID uuid = ClusterUID.make(first, second);
458                         Path readDir = dbFolder.resolve(parts1[1]);
459                         int offset = Integer.parseInt(parts1[2]);
460                         int length = Integer.parseInt(parts1[3]);
461                         clusterLRU.map(new ClusterInfo(this, clusterLRU, readDir, uuid, offset, length));
462                 }
463                 // Files
464                 for (String fileKey : state.files) {
465     //                  LOGGER.info("loadFile: " + fileKey);
466                         String[] parts = fileKey.split("#");
467                         Path readDir = dbFolder.resolve(parts[1]);
468                         int offset = Integer.parseInt(parts[2]);
469                         int length = Integer.parseInt(parts[3]);
470                         FileInfo info = new FileInfo(fileLRU, readDir, parts[0], offset, length);
471                         fileLRU.map(info);
472                 }
473                 // Update chunks
474                 for (String fileKey : state.stream) {
475     //                  LOGGER.info("loadStream: " + fileKey);
476                         String[] parts = fileKey.split("#");
477                         Path readDir = dbFolder.resolve(parts[1]);
478                         int offset = Integer.parseInt(parts[2]);
479                         int length = Integer.parseInt(parts[3]);
480                         ClusterStreamChunk info = new ClusterStreamChunk(this,
481                                         streamLRU, readDir, parts[0], offset, length);
482                         streamLRU.map(info);
483                 }
484                 // Change sets
485                 for (String fileKey : state.cs) {
486                         String[] parts = fileKey.split("#");
487                         Path readDir = dbFolder.resolve(parts[1]);
488                         Long revisionId = Long.parseLong(parts[0]);
489                         int offset = Integer.parseInt(parts[2]);
490                         int length = Integer.parseInt(parts[3]);
491                         ChangeSetInfo info = new ChangeSetInfo(csLRU, readDir, revisionId, offset, length);
492                         csLRU.map(info);
493                 }
494                 
495                 releaseAll();
496                 } catch (IllegalAcornStateException | AcornAccessVerificationException e) {
497                     // ROLLBACK ONE DIR UNTIL WE ARE FINE!
498                     throw new IOException(e);
499                 }
500         }
501
502         public <T> T clone(ClusterUID uid, ClusterCreator creator) throws DatabaseException, AcornAccessVerificationException, IllegalAcornStateException, IOException {
503                 
504                 clusterLRU.ensureUpdates(uid);
505                 
506                 ClusterInfo info = clusterLRU.getWithoutMutex(uid);
507                 return info.clone(uid, creator);
508         }
509
510         //private int loadCounter = 0;
511
512         public static void startLog(String msg) {
513                 tasks.put(msg, ThreadLogger.getInstance().begin(msg));
514         }
515
516         public static void endLog(String msg) {
517                 ITask task = tasks.get(msg);
518                 if (task != null)
519                         task.finish();
520         }
521
522         static Map<String, ITask> tasks = new HashMap<String, ITask>();
523
524         public void update(ClusterUID uid, ClusterImpl clu) throws AcornAccessVerificationException, IllegalAcornStateException {
525                 ClusterInfo info = clusterLRU.getWithoutMutex(uid);
526                 info.acquireMutex();
527                 try {
528                         info.update(clu);
529                 } finally {
530                         info.releaseMutex();
531                 }
532         }
533
534         public long getClusterIdOrCreate(ClusterUID clusterUID) {
535                 return 1;
536         }
537
538         public int getResourceKey(ClusterUID uid, int index) throws AcornAccessVerificationException {
539                 return clusterLRU.getResourceKey(uid, index);
540         }
541
542         public int getResourceKeyWitoutMutex(ClusterUID uid, int index) throws IllegalAcornStateException {
543                 return clusterLRU.getResourceKeyWithoutMutex(uid, index);
544         }
545
546         public ClusterIds getClusterIds() throws IllegalAcornStateException {
547                 clusterLRU.acquireMutex();
548
549                 try {
550                         Collection<ClusterInfo> infos = clusterLRU.values();
551                         final int status = infos.size();
552                         final long[] firsts = new long[status];
553                         final long[] seconds = new long[status];
554
555                         int index = 0;
556                         for (ClusterInfo info : infos) {
557                                 firsts[index] = 0;
558                                 seconds[index] = info.getKey().second;
559                                 index++;
560                         }
561
562                         return new ClusterIds() {
563
564                                 @Override
565                                 public int getStatus() {
566                                         return status;
567                                 }
568
569                                 @Override
570                                 public long[] getFirst() {
571                                         return firsts;
572                                 }
573
574                                 @Override
575                                 public long[] getSecond() {
576                                         return seconds;
577                                 }
578
579                         };
580
581                 } catch (Throwable t) {
582                         throw new IllegalAcornStateException(t);
583                 } finally {
584                         clusterLRU.releaseMutex();
585                 }
586         }
587
588         public void addIntoCurrentChangeSet(String ccs) throws IllegalAcornStateException {
589                 csLRU.acquireMutex();
590
591                 try {
592                         currentChanges.add(ccs);
593                 } catch (Throwable t) {
594                         throw new IllegalAcornStateException(t);
595                 } finally {
596                         csLRU.releaseMutex();
597                 }
598         }
599
600         public void commitChangeSet(long changeSetId, byte[] data) throws IllegalAcornStateException {
601                 csLRU.acquireMutex();
602                 try {
603                         ArrayList<String> csids = new ArrayList<String>(currentChanges);
604                         currentChanges = new ArrayList<String>();
605                         new ChangeSetInfo(csLRU, changeSetId, data, csids);
606                 } catch (Throwable t) {
607                         throw new IllegalAcornStateException(t);
608                 } finally {
609                         csLRU.releaseMutex();
610                 }
611         }
612
613         public byte[] getMetadata(long changeSetId) throws AcornAccessVerificationException, IllegalAcornStateException {
614                 
615                 ChangeSetInfo info = csLRU.getWithoutMutex(changeSetId);
616                 if (info == null) return null;
617         info.acquireMutex();
618         try {
619             return info.getMetadataBytes();
620         } catch (IllegalAcornStateException | AcornAccessVerificationException e) {
621             throw e;
622         } catch (Throwable t) {
623                         throw new IllegalAcornStateException(t);
624                 } finally {
625                         info.releaseMutex();
626                 }
627         }
628
629         public byte[] getResourceFile(final byte[] clusterUID, final int resourceIndex) throws AcornAccessVerificationException, IllegalAcornStateException {
630
631                 ClusterUID uid = ClusterUID.make(clusterUID, 0);
632                 String key = uid.toString() + "_" + resourceIndex;
633                 FileInfo info = fileLRU.getWithoutMutex(key);
634                 if(info == null) return null;
635                 info.acquireMutex();
636                 try {
637                         return info.getResourceFile();
638                 } catch (IllegalAcornStateException | AcornAccessVerificationException e) {
639                     throw e;
640                 } catch (Throwable t) {
641                         throw new IllegalAcornStateException(t);
642                 } finally {
643                         info.releaseMutex();
644                 }
645         }
646
647         public ResourceSegment getResourceSegment(final byte[] clusterUID, final int resourceIndex, final long segmentOffset, short segmentSize) throws AcornAccessVerificationException, IllegalAcornStateException {
648                 ClusterUID uid = ClusterUID.make(clusterUID, 0);
649
650                 String key = uid.toString() + "_" + resourceIndex;
651                 FileInfo info = fileLRU.getWithoutMutex(key);
652                 if(info == null) return null;
653                 info.acquireMutex();
654                 try {
655                         return info.getResourceSegment(clusterUID, resourceIndex, segmentOffset, segmentSize);
656                 } catch (Throwable t) {
657                         throw new IllegalAcornStateException(t);
658                 } finally {
659                         info.releaseMutex();
660                 }
661         }
662
663         public void modiFileEx(ClusterUID uid, int resourceKey, long offset, long size, byte[] bytes, long pos, ClusterSupport support) throws IllegalAcornStateException {
664                 try {
665                         String key = uid.toString() + "_" + ClusterTraits.getResourceIndexFromResourceKey(resourceKey);
666
667                         FileInfo info = null;
668                         fileLRU.acquireMutex();
669                         try {
670                                 info = fileLRU.get(key);
671                                 if (info == null) {
672                                         info = new FileInfo(fileLRU, key, (int) (offset + size));
673                                 }
674                         } catch (Throwable t) {
675                                 throw new IllegalAcornStateException(t);
676                         } finally {
677                                 fileLRU.releaseMutex();
678                         }
679                         
680                         info.acquireMutex();
681                         try {
682                                 info.updateData(bytes, offset, pos, size);
683                         } catch (Throwable t) {
684                                 throw new IllegalAcornStateException(t);
685                         } finally {
686                                 info.releaseMutex();
687                         }
688                 } catch (DatabaseException e) {
689                         e.printStackTrace();
690                 }
691         }
692
693     public void shutdown() {
694         clusterLRU.shutdown();
695         fileLRU.shutdown();
696         streamLRU.shutdown();
697         csLRU.shutdown();
698     }
699
700     public void notSafeToMakeSnapshot(IllegalAcornStateException t) {
701         this.safeToMakeSnapshot.compareAndSet(true, false);
702         this.cause = t;
703     }
704
705     public long getTailChangeSetId() {
706         return state.tailChangeSetId;
707     }
708     
709 }