Skip to content

Commit 37503bc

Browse files
committed
Update Striped64 & DoubleAdded to avoid ClassLoader leak
1 parent b90fef7 commit 37503bc

2 files changed

Lines changed: 65 additions & 67 deletions

File tree

simpleclient/src/main/java/io/prometheus/client/DoubleAdder.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
* Written by Doug Lea with assistance from members of JCP JSR-166
33
* Expert Group and released to the public domain, as explained at
44
* http://creativecommons.org/publicdomain/zero/1.0/
5+
*
6+
* Source: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/DoubleAdder.java?revision=1.12
57
*/
68

79
package io.prometheus.client;
10+
811
import java.io.IOException;
9-
import java.io.Serializable;
1012
import java.io.ObjectInputStream;
1113
import java.io.ObjectOutputStream;
14+
import java.io.Serializable;
1215

1316
/**
1417
* One or more variables that together maintain an initially zero
@@ -19,12 +22,12 @@
1922
* variables maintaining the sum.
2023
*
2124
* <p>This class extends {@link Number}, but does <em>not</em> define
22-
* methods such as {@code hashCode} and {@code compareTo} because
23-
* instances are expected to be mutated, and so are not useful as
24-
* collection keys.
25+
* methods such as {@code equals}, {@code hashCode} and {@code
26+
* compareTo} because instances are expected to be mutated, and so are
27+
* not useful as collection keys.
2528
*
2629
* <p><em>jsr166e note: This class is targeted to be placed in
27-
* java.util.concurrent.atomic<em>
30+
* java.util.concurrent.atomic.</em>
2831
*
2932
* @since 1.8
3033
* @author Doug Lea
@@ -46,8 +49,8 @@ public class DoubleAdder extends Striped64 implements Serializable {
4649
*/
4750
final long fn(long v, long x) {
4851
return Double.doubleToRawLongBits
49-
(Double.longBitsToDouble(v) +
50-
Double.longBitsToDouble(x));
52+
(Double.longBitsToDouble(v) +
53+
Double.longBitsToDouble(x));
5154
}
5255

5356
/**
@@ -62,28 +65,28 @@ public DoubleAdder() {
6265
* @param x the value to add
6366
*/
6467
public void add(double x) {
65-
Cell[] as; long b, v; HashCode hc; Cell a; int n;
68+
Cell[] as; long b, v; int[] hc; Cell a; int n;
6669
if ((as = cells) != null ||
67-
!casBase(b = base,
68-
Double.doubleToRawLongBits
69-
(Double.longBitsToDouble(b) + x))) {
70+
!casBase(b = base,
71+
Double.doubleToRawLongBits
72+
(Double.longBitsToDouble(b) + x))) {
7073
boolean uncontended = true;
71-
int h = (hc = threadHashCode.get()).code;
72-
if (as == null || (n = as.length) < 1 ||
73-
(a = as[(n - 1) & h]) == null ||
74-
!(uncontended = a.cas(v = a.value,
75-
Double.doubleToRawLongBits
76-
(Double.longBitsToDouble(v) + x))))
74+
if ((hc = threadHashCode.get()) == null ||
75+
as == null || (n = as.length) < 1 ||
76+
(a = as[(n - 1) & hc[0]]) == null ||
77+
!(uncontended = a.cas(v = a.value,
78+
Double.doubleToRawLongBits
79+
(Double.longBitsToDouble(v) + x))))
7780
retryUpdate(Double.doubleToRawLongBits(x), hc, uncontended);
7881
}
7982
}
8083

