Repository navigation
Expand file tree
/
Copy pathHashStore.java
More file actions
372 lines (332 loc) · 12.2 KB
/
Copy pathHashStore.java
File metadata and controls
372 lines (332 loc) · 12.2 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
package datadog.trace.api;
import datadog.apicontract.Restricted;
import datadog.trace.api.TagMap.BucketGroup;
import datadog.trace.api.TagMap.Entry;
import datadog.trace.api.TagMap.EntryReader;
import datadog.trace.api.function.TriConsumer;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* The custom-tag half of a {@link TagMap}: custom tags by name, in their own bucket array ({@link
* HashSupport}), reading through to the frozen parent's custom store. Name-keyed methods take only
* custom tag names (asserted): a known tag is resolved to its id first, and goes to the {@link
* IdStore}.
*
* <p>Created lazily by its owner -- a {@link TagMap}, or a span in the tracer core -- on the first
* custom write ({@link #create}); until then the owner borrows its parent's custom store, if any,
* read-only. No locking: guarded by the owner's lock.
*/
@Restricted(allowedIn = {"datadog.trace.core"})
public final class HashStore {
final Object[] buckets;
/** Local entries. */
int count;
/** The frozen parent's custom store, or null. */
final HashStore parent;
/**
* Parent keys removed locally (read-through tombstones): a tombstone stops read-through for its
* key. Allocated on the first such removal, so while it is {@code null} the hot paths skip the
* tombstone checks.
*/
private Set<String> removedFromParent;
/**
* {@code parent}'s custom store, for an owner to borrow read-only until its first custom write,
* or null.
*/
public static HashStore inheritedFrom(TagMap parent) {
return parent == null ? null : parent.nameStore;
}
/**
* A fresh custom store for an owner's first custom write, reading through to {@code borrowed},
* the store it borrowed until now (may be null).
*/
public static HashStore create(HashStore borrowed) {
return new HashStore(borrowed);
}
HashStore(HashStore parent) {
this.buckets = HashSupport.newBuckets();
this.parent = parent;
}
/** Exact count, including the visible part of the read-through chain. */
int size() {
return this.parent == null ? this.count : this.count + this.visibleParentCount();
}
/** Ancestor entries not shadowed or tombstoned by a nearer level, nearest-level-wins. */
private int visibleParentCount() {
// a plain loop, not forEachParent: counting through a lambda would allocate a counter
int count = 0;
for (HashStore ancestor = this.parent; ancestor != null; ancestor = ancestor.parent) {
Object[] parentBuckets = ancestor.buckets;
for (int i = 0; i < parentBuckets.length; ++i) {
Object bucket = parentBuckets[i];
if (bucket instanceof Entry) {
Entry entry = (Entry) bucket;
if (this.parentEntryVisible(entry, ancestor)) {
count++;
}
} else if (bucket instanceof BucketGroup) {
for (BucketGroup group = (BucketGroup) bucket; group != null; group = group.prev) {
for (int j = 0; j < BucketGroup.LEN; ++j) {
Entry entry = group._entryAt(j);
if (entry != null && this.parentEntryVisible(entry, ancestor)) {
count++;
}
}
}
}
}
}
return count;
}
boolean isEmpty() {
if (this.count != 0) {
return false;
}
HashStore parent = this.parent;
if (parent == null) {
return true;
}
if (this.removedFromParent == null) {
return parent.isEmpty(); // nothing hidden: the parent's tags are all visible
}
return this.visibleParentCount() == 0;
}
/** Cheap: no level of the chain holds a local entry (ignores shadowing and tombstones). */
boolean isDefinitelyEmpty() {
for (HashStore level = this; level != null; level = level.parent) {
if (level.count != 0) {
return false;
}
}
return true;
}
/** Upper bound: the local counts of every level. */
int estimateSize() {
int total = 0;
for (HashStore level = this; level != null; level = level.parent) {
total += level.count;
}
return total;
}
/** The custom tag {@code tag}'s entry, reading through to the parent chain. */
public Entry get(String tag) {
assert KnownTagCodec.keyOf(tag) == 0 : "'" + tag + "' is a known tag; look it up by its id";
return this.get(tag, Entry.bucketHash(Entry.customHash(tag)));
}
private Entry get(String tag, int hash) {
Entry local = HashSupport.find(this.buckets, hash, tag);
if (local != null) {
return local;
}
HashStore parent = this.parent;
if (parent == null) {
return null;
}
if (this.removedFromParent != null && this.removedFromParent.contains(tag)) {
return null; // tombstoned: removed locally, do not read through
}
return parent.get(tag, hash);
}
/*
* Sets the custom tag {@code tag}, which must not be a known tag's name.
*/
public void set(String tag, Object value) {
if (value == null) {
this.remove(tag); // a null value removes the tag
return;
}
this.put(Entry.newAnyEntry(tag, value));
}
public void set(String tag, CharSequence value) {
if (value == null) {
this.remove(tag); // a null value removes the tag
return;
}
this.put(Entry.newObjectEntry(tag, value));
}
public void set(String tag, boolean value) {
this.put(Entry.newBooleanEntry(tag, value));
}
public void set(String tag, int value) {
this.put(Entry.newIntEntry(tag, value));
}
public void set(String tag, long value) {
this.put(Entry.newLongEntry(tag, value));
}
public void set(String tag, float value) {
this.put(Entry.newFloatEntry(tag, value));
}
public void set(String tag, double value) {
this.put(Entry.newDoubleEntry(tag, value));
}
/**
* Inserts or replaces a custom tag's entry, returning the local entry it replaced (or null). Does
* not consult the read-through parent.
*/
public Entry put(Entry newEntry) {
// Re-setting a key clears any read-through tombstone for it (the new value overrides the
// removal). Gated on the lazy field, so this is a no-op for the common no-tombstone case.
if (this.removedFromParent != null) {
this.removedFromParent.remove(newEntry.tag);
}
Entry replaced = HashSupport.put(this.buckets, newEntry);
if (replaced == null) {
this.count++;
}
return replaced;
}
/** {@link #put}, returning the prior visible entry -- local, else read through. */
public Entry getAndPut(Entry newEntry) {
// Capture whether the key was tombstoned BEFORE put clears it: a tombstoned key had no visible
// prior value (it was removed), so getAndPut must report null rather than the parent's.
boolean wasTombstoned =
this.removedFromParent != null && this.removedFromParent.contains(newEntry.tag);
Entry priorLocal = this.put(newEntry);
if (priorLocal != null) {
return priorLocal;
}
if (wasTombstoned || this.parent == null) {
return null;
}
return this.parent.get(newEntry.tag, newEntry.hash());
}
/**
* Removes the custom tag {@code tag}, returning the prior visible entry. Removing a key the
* parent chain still exposes also installs a tombstone, so it stops reading through.
*/
public Entry remove(String tag) {
assert KnownTagCodec.keyOf(tag) == 0 : "'" + tag + "' is a known tag; remove it by its id";
int hash = Entry.bucketHash(Entry.customHash(tag));
Entry localRemoved = HashSupport.remove(this.buckets, hash, tag);
if (localRemoved != null) {
this.count--;
}
HashStore parent = this.parent;
if (parent != null) {
boolean alreadyTombstoned =
this.removedFromParent != null && this.removedFromParent.contains(tag);
if (!alreadyTombstoned) {
Entry parentEntry = parent.get(tag, hash);
if (parentEntry != null) {
if (this.removedFromParent == null) {
// Small initial capacity: this set is rare and almost always holds only a handful of
// tombstoned keys, so the default 16-bucket HashSet table would be wasteful.
this.removedFromParent = new HashSet<>(4);
}
this.removedFromParent.add(tag);
return localRemoved != null ? localRemoved : parentEntry;
}
}
}
return localRemoved;
}
/**
* Whether {@code parentEntry}, from the ancestor level {@code fromAncestor}, is visible through
* this leaf: not shadowed and not tombstoned by any nearer level.
*/
boolean parentEntryVisible(Entry parentEntry, HashStore fromAncestor) {
int hash = parentEntry.hash();
String tag = parentEntry.tag;
for (HashStore nearer = this; nearer != fromAncestor; nearer = nearer.parent) {
if (nearer.removedFromParent != null && nearer.removedFromParent.contains(tag)) {
return false;
}
if (HashSupport.find(nearer.buckets, hash, tag) != null) {
return false;
}
}
return true;
}
/** Visits the local entries, then the visible inherited ones. */
void forEach(Consumer<? super EntryReader> consumer) {
HashSupport.forEach(this.buckets, consumer);
if (this.parent != null) {
this.forEachParent(consumer, null, (c, unused, entry) -> c.accept(entry));
}
}
<T> void forEach(T thisObj, BiConsumer<T, ? super EntryReader> consumer) {
HashSupport.forEach(this.buckets, thisObj, consumer);
if (this.parent != null) {
this.forEachParent(thisObj, consumer, (t, c, entry) -> c.accept(t, entry));
}
}
<T, U> void forEach(T thisObj, U otherObj, TriConsumer<T, U, ? super EntryReader> consumer) {
HashSupport.forEach(this.buckets, thisObj, otherObj, consumer);
if (this.parent != null) {
this.forEachParent(thisObj, otherObj, consumer);
}
}
private <T, U> void forEachParent(
T thisObj, U otherObj, TriConsumer<T, U, ? super EntryReader> consumer) {
for (HashStore ancestor = this.parent; ancestor != null; ancestor = ancestor.parent) {
Object[] parentBuckets = ancestor.buckets;
for (int i = 0; i < parentBuckets.length; ++i) {
Object bucket = parentBuckets[i];
if (bucket instanceof Entry) {
Entry entry = (Entry) bucket;
if (this.parentEntryVisible(entry, ancestor)) {
consumer.accept(thisObj, otherObj, entry);
}
} else if (bucket instanceof BucketGroup) {
for (BucketGroup group = (BucketGroup) bucket; group != null; group = group.prev) {
for (int j = 0; j < BucketGroup.LEN; ++j) {
Entry entry = group._entryAt(j);
if (entry != null && this.parentEntryVisible(entry, ancestor)) {
consumer.accept(thisObj, otherObj, entry);
}
}
}
}
}
}
}
/**
* An independently mutable copy: the same (frozen, shared) parent, this store's own entries
* (shared Entry objects) and its tombstones.
*/
HashStore copy() {
HashStore copy = new HashStore(this.parent);
HashSupport.copyInto(copy.buckets, this.buckets);
copy.count = this.count;
if (this.removedFromParent != null) {
copy.removedFromParent = new HashSet<>(this.removedFromParent);
}
return copy;
}
/** Copies the entries of the parentless store {@code that}, sharing Entry objects. */
void putAllLocal(HashStore that) {
if (that.count == 0) {
return;
}
if (this.count == 0) {
HashSupport.copyInto(this.buckets, that.buckets);
this.count = that.count;
} else {
this.count += HashSupport.merge(this.buckets, that.buckets);
}
}
void fillMapLocal(Map<? super String, Object> map) {
HashSupport.fillMap(this.buckets, map);
}
void fillStringMapLocal(Map<? super String, ? super String> map) {
HashSupport.fillStringMap(this.buckets, map);
}
/** Tests only: throws if the store's structure is inconsistent. */
void checkIntegrity() {
HashSupport.checkBuckets(this.buckets);
if (this.count != this.computeSize()) {
throw new IllegalStateException("incorrect size");
}
}
/** Tests only: the local count, recomputed from the structure. */
int computeSize() {
return HashSupport.count(this.buckets);
}
/** For deep debugging. */
String toInternalString() {
return HashSupport.toInternalString(this.buckets);
}
}