• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Hyshmily / hotkey / 28290463292

27 Jun 2026 01:22PM UTC coverage: 91.227% (-0.7%) from 91.882%
28290463292

push

github

Hyshmily
test: fix CI tests

Signed-off-by: Hyshmily <cxm8607@outlook.com>

1250 of 1427 branches covered (87.6%)

Branch coverage included in aggregate %.

3544 of 3828 relevant lines covered (92.58%)

4.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.38
/common/src/main/java/io/github/hyshmily/hotkey/sharding/RingManager.java
1
/*
2
 * Copyright 2026 Hyshmily. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package io.github.hyshmily.hotkey.sharding;
17

18
import java.util.HashSet;
19
import java.util.Set;
20
import java.util.function.IntConsumer;
21
import lombok.Getter;
22
import lombok.Setter;
23

24
/**
25
 * Manages the consistent-hash ring for Worker shard routing.
26
 *
27
 * <p>Operates in two modes:
28
 * <ul>
29
 *   <li><b>Auto mode</b> (default) — the ring is rebuilt automatically from
30
 *       the live Worker set reported by {@link ClusterHealthView}.</li>
31
 *   <li><b>Manual mode</b> — an operator has pinned the ring to a specific
32
 *       set of nodes via {@link #addNode} / {@link #removeNode}.</li>
33
 * </ul>
34
 */
35
public class RingManager {
36

37
  @Getter
38
  private final ConsistentHashRing ring;
39

40
  @Getter
41
  private final int virtualNodeCount;
42

43
  private volatile Set<String> overrideNodes; // null=auto mode
44

45
  @Setter
46
  private IntConsumer onRingReconciled;
47

48
  /**
49
   * Creates a ring manager with the given virtual-node count.
50
   *
51
   * @param virtualNodeCount virtual copies per physical shard on the ring
52
   */
53
  public RingManager(int virtualNodeCount) {
2✔
54
    this.virtualNodeCount = virtualNodeCount;
3✔
55
    this.ring = new ConsistentHashRing(virtualNodeCount);
6✔
56
  }
1✔
57

58
  /**
59
   * Rebuild the ring from the health view (auto mode) or the override set (manual mode).
60
   *
61
   * @param healthView the current cluster health view; must not be {@code null}
62
   * @throws NullPointerException if {@code healthView} is {@code null}
63
   */
64
  public synchronized void reconcileFromHealthView(ClusterHealthView healthView) {
65
    if (overrideNodes != null) {
3✔
66
      if (!overrideNodes.equals(ring.getNodes())) {
7!
67
        ring.rebuild(overrideNodes);
×
68
      }
69
      return;
1✔
70
    }
71

72
    Set<String> alive = healthView.getAliveWorkerIds();
3✔
73
    if (!alive.equals(ring.getNodes())) {
6✔
74
      ring.rebuild(alive);
4✔
75

76
      if (onRingReconciled != null) {
3✔
77
        onRingReconciled.accept(alive.size());
5✔
78
      }
79
    }
80
  }
1✔
81

82
  /**
83
   * Add a physical node to the ring. Switches to manual mode.
84
   *
85
   * @param nodeId the node identifier to addDirect; must not be {@code null}
86
   * @throws NullPointerException if {@code nodeId} is {@code null}
87
   */
88
  public synchronized void addNode(String nodeId) {
89
    if (overrideNodes == null) {
3✔
90
      overrideNodes = new HashSet<>(ring.getNodes());
8✔
91
    }
92
    overrideNodes.add(nodeId);
5✔
93
    ring.rebuild(overrideNodes);
5✔
94
  }
1✔
95

96
  /**
97
   * Remove a physical node from the ring. Switches to manual mode.
98
   *
99
   * @param nodeId the node identifier to remove; must not be {@code null}
100
   * @throws NullPointerException if {@code nodeId} is {@code null}
101
   */
102
  public synchronized void removeNode(String nodeId) {
103
    if (overrideNodes == null) {
3✔
104
      overrideNodes = new HashSet<>(ring.getNodes());
8✔
105
    }
106
    overrideNodes.remove(nodeId);
5✔
107
    ring.rebuild(overrideNodes);
5✔
108
  }
1✔
109

110
  /**
111
   * Reset to auto mode — the ring will be rebuilt from the health view on next reconciliation.
112
   */
113
  public synchronized void resetToAuto() {
114
    overrideNodes = null;
3✔
115
  }
1✔
116

117
  /**
118
   * Whether the ring is in manual mode (operator-pinned node set).
119
   *
120
   * @return {@code true} if in manual mode
121
   */
122
  public boolean isManualMode() {
123
    return overrideNodes != null;
7✔
124
  }
125

126
  /**
127
   * Return the current set of nodes on the ring.
128
   *
129
   * @return the set of live (or pinned) node identifiers
130
   */
131
  public Set<String> getCurrentNodes() {
132
    return ring.getNodes();
4✔
133
  }
134

135
  /**
136
   * Return the number of physical nodes currently on the ring.
137
   *
138
   * @return the node count
139
   */
140
  public int nodeCount() {
141
    return ring.nodeCount();
4✔
142
  }
143

144
  /**
145
   * Route a key to the responsible Worker node.
146
   *
147
   * @param key        the cache key to route; must not be {@code null}
148
   * @param healthView the current cluster health view; must not be {@code null}
149
   * @return the node identifier that owns the key, or {@code null} if no node is available
150
   * @throws NullPointerException if {@code key} or {@code healthView} is {@code null}
151
   */
152
  public String routeNode(String key, ClusterHealthView healthView) {
153
    Set<String> alive = healthView.getAliveWorkerIds();
3✔
154
    return ring.locateNode(key, alive::contains);
10✔
155
  }
156

157
  /**
158
   * Route a key to its target Worker node, using a pre-snapshotted alive-set
159
   * supplied by the caller. This avoids re-fetching {@link
160
   * ClusterHealthView#getAliveWorkerIds()} per key inside hot loops.
161
   *
162
   * @param key        the cache key to route
163
   * @param aliveNodes the already-snapshotted set of alive Worker ids; must not
164
   *                   be {@code null} or modified concurrently
165
   * @return the target Worker node id, or {@code null} if no alive nodes
166
   */
167
  public String routeNode(String key, Set<String> aliveNodes) {
168
    if (aliveNodes == null || aliveNodes.isEmpty()) {
5!
169
      return null;
×
170
    }
171
    return ring.locateNode(key, aliveNodes::contains);
10✔
172
  }
173
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc