|
10 | 10 | import org.springframework.util.backoff.BackOffExecution;
|
11 | 11 | import org.springframework.util.backoff.ExponentialBackOff;
|
12 | 12 |
|
13 |
| -import java.util.HashMap; |
14 |
| -import java.util.Map; |
| 13 | +import java.util.*; |
15 | 14 | import java.util.concurrent.*;
|
16 | 15 | import java.util.concurrent.atomic.AtomicBoolean;
|
17 | 16 | import java.util.concurrent.locks.ReentrantLock;
|
|
43 | 42 |
|
44 | 43 | public class EventScheduler<R extends CustomResource> implements Watcher<R> {
|
45 | 44 |
|
46 |
| - // todo this if this is 0 its just works? We want also limit the number of retries etc |
47 |
| - private final static ExponentialBackOff backOff = new ExponentialBackOff(0L, 1.5); |
| 45 | + // todo limit number of back offs |
| 46 | + private final static ExponentialBackOff backOff = new ExponentialBackOff(2000L, 1.5); |
48 | 47 |
|
49 | 48 | private final static Logger log = LoggerFactory.getLogger(EventScheduler.class);
|
50 | 49 | private final EventDispatcher eventDispatcher;
|
51 | 50 | private final ScheduledThreadPoolExecutor executor;
|
52 | 51 | private final HashMap<CustomResourceEvent, BackOffExecution> backoffSchedulerCache = new HashMap<>();
|
53 |
| - private final Map<CustomResourceEvent, ScheduledFuture<?>> eventCache = new ConcurrentHashMap<>(); |
| 52 | + |
| 53 | + private final Set<CustomResourceEvent> eventsNotScheduledYet = Collections.synchronizedSet(new HashSet<>()); |
| 54 | + private final Map<CustomResourceEvent, ScheduledFuture<?>> eventsScheduledForProcessing = new ConcurrentHashMap<>(); |
| 55 | + private final Set<CustomResourceEvent> eventsUnderProcessing = Collections.synchronizedSet(new HashSet<>()); |
| 56 | + |
54 | 57 | private AtomicBoolean processingEnabled = new AtomicBoolean(false);
|
55 | 58 | private ReentrantLock lock = new ReentrantLock();
|
56 | 59 |
|
@@ -90,44 +93,85 @@ void scheduleEvent(CustomResourceEvent newEvent) {
|
90 | 93 | // we have to lock since the fabric8 client event handling is multi-threaded,
|
91 | 94 | // so in the following part could be a race condition when multiple events are received for same resource.
|
92 | 95 | lock.lock();
|
93 |
| - AtomicBoolean scheduleEvent = new AtomicBoolean(true); |
94 |
| - eventCache |
95 |
| - .entrySet() |
96 |
| - .parallelStream() |
97 |
| - .forEach(entry -> { |
98 |
| - CustomResourceEvent queuedEvent = entry.getKey(); |
99 |
| - ScheduledFuture<?> scheduledFuture = entry.getValue(); |
100 |
| - // Cleaning cache |
101 |
| - if (scheduledFuture.isDone() || scheduledFuture.isCancelled()) { |
102 |
| - log.debug("Event dropped from cache because is done or cancelled. [{}]", queuedEvent.getEventInfo()); |
103 |
| - eventCache.remove(queuedEvent, scheduledFuture); |
104 |
| - } |
105 |
| - // If newEvent is newer than existing in queue, cancel and remove queuedEvent |
106 |
| - if (newEvent.isSameResourceAndNewerGeneration(queuedEvent)) { |
107 |
| - log.debug("Queued event canceled because incoming event is newer. [{}]", queuedEvent.getEventInfo()); |
108 |
| - scheduledFuture.cancel(false); |
109 |
| - eventCache.remove(queuedEvent, scheduledFuture); |
110 |
| - } |
111 |
| - // If newEvent is older than existing in queue, don't schedule and remove from cache |
112 |
| - if (queuedEvent.isSameResourceAndNewerGeneration(newEvent)) { |
113 |
| - log.debug("Incoming event canceled because queued event is newer. [{}]", newEvent.getEventInfo()); |
114 |
| - // todo this is not in cache at this point, or? (ask Marek) |
115 |
| - eventCache.remove(newEvent); |
116 |
| - scheduleEvent.set(false); |
117 |
| - } |
118 |
| - }); |
119 |
| - if (!scheduleEvent.get()) return; |
| 96 | + |
| 97 | + // if there is an event waiting for to be scheduled we just replace that. |
| 98 | + if (eventsNotScheduledYet.contains(newEvent)) { |
| 99 | + // although the objects equal in name and metadata the data itself can be different |
| 100 | + eventsNotScheduledYet.add(newEvent); |
| 101 | + } else if (eventsUnderProcessing.contains(newEvent)) { |
| 102 | + // we add new event that will be scheduled when previous processing finished |
| 103 | + eventsNotScheduledYet.add(newEvent); |
| 104 | + } else { |
| 105 | + AtomicBoolean scheduleEvent = new AtomicBoolean(true); |
| 106 | + if (eventsScheduledForProcessing.containsKey(newEvent)) { |
| 107 | + eventsScheduledForProcessing |
| 108 | + .entrySet() |
| 109 | + .parallelStream() |
| 110 | + .forEach(entry -> { |
| 111 | + CustomResourceEvent queuedEvent = entry.getKey(); |
| 112 | + ScheduledFuture<?> scheduledFuture = entry.getValue(); |
| 113 | + // Cleaning cache |
| 114 | + if (scheduledFuture.isDone() || scheduledFuture.isCancelled()) { |
| 115 | + log.debug("Event dropped from cache because is done or cancelled. [{}]", queuedEvent.getEventInfo()); |
| 116 | + eventsScheduledForProcessing.remove(queuedEvent, scheduledFuture); |
| 117 | + } |
| 118 | + // If newEvent is newer than existing in queue, cancel and remove queuedEvent |
| 119 | + if (newEvent.isSameResourceAndNewerGeneration(queuedEvent)) { |
| 120 | + log.debug("Queued event canceled because incoming event is newer. [{}]", queuedEvent.getEventInfo()); |
| 121 | + scheduledFuture.cancel(false); |
| 122 | + eventsScheduledForProcessing.remove(queuedEvent, scheduledFuture); |
| 123 | + } |
| 124 | + // If newEvent is older than existing in queue, don't schedule and remove from cache |
| 125 | + if (queuedEvent.isSameResourceAndNewerGeneration(newEvent)) { |
| 126 | + log.debug("Incoming event canceled because queued event is newer. [{}]", newEvent.getEventInfo()); |
| 127 | + // todo this is not in cache at this point, or? (ask Marek) |
| 128 | + eventsScheduledForProcessing.remove(newEvent); |
| 129 | + scheduleEvent.set(false); |
| 130 | + } |
| 131 | + }); |
| 132 | + } |
| 133 | + if (!scheduleEvent.get()) return; |
| 134 | + } |
| 135 | + |
120 | 136 | backoffSchedulerCache.put(newEvent, backOff.start());
|
121 | 137 | ScheduledFuture<?> scheduledTask = executor.schedule(new EventConsumer(newEvent, eventDispatcher, this),
|
122 | 138 | backoffSchedulerCache.get(newEvent).nextBackOff(), TimeUnit.MILLISECONDS);
|
123 |
| - eventCache.put(newEvent, scheduledTask); |
| 139 | + eventsScheduledForProcessing.put(newEvent, scheduledTask); |
124 | 140 | } finally {
|
125 | 141 | lock.unlock();
|
126 | 142 | }
|
127 | 143 | }
|
128 | 144 |
|
129 |
| - void retryFailedEvent(CustomResourceEvent event) { |
130 |
| - scheduleEvent(event); |
| 145 | + void eventProcessingStarted(CustomResourceEvent event) { |
| 146 | + try { |
| 147 | + lock.lock(); |
| 148 | + eventsScheduledForProcessing.remove(event); |
| 149 | + eventsUnderProcessing.add(event); |
| 150 | + } finally { |
| 151 | + lock.unlock(); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + void eventProcessingFinishedSuccessfully(CustomResourceEvent event) { |
| 156 | + try { |
| 157 | + lock.lock(); |
| 158 | + eventsUnderProcessing.remove(event); |
| 159 | + backoffSchedulerCache.remove(event); |
| 160 | + // todo schedule from not processed yet if such |
| 161 | + } finally { |
| 162 | + lock.unlock(); |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + void eventProcessingFailed(CustomResourceEvent event) { |
| 167 | + try { |
| 168 | + lock.lock(); |
| 169 | + eventsUnderProcessing.remove(event); |
| 170 | + // retry |
| 171 | + scheduleEvent(event); |
| 172 | + } finally { |
| 173 | + lock.unlock(); |
| 174 | + } |
131 | 175 | }
|
132 | 176 |
|
133 | 177 | @Override
|
|
0 commit comments