1
- package com .github .containersolutions .operator ;
1
+ package com .github .containersolutions .operator . processing ;
2
2
3
3
4
4
import com .google .common .util .concurrent .ThreadFactoryBuilder ;
10
10
import org .springframework .util .backoff .BackOffExecution ;
11
11
import org .springframework .util .backoff .ExponentialBackOff ;
12
12
13
- import java .util .*;
14
- import java .util .concurrent .*;
13
+ import java .util .HashMap ;
14
+ import java .util .Map ;
15
+ import java .util .concurrent .ScheduledFuture ;
16
+ import java .util .concurrent .ScheduledThreadPoolExecutor ;
17
+ import java .util .concurrent .ThreadFactory ;
18
+ import java .util .concurrent .TimeUnit ;
15
19
import java .util .concurrent .atomic .AtomicBoolean ;
16
20
import java .util .concurrent .locks .ReentrantLock ;
17
21
@@ -50,14 +54,15 @@ public class EventScheduler<R extends CustomResource> implements Watcher<R> {
50
54
private final ScheduledThreadPoolExecutor executor ;
51
55
private final HashMap <CustomResourceEvent , BackOffExecution > backoffSchedulerCache = new HashMap <>();
52
56
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 <>());
57
+ // note that these hash maps does not needs to be concurrent, since we are already locking all methods where are used
58
+ private final Map <String , CustomResourceEvent > eventsNotScheduledYet = new HashMap <>();
59
+ private final Map <String , ResourceScheduleHolder > eventsScheduledForProcessing = new HashMap <>();
60
+ private final Map <String , CustomResourceEvent > eventsUnderProcessing = new HashMap <>();
56
61
57
62
private AtomicBoolean processingEnabled = new AtomicBoolean (false );
58
63
private ReentrantLock lock = new ReentrantLock ();
59
64
60
- EventScheduler (EventDispatcher <R > eventDispatcher ) {
65
+ public EventScheduler (EventDispatcher <R > eventDispatcher ) {
61
66
this .eventDispatcher = eventDispatcher ;
62
67
ThreadFactory threadFactory = new ThreadFactoryBuilder ()
63
68
.setNameFormat ("event-consumer-%d" )
@@ -67,7 +72,7 @@ public class EventScheduler<R extends CustomResource> implements Watcher<R> {
67
72
executor .setRemoveOnCancelPolicy (true );
68
73
}
69
74
70
- void startProcessing () {
75
+ public void startProcessing () {
71
76
processingEnabled .set (true );
72
77
}
73
78
@@ -86,6 +91,7 @@ public void eventReceived(Watcher.Action action, R resource) {
86
91
// with the incoming event to be scheduled/executed until that one is not finished
87
92
88
93
// todo handle delete event: cleanup when a real delete arrived
94
+ // todo discuss new version vs new generation comparison
89
95
void scheduleEvent (CustomResourceEvent newEvent ) {
90
96
log .debug ("Current queue size {}" , executor .getQueue ().size ());
91
97
log .info ("Scheduling event: {}" , newEvent .getEventInfo ());
@@ -95,58 +101,58 @@ void scheduleEvent(CustomResourceEvent newEvent) {
95
101
lock .lock ();
96
102
97
103
// 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 ;
104
+ if (eventsNotScheduledYet .containsKey (newEvent .resourceKey ()) &&
105
+ newEvent .isSameResourceAndNewerVersion (eventsNotScheduledYet .get (newEvent .resourceKey ()))) {
106
+ log .debug ("Replacing event which is not scheduled yet, since incoming event is more recent. new Event:{}"
107
+ , newEvent );
108
+ eventsNotScheduledYet .put (newEvent .resourceKey (), newEvent );
109
+ return ;
110
+ } else if (eventsUnderProcessing .containsKey (newEvent .resourceKey ()) &&
111
+ newEvent .isSameResourceAndNewerVersion (eventsUnderProcessing .get (newEvent .resourceKey ()))) {
112
+ log .debug ("Scheduling event for later processing since there is an event under processing for same kind." +
113
+ " New event: {}" , newEvent );
114
+ eventsNotScheduledYet .put (newEvent .resourceKey (), newEvent );
115
+ return ;
134
116
}
135
117
118
+ if (eventsScheduledForProcessing .containsKey (newEvent .resourceKey ())) {
119
+ ResourceScheduleHolder scheduleHolder = eventsScheduledForProcessing .get (newEvent .resourceKey ());
120
+ CustomResourceEvent queuedEvent = scheduleHolder .getCustomResourceEvent ();
121
+ ScheduledFuture <?> scheduledFuture = scheduleHolder .getScheduledFuture ();
122
+ // If newEvent is newer than existing in queue, cancel and remove queuedEvent
123
+ if (newEvent .isSameResourceAndNewerVersion (queuedEvent )) {
124
+ log .debug ("Queued event canceled because incoming event is newer. [{}]" , queuedEvent );
125
+ scheduledFuture .cancel (false );
126
+ eventsScheduledForProcessing .remove (queuedEvent .resourceKey ());
127
+ }
128
+ // If newEvent is older than existing in queue, don't schedule and remove from cache
129
+ if (queuedEvent .isSameResourceAndNewerVersion (newEvent )) {
130
+ log .debug ("Incoming event discarded because queued event is newer. [{}]" , newEvent );
131
+ return ;
132
+ }
133
+ }
136
134
backoffSchedulerCache .put (newEvent , backOff .start ());
137
135
ScheduledFuture <?> scheduledTask = executor .schedule (new EventConsumer (newEvent , eventDispatcher , this ),
138
136
backoffSchedulerCache .get (newEvent ).nextBackOff (), TimeUnit .MILLISECONDS );
139
- eventsScheduledForProcessing .put (newEvent , scheduledTask );
137
+ eventsScheduledForProcessing .put (newEvent . resourceKey (), new ResourceScheduleHolder ( newEvent , scheduledTask ) );
140
138
} finally {
141
139
lock .unlock ();
142
140
}
143
141
}
144
142
145
- void eventProcessingStarted (CustomResourceEvent event ) {
143
+ boolean eventProcessingStarted (CustomResourceEvent event ) {
146
144
try {
147
145
lock .lock ();
148
- eventsScheduledForProcessing .remove (event );
149
- eventsUnderProcessing .add (event );
146
+ ResourceScheduleHolder res = eventsScheduledForProcessing .remove (event .resourceKey ());
147
+ if (res == null ) {
148
+ // if its still scheduled for processing.
149
+ // note that it can happen that we scheduled an event for processing, it took some time that is was picked
150
+ // by executor, and it was removed during that time from the schedule but not cancelled yet. So to be correct
151
+ // this should be checked also here. In other word scheduleEvent function can run in parallel with eventDispatcher.
152
+ return false ;
153
+ }
154
+ eventsUnderProcessing .put (event .resourceKey (), event );
155
+ return true ;
150
156
} finally {
151
157
lock .unlock ();
152
158
}
@@ -155,9 +161,13 @@ void eventProcessingStarted(CustomResourceEvent event) {
155
161
void eventProcessingFinishedSuccessfully (CustomResourceEvent event ) {
156
162
try {
157
163
lock .lock ();
158
- eventsUnderProcessing .remove (event );
164
+ eventsUnderProcessing .remove (event . resourceKey () );
159
165
backoffSchedulerCache .remove (event );
160
- // todo schedule from not processed yet if such
166
+
167
+ CustomResourceEvent notScheduledYetEvent = eventsNotScheduledYet .remove (event .resourceKey ());
168
+ if (notScheduledYetEvent != null ) {
169
+ scheduleEvent (notScheduledYetEvent );
170
+ }
161
171
} finally {
162
172
lock .unlock ();
163
173
}
@@ -167,13 +177,13 @@ void eventProcessingFailed(CustomResourceEvent event) {
167
177
try {
168
178
lock .lock ();
169
179
eventsUnderProcessing .remove (event );
170
- // retry
171
180
scheduleEvent (event );
172
181
} finally {
173
182
lock .unlock ();
174
183
}
175
184
}
176
185
186
+ // todo review this in light of new restart functionality from master
177
187
@ Override
178
188
public void onClose (KubernetesClientException e ) {
179
189
processingEnabled .set (false );
@@ -187,6 +197,23 @@ public void onClose(KubernetesClientException e) {
187
197
}
188
198
}
189
199
200
+ private static class ResourceScheduleHolder {
201
+ private CustomResourceEvent customResourceEvent ;
202
+ private ScheduledFuture <?> scheduledFuture ;
203
+
204
+ public ResourceScheduleHolder (CustomResourceEvent customResourceEvent , ScheduledFuture <?> scheduledFuture ) {
205
+ this .customResourceEvent = customResourceEvent ;
206
+ this .scheduledFuture = scheduledFuture ;
207
+ }
208
+
209
+ public CustomResourceEvent getCustomResourceEvent () {
210
+ return customResourceEvent ;
211
+ }
212
+
213
+ public ScheduledFuture <?> getScheduledFuture () {
214
+ return scheduledFuture ;
215
+ }
216
+ }
190
217
}
191
218
192
219
0 commit comments