-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBudgetCounters.java
More file actions
65 lines (56 loc) · 1.99 KB
/
BudgetCounters.java
File metadata and controls
65 lines (56 loc) · 1.99 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
package dev.arcp.runtime.lease;
import dev.arcp.core.error.BudgetExhaustedException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* §9.6 per-currency budget counters. Reads are cheap; mutations are CAS-on-{@link AtomicReference}
* to keep {@link BigDecimal} arithmetic exact without locking.
*/
public final class BudgetCounters {
private final ConcurrentHashMap<String, AtomicReference<BigDecimal>> counters =
new ConcurrentHashMap<>();
public BudgetCounters(Map<String, BigDecimal> initial) {
for (var e : initial.entrySet()) {
counters.put(e.getKey(), new AtomicReference<>(e.getValue()));
}
}
public boolean tracks(String currency) {
return counters.containsKey(currency);
}
public BigDecimal remaining(String currency) {
var ref = counters.get(currency);
return ref == null ? BigDecimal.ZERO : ref.get();
}
public Map<String, BigDecimal> snapshot() {
return Collections.unmodifiableMap(
counters.entrySet().stream()
.collect(
Collectors.toMap(
Map.Entry::getKey, e -> e.getValue().get(), (a, b) -> a, LinkedHashMap::new)));
}
/** §9.6: negative metric values produce no decrement. */
public void decrement(String currency, BigDecimal amount) {
if (amount.signum() < 0) {
return;
}
var ref = counters.get(currency);
if (ref == null) {
return;
}
ref.updateAndGet(b -> b.subtract(amount));
}
/** Returns null if all budgets are positive; otherwise throws. */
public void ensureAllPositive() throws BudgetExhaustedException {
for (var e : counters.entrySet()) {
if (e.getValue().get().signum() <= 0) {
throw new BudgetExhaustedException(
"budget exhausted: " + e.getKey() + " remaining " + e.getValue().get());
}
}
}
}