4
4
5
5
package net .snowflake .client .core ;
6
6
7
+ import static net .snowflake .client .core .FileUtil .isWritable ;
7
8
import static net .snowflake .client .jdbc .SnowflakeUtil .isWindows ;
8
9
import static net .snowflake .client .jdbc .SnowflakeUtil .systemGetEnv ;
9
10
import static net .snowflake .client .jdbc .SnowflakeUtil .systemGetProperty ;
27
28
import java .nio .file .attribute .PosixFilePermission ;
28
29
import java .nio .file .attribute .PosixFilePermissions ;
29
30
import java .util .Date ;
31
+ import java .util .function .Supplier ;
30
32
import java .util .stream .Collectors ;
31
33
import java .util .stream .Stream ;
32
34
import net .snowflake .client .log .SFLogger ;
@@ -43,7 +45,6 @@ class FileCacheManager {
43
45
private String cacheDirectorySystemProperty ;
44
46
private String cacheDirectoryEnvironmentVariable ;
45
47
private String baseCacheFileName ;
46
- private long cacheExpirationInMilliseconds ;
47
48
private long cacheFileLockExpirationInMilliseconds ;
48
49
49
50
private File cacheFile ;
@@ -74,12 +75,6 @@ FileCacheManager setBaseCacheFileName(String baseCacheFileName) {
74
75
return this ;
75
76
}
76
77
77
- FileCacheManager setCacheExpirationInSeconds (long cacheExpirationInSeconds ) {
78
- // converting from seconds to milliseconds
79
- this .cacheExpirationInMilliseconds = cacheExpirationInSeconds * 1000 ;
80
- return this ;
81
- }
82
-
83
78
FileCacheManager setCacheFileLockExpirationInSeconds (long cacheFileLockExpirationInSeconds ) {
84
79
this .cacheFileLockExpirationInMilliseconds = cacheFileLockExpirationInSeconds * 1000 ;
85
80
return this ;
@@ -90,17 +85,22 @@ FileCacheManager setOnlyOwnerPermissions(boolean onlyOwnerPermissions) {
90
85
return this ;
91
86
}
92
87
88
+ synchronized String getCacheFilePath () {
89
+ return cacheFile .getAbsolutePath ();
90
+ }
91
+
93
92
/**
94
93
* Override the cache file.
95
94
*
96
95
* @param newCacheFile a file object to override the default one.
97
96
*/
98
- void overrideCacheFile (File newCacheFile ) {
97
+ synchronized void overrideCacheFile (File newCacheFile ) {
99
98
if (!newCacheFile .exists ()) {
100
99
logger .debug ("Cache file doesn't exists. File: {}" , newCacheFile );
101
100
}
102
101
if (onlyOwnerPermissions ) {
103
- FileUtil .throwWhenPermiossionDifferentThanReadWriteForOwner (
102
+ FileUtil .throwWhenFilePermissionsWiderThanUserOnly (newCacheFile , "Override cache file" );
103
+ FileUtil .throwWhenParentDirectoryPermissionsWiderThanUserOnly (
104
104
newCacheFile , "Override cache file" );
105
105
} else {
106
106
FileUtil .logFileUsage (cacheFile , "Override cache file" , false );
@@ -110,7 +110,7 @@ void overrideCacheFile(File newCacheFile) {
110
110
this .baseCacheFileName = newCacheFile .getName ();
111
111
}
112
112
113
- FileCacheManager build () {
113
+ synchronized FileCacheManager build () {
114
114
// try to get cacheDir from system property or environment variable
115
115
String cacheDirPath =
116
116
this .cacheDirectorySystemProperty != null
@@ -134,32 +134,30 @@ FileCacheManager build() {
134
134
if (cacheDirPath != null ) {
135
135
this .cacheDir = new File (cacheDirPath );
136
136
} else {
137
- // use user home directory to store the cache file
138
- String homeDir = systemGetProperty ("user.home" );
139
- if (homeDir != null ) {
140
- // Checking if home directory is writable.
141
- File homeFile = new File (homeDir );
142
- if (!homeFile .canWrite ()) {
143
- logger .debug ("Home directory not writeable, skip using cache" , false );
144
- homeDir = null ;
145
- }
146
- }
147
- if (homeDir == null ) {
148
- // if still home directory is null, no cache dir is set.
137
+ this .cacheDir = getDefaultCacheDir ();
138
+ }
139
+ if (cacheDir == null ) {
140
+ return this ;
141
+ }
142
+ if (!cacheDir .exists ()) {
143
+ try {
144
+ Files .createDirectories (
145
+ cacheDir .toPath (),
146
+ PosixFilePermissions .asFileAttribute (
147
+ Stream .of (
148
+ PosixFilePermission .OWNER_READ ,
149
+ PosixFilePermission .OWNER_WRITE ,
150
+ PosixFilePermission .OWNER_EXECUTE )
151
+ .collect (Collectors .toSet ())));
152
+ } catch (IOException e ) {
153
+ logger .info (
154
+ "Failed to create the cache directory: {}. Ignored. {}" ,
155
+ e .getMessage (),
156
+ cacheDir .getAbsoluteFile ());
149
157
return this ;
150
158
}
151
- if (Constants .getOS () == Constants .OS .WINDOWS ) {
152
- this .cacheDir =
153
- new File (
154
- new File (new File (new File (homeDir , "AppData" ), "Local" ), "Snowflake" ), "Caches" );
155
- } else if (Constants .getOS () == Constants .OS .MAC ) {
156
- this .cacheDir = new File (new File (new File (homeDir , "Library" ), "Caches" ), "Snowflake" );
157
- } else {
158
- this .cacheDir = new File (new File (homeDir , ".cache" ), "snowflake" );
159
- }
160
159
}
161
-
162
- if (!this .cacheDir .mkdirs () && !this .cacheDir .exists ()) {
160
+ if (!this .cacheDir .exists ()) {
163
161
logger .debug (
164
162
"Cannot create the cache directory {}. Giving up." , this .cacheDir .getAbsolutePath ());
165
163
return this ;
@@ -199,12 +197,72 @@ FileCacheManager build() {
199
197
return this ;
200
198
}
201
199
202
- /** Reads the cache file. */
203
- JsonNode readCacheFile () {
204
- if (cacheFile == null || !this .checkCacheLockFile ()) {
205
- // no cache or the cache is not valid.
200
+ static File getDefaultCacheDir () {
201
+ if (Constants .getOS () == Constants .OS .LINUX ) {
202
+ String xdgCacheHome = getXdgCacheHome ();
203
+ if (xdgCacheHome != null ) {
204
+ return new File (xdgCacheHome , "snowflake" );
205
+ }
206
+ }
207
+
208
+ String homeDir = getHomeDirProperty ();
209
+ if (homeDir == null ) {
210
+ // if still home directory is null, no cache dir is set.
211
+ return null ;
212
+ }
213
+ if (Constants .getOS () == Constants .OS .WINDOWS ) {
214
+ return new File (
215
+ new File (new File (new File (homeDir , "AppData" ), "Local" ), "Snowflake" ), "Caches" );
216
+ } else if (Constants .getOS () == Constants .OS .MAC ) {
217
+ return new File (new File (new File (homeDir , "Library" ), "Caches" ), "Snowflake" );
218
+ } else {
219
+ return new File (new File (homeDir , ".cache" ), "snowflake" );
220
+ }
221
+ }
222
+
223
+ private static String getXdgCacheHome () {
224
+ String xdgCacheHome = systemGetEnv ("XDG_CACHE_HOME" );
225
+ if (xdgCacheHome != null && isWritable (xdgCacheHome )) {
226
+ return xdgCacheHome ;
227
+ }
228
+ return null ;
229
+ }
230
+
231
+ private static String getHomeDirProperty () {
232
+ String homeDir = systemGetProperty ("user.home" );
233
+ if (homeDir != null && isWritable (homeDir )) {
234
+ return homeDir ;
235
+ }
236
+ return null ;
237
+ }
238
+
239
+ synchronized <T > T withLock (Supplier <T > supplier ) {
240
+ if (cacheFile == null ) {
241
+ logger .error ("No cache file assigned" , false );
242
+ return null ;
243
+ }
244
+ if (cacheLockFile == null ) {
245
+ logger .error ("No cache lock file assigned" , false );
206
246
return null ;
247
+ } else if (cacheLockFile .exists ()) {
248
+ deleteCacheLockIfExpired ();
207
249
}
250
+
251
+ if (!tryToLockCacheFile ()) {
252
+ logger .debug ("Failed to lock the file. Skipping cache operation" , false );
253
+ return null ;
254
+ }
255
+ try {
256
+ return supplier .get ();
257
+ } finally {
258
+ if (!unlockCacheFile ()) {
259
+ logger .debug ("Failed to unlock cache file" , false );
260
+ }
261
+ }
262
+ }
263
+
264
+ /** Reads the cache file. */
265
+ synchronized JsonNode readCacheFile () {
208
266
try {
209
267
if (!cacheFile .exists ()) {
210
268
logger .debug ("Cache file doesn't exists. File: {}" , cacheFile );
@@ -215,7 +273,8 @@ JsonNode readCacheFile() {
215
273
new InputStreamReader (new FileInputStream (cacheFile ), DEFAULT_FILE_ENCODING )) {
216
274
217
275
if (onlyOwnerPermissions ) {
218
- FileUtil .throwWhenPermiossionDifferentThanReadWriteForOwner (cacheFile , "Read cache" );
276
+ FileUtil .throwWhenFilePermissionsWiderThanUserOnly (cacheFile , "Read cache" );
277
+ FileUtil .throwWhenParentDirectoryPermissionsWiderThanUserOnly (cacheFile , "Read cache" );
219
278
FileUtil .throwWhenOwnerDifferentThanCurrentUser (cacheFile , "Read cache" );
220
279
} else {
221
280
FileUtil .logFileUsage (cacheFile , "Read cache" , false );
@@ -228,38 +287,29 @@ JsonNode readCacheFile() {
228
287
return null ;
229
288
}
230
289
231
- void writeCacheFile (JsonNode input ) {
290
+ synchronized void writeCacheFile (JsonNode input ) {
232
291
logger .debug ("Writing cache file. File: {}" , cacheFile );
233
- if (cacheFile == null || !tryLockCacheFile ()) {
234
- // no cache file or it failed to lock file
235
- logger .debug (
236
- "No cache file exists or failed to lock the file. Skipping writing the cache" , false );
237
- return ;
238
- }
239
- // NOTE: must unlock cache file
240
292
try {
241
293
if (input == null ) {
242
294
return ;
243
295
}
244
296
try (Writer writer =
245
297
new OutputStreamWriter (new FileOutputStream (cacheFile ), DEFAULT_FILE_ENCODING )) {
246
298
if (onlyOwnerPermissions ) {
247
- FileUtil .throwWhenPermiossionDifferentThanReadWriteForOwner (cacheFile , "Write to cache" );
299
+ FileUtil .throwWhenFilePermissionsWiderThanUserOnly (cacheFile , "Write to cache" );
300
+ FileUtil .throwWhenParentDirectoryPermissionsWiderThanUserOnly (
301
+ cacheFile , "Write to cache" );
248
302
} else {
249
303
FileUtil .logFileUsage (cacheFile , "Write to cache" , false );
250
304
}
251
305
writer .write (input .toString ());
252
306
}
253
307
} catch (IOException ex ) {
254
308
logger .debug ("Failed to write the cache file. File: {}" , cacheFile );
255
- } finally {
256
- if (!unlockCacheFile ()) {
257
- logger .debug ("Failed to unlock cache file" , false );
258
- }
259
309
}
260
310
}
261
311
262
- void deleteCacheFile () {
312
+ synchronized void deleteCacheFile () {
263
313
logger .debug ("Deleting cache file. File: {}, lock file: {}" , cacheFile , cacheLockFile );
264
314
265
315
if (cacheFile == null ) {
@@ -277,76 +327,52 @@ void deleteCacheFile() {
277
327
*
278
328
* @return true if success or false
279
329
*/
280
- private boolean tryLockCacheFile () {
330
+ private synchronized boolean tryToLockCacheFile () {
281
331
int cnt = 0 ;
282
332
boolean locked = false ;
283
- while (cnt < 100 && !(locked = lockCacheFile ())) {
333
+ while (cnt < 5 && !(locked = lockCacheFile ())) {
284
334
try {
285
- Thread .sleep (100 );
335
+ Thread .sleep (10 );
286
336
} catch (InterruptedException ex ) {
287
337
// doesn't matter
288
338
}
289
339
++cnt ;
290
340
}
291
341
if (!locked ) {
292
- logger .debug ("Failed to lock the cache file." , false );
342
+ deleteCacheLockIfExpired ();
343
+ if (!lockCacheFile ()) {
344
+ logger .debug ("Failed to lock the cache file." , false );
345
+ }
293
346
}
294
347
return locked ;
295
348
}
296
349
297
- /**
298
- * Lock cache file by creating a lock directory
299
- *
300
- * @return true if success or false
301
- */
302
- private boolean lockCacheFile () {
303
- return cacheLockFile .mkdirs ();
304
- }
305
-
306
- /**
307
- * Unlock cache file by deleting a lock directory
308
- *
309
- * @return true if success or false
310
- */
311
- private boolean unlockCacheFile () {
312
- return cacheLockFile .delete ();
313
- }
314
-
315
- private boolean checkCacheLockFile () {
350
+ private synchronized void deleteCacheLockIfExpired () {
316
351
long currentTime = new Date ().getTime ();
317
- long cacheFileTs = fileCreationTime (cacheFile );
318
-
319
- if (!cacheLockFile .exists ()
320
- && cacheFileTs > 0
321
- && currentTime - this .cacheExpirationInMilliseconds <= cacheFileTs ) {
322
- logger .debug ("No cache file lock directory exists and cache file is up to date." , false );
323
- return true ;
324
- }
325
-
326
352
long lockFileTs = fileCreationTime (cacheLockFile );
327
353
if (lockFileTs < 0 ) {
328
- // failed to get the timestamp of lock directory
329
- return false ;
330
- }
331
- if (lockFileTs < currentTime - this .cacheFileLockExpirationInMilliseconds ) {
354
+ logger .debug ("Failed to get the timestamp of lock directory" );
355
+ } else if (lockFileTs < currentTime - this .cacheFileLockExpirationInMilliseconds ) {
332
356
// old lock file
333
- if (!cacheLockFile .delete ()) {
334
- logger .debug ("Failed to delete the directory. Dir: {}" , cacheLockFile );
335
- return false ;
357
+ try {
358
+ if (!cacheLockFile .delete ()) {
359
+ logger .debug ("Failed to delete the directory. Dir: {}" , cacheLockFile );
360
+ } else {
361
+ logger .debug ("Deleted expired cache lock directory." , false );
362
+ }
363
+ } catch (Exception e ) {
364
+ logger .debug (
365
+ "Failed to delete the directory. Dir: {}, Error: {}" , cacheLockFile , e .getMessage ());
336
366
}
337
- logger .debug ("Deleted the cache lock directory, because it was old." , false );
338
- return currentTime - this .cacheExpirationInMilliseconds <= cacheFileTs ;
339
367
}
340
- logger .debug ("Failed to lock the file. Ignored." , false );
341
- return false ;
342
368
}
343
369
344
370
/**
345
371
* Gets file/dir creation time in epoch (ms)
346
372
*
347
373
* @return epoch time in ms
348
374
*/
349
- private static long fileCreationTime (File targetFile ) {
375
+ private static synchronized long fileCreationTime (File targetFile ) {
350
376
if (!targetFile .exists ()) {
351
377
logger .debug ("File not exists. File: {}" , targetFile );
352
378
return -1 ;
@@ -361,7 +387,21 @@ private static long fileCreationTime(File targetFile) {
361
387
return -1 ;
362
388
}
363
389
364
- String getCacheFilePath () {
365
- return cacheFile .getAbsolutePath ();
390
+ /**
391
+ * Lock cache file by creating a lock directory
392
+ *
393
+ * @return true if success or false
394
+ */
395
+ private synchronized boolean lockCacheFile () {
396
+ return cacheLockFile .mkdirs ();
397
+ }
398
+
399
+ /**
400
+ * Unlock cache file by deleting a lock directory
401
+ *
402
+ * @return true if success or false
403
+ */
404
+ private synchronized boolean unlockCacheFile () {
405
+ return cacheLockFile .delete ();
366
406
}
367
407
}
0 commit comments