Skip to content

Commit ab79d24

Browse files
SNOW-1895884: Token cache refactor (#2044)
1 parent 2d36a01 commit ab79d24

16 files changed

+692
-395
lines changed

src/main/java/net/snowflake/client/config/SFClientConfigParser.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ public static SFClientConfig loadSFClientConfig(String configFilePath) throws IO
5858
derivedConfigFilePath = driverLocation;
5959
} else {
6060
// 4. Read SF_CLIENT_CONFIG_FILE_NAME if it is present in user home directory.
61-
String userHomeFilePath =
62-
Paths.get(systemGetProperty("user.home"), SF_CLIENT_CONFIG_FILE_NAME).toString();
63-
if (Files.exists(Paths.get(userHomeFilePath))) {
64-
logger.info("Using config file specified from home directory: {}", userHomeFilePath);
65-
derivedConfigFilePath = userHomeFilePath;
61+
String homeDirectory = systemGetProperty("user.home");
62+
if (homeDirectory != null) {
63+
String userHomeFilePath = Paths.get(homeDirectory, SF_CLIENT_CONFIG_FILE_NAME).toString();
64+
if (Files.exists(Paths.get(userHomeFilePath))) {
65+
logger.info("Using config file specified from home directory: {}", userHomeFilePath);
66+
derivedConfigFilePath = userHomeFilePath;
67+
}
6668
}
6769
}
6870
}

src/main/java/net/snowflake/client/core/FileCacheManager.java

+138-98
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package net.snowflake.client.core;
66

