|
24 | 24 | import com.google.cloud.pubsublite.Offset; |
25 | 25 | import com.google.cloud.pubsublite.SequencedMessage; |
26 | 26 | import com.google.cloud.pubsublite.internal.CheckedApiException; |
27 | | -import com.google.cloud.pubsublite.internal.CloseableMonitor; |
28 | 27 | import com.google.cloud.pubsublite.internal.ExtractStatus; |
29 | | -import com.google.cloud.pubsublite.internal.TrivialProxyService; |
| 28 | +import com.google.cloud.pubsublite.internal.ProxyService; |
30 | 29 | import com.google.cloud.pubsublite.internal.wire.Committer; |
31 | | -import com.google.common.collect.ImmutableList; |
| 30 | +import com.google.common.flogger.GoogleLogger; |
32 | 31 | import com.google.errorprone.annotations.concurrent.GuardedBy; |
33 | 32 | import java.util.ArrayDeque; |
34 | 33 | import java.util.Deque; |
35 | | -import java.util.List; |
36 | 34 | import java.util.Optional; |
37 | 35 | import java.util.PriorityQueue; |
| 36 | +import java.util.concurrent.atomic.AtomicBoolean; |
38 | 37 |
|
39 | | -public class AckSetTrackerImpl extends TrivialProxyService implements AckSetTracker { |
40 | | - // Receipt represents an unacked message. It can be cleared, which will cause the ack to be |
41 | | - // ignored. |
| 38 | +public class AckSetTrackerImpl extends ProxyService implements AckSetTracker { |
| 39 | + private static final GoogleLogger LOGGER = GoogleLogger.forEnclosingClass(); |
| 40 | + |
| 41 | + // Receipt represents an unacked message. If the tracker generation is incremented, the ack will |
| 42 | + // be ignored. |
42 | 43 | private static class Receipt { |
43 | 44 | final Offset offset; |
| 45 | + final long generation; |
44 | 46 |
|
45 | | - private final CloseableMonitor m = new CloseableMonitor(); |
46 | | - |
47 | | - @GuardedBy("m.monitor") |
48 | | - private boolean wasAcked = false; |
| 47 | + private final AtomicBoolean wasAcked = new AtomicBoolean(); |
49 | 48 |
|
50 | | - @GuardedBy("m.monitor") |
51 | | - private Optional<AckSetTrackerImpl> tracker; |
| 49 | + private final AckSetTrackerImpl tracker; |
52 | 50 |
|
53 | | - Receipt(Offset offset, AckSetTrackerImpl tracker) { |
| 51 | + Receipt(Offset offset, long generation, AckSetTrackerImpl tracker) { |
54 | 52 | this.offset = offset; |
55 | | - this.tracker = Optional.of(tracker); |
56 | | - } |
57 | | - |
58 | | - void clear() { |
59 | | - try (CloseableMonitor.Hold h = m.enter()) { |
60 | | - tracker = Optional.empty(); |
61 | | - } |
| 53 | + this.generation = generation; |
| 54 | + this.tracker = tracker; |
62 | 55 | } |
63 | 56 |
|
64 | | - void onAck() { |
65 | | - try (CloseableMonitor.Hold h = m.enter()) { |
66 | | - if (!tracker.isPresent()) { |
67 | | - return; |
68 | | - } |
69 | | - if (wasAcked) { |
70 | | - CheckedApiException e = |
71 | | - new CheckedApiException("Duplicate acks are not allowed.", Code.FAILED_PRECONDITION); |
72 | | - tracker.get().onPermanentError(e); |
73 | | - throw e.underlying; |
74 | | - } |
75 | | - wasAcked = true; |
76 | | - tracker.get().onAck(offset); |
| 57 | + void onAck() throws ApiException { |
| 58 | + if (wasAcked.getAndSet(true)) { |
| 59 | + CheckedApiException e = |
| 60 | + new CheckedApiException("Duplicate acks are not allowed.", Code.FAILED_PRECONDITION); |
| 61 | + tracker.onPermanentError(e); |
| 62 | + throw e.underlying; |
77 | 63 | } |
| 64 | + tracker.onAck(offset, generation); |
78 | 65 | } |
79 | 66 | } |
80 | 67 |
|
81 | | - private final CloseableMonitor monitor = new CloseableMonitor(); |
82 | | - |
83 | | - @GuardedBy("monitor.monitor") |
| 68 | + @GuardedBy("this") |
84 | 69 | private final Committer committer; |
85 | 70 |
|
86 | | - @GuardedBy("monitor.monitor") |
| 71 | + @GuardedBy("this") |
87 | 72 | private final Deque<Receipt> receipts = new ArrayDeque<>(); |
88 | 73 |
|
89 | | - @GuardedBy("monitor.monitor") |
| 74 | + @GuardedBy("this") |
90 | 75 | private final PriorityQueue<Offset> acks = new PriorityQueue<>(); |
91 | 76 |
|
| 77 | + @GuardedBy("this") |
| 78 | + private long generation = 0L; |
| 79 | + |
| 80 | + @GuardedBy("this") |
| 81 | + private boolean shutdown = false; |
| 82 | + |
92 | 83 | public AckSetTrackerImpl(Committer committer) throws ApiException { |
93 | | - super(committer); |
94 | 84 | this.committer = committer; |
| 85 | + addServices(committer); |
95 | 86 | } |
96 | 87 |
|
97 | 88 | // AckSetTracker implementation. |
98 | 89 | @Override |
99 | | - public Runnable track(SequencedMessage message) throws CheckedApiException { |
100 | | - final Offset messageOffset = message.offset(); |
101 | | - try (CloseableMonitor.Hold h = monitor.enter()) { |
102 | | - checkArgument( |
103 | | - receipts.isEmpty() || receipts.peekLast().offset.value() < messageOffset.value()); |
104 | | - Receipt receipt = new Receipt(messageOffset, this); |
105 | | - receipts.addLast(receipt); |
106 | | - return receipt::onAck; |
107 | | - } |
| 90 | + public synchronized Runnable track(SequencedMessage message) throws CheckedApiException { |
| 91 | + checkArgument( |
| 92 | + receipts.isEmpty() || receipts.peekLast().offset.value() < message.offset().value()); |
| 93 | + Receipt receipt = new Receipt(message.offset(), generation, this); |
| 94 | + receipts.addLast(receipt); |
| 95 | + return receipt::onAck; |
108 | 96 | } |
109 | 97 |
|
110 | 98 | @Override |
111 | | - public void waitUntilCommitted() throws CheckedApiException { |
112 | | - List<Receipt> receiptsCopy; |
113 | | - try (CloseableMonitor.Hold h = monitor.enter()) { |
114 | | - receiptsCopy = ImmutableList.copyOf(receipts); |
| 99 | + public synchronized void waitUntilCommitted() throws CheckedApiException { |
| 100 | + ++generation; |
| 101 | + receipts.clear(); |
| 102 | + acks.clear(); |
| 103 | + committer.waitUntilEmpty(); |
| 104 | + } |
| 105 | + |
| 106 | + private synchronized void onAck(Offset offset, long generation) { |
| 107 | + if (shutdown) { |
| 108 | + LOGGER.atFine().log("Dropping ack after tracker shutdown."); |
| 109 | + return; |
115 | 110 | } |
116 | | - // Clearing receipts here avoids deadlocks due to locks acquired in different order. |
117 | | - receiptsCopy.forEach(Receipt::clear); |
118 | | - try (CloseableMonitor.Hold h = monitor.enter()) { |
119 | | - receipts.clear(); |
120 | | - acks.clear(); |
121 | | - committer.waitUntilEmpty(); |
| 111 | + if (generation != this.generation) { |
| 112 | + LOGGER.atFine().log("Dropping ack from wrong generation (admin seek occurred)."); |
| 113 | + return; |
| 114 | + } |
| 115 | + acks.add(offset); |
| 116 | + Optional<Offset> prefixAckedOffset = Optional.empty(); |
| 117 | + while (!receipts.isEmpty() |
| 118 | + && !acks.isEmpty() |
| 119 | + && receipts.peekFirst().offset.value() == acks.peek().value()) { |
| 120 | + prefixAckedOffset = Optional.of(acks.remove()); |
| 121 | + receipts.removeFirst(); |
| 122 | + } |
| 123 | + // Convert from last acked to first unacked. |
| 124 | + if (prefixAckedOffset.isPresent()) { |
| 125 | + ApiFuture<?> future = committer.commitOffset(Offset.of(prefixAckedOffset.get().value() + 1)); |
| 126 | + ExtractStatus.addFailureHandler(future, this::onPermanentError); |
122 | 127 | } |
123 | 128 | } |
124 | 129 |
|
125 | | - private void onAck(Offset offset) { |
126 | | - try (CloseableMonitor.Hold h = monitor.enter()) { |
127 | | - acks.add(offset); |
128 | | - Optional<Offset> prefixAckedOffset = Optional.empty(); |
129 | | - while (!receipts.isEmpty() |
130 | | - && !acks.isEmpty() |
131 | | - && receipts.peekFirst().offset.value() == acks.peek().value()) { |
132 | | - prefixAckedOffset = Optional.of(acks.remove()); |
133 | | - receipts.removeFirst(); |
134 | | - } |
135 | | - // Convert from last acked to first unacked. |
136 | | - if (prefixAckedOffset.isPresent()) { |
137 | | - ApiFuture<?> future = |
138 | | - committer.commitOffset(Offset.of(prefixAckedOffset.get().value() + 1)); |
139 | | - ExtractStatus.addFailureHandler(future, this::onPermanentError); |
140 | | - } |
141 | | - } |
| 130 | + @Override |
| 131 | + protected void start() throws CheckedApiException {} |
| 132 | + |
| 133 | + @Override |
| 134 | + protected synchronized void stop() throws CheckedApiException { |
| 135 | + shutdown = true; |
142 | 136 | } |
| 137 | + |
| 138 | + @Override |
| 139 | + protected void handlePermanentError(CheckedApiException error) {} |
143 | 140 | } |
0 commit comments