8184
/**
8285
* Returns the current sum. The returned value is <em>NOT</em> an
83-
* atomic snapshot: Invocation in the absence of concurrent
86+
* atomic snapshot; invocation in the absence of concurrent
8487
* updates returns an accurate result, but concurrent updates that
8588
* occur while the sum is being calculated might not be
86-
* incorporated. Also, because double-precision arithmetic is not
89+
* incorporated. Also, because floating-point arithmetic is not
8790
* strictly associative, the returned result need not be identical
8891
* to the value that would be obtained in a sequential series of
8992
* updates to a single variable.
@@ -184,14 +187,13 @@ public float floatValue() {
184187
return (float)sum();
185188
}
186189

187-
private void writeObject(java.io.ObjectOutputStream s)
188-
throws java.io.IOException {
190+
private void writeObject(ObjectOutputStream s) throws IOException {
189191
s.defaultWriteObject();
190192
s.writeDouble(sum());
191193
}
192194

193195
private void readObject(ObjectInputStream s)
194-
throws IOException, ClassNotFoundException {
196+
throws IOException, ClassNotFoundException {
195197
s.defaultReadObject();
196198
busy = 0;
197199
cells = null;

simpleclient/src/main/java/io/prometheus/client/Striped64.java

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
* Written by Doug Lea with assistance from members of JCP JSR-166
33
* Expert Group and released to the public domain, as explained at
44
* http://creativecommons.org/publicdomain/zero/1.0/
5+
*
6+
* Source: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.10
57
*/
68

79
package io.prometheus.client;
10+
811
import java.util.Random;
912

1013
/**
@@ -40,7 +43,7 @@ abstract class Striped64 extends Number {
4043
*
4144
* A single spinlock ("busy") is used for initializing and
4245
* resizing the table, as well as populating slots with new Cells.
43-
* There is no need for a blocking lock: When the lock is not
46+
* There is no need for a blocking lock; when the lock is not
4447
* available, threads try other slots (or the base). During these
4548
* retries, there is increased contention and reduced locality,
4649
* which is still better than alternatives.
@@ -102,7 +105,7 @@ final boolean cas(long cmp, long val) {
102105
UNSAFE = getUnsafe();
103106
Class<?> ak = Cell.class;
104107
valueOffset = UNSAFE.objectFieldOffset
105-
(ak.getDeclaredField("value"));
108+
(ak.getDeclaredField("value"));
106109
} catch (Exception e) {
107110
throw new Error(e);
108111
}
@@ -111,32 +114,17 @@ final boolean cas(long cmp, long val) {
111114
}
112115

113116
/**
114-
* Holder for the thread-local hash code. The code is initially
115-
* random, but may be set to a different value upon collisions.
117+
* ThreadLocal holding a single-slot int array holding hash code.
118+
* Unlike the JDK8 version of this class, we use a suboptimal
119+
* int[] representation to avoid introducing a new type that can
120+
* impede class-unloading when ThreadLocals are not removed.
116121
*/
117-
static final class HashCode {
118-
static final Random rng = new Random();
119-
int code;
120-
HashCode() {
121-
int h = rng.nextInt(); // Avoid zero to allow xorShift rehash
122-
code = (h == 0) ? 1 : h;
123-
}
124-
}
125-
126-
/**
127-
* The corresponding ThreadLocal class
128-
*/
129-
static final class ThreadHashCode extends ThreadLocal<HashCode> {
130-
public HashCode initialValue() { return new HashCode(); }
131-
}
122+
static final ThreadLocal<int[]> threadHashCode = new ThreadLocal<int[]>();
132123

133124
/**
134-
* Static per-thread hash codes. Shared across all instances to
135-
* reduce ThreadLocal pollution and because adjustments due to
136-
* collisions in one table are likely to be appropriate for
137-
* others.
125+
* Generator of new random hash codes
138126
*/
139-
static final ThreadHashCode threadHashCode = new ThreadHashCode();
127+
static final Random rng = new Random();
140128

