Skip to content

Commit 90d7465

Browse files
committed
Fix hash function, refine logic and add lots of comments
Signed-off-by: nickhill <nickhill@us.ibm.com>
1 parent 80e3445 commit 90d7465

1 file changed

Lines changed: 45 additions & 22 deletions

File tree

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

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,7 @@ private static int labelsHashCode(String... labelValues) {
117117
}
118118

119119
private static int hash(int hashCode, int length) {
120-
// Multiply by -127, and left-shift to use least bit as part of hash
121-
// Based on the equivalent function in java.util.IdentityHashMap
122-
return ((hashCode << 1) - (hashCode << 8)) & (length - 1);
120+
return hashCode & (length - 1); // length is a power of 2
123121
}
124122

125123
/**
@@ -165,6 +163,9 @@ public Child labels() {
165163
validateCount(0);
166164
return noLabelsChild;
167165
}
166+
167+
// Logic for the fixed-arg overloads is identical apart from number of strings
168+
// used for hashcode generation and key comparison
168169

169170
public Child labels(String v1) {
170171
validateCount(1);
@@ -232,49 +233,64 @@ public Child labels(String v1, String v2, String v3, String v4) {
232233
}
233234

234235
private Child labelsMiss(int hashCode, ChildEntry<Child>[] arr, int pos, String... values) {
235-
return updateChild(values, hashCode, arr, pos,
236+
return updateChild(values, hashCode, true, arr, pos,
236237
new ChildEntry<Child>(values, hashCode, newChild()));
237238
}
238239

239240
/**
240241
* Multi-purpose used for set, remove and get after not found (cache miss)
241242
*
242-
* @param pos -1 for set and remove, otherwise insert (null) position in arr
243+
* @param getIfPresent if true, entry won't be updated if it already exists
244+
* @param pos insertion position for new entry if known (get case), otherwise -1
243245
* @param newEntry null for remove, otherwise new entry to add
244246
* @return new/existing Child in case of get, prior Child in case of set/remove
245247
*/
246248
@SuppressWarnings("unchecked")
247-
private Child updateChild(String[] values, int hashCode,
249+
private Child updateChild(String[] values, int hashCode, boolean getIfPresent,
248250
ChildEntry<Child>[] arr, int pos, ChildEntry<Child> newEntry) {
249-
final boolean set = pos == -1;
250-
int arrLen = arr.length;
251+
// This loop is just for retries after CAS failures of the children field
251252
while (true) {
253+
final int arrLen = arr.length;
252254
final ChildEntry<Child>[] newArr;
255+
// This gets set to the entry we're replacing, if/when applicable
253256
ChildEntry<Child> replacing = null;
257+
// If pos >= 0, we already know where the new entry should go
254258
if (pos == -1) {
259+
// Scan the table looking for the a matching entry starting at
260+
// the labels' hash position
255261
pos = hash(hashCode, arrLen);
256262
while (true) {
257263
ChildEntry<Child> ce = arr[pos];
258264
if (ce == null) {
265+
// If we reach a null entry then the labels aren't present, and
266+
// our current pos is the target location for inserting the new entry
259267
if (newEntry == null) {
268+
// Nothing to do in removal case
260269
return null;
261270
}
262271
break;
263272
}
264273
if (ce.hashCode == hashCode && Arrays.equals(ce.labels, values)) {
265-
if (set) {
266-
replacing = ce;
267-
break;
274+
// We found an entry matching the target labels
275+
if (getIfPresent) {
276+
return ce.child;
268277
}
269-
return ce.child;
278+
replacing = ce;
279+
break;
270280
}
271-
pos = nextIdx(pos, arrLen);
281+
pos = nextIdx(pos, arrLen); // wrap around
272282
}
273283
}
274284
boolean resizeNeeded;
285+
// If we reached here we'll be updating the table by either inserting
286+
// replacing, or removing an entry
275287
if (newEntry == null | replacing != null) {
288+
// No need to increase the table capacity if removing or replacing
276289
resizeNeeded = false;
277290
} else {
291+
// We are inserting a new entry so count the existing entries to determine
292+
// how full the table is - we resize (double) it if greater than half full.
293+
// This avoids having to separately track the count
278294
int count = 1;
279295
for (ChildEntry<Child> ce : arr) {
280296
if (ce != null) {
@@ -284,10 +300,14 @@ private Child updateChild(String[] values, int hashCode,
284300
resizeNeeded = count > arrLen / 2;
285301
}
286302
if (resizeNeeded) {
287-
newArr = new ChildEntry[arrLen * 2];
288-
int newArrLen = newArr.length;
303+
// Double the size of the table
304+
final int newArrLen = arrLen * 2;
305+
newArr = new ChildEntry[newArrLen];
306+
// Insert our new entry first
289307
newArr[hash(hashCode, newArrLen)] = newEntry;
308+
// Then re-hash/insert the existing entries
290309
for (ChildEntry<Child> ce : arr) {
310+
// Exclude the entry we're replacing (if applicable - replacing will be non-null)
291311
if (ce != null & ce != replacing) {
292312
for (int j = hash(ce.hashCode, newArrLen);; j = nextIdx(j, newArrLen)) {
293313
if (newArr[j] == null) {
@@ -298,17 +318,20 @@ private Child updateChild(String[] values, int hashCode,
298318
}
299319
}
300320
} else {
321+
// If we're not resizing the table, just make a straight copy
322+
// and insert our new entry at the target position
301323
newArr = Arrays.copyOf(arr, arrLen, ChildEntry[].class);
302324
newArr[pos] = newEntry;
303325
}
326+
// Attempt to update the shared field atomically
304327
if (UPDATER.compareAndSet(this, arr, newArr)) {
305-
if (set) {
306-
return replacing != null ? replacing.child : null;
307-
}
308-
return newEntry.child;
328+
// Successful table modification
329+
// Return the new entry in the get case, old entry in the remove/set cases
330+
return getIfPresent ? newEntry.child
331+
: (replacing != null ? replacing.child : null);
309332
}
333+
// Someone else changed the table, read it and start again
310334
arr = children;
311-
arrLen = arr.length;
312335
pos = -1;
313336
}
314337
}
@@ -319,7 +342,7 @@ private Child updateChild(String[] values, int hashCode,
319342
* Any references to the Child are invalidated.
320343
*/
321344
public void remove(String... labelValues) {
322-
updateChild(labelValues, labelsHashCode(labelValues), children, -1, null);
345+
updateChild(labelValues, labelsHashCode(labelValues), false, children, -1, null);
323346
initializeNoLabelsChild();
324347
}
325348

@@ -369,7 +392,7 @@ protected void initializeNoLabelsChild() {
369392
public <T extends Collector> T setChild(Child child, String... labelValues) {
370393
validateCount(labelValues.length);
371394
int hashCode = labelsHashCode(labelValues);
372-
updateChild(labelValues, hashCode, children, -1,
395+
updateChild(labelValues, hashCode, false, children, -1,
373396
new ChildEntry<Child>(labelValues, hashCode, child));
374397
return (T)this;
375398
}

0 commit comments

Comments
 (0)