1
1
/**
2
2
* Copyright 2013 Netflix, Inc.
3
- *
3
+ *
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
6
6
* You may obtain a copy of the License at
7
- *
7
+ *
8
8
* http://www.apache.org/licenses/LICENSE-2.0
9
- *
9
+ *
10
10
* Unless required by applicable law or agreed to in writing, software
11
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
17
18
18
import static org .mockito .Mockito .*;
19
19
20
- import java .util .LinkedList ;
21
- import java .util .Queue ;
20
+ import java .util .PriorityQueue ;
22
21
import java .util .concurrent .TimeUnit ;
22
+ import java .util .concurrent .atomic .AtomicInteger ;
23
23
24
24
import org .junit .Test ;
25
25
import org .mockito .InOrder ;
@@ -39,46 +39,70 @@ public static CurrentThreadScheduler getInstance() {
39
39
return INSTANCE ;
40
40
}
41
41
42
- private static final ThreadLocal <Queue < DiscardableAction <?>>> QUEUE = new ThreadLocal <Queue < DiscardableAction <?> >>();
42
+ private static final ThreadLocal <PriorityQueue < TimedAction >> QUEUE = new ThreadLocal <PriorityQueue < TimedAction >>();
43
43
44
44
private CurrentThreadScheduler () {
45
45
}
46
46
47
+ private final AtomicInteger counter = new AtomicInteger (0 );
48
+
47
49
@ Override
48
50
public <T > Subscription schedule (T state , Func2 <Scheduler , T , Subscription > action ) {
49
51
DiscardableAction <T > discardableAction = new DiscardableAction <T >(state , action );
50
- enqueue (discardableAction );
52
+ enqueue (discardableAction , now () );
51
53
return discardableAction ;
52
54
}
53
55
54
56
@ Override
55
57
public <T > Subscription schedule (T state , Func2 <Scheduler , T , Subscription > action , long dueTime , TimeUnit unit ) {
56
- // since we are executing immediately on this thread we must cause this thread to sleep
57
- // TODO right now the 'enqueue' does not take delay into account so if another task is enqueued after this it will
58
- // wait behind the sleeping action ... should that be the case or should it be allowed to proceed ahead of the delayed action?
59
- return schedule (state , new SleepingAction <T >(action , this , dueTime , unit ));
58
+ long execTime = now () + unit .toMillis (dueTime );
59
+
60
+ DiscardableAction <T > discardableAction = new DiscardableAction <T >(state , new SleepingAction <T >(action , this , execTime ));
61
+ enqueue (discardableAction , execTime );
62
+ return discardableAction ;
60
63
}
61
64
62
- private void enqueue (DiscardableAction <?> action ) {
63
- Queue < DiscardableAction <?> > queue = QUEUE .get ();
65
+ private void enqueue (DiscardableAction <?> action , long execTime ) {
66
+ PriorityQueue < TimedAction > queue = QUEUE .get ();
64
67
boolean exec = queue == null ;
65
68
66
69
if (exec ) {
67
- queue = new LinkedList < DiscardableAction <?> >();
70
+ queue = new PriorityQueue < TimedAction >();
68
71
QUEUE .set (queue );
69
72
}
70
73
71
- queue .add (action );
74
+ queue .add (new TimedAction ( action , execTime , counter . incrementAndGet ()) );
72
75
73
76
if (exec ) {
74
77
while (!queue .isEmpty ()) {
75
- queue .poll ().call (this );
78
+ queue .poll ().action . call (this );
76
79
}
77
80
78
81
QUEUE .set (null );
79
82
}
80
83
}
81
84
85
+ private static class TimedAction implements Comparable <TimedAction > {
86
+ final DiscardableAction <?> action ;
87
+ final Long execTime ;
88
+ final Integer count ; // In case if time between enqueueing took less than 1ms
89
+
90
+ private TimedAction (DiscardableAction <?> action , Long execTime , Integer count ) {
91
+ this .action = action ;
92
+ this .execTime = execTime ;
93
+ this .count = count ;
94
+ }
95
+
96
+ @ Override
97
+ public int compareTo (TimedAction that ) {
98
+ int result = execTime .compareTo (that .execTime );
99
+ if (result == 0 ) {
100
+ return count .compareTo (that .count );
101
+ }
102
+ return result ;
103
+ }
104
+ }
105
+
82
106
public static class UnitTest {
83
107
84
108
@ Test
@@ -146,6 +170,58 @@ public void testSequenceOfActions() {
146
170
147
171
}
148
172
173
+ @ Test
174
+ public void testSequenceOfDelayedActions () {
175
+ final CurrentThreadScheduler scheduler = new CurrentThreadScheduler ();
176
+
177
+ final Action0 first = mock (Action0 .class );
178
+ final Action0 second = mock (Action0 .class );
179
+
180
+ scheduler .schedule (new Action0 () {
181
+ @ Override
182
+ public void call () {
183
+ scheduler .schedule (first , 30 , TimeUnit .MILLISECONDS );
184
+ scheduler .schedule (second , 10 , TimeUnit .MILLISECONDS );
185
+ }
186
+ });
187
+
188
+ InOrder inOrder = inOrder (first , second );
189
+
190
+ inOrder .verify (second , times (1 )).call ();
191
+ inOrder .verify (first , times (1 )).call ();
192
+
193
+
194
+ }
195
+
196
+ @ Test
197
+ public void testMixOfDelayedAndNonDelayedActions () {
198
+ final CurrentThreadScheduler scheduler = new CurrentThreadScheduler ();
199
+
200
+ final Action0 first = mock (Action0 .class );
201
+ final Action0 second = mock (Action0 .class );
202
+ final Action0 third = mock (Action0 .class );
203
+ final Action0 fourth = mock (Action0 .class );
204
+
205
+ scheduler .schedule (new Action0 () {
206
+ @ Override
207
+ public void call () {
208
+ scheduler .schedule (first );
209
+ scheduler .schedule (second , 300 , TimeUnit .MILLISECONDS );
210
+ scheduler .schedule (third , 100 , TimeUnit .MILLISECONDS );
211
+ scheduler .schedule (fourth );
212
+ }
213
+ });
214
+
215
+ InOrder inOrder = inOrder (first , second , third , fourth );
216
+
217
+ inOrder .verify (first , times (1 )).call ();
218
+ inOrder .verify (fourth , times (1 )).call ();
219
+ inOrder .verify (third , times (1 )).call ();
220
+ inOrder .verify (second , times (1 )).call ();
221
+
222
+
223
+ }
224
+
149
225
}
150
226
151
227
}
0 commit comments