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