141129
/** Number of CPUS, to place bound on table size */
142130
static final int NCPU = Runtime.getRuntime().availableProcessors();
@@ -199,8 +187,15 @@ final boolean casBusy() {
199187
* @param hc the hash code holder
200188
* @param wasUncontended false if CAS failed before call
201189
*/
202-
final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
203-
int h = hc.code;
190+
final void retryUpdate(long x, int[] hc, boolean wasUncontended) {
191+
int h;
192+
if (hc == null) {
193+
threadHashCode.set(hc = new int[1]); // Initialize randomly
194+
int r = rng.nextInt(); // Avoid zero to allow xorShift rehash
195+
h = hc[0] = (r == 0) ? 1 : r;
196+
}
197+
else
198+
h = hc[0];
204199
boolean collide = false; // True if last slot nonempty
205200
for (;;) {
206201
Cell[] as; Cell a; int n; long v;
@@ -213,8 +208,8 @@ final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
213208
try { // Recheck under lock
214209
Cell[] rs; int m, j;
215210
if ((rs = cells) != null &&
216-
(m = rs.length) > 0 &&
217-
rs[j = (m - 1) & h] == null) {
211+
(m = rs.length) > 0 &&
212+
rs[j = (m - 1) & h] == null) {
218213
rs[j] = r;
219214
created = true;
220215
}
@@ -253,6 +248,7 @@ else if (busy == 0 && casBusy()) {
253248
h ^= h << 13; // Rehash
254249
h ^= h >>> 17;
255250
h ^= h << 5;
251+
hc[0] = h; // Record index for next time
256252
}
257253
else if (busy == 0 && cells == as && casBusy()) {
258254
boolean init = false;
@@ -272,7 +268,6 @@ else if (busy == 0 && cells == as && casBusy()) {
272268
else if (casBase(v = base, fn(v, x)))
273269
break; // Fall back on using base
274270
}
275-
hc.code = h; // Record index for next time
276271
}
277272

278273

@@ -301,9 +296,9 @@ final void internalReset(long initialValue) {
301296
UNSAFE = getUnsafe();
302297
Class<?> sk = Striped64.class;
303298
baseOffset = UNSAFE.objectFieldOffset
304-
(sk.getDeclaredField("base"));
299+
(sk.getDeclaredField("base"));
305300
busyOffset = UNSAFE.objectFieldOffset
306-
(sk.getDeclaredField("busy"));
301+
(sk.getDeclaredField("busy"));
307302
} catch (Exception e) {
308303
throw new Error(e);
309304
}
@@ -319,22 +314,23 @@ final void internalReset(long initialValue) {
319314
private static sun.misc.Unsafe getUnsafe() {
320315
try {
321316
return sun.misc.Unsafe.getUnsafe();
322-
} catch (SecurityException se) {
323-
try {
324-
return java.security.AccessController.doPrivileged
325-
(new java.security
326-
.PrivilegedExceptionAction<sun.misc.Unsafe>() {
317+
} catch (SecurityException tryReflectionInstead) {}
318+
try {
319+
return java.security.AccessController.doPrivileged
320+
(new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {
327321
public sun.misc.Unsafe run() throws Exception {
328-
java.lang.reflect.Field f = sun.misc
329-
.Unsafe.class.getDeclaredField("theUnsafe");
330-
f.setAccessible(true);
331-
return (sun.misc.Unsafe) f.get(null);
322+
Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
323+
for (java.lang.reflect.Field f : k.getDeclaredFields()) {
324+
f.setAccessible(true);
325+
Object x = f.get(null);
326+
if (k.isInstance(x))
327+
return k.cast(x);
328+
}
329+
throw new NoSuchFieldError("the Unsafe");
332330
}});
333-
} catch (java.security.PrivilegedActionException e) {
334-
throw new RuntimeException("Could not initialize intrinsics",
335-
e.getCause());
336-
}
331+
} catch (java.security.PrivilegedActionException e) {
332+
throw new RuntimeException("Could not initialize intrinsics",
333+
e.getCause());
337334
}
338335
}
339-
340336
}

0 commit comments

Comments
 (0)