forked from getsentry/sentry-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHostnameCache.java
More file actions
142 lines (121 loc) · 4.65 KB
/
HostnameCache.java
File metadata and controls
142 lines (121 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package io.sentry;
import io.sentry.util.Objects;
import java.net.InetAddress;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Time sensitive cache in charge of keeping track of the hostname. The {@code
* InetAddress.getLocalHost().getCanonicalHostName()} call can be quite expensive and could be
* called for the creation of each {@link SentryEvent}. This system will prevent unnecessary costs
* by keeping track of the hostname for a period defined during the construction. For performance
* purposes, the operation of retrieving the hostname will automatically fail after a period of time
* defined by {@link #GET_HOSTNAME_TIMEOUT} without result.
*
* <p>HostnameCache is a singleton and its instance should be obtained through {@link
* HostnameCache#getInstance()}.
*/
final class HostnameCache {
private static final long HOSTNAME_CACHE_DURATION = TimeUnit.HOURS.toMillis(5);
/** Time before the get hostname operation times out (in ms). */
private static final long GET_HOSTNAME_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
@Nullable private static HostnameCache INSTANCE;
/** Time for which the cache is kept. */
private final long cacheDuration;
/** Current value for hostname (might change over time). */
@Nullable private volatile String hostname;
/** Time at which the cache should expire. */
private volatile long expirationTimestamp;
/** Whether a cache update thread is currently running or not. */
private final @NotNull AtomicBoolean updateRunning = new AtomicBoolean(false);
private final @NotNull Callable<InetAddress> getLocalhost;
private final @NotNull ExecutorService executorService =
Executors.newSingleThreadExecutor(new HostnameCacheThreadFactory());
static @NotNull HostnameCache getInstance() {
if (INSTANCE == null) {
INSTANCE = new HostnameCache();
}
return INSTANCE;
}
private HostnameCache() {
this(HOSTNAME_CACHE_DURATION);
}
HostnameCache(long cacheDuration) {
this(cacheDuration, () -> InetAddress.getLocalHost());
}
/**
* Sets up a cache for the hostname.
*
* @param cacheDuration cache duration in milliseconds.
* @param getLocalhost a callback to obtain the localhost address - this is mostly here because of
* testability
*/
HostnameCache(long cacheDuration, final @NotNull Callable<InetAddress> getLocalhost) {
this.cacheDuration = cacheDuration;
this.getLocalhost = Objects.requireNonNull(getLocalhost, "getLocalhost is required");
updateCache();
}
void close() {
this.executorService.shutdown();
}
boolean isClosed() {
return this.executorService.isShutdown();
}
/**
* Gets the hostname of the current machine.
*
* <p>Gets the value from the cache if possible otherwise calls {@link #updateCache()}.
*
* @return the hostname of the current machine.
*/
@Nullable
String getHostname() {
if (expirationTimestamp < System.currentTimeMillis()
&& updateRunning.compareAndSet(false, true)) {
updateCache();
}
return hostname;
}
/** Force an update of the cache to get the current value of the hostname. */
private void updateCache() {
final Callable<Void> hostRetriever =
() -> {
try {
hostname = getLocalhost.call().getCanonicalHostName();
expirationTimestamp = System.currentTimeMillis() + cacheDuration;
} finally {
updateRunning.set(false);
}
return null;
};
try {
final Future<Void> futureTask = executorService.submit(hostRetriever);
futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
handleCacheUpdateFailure();
} catch (ExecutionException | TimeoutException | RuntimeException e) {
handleCacheUpdateFailure();
}
}
private void handleCacheUpdateFailure() {
expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1);
}
private static final class HostnameCacheThreadFactory implements ThreadFactory {
private int cnt;
@Override
public @NotNull Thread newThread(final @NotNull Runnable r) {
final Thread ret = new Thread(r, "SentryHostnameCache-" + cnt++);
ret.setDaemon(true);
return ret;
}
}
}