GlueGen v2.6.0-rc-20250712
GlueGen, Native Binding Generator for Java™ (public API).
TempFileCache.java
Go to the documentation of this file.
1/**
2 * Copyright 2011 JogAmp Community. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification, are
5 * permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice, this list of
8 * conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 * of conditions and the following disclaimer in the documentation and/or other materials
12 * provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 *
24 * The views and conclusions contained in the software and documentation are those of the
25 * authors and should not be interpreted as representing official policies, either expressed
26 * or implied, of JogAmp Community.
27 */
28package com.jogamp.common.util.cache;
29
30import java.io.File;
31import java.io.FileOutputStream;
32import java.io.FilenameFilter;
33import java.io.IOException;
34import java.nio.channels.FileChannel;
35import java.nio.channels.FileLock;
36
37import com.jogamp.common.util.IOUtil;
38import com.jogamp.common.util.InterruptSource;
39
40import jogamp.common.Debug;
41
42public class TempFileCache {
43 private static final boolean DEBUG = Debug.debug("TempFileCache");
44
45 // Flag indicating that we got a fatal error in the static initializer.
46 private static boolean staticInitError = false;
47
48 // Flag indicating that the temp root folder can be used for executable files
49 private static boolean staticTempIsExecutable = true;
50
51 private static final String tmpDirPrefix = "file_cache";
52
53 // Lifecycle: For one user's JVMs, ClassLoader and time.
54 private static final File tmpBaseDir;
55
56 // Get the value of the tmproot system property
57 // Lifecycle: For one user's concurrently running JVMs and ClassLoader
58 /* package */ static final String tmpRootPropName = "jnlp.jogamp.tmp.cache.root";
59
60 // String representing the name of the temp root directory relative to the
61 // tmpBaseDir. Its value is "jlnNNNNN", which is the unique filename created
62 // by File.createTempFile() without the ".tmp" extension.
63 //
64 // Lifecycle: For one user's concurrently running JVMs and ClassLoader
65 //
66 private static String tmpRootPropValue;
67
68 // Lifecycle: For one user's concurrently running JVMs and ClassLoader
69 private static File tmpRootDir;
70
71 // Flag indicating that we got a fatal error in the initializer.
72 private boolean initError = false;
73
74 private File individualTmpDir;
75
76 static {
77 // Global Lock !
78 synchronized (System.out) {
79 // Create / initialize the temp root directory, starting the Reaper
80 // thread to reclaim old installations if necessary. If we get an
81 // exception, set an error code.
82 File _tmpBaseDir = null;
83 try {
84 _tmpBaseDir = new File(IOUtil.getTempDir(true /* executable */), tmpDirPrefix);
85 _tmpBaseDir = IOUtil.testDir(_tmpBaseDir, true /* create */, false /* executable */); // executable already checked
86 staticTempIsExecutable = true;
87 } catch (final Exception ex) {
88 System.err.println("Warning: Caught Exception while retrieving executable temp base directory:");
89 ex.printStackTrace();
90 staticTempIsExecutable = false;
91 try {
92 _tmpBaseDir = new File(IOUtil.getTempDir(false /* executable */), tmpDirPrefix);
93 _tmpBaseDir = IOUtil.testDir(_tmpBaseDir, true /* create */, false /* executable */);
94 } catch (final Exception ex2) {
95 System.err.println("Warning: Caught Exception while retrieving non-executable temp base directory:");
96 ex2.printStackTrace();
97 staticInitError = true;
98 }
99 }
100 tmpBaseDir = _tmpBaseDir;
101
102 if (DEBUG) {
103 final String tmpBaseDirAbsPath = null != tmpBaseDir ? tmpBaseDir.getAbsolutePath() : null;
104 System.err.println("TempFileCache: Static Initialization ---------------------------------------------- OK: "+(!staticInitError));
105 System.err.println("TempFileCache: Thread: "+Thread.currentThread().getName()+
106 ", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+
107 ", tempBaseDir "+tmpBaseDirAbsPath+", executable "+staticTempIsExecutable);
108 }
109
110 if(!staticInitError) {
111 try {
112 initTmpRoot();
113 } catch (final Exception ex) {
114 System.err.println("Warning: Caught Exception due to initializing TmpRoot:");
115 ex.printStackTrace();
116 staticInitError = true;
117 staticTempIsExecutable = false;
118 }
119 }
120 if (DEBUG) {
121 System.err.println("------------------------------------------------------------------ OK: "+(!staticInitError));
122 }
123 }
124 }
125
126 /**
127 * Documented way to kick off static initialization
128 * @return true is static initialization was successful
129 */
130 public static boolean initSingleton() {
131 return !staticInitError;
132 }
133
134 /**
135 * This method is called by the static initializer to create / initialize
136 * the temp root directory that will hold the temp directories for this
137 * instance of the JVM. This is done as follows:
138 *
139 * 1. Synchronize on a global lock. Note that for this purpose we will
140 * use System.out in the absence of a true global lock facility.
141 * We are careful not to hold this lock too long.
142 * The global lock is claimed in the static initializer block, calling this method!
143 *
144 * 2. Check for the existence of the "jnlp.jogamp.tmp.cache.root"
145 * system property.
146 *
147 * a. If set, then some other thread in a different ClassLoader has
148 * already created the tmpRootDir, so we just need to
149 * use it. The remaining steps are skipped.
150 * However, we check the existence of the tmpRootDir
151 * and if non existent, we assume a new launch and continue.
152 *
153 * b. If not set, then we are the first thread in this JVM to run,
154 * and we need to create the the tmpRootDir.
155 *
156 * 3. Create the tmpRootDir, along with the appropriate locks.
157 * Note that we perform the operations in the following order,
158 * prior to creating tmpRootDir itself, to work around the fact that
159 * the file creation and file lock steps are not atomic, and we need
160 * to ensure that a newly-created tmpRootDir isn't reaped by a
161 * concurrently running JVM.
162 *
163 * create jlnNNNN.tmp using File.createTempFile()
164 * lock jlnNNNN.tmp
165 * create jlnNNNN.lck while holding the lock on the .tmp file
166 * lock jlnNNNN.lck
167 *
168 * Since the Reaper thread will enumerate the list of *.lck files
169 * before starting, we can guarantee that if there exists a *.lck file
170 * for an active process, then the corresponding *.tmp file is locked
171 * by that active process. This guarantee lets us avoid reaping an
172 * active process' files.
173 *
174 * 4. Set the "jnlp.jogamp.tmp.cache.root" system property.
175 *
176 * 5. Add a shutdown hook to cleanup jlnNNNN.lck and jlnNNNN.tmp. We
177 * don't actually expect that this shutdown hook will ever be called,
178 * but the act of doing this, ensures that the locks never get
179 * garbage-collected, which is necessary for correct behavior when
180 * the first ClassLoader is later unloaded, while subsequent Applets
181 * are still running.
182 *
183 * 6. Start the Reaper thread to cleanup old installations.
184 */
185 private static void initTmpRoot() throws IOException {
186 tmpRootPropValue = System.getProperty(tmpRootPropName);
187
188 if (tmpRootPropValue != null) {
189 // Make sure that the property is not set to an illegal value
190 if (tmpRootPropValue.indexOf('/') >= 0 ||
191 tmpRootPropValue.indexOf(File.separatorChar) >= 0) {
192 throw new IOException("Illegal value of: " + tmpRootPropName);
193 }
194
195 // Set tmpRootDir = ${tmpbase}/${jnlp.applet.launcher.tmproot}
196 if (DEBUG) {
197 System.err.println("TempFileCache: Trying existing value of: " +
198 tmpRootPropName + "=" + tmpRootPropValue);
199 }
200 tmpRootDir = new File(tmpBaseDir, tmpRootPropValue);
201 if (DEBUG) {
202 System.err.println("TempFileCache: Trying tmpRootDir = " + tmpRootDir.getAbsolutePath());
203 }
204 if (tmpRootDir.isDirectory()) {
205 if (!tmpRootDir.canWrite()) {
206 throw new IOException("Temp root directory is not writable: " + tmpRootDir.getAbsolutePath());
207 }
208 } else {
209 // It is possible to move to a new GlueGen version within the same JVM
210 // In case tmpBaseDir has changed, we should assume a new tmpRootDir.
211 System.err.println("TempFileCache: None existing tmpRootDir = " + tmpRootDir.getAbsolutePath()+", assuming new path due to update");
212 tmpRootPropValue = null;
213 tmpRootDir = null;
214 System.clearProperty(tmpRootPropName);
215 }
216 }
217
218 if (tmpRootPropValue == null) {
219 // Create ${tmpbase}/jlnNNNN.tmp then lock the file
220 final File tmpFile = File.createTempFile("jln", ".tmp", tmpBaseDir);
221 if (DEBUG) {
222 System.err.println("TempFileCache: tmpFile = " + tmpFile.getAbsolutePath());
223 }
224 final FileOutputStream tmpOut = new FileOutputStream(tmpFile);
225 final FileChannel tmpChannel = tmpOut.getChannel();
226 final FileLock tmpLock = tmpChannel.lock();
227
228 // Strip off the ".tmp" to get the name of the tmprootdir
229 final String tmpFileName = tmpFile.getAbsolutePath();
230 final String tmpRootName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp"));
231
232 // create ${tmpbase}/jlnNNNN.lck then lock the file
233 final String lckFileName = tmpRootName + ".lck";
234 final File lckFile = new File(lckFileName);
235 if (DEBUG) {
236 System.err.println("TempFileCache: lckFile = " + lckFile.getAbsolutePath());
237 }
238 lckFile.createNewFile();
239 final FileOutputStream lckOut = new FileOutputStream(lckFile);
240 final FileChannel lckChannel = lckOut.getChannel();
241 final FileLock lckLock = lckChannel.lock();
242
243 // Create tmprootdir
244 tmpRootDir = new File(tmpRootName);
245 if (DEBUG) {
246 System.err.println("TempFileCache: tmpRootDir = " + tmpRootDir.getAbsolutePath());
247 }
248 if (!tmpRootDir.mkdir()) {
249 throw new IOException("Cannot create " + tmpRootDir);
250 }
251
252 // Add shutdown hook to cleanup the OutputStream, FileChannel,
253 // and FileLock for the jlnNNNN.lck and jlnNNNN.lck files.
254 // We do this so that the locks never get garbage-collected.
255 Runtime.getRuntime().addShutdownHook(new InterruptSource.Thread() {
256 /* @Override */
257 @Override
258 public void run() {
259 // NOTE: we don't really expect that this code will ever
260 // be called. If it does, we will close the output
261 // stream, which will in turn close the channel.
262 // We will then release the lock.
263 try {
264 tmpOut.close();
265 tmpLock.release();
266 lckOut.close();
267 lckLock.release();
268 } catch (final IOException ex) {
269 // Do nothing
270 }
271 }
272 });
273
274 // Set the system property...
275 tmpRootPropValue = tmpRootName.substring(tmpRootName.lastIndexOf(File.separator) + 1);
276 System.setProperty(tmpRootPropName, tmpRootPropValue);
277 if (DEBUG) {
278 System.err.println("TempFileCache: Setting " + tmpRootPropName + "=" + tmpRootPropValue);
279 }
280
281 // Start a new Reaper thread to do stuff...
282 final Thread reaperThread = new InterruptSource.Thread() {
283 /* @Override */
284 @Override
285 public void run() {
286 deleteOldTempDirs();
287 }
288 };
289 reaperThread.setName("TempFileCache-Reaper");
290 reaperThread.start();
291 }
292 }
293
294 /**
295 * Called by the Reaper thread to delete old temp directories
296 * Only one of these threads will run per JVM invocation.
297 */
298 private static void deleteOldTempDirs() {
299 if (DEBUG) {
300 System.err.println("TempFileCache: *** Reaper: deleteOldTempDirs in " +
301 tmpBaseDir.getAbsolutePath());
302 }
303
304 // enumerate list of jnl*.lck files, ignore our own jlnNNNN file
305 final String ourLockFile = tmpRootPropValue + ".lck";
306 final FilenameFilter lckFilter = new FilenameFilter() {
307 /* @Override */
308 @Override
309 public boolean accept(final File dir, final String name) {
310 return name.endsWith(".lck") && !name.equals(ourLockFile);
311 }
312 };
313
314 // For each file <file>.lck in the list we will first try to lock
315 // <file>.tmp if that succeeds then we will try to lock <file>.lck
316 // (which should always succeed unless there is a problem). If we can
317 // get the lock on both files, then it must be an old installation, and
318 // we will delete it.
319 final String[] fileNames = tmpBaseDir.list(lckFilter);
320 if (fileNames != null) {
321 for (int i = 0; i < fileNames.length; i++) {
322 final String lckFileName = fileNames[i];
323 final String tmpDirName = lckFileName.substring(0, lckFileName.lastIndexOf(".lck"));
324 final String tmpFileName = tmpDirName + ".tmp";
325
326 final File lckFile = new File(tmpBaseDir, lckFileName);
327 final File tmpFile = new File(tmpBaseDir, tmpFileName);
328 final File tmpDir = new File(tmpBaseDir, tmpDirName);
329
330 if (lckFile.exists() && tmpFile.exists() && tmpDir.isDirectory()) {
331 FileOutputStream tmpOut = null;
332 FileChannel tmpChannel = null;
333 FileLock tmpLock = null;
334
335 try {
336 tmpOut = new FileOutputStream(tmpFile);
337 tmpChannel = tmpOut.getChannel();
338 tmpLock = tmpChannel.tryLock();
339 } catch (final Exception ex) {
340 // Ignore exceptions
341 if (DEBUG) {
342 ex.printStackTrace();
343 }
344 }
345
346 if (tmpLock != null) {
347 FileOutputStream lckOut = null;
348 FileChannel lckChannel = null;
349 FileLock lckLock = null;
350
351 try {
352 lckOut = new FileOutputStream(lckFile);
353 lckChannel = lckOut.getChannel();
354 lckLock = lckChannel.tryLock();
355 } catch (final Exception ex) {
356 if (DEBUG) {
357 ex.printStackTrace();
358 }
359 }
360
361 if (lckLock != null) {
362 // Recursively remove the old tmpDir and all of
363 // its contents
364 removeAll(tmpDir);
365
366 // Close the streams and delete the .lck and .tmp
367 // files. Note that there is a slight race condition
368 // in that another process could open a stream at
369 // the same time we are trying to delete it, which will
370 // prevent deletion, but we won't worry about it, since
371 // the worst that will happen is we might have an
372 // occasional 0-byte .lck or .tmp file left around
373 try {
374 lckOut.close();
375 } catch (final IOException ex) {
376 }
377 lckFile.delete();
378 try {
379 tmpOut.close();
380 } catch (final IOException ex) {
381 }
382 tmpFile.delete();
383 } else {
384 try {
385 // Close the file and channel for the *.lck file
386 if (lckOut != null) {
387 lckOut.close();
388 }
389 // Close the file/channel and release the lock
390 // on the *.tmp file
391 tmpOut.close();
392 tmpLock.release();
393 } catch (final IOException ex) {
394 if (DEBUG) {
395 ex.printStackTrace();
396 }
397 }
398 }
399 }
400 } else {
401 if (DEBUG) {
402 System.err.println("TempFileCache: Skipping: " + tmpDir.getAbsolutePath());
403 }
404 }
405 }
406 }
407 }
408
409 /**
410 * Remove the specified file or directory. If "path" is a directory, then
411 * recursively remove all entries, then remove the directory itself.
412 */
413 private static void removeAll(final File path) {
414 if (DEBUG) {
415 System.err.println("TempFileCache: removeAll(" + path + ")");
416 }
417
418 if (path.isDirectory()) {
419 // Recursively remove all files/directories in this directory
420 final File[] list = path.listFiles();
421 if (list != null) {
422 for (int i = 0; i < list.length; i++) {
423 removeAll(list[i]);
424 }
425 }
426 }
427 path.delete();
428 }
429
430 /** Create the {@link #getTempDir()} */
431 public TempFileCache () {
432 if (DEBUG) {
433 System.err.println("TempFileCache: new TempFileCache() --------------------- (static ok: "+(!staticInitError)+")");
434 System.err.println("TempFileCache: Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+", this 0x"+Integer.toHexString(hashCode()));
435 }
436 if(!staticInitError) {
437 try {
438 createTmpDir();
439 } catch (final Exception ex) {
440 ex.printStackTrace();
441 initError = true;
442 }
443 }
444 if (DEBUG) {
445 System.err.println("TempFileCache: tempDir "+individualTmpDir+" (ok: "+(!initError)+")");
446 System.err.println("----------------------------------------------------------");
447 }
448 }
449
450 /** Delete the {@link #getTempDir()} recursively and remove it's reference. */
451 public void destroy() {
452 if (DEBUG) {
453 System.err.println("TempFileCache: destroy() --------------------- (static ok: "+(!staticInitError)+")");
454 System.err.println("TempFileCache: Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+", this 0x"+Integer.toHexString(hashCode()));
455 }
456 if(!staticInitError) {
457 try {
458 removeAll(individualTmpDir);
459 } catch (final Exception ex) {
460 ex.printStackTrace();
461 }
462 }
463 individualTmpDir = null;
464 if (DEBUG) {
465 System.err.println("TempFileCache: destroy() END");
466 }
467 }
468
469 /**
470 * @param forExecutables if {@code true}, method also tests whether the underlying {@link #getBaseDir()} is suitable to load native libraries or launch executables
471 * @return true if static and object initialization was successful
472 * @see #isTempExecutable()
473 * @see #isValid()
474 */
475 public boolean isValid(final boolean forExecutables) {
476 return !staticInitError && !initError && ( !forExecutables || staticTempIsExecutable );
477 }
478
479 /**
480 * Base temp directory used by {@link TempFileCache}.
481 *
482 * <p>
483 * Lifecycle: For one user's concurrently running JVMs and ClassLoader
484 * </p>
485 *
486 * This is set to:
487 * <pre>
488 * ${java.io.tmpdir}/tmpDirPrefix
489 * </pre>
490 *
491 * @return
492 */
493 public static File getBaseDir() { return tmpBaseDir; }
494
495 /**
496 * Root temp directory for this JVM instance. Used to store individual
497 * directories.
498 * <p>
499 * This directory is a sub-folder to {@link #getBaseDir()}.
500 * </p>
501 *
502 * <p>
503 * Lifecycle: For one user's concurrently running JVMs and ClassLoader
504 * </p>
505 *
506 * <pre>
507 * tmpBaseDir/tmpRootPropValue
508 * </pre>
509 *
510 * <p>
511 * Use Case: Per ClassLoader files, eg. native libraries.
512 * </p>
513 *
514 * <p>
515 * Old temp directories are cleaned up the next time a JVM is launched that
516 * uses TempFileCache.
517 * </p>
518 *
519 *
520 * @return
521 */
522 public static File getRootDir() { return tmpRootDir; }
523
524 /**
525 * Temporary directory for individual files (eg. native libraries of one ClassLoader instance).
526 * <p>
527 * This directory is a sub-folder to {@link #getRootDir()}.
528 * </p>
529 *
530 * <p>
531 * Lifecycle: Within each JVM .. use case dependent, ie. per ClassLoader <b>and</b> per {@link TempFileCache} instance!
532 * </p>
533 * <p>
534 * The directory name is:
535 *
536 * <pre>
537 * tmpRootDir/jlnMMMMM
538 * </pre>
539 *
540 * where jlnMMMMM is the unique filename created by File.createTempFile()
541 * without the ".tmp" extension.
542 * </p>
543 *
544 * @return
545 */
546 public File getTempDir() { return individualTmpDir; }
547
548
549 /**
550 * Create the temp directory in tmpRootDir. To do this, we create a temp
551 * file with a ".tmp" extension, and then create a directory of the
552 * same name but without the ".tmp". The temp file, directory, and all
553 * files in the directory will be reaped the next time this is started.
554 * We avoid deleteOnExit, because it doesn't work reliably.
555 */
556 private void createTmpDir() throws IOException {
557 final File tmpFile = File.createTempFile("jln", ".tmp", tmpRootDir);
558 final String tmpFileName = tmpFile.getAbsolutePath();
559 final String tmpDirName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp"));
560 individualTmpDir = new File(tmpDirName);
561 if (!individualTmpDir.mkdir()) {
562 throw new IOException("Cannot create " + individualTmpDir);
563 }
564 }
565}
static File getTempDir(final boolean executable)
Returns a platform independent writable directory for temporary files consisting of the platform's te...
Definition: IOUtil.java:1246
static File testDir(final File dir, final boolean create, final boolean executable)
Returns the directory dir, which is processed and tested as described below.
Definition: IOUtil.java:1167
File getTempDir()
Temporary directory for individual files (eg.
boolean isValid(final boolean forExecutables)
static boolean initSingleton()
Documented way to kick off static initialization.
static File getRootDir()
Root temp directory for this JVM instance.
void destroy()
Delete the getTempDir() recursively and remove it's reference.
static File getBaseDir()
Base temp directory used by TempFileCache.