26public final class TunableRegistry {
50 private record TypeHandlerData<T>(Class<T> cls,
TypeHandler<T> handler) {}
52 private record ComplexMigrationRecorder(
53 Set<String> publishedPaths, ComplexMigrationRecorder previous) {}
55 private record RevisionParentLink(Object child, ComplexTunable parent) {}
57 private record RevisionParentLinkState(String path, RevisionParentLink link) {}
59 private record ComplexPathState(
61 ComplexTunable tunable,
63 List<RevisionParentLinkState> descendantLinks) {}
65 private record ComplexChildPathState(String path, TunableBase previousTunable) {}
67 private static final class WeakIdentityKey
extends WeakReference<Object> {
68 private final int m_hash;
70 WeakIdentityKey(Object value, ReferenceQueue<Object> queue) {
71 super(Objects.requireNonNull(value), queue);
72 m_hash = System.identityHashCode(value);
75 WeakIdentityKey(Object value) {
76 super(Objects.requireNonNull(value));
77 m_hash = System.identityHashCode(value);
81 public int hashCode() {
86 public boolean equals(Object obj) {
88 || obj instanceof WeakIdentityKey other &&
get() !=
null &&
get() == other.get();
92 private static final class WeakIdentityMap<K, V> {
93 private final ReferenceQueue<Object> m_queue =
new ReferenceQueue<>();
94 private final Map<WeakIdentityKey, V> m_values =
new HashMap<>();
96 private void cleanStaleEntries() {
98 while ((key = (WeakIdentityKey) m_queue.poll()) !=
null) {
105 return m_values.get(
new WeakIdentityKey(key));
108 V getOrDefault(K key, V defaultValue) {
110 return value !=
null ? value : defaultValue;
113 void put(K key, V value) {
115 m_values.put(
new WeakIdentityKey(key, m_queue), value);
120 m_values.remove(
new WeakIdentityKey(key));
130 return m_values.isEmpty();
133 @SuppressWarnings(
"unchecked")
134 List<K> keySnapshot() {
136 List<K> keys =
new ArrayList<>();
137 var iterator = m_values.keySet().iterator();
138 while (iterator.hasNext()) {
139 Object key = iterator.next().get();
150 private static final List<TypeHandlerData<?>> s_typeHandlers =
new ArrayList<>();
151 private static final PrefixMap<TunableBackend> s_backends =
new StringPrefixMap<>();
152 private static volatile TunableBackend[] s_backendSnapshot =
new TunableBackend[0];
153 private static final ThreadLocal<ComplexMigrationRecorder> s_complexMigrationRecorder =
155 private static final IdentityHashMap<TunableBase, Boolean> s_pendingChangedResets =
156 new IdentityHashMap<>();
157 private static final IdentityHashMap<ComplexTunable, Boolean> s_updatedComplexTunables =
158 new IdentityHashMap<>();
159 private static final List<Runnable> s_pendingUpdateCallbacks =
new ArrayList<>();
160 private static int s_updateDepth;
161 private static final ConcurrentMap<String, TunableTable> s_tables =
new ConcurrentHashMap<>();
162 private static final Object s_complexPathsMutex =
new Object();
163 private static final IdentityHashMap<ComplexTunable, List<String>> s_complexPaths =
164 new IdentityHashMap<>();
165 private static final Map<String, ComplexTunable> s_complexByPath =
new HashMap<>();
166 private static final Map<String, TunableBase> s_complexChildrenByPath =
new HashMap<>();
167 private static final WeakIdentityMap<Object, Long> s_tuneRevisions =
new WeakIdentityMap<>();
168 private static final WeakIdentityMap<Object, WeakIdentityMap<ComplexTunable, Integer>>
169 s_revisionParents =
new WeakIdentityMap<>();
170 private static final Map<String, RevisionParentLink> s_revisionParentLinksByPath =
172 private static final TunableBackend s_missingBackend =
new NoopTunableBackend();
173 private static Consumer<String> s_reportWarning = TunableRegistry::defaultReportWarning;
175 private static final class NoopTunableBackend
implements TunableBackend {
177 public boolean publish(String path, TunableBase tunable) {
182 public boolean publishComplex(String path, ComplexTunable tunable) {
187 public void remove(String path) {}
190 public List<PublishedTunable> removePrefix(String prefix) {
195 public void update() {}
198 public void close() {}
201 private TunableRegistry() {
202 throw new UnsupportedOperationException(
"This is a utility class!");
205 private static void defaultReportWarning(String msg) {
207 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
208 StringBuilder traceString =
new StringBuilder();
209 for (
int i = 1; i < stackTrace.length; i++) {
210 String loc = stackTrace[i].toString();
211 traceString.append(
"\tat ").append(loc).append(
'\n');
214 System.err.println(
"Tunable warning: " + msg +
"\n" + traceString.toString());
225 synchronized (TunableRegistry.class) {
227 s_reportWarning = TunableRegistry::defaultReportWarning;
229 s_reportWarning = func;
240 synchronized (TunableRegistry.class) {
241 return s_reportWarning;
251 synchronized (TunableRegistry.class) {
252 s_reportWarning.accept(msg);
265 synchronized (s_typeHandlers) {
268 boolean replace =
false;
270 for (var entry : s_typeHandlers) {
271 if (entry.cls.equals(cls)) {
276 if (entry.cls.isAssignableFrom(cls)) {
284 s_typeHandlers.set(i,
new TypeHandlerData<>(cls, handler));
286 s_typeHandlers.add(i,
new TypeHandlerData<>(cls, handler));
298 @SuppressWarnings({
"PMD.CompareObjectsWithEquals",
"PMD.AvoidCatchingGenericException"})
301 List<TunableBackend> closeBackends =
new ArrayList<>();
302 synchronized (s_backends) {
303 final List<TunableBackend> oldBackends =
new ArrayList<>(s_backends.values());
304 s_backends.remove(normalizedPrefix);
305 s_backends.put(normalizedPrefix, backend);
306 updateBackendSnapshot();
309 IdentityHashMap<TunableBackend, Boolean> displacedBackends =
new IdentityHashMap<>();
311 if (oldBackend != backend) {
312 migrations.addAll(oldBackend.removePrefix(normalizedPrefix));
313 displacedBackends.put(oldBackend, Boolean.TRUE);
320 Set<String> publishedComplexChildren =
new HashSet<>();
322 for (var published : migrations) {
323 if (published.isComplex()) {
324 complexMigrations.add(published);
327 complexMigrations.sort((a, b) -> Integer.compare(a.path().length(), b.path().length()));
329 for (var published : complexMigrations) {
330 if (publishedComplexChildren.contains(published.path())) {
333 TunableBackend targetBackend = getBackendForNormalizedPath(published.path());
334 publishMigratedComplex(targetBackend, published, publishedComplexChildren);
337 for (var published : migrations) {
338 if (published.isComplex() || publishedComplexChildren.contains(published.path())) {
341 TunableBackend targetBackend = getBackendForNormalizedPath(published.path());
342 targetBackend.
publish(published.path(), published.tunable());
345 IdentityHashMap<TunableBackend, Boolean> currentBackends =
new IdentityHashMap<>();
347 currentBackends.put(currentBackend, Boolean.TRUE);
349 for (
TunableBackend displacedBackend : displacedBackends.keySet()) {
350 if (!currentBackends.containsKey(displacedBackend)) {
351 closeBackends.add(displacedBackend);
358 closeBackend.close();
359 }
catch (Exception e) {
360 System.out.println(
"Unexpected exception when closing backend: " + e);
365 private static void publishMigratedComplex(
368 Set<String> publishedComplexChildren) {
369 ComplexMigrationRecorder previous = s_complexMigrationRecorder.get();
370 s_complexMigrationRecorder.set(
371 new ComplexMigrationRecorder(publishedComplexChildren, previous));
373 targetBackend.
publishComplex(published.path(), published.complex());
375 if (previous ==
null) {
376 s_complexMigrationRecorder.remove();
378 s_complexMigrationRecorder.set(previous);
383 private static void recordComplexMigrationPublish(String path) {
384 ComplexMigrationRecorder recorder = s_complexMigrationRecorder.get();
385 while (recorder !=
null) {
386 recorder.publishedPaths().add(path);
387 recorder = recorder.previous();
391 private static void updateBackendSnapshot() {
392 IdentityHashMap<TunableBackend, Boolean> uniqueBackends =
new IdentityHashMap<>();
393 List<TunableBackend> snapshot =
new ArrayList<>();
394 for (TunableBackend backend : s_backends.values()) {
395 if (uniqueBackends.put(backend, Boolean.TRUE) ==
null) {
396 snapshot.add(backend);
399 s_backendSnapshot = snapshot.toArray(
new TunableBackend[0]);
410 synchronized (s_typeHandlers) {
411 for (var entry : s_typeHandlers) {
412 if (entry.cls.isInstance(value)) {
413 @SuppressWarnings(
"unchecked")
432 synchronized (s_backends) {
433 backend = getBackendForNormalizedPath(normalized);
435 if (isMissingBackend(backend)) {
441 private static TunableBackend getBackendForNormalizedPath(String path) {
442 String candidate = path;
445 if (backend !=
null) {
449 int slash = candidate.lastIndexOf(
'/');
453 candidate = candidate.substring(0, slash);
456 TunableBackend rootBackend = s_backends.get(
"/");
457 if (rootBackend !=
null) {
460 TunableBackend defaultBackend = s_backends.get(
"");
461 if (defaultBackend !=
null) {
462 return defaultBackend;
464 return s_missingBackend;
467 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
468 private static
boolean isMissingBackend(TunableBackend backend) {
469 return backend == s_missingBackend;
472 static void notifyChanged(TunableBase tunable) {
473 for (TunableBackend backend : s_backendSnapshot) {
495 synchronized (s_complexPathsMutex) {
496 return s_tuneRevisions.getOrDefault(getRevisionOwner(tunable), 0L);
510 synchronized (s_complexPathsMutex) {
511 return s_tuneRevisions.getOrDefault(tunable, 0L);
526 synchronized (s_complexPathsMutex) {
527 incrementTuneRevision(getRevisionOwner(tunable),
new IdentityHashMap<>());
540 synchronized (s_backends) {
541 if (s_updateDepth > 0) {
542 s_pendingChangedResets.put(tunable, Boolean.TRUE);
544 tunable.resetChanged();
558 synchronized (s_backends) {
559 if (s_updateDepth > 0) {
560 s_pendingUpdateCallbacks.add(callback);
576 synchronized (s_backends) {
577 if (s_updateDepth > 0 && s_updatedComplexTunables.put(tunable, Boolean.TRUE) !=
null) {
580 tunable.updateTunable();
584 private static void finishUpdate() {
586 if (s_updateDepth != 0) {
590 s_updatedComplexTunables.clear();
592 if (!s_pendingChangedResets.isEmpty()) {
593 for (TunableBase tunable : s_pendingChangedResets.keySet()) {
594 tunable.resetChanged();
596 s_pendingChangedResets.clear();
599 if (s_pendingUpdateCallbacks.isEmpty()) {
603 List<Runnable> callbacks =
new ArrayList<>(s_pendingUpdateCallbacks);
604 s_pendingUpdateCallbacks.clear();
605 for (Runnable callback : callbacks) {
630 tunable.publishTunable(
getTable(path));
642 return publish(path, t.getInnerTunable());
645 boolean missingBackend =
false;
646 synchronized (s_backends) {
648 if (isMissingBackend(backend)) {
649 missingBackend =
true;
651 ComplexChildPathState childPathState = addComplexChildPath(normalized, tunable);
652 if (backend.publish(path, tunable)) {
653 recordComplexMigrationPublish(normalized);
656 restoreComplexChildPath(childPathState);
659 if (missingBackend) {
674 boolean missingBackend =
false;
675 synchronized (s_backends) {
677 if (isMissingBackend(backend)) {
678 missingBackend =
true;
680 ComplexPathState pathState = addComplexPath(normalized, tunable);
681 if (backend.publishComplex(path, tunable)) {
682 recordComplexMigrationPublish(normalized);
684 }
else if (pathState.addedPath()) {
685 restoreComplexPath(pathState);
689 if (missingBackend) {
703 for (String path : getComplexPaths(parent)) {
715 static void publishChild(ComplexTunable parent, String name, ComplexTunable tunable) {
716 for (String path : getComplexPaths(parent)) {
717 publish(PathUtil.normalizeName(path +
"/" + name), tunable);
727 static void removeChild(ComplexTunable parent, String name) {
728 for (String path : getComplexPaths(parent)) {
729 remove(PathUtil.normalizeName(path +
"/" + name));
739 static void setChildChanged(ComplexTunable parent, String name) {
740 List<TunableBase> children =
new ArrayList<>();
741 synchronized (s_complexPathsMutex) {
742 List<String> paths = s_complexPaths.get(parent);
746 for (String path : paths) {
747 TunableBase child = s_complexChildrenByPath.get(PathUtil.normalizeName(path +
"/" + name));
748 if (child !=
null && !children.contains(child)) {
753 for (TunableBase child : children) {
763 public static void remove(String path) {
765 synchronized (s_backends) {
767 for (var entry : s_backends.values()) {
769 entry.removePrefix(childPrefix);
772 removeComplexChildPaths(path);
773 removeComplexPaths(path);
782 for (String path : getComplexPaths(tunable)) {
789 synchronized (s_backends) {
811 synchronized (s_backends) {
820 @SuppressWarnings(
"PMD.AvoidCatchingGenericException")
822 List<TunableBackend> backends =
new ArrayList<>();
823 synchronized (s_typeHandlers) {
824 s_typeHandlers.clear();
826 synchronized (s_backends) {
827 backends.addAll(Arrays.asList(s_backendSnapshot));
829 updateBackendSnapshot();
830 s_pendingChangedResets.clear();
831 s_updatedComplexTunables.clear();
832 s_pendingUpdateCallbacks.clear();
836 backend.removePrefix(
"");
839 }
catch (Exception e) {
840 System.out.println(
"Unexpected exception when closing backend: " + e);
843 synchronized (s_complexPathsMutex) {
844 s_complexPaths.clear();
845 s_complexByPath.clear();
846 s_complexChildrenByPath.clear();
847 s_tuneRevisions.clear();
848 s_revisionParents.clear();
849 s_revisionParentLinksByPath.clear();
853 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
856 TunableBase inner = custom.getInnerTunable();
857 if (inner != null && inner != tunable) {
858 return getRevisionOwner(inner);
864 private static void incrementTuneRevision(
865 Object tunable, IdentityHashMap<Object, Boolean> visited) {
866 if (visited.put(tunable, Boolean.TRUE) !=
null) {
869 s_tuneRevisions.put(tunable, s_tuneRevisions.getOrDefault(tunable, 0L) + 1L);
870 WeakIdentityMap<ComplexTunable, Integer> parents = s_revisionParents.get(tunable);
871 if (parents ==
null) {
875 incrementTuneRevision(parent, visited);
879 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
880 private static
ComplexTunable findNearestComplexParent(String path, Object child) {
881 String bestPrefix =
null;
883 for (var entry : s_complexByPath.entrySet()) {
884 ComplexTunable parent = entry.getValue();
885 if (parent == child) {
889 if (path.startsWith(childPrefix)
890 && (bestPrefix ==
null || childPrefix.length() > bestPrefix.length())) {
891 bestPrefix = childPrefix;
898 private static void addRevisionParent(Object child, ComplexTunable parent) {
899 WeakIdentityMap<ComplexTunable, Integer> parents = s_revisionParents.get(child);
900 if (parents ==
null) {
901 parents =
new WeakIdentityMap<>();
902 s_revisionParents.put(child, parents);
904 Integer count = parents.get(parent);
905 parents.put(parent, count ==
null ? 1 : count + 1);
908 private static void removeRevisionParent(Object child, ComplexTunable parent) {
909 WeakIdentityMap<ComplexTunable, Integer> parents = s_revisionParents.get(child);
910 if (parents ==
null) {
913 Integer count = parents.get(parent);
916 }
else if (count > 1) {
917 parents.put(parent, count - 1);
919 parents.remove(parent);
920 if (parents.isEmpty()) {
921 s_revisionParents.remove(child);
926 private static void addRevisionParentLink(String path, Object child, ComplexTunable parent) {
927 RevisionParentLink oldLink =
928 s_revisionParentLinksByPath.put(path,
new RevisionParentLink(child, parent));
929 if (oldLink !=
null) {
930 removeRevisionParent(oldLink.child(), oldLink.parent());
932 if (parent ==
null) {
933 s_revisionParentLinksByPath.remove(path);
936 addRevisionParent(child, parent);
939 private static void linkRevisionParentForPath(String path, Object child) {
940 addRevisionParentLink(path, child, findNearestComplexParent(path, child));
943 private static void removeRevisionParentLink(String path) {
944 RevisionParentLink link = s_revisionParentLinksByPath.remove(path);
946 removeRevisionParent(link.child(), link.parent());
950 private static void restoreRevisionParentLinks(List<RevisionParentLinkState> states) {
951 for (
int i = states.size() - 1; i >= 0; i--) {
952 RevisionParentLinkState state = states.get(i);
953 removeRevisionParentLink(state.path());
954 if (state.link() !=
null) {
955 s_revisionParentLinksByPath.put(state.path(), state.link());
956 addRevisionParent(state.link().child(), state.link().parent());
961 private static List<RevisionParentLinkState> linkExistingComplexDescendants(String path) {
962 List<RevisionParentLinkState> previousLinks =
new ArrayList<>();
963 String childPrefix = PathUtil.childTablePath(path);
964 for (var entry : s_complexChildrenByPath.entrySet()) {
965 if (entry.getKey().startsWith(childPrefix)) {
967 new RevisionParentLinkState(
968 entry.getKey(), s_revisionParentLinksByPath.get(entry.getKey())));
969 linkRevisionParentForPath(entry.getKey(), getRevisionOwner(entry.getValue()));
972 for (var entry : s_complexByPath.entrySet()) {
973 if (!entry.getKey().equals(path) && entry.getKey().startsWith(childPrefix)) {
975 new RevisionParentLinkState(
976 entry.getKey(), s_revisionParentLinksByPath.get(entry.getKey())));
977 linkRevisionParentForPath(entry.getKey(), entry.getValue());
980 return previousLinks;
983 private static ComplexChildPathState addComplexChildPath(String path, TunableBase tunable) {
984 synchronized (s_complexPathsMutex) {
985 String normalized = PathUtil.normalizeName(path);
986 TunableBase previousTunable = s_complexChildrenByPath.put(normalized, tunable);
987 if (previousTunable !=
null) {
988 removeRevisionParentLink(normalized);
990 linkRevisionParentForPath(normalized, getRevisionOwner(tunable));
991 return new ComplexChildPathState(normalized, previousTunable);
995 private static void restoreComplexChildPath(ComplexChildPathState state) {
996 synchronized (s_complexPathsMutex) {
997 removeRevisionParentLink(state.path());
998 if (state.previousTunable() !=
null) {
999 s_complexChildrenByPath.put(state.path(), state.previousTunable());
1000 linkRevisionParentForPath(state.path(), getRevisionOwner(state.previousTunable()));
1002 s_complexChildrenByPath.remove(state.path());
1007 private static ComplexPathState addComplexPath(String path, ComplexTunable tunable) {
1008 synchronized (s_complexPathsMutex) {
1009 String normalized = PathUtil.normalizeName(path);
1010 if (s_complexByPath.containsKey(normalized)) {
1011 return new ComplexPathState(normalized, tunable,
false, List.of());
1013 linkRevisionParentForPath(normalized, tunable);
1014 s_complexByPath.put(normalized, tunable);
1015 s_complexPaths.computeIfAbsent(tunable, k ->
new ArrayList<>()).add(normalized);
1016 List<RevisionParentLinkState> descendantLinks = linkExistingComplexDescendants(normalized);
1017 return new ComplexPathState(normalized, tunable,
true, descendantLinks);
1021 private static void restoreComplexPath(ComplexPathState state) {
1022 synchronized (s_complexPathsMutex) {
1023 removeComplexPath(state.path(), state.tunable());
1024 restoreRevisionParentLinks(state.descendantLinks());
1028 private static void removeComplexPaths(String path) {
1029 synchronized (s_complexPathsMutex) {
1030 List<String> paths =
new ArrayList<>();
1031 for (String complexPath : s_complexByPath.keySet()) {
1032 if (PathUtil.isPathOrDescendant(complexPath, path)) {
1033 paths.add(complexPath);
1036 for (String complexPath : paths) {
1037 ComplexTunable tunable = s_complexByPath.remove(complexPath);
1038 removeRevisionParentLink(complexPath);
1039 removeComplexPath(complexPath, tunable);
1044 private static void removeComplexChildPaths(String path) {
1045 synchronized (s_complexPathsMutex) {
1046 List<String> paths =
new ArrayList<>();
1047 for (String childPath : s_complexChildrenByPath.keySet()) {
1048 if (PathUtil.isPathOrDescendant(childPath, path)) {
1049 paths.add(childPath);
1052 for (String childPath : paths) {
1053 s_complexChildrenByPath.remove(childPath);
1054 removeRevisionParentLink(childPath);
1059 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
1060 private static
void removeComplexPath(String path, ComplexTunable tunable) {
1061 if (tunable ==
null) {
1064 synchronized (s_complexPathsMutex) {
1065 if (s_complexByPath.get(path) == tunable) {
1066 s_complexByPath.remove(path);
1068 removeRevisionParentLink(path);
1069 List<String> paths = s_complexPaths.get(tunable);
1070 if (paths ==
null) {
1074 if (paths.isEmpty()) {
1075 s_complexPaths.remove(tunable);
1080 private static List<String> getComplexPaths(ComplexTunable tunable) {
1081 synchronized (s_complexPathsMutex) {
1082 List<String> paths = s_complexPaths.get(tunable);
1083 if (paths ==
null) {
1086 return List.copyOf(paths);