7+
import static net.snowflake.client.core.FileUtil.isWritable;
78
import static net.snowflake.client.jdbc.SnowflakeUtil.isWindows;
89
import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetEnv;
910
import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty;
@@ -27,6 +28,7 @@
2728
import java.nio.file.attribute.PosixFilePermission;
2829
import java.nio.file.attribute.PosixFilePermissions;
2930
import java.util.Date;
31+
import java.util.function.Supplier;
3032
import java.util.stream.Collectors;
3133
import java.util.stream.Stream;
3234
import net.snowflake.client.log.SFLogger;
@@ -43,7 +45,6 @@ class FileCacheManager {
4345
private String cacheDirectorySystemProperty;
4446
private String cacheDirectoryEnvironmentVariable;
4547
private String baseCacheFileName;
46-
private long cacheExpirationInMilliseconds;
4748
private long cacheFileLockExpirationInMilliseconds;
4849

4950
private File cacheFile;
@@ -74,12 +75,6 @@ FileCacheManager setBaseCacheFileName(String baseCacheFileName) {
7475
return this;
7576
}
7677

77-
FileCacheManager setCacheExpirationInSeconds(long cacheExpirationInSeconds) {
78-
// converting from seconds to milliseconds
79-
this.cacheExpirationInMilliseconds = cacheExpirationInSeconds * 1000;
80-
return this;
81-
}
82-
8378
FileCacheManager setCacheFileLockExpirationInSeconds(long cacheFileLockExpirationInSeconds) {
8479
this.cacheFileLockExpirationInMilliseconds = cacheFileLockExpirationInSeconds * 1000;
8580
return this;
@@ -90,17 +85,22 @@ FileCacheManager setOnlyOwnerPermissions(boolean onlyOwnerPermissions) {
9085
return this;
9186
}
9287

88+
synchronized String getCacheFilePath() {
89+
return cacheFile.getAbsolutePath();
90+
}
91+
9392
/**
9493
* Override the cache file.
9594
*
9695
* @param newCacheFile a file object to override the default one.
9796
*/
98-
void overrideCacheFile(File newCacheFile) {
97+
synchronized void overrideCacheFile(File newCacheFile) {
9998
if (!newCacheFile.exists()) {
10099
logger.debug("Cache file doesn't exists. File: {}", newCacheFile);
101100
}
102101
if (onlyOwnerPermissions) {
103-
FileUtil.throwWhenPermiossionDifferentThanReadWriteForOwner(
102+
FileUtil.throwWhenFilePermissionsWiderThanUserOnly(newCacheFile, "Override cache file");
103+
FileUtil.throwWhenParentDirectoryPermissionsWiderThanUserOnly(
104104
newCacheFile, "Override cache file");
105105
} else {
106106
FileUtil.logFileUsage(cacheFile, "Override cache file", false);
@@ -110,7 +110,7 @@ void overrideCacheFile(File newCacheFile) {
110110
this.baseCacheFileName = newCacheFile.getName();
111111
}
112112

113-
FileCacheManager build() {
113+
synchronized FileCacheManager build() {
114114
// try to get cacheDir from system property or environment variable
115115
String cacheDirPath =
116116
this.cacheDirectorySystemProperty != null
@@ -134,32 +134,30 @@ FileCacheManager build() {
134134
if (cacheDirPath != null) {
135135
this.cacheDir = new File(cacheDirPath);
136136
} 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());
149157
return this;
150158
}
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-
}
160159
}
161-
162-
if (!this.cacheDir.mkdirs() && !this.cacheDir.exists()) {
160+
if (!this.cacheDir.exists()) {
163161
logger.debug(
164162
"Cannot create the cache directory {}. Giving up.", this.cacheDir.getAbsolutePath());
165163
return this;
@@ -199,12 +197,72 @@ FileCacheManager build() {
199197
return this;
200198
}
201199

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);
206246
return null;
247+
} else if (cacheLockFile.exists()) {
248+
deleteCacheLockIfExpired();
207249
}
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() {
208266
try {
209267
if (!cacheFile.exists()) {
210268
logger.debug("Cache file doesn't exists. File: {}", cacheFile);
@@ -215,7 +273,8 @@ JsonNode readCacheFile() {
215273
new InputStreamReader(new FileInputStream(cacheFile), DEFAULT_FILE_ENCODING)) {
216274

217275
if (onlyOwnerPermissions) {
218-
FileUtil.throwWhenPermiossionDifferentThanReadWriteForOwner(cacheFile, "Read cache");
276+
FileUtil.throwWhenFilePermissionsWiderThanUserOnly(cacheFile, "Read cache");
277+
FileUtil.throwWhenParentDirectoryPermissionsWiderThanUserOnly(cacheFile, "Read cache");
219278
FileUtil.throwWhenOwnerDifferentThanCurrentUser(cacheFile, "Read cache");
220279
} else {
221280
FileUtil.logFileUsage(cacheFile, "Read cache", false);
@@ -228,38 +287,29 @@ JsonNode readCacheFile() {
228287
return null;
229288
}
230289

231-
void writeCacheFile(JsonNode input) {
290+
synchronized void writeCacheFile(JsonNode input) {
232291
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
240292
try {
241293
if (input == null) {
242294
return;
243295
}
244296
try (Writer writer =
245297
new OutputStreamWriter(new FileOutputStream(cacheFile), DEFAULT_FILE_ENCODING)) {
246298
if (onlyOwnerPermissions) {
247-
FileUtil.throwWhenPermiossionDifferentThanReadWriteForOwner(cacheFile, "Write to cache");
299+
FileUtil.throwWhenFilePermissionsWiderThanUserOnly(cacheFile, "Write to cache");
300+
FileUtil.throwWhenParentDirectoryPermissionsWiderThanUserOnly(
301+
cacheFile, "Write to cache");
248302
} else {
249303
FileUtil.logFileUsage(cacheFile, "Write to cache", false);
250304
}
251305
writer.write(input.toString());
252306
}
253307
} catch (IOException ex) {
254308
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-
}
259309
}
260310
}
261311

262-
void deleteCacheFile() {
312+
synchronized void deleteCacheFile() {
263313
logger.debug("Deleting cache file. File: {}, lock file: {}", cacheFile, cacheLockFile);
264314

265315
if (cacheFile == null) {
@@ -277,76 +327,52 @@ void deleteCacheFile() {
277327
*
278328
* @return true if success or false
279329
*/
280-
private boolean tryLockCacheFile() {
330+
private synchronized boolean tryToLockCacheFile() {
281331
int cnt = 0;
282332
boolean locked = false;
283-
while (cnt < 100 && !(locked = lockCacheFile())) {
333+
while (cnt < 5 && !(locked = lockCacheFile())) {
284334
try {
285-
Thread.sleep(100);
335+
Thread.sleep(10);
286336
} catch (InterruptedException ex) {
287337
// doesn't matter
288338
}
289339
++cnt;
290340
}
291341
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+
}
293346
}
294347
return locked;
295348
}
296349

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() {
316351
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-
326352
long lockFileTs = fileCreationTime(cacheLockFile);
327353
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) {
332356
// 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());
336366
}
337-
logger.debug("Deleted the cache lock directory, because it was old.", false);
338-
return currentTime - this.cacheExpirationInMilliseconds <= cacheFileTs;
339367
}
340-
logger.debug("Failed to lock the file. Ignored.", false);
341-
return false;
342368
}
343369

344370
/**
345371
* Gets file/dir creation time in epoch (ms)
346372
*
347373
* @return epoch time in ms
348374
*/
349-
private static long fileCreationTime(File targetFile) {
375+
private static synchronized long fileCreationTime(File targetFile) {
350376
if (!targetFile.exists()) {
351377
logger.debug("File not exists. File: {}", targetFile);
352378
return -1;
@@ -361,7 +387,21 @@ private static long fileCreationTime(File targetFile) {
361387
return -1;
362388
}
363389

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();
366406
}
367407
}

0 commit comments

Comments
 (0)