Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
290d9702
Commit
290d9702
authored
Apr 06, 2016
by
Jordan Liggitt
Committed by
deads2k
Apr 07, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
make delayed workqueue use channels with single writer
parent
d12a4d6d
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
171 additions
and
159 deletions
+171
-159
delaying_queue.go
pkg/util/workqueue/delaying_queue.go
+91
-124
delaying_queue_test.go
pkg/util/workqueue/delaying_queue_test.go
+72
-35
queue.go
pkg/util/workqueue/queue.go
+8
-0
No files found.
pkg/util/workqueue/delaying_queue.go
View file @
290d9702
...
@@ -18,7 +18,6 @@ package workqueue
...
@@ -18,7 +18,6 @@ package workqueue
import
(
import
(
"sort"
"sort"
"sync"
"time"
"time"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util"
...
@@ -40,38 +39,35 @@ func NewDelayingQueue() DelayingInterface {
...
@@ -40,38 +39,35 @@ func NewDelayingQueue() DelayingInterface {
func
newDelayingQueue
(
clock
util
.
Clock
)
DelayingInterface
{
func
newDelayingQueue
(
clock
util
.
Clock
)
DelayingInterface
{
ret
:=
&
delayingType
{
ret
:=
&
delayingType
{
Type
:
New
(),
Interface
:
New
(),
clock
:
clock
,
clock
:
clock
,
waitingCond
:
sync
.
NewCond
(
&
sync
.
Mutex
{}),
heartbeat
:
time
.
Tick
(
maxWait
),
stopCh
:
make
(
chan
struct
{}),
waitingForAddCh
:
make
(
chan
waitFor
,
1000
),
}
}
go
ret
.
waitingLoop
()
go
ret
.
waitingLoop
()
go
ret
.
heartbeat
()
return
ret
return
ret
}
}
// delayingType wraps a
Typ
e and provides delayed re-enquing
// delayingType wraps a
n Interfac
e and provides delayed re-enquing
type
delayingType
struct
{
type
delayingType
struct
{
*
Typ
e
Interfac
e
// clock tracks time for delayed firing
// clock tracks time for delayed firing
clock
util
.
Clock
clock
util
.
Clock
// stopCh lets us signal a shutdown to the waiting loop
stopCh
chan
struct
{}
// heartbeat ensures we wait no more than maxWait before firing
heartbeat
<-
chan
time
.
Time
// waitingForAdd is an ordered slice of items to be added to the contained work queue
// waitingForAdd is an ordered slice of items to be added to the contained work queue
waitingForAdd
[]
waitFor
waitingForAdd
[]
waitFor
// waitingLock synchronizes access to waitingForAdd
// waitingForAddCh is a buffered channel that feeds waitingForAdd
waitingLock
sync
.
Mutex
waitingForAddCh
chan
waitFor
// waitingCond is used to notify the adding go func that it needs to check for items to add
waitingCond
*
sync
.
Cond
// nextCheckTime is used to decide whether to add a notification timer. If the requested time
// is beyond the time we're already waiting for, we don't add a new timer thread
nextCheckTime
*
time
.
Time
// nextCheckLock serializes access to the notification time
nextCheckLock
sync
.
Mutex
// nextCheckCancel is a channel to close to cancel the notification
nextCheckCancel
chan
struct
{}
}
}
// waitFor holds the data to add and the time it should be added
// waitFor holds the data to add and the time it should be added
...
@@ -82,25 +78,28 @@ type waitFor struct {
...
@@ -82,25 +78,28 @@ type waitFor struct {
// ShutDown gives a way to shut off this queue
// ShutDown gives a way to shut off this queue
func
(
q
*
delayingType
)
ShutDown
()
{
func
(
q
*
delayingType
)
ShutDown
()
{
q
.
Typ
e
.
ShutDown
()
q
.
Interfac
e
.
ShutDown
()
q
.
waitingCond
.
Broadcast
(
)
close
(
q
.
stopCh
)
}
}
// AddAfter adds the given item to the work queue after the given delay
func
(
q
*
delayingType
)
AddAfter
(
item
interface
{},
duration
time
.
Duration
)
{
func
(
q
*
delayingType
)
AddAfter
(
item
interface
{},
duration
time
.
Duration
)
{
q
.
waitingLock
.
Lock
()
// don't add if we're already shutting down
defer
q
.
waitingLock
.
Unlock
()
if
q
.
ShuttingDown
()
{
waitEntry
:=
waitFor
{
data
:
item
,
readyAt
:
q
.
clock
.
Now
()
.
Add
(
duration
)}
return
}
insertionIndex
:=
sort
.
Search
(
len
(
q
.
waitingForAdd
),
func
(
i
int
)
bool
{
return
waitEntry
.
readyAt
.
Before
(
q
.
waitingForAdd
[
i
]
.
readyAt
)
})
tail
:=
q
.
waitingForAdd
[
insertionIndex
:
]
// immediately add things with no delay
q
.
waitingForAdd
=
append
(
make
([]
waitFor
,
0
,
len
(
q
.
waitingForAdd
)
+
1
),
q
.
waitingForAdd
[
:
insertionIndex
]
...
)
if
duration
<=
0
{
q
.
waitingForAdd
=
append
(
q
.
waitingForAdd
,
waitEntry
)
q
.
Add
(
item
)
q
.
waitingForAdd
=
append
(
q
.
waitingForAdd
,
tail
...
)
return
}
q
.
notifyAt
(
waitEntry
.
readyAt
)
select
{
case
<-
q
.
stopCh
:
// unblock if ShutDown() is called
case
q
.
waitingForAddCh
<-
waitFor
{
data
:
item
,
readyAt
:
q
.
clock
.
Now
()
.
Add
(
duration
)}
:
}
}
}
// maxWait keeps a max bound on the wait time. It's just insurance against weird things happening.
// maxWait keeps a max bound on the wait time. It's just insurance against weird things happening.
...
@@ -112,115 +111,83 @@ const maxWait = 10 * time.Second
...
@@ -112,115 +111,83 @@ const maxWait = 10 * time.Second
func
(
q
*
delayingType
)
waitingLoop
()
{
func
(
q
*
delayingType
)
waitingLoop
()
{
defer
utilruntime
.
HandleCrash
()
defer
utilruntime
.
HandleCrash
()
// Make a placeholder channel to use when there are no items in our list
never
:=
make
(
<-
chan
time
.
Time
)
for
{
for
{
if
q
.
shuttingDown
{
if
q
.
Interface
.
ShuttingDown
()
{
// discard waiting entries
q
.
waitingForAdd
=
nil
return
return
}
}
func
()
{
now
:=
q
.
clock
.
Now
()
q
.
waitingCond
.
L
.
Lock
()
defer
q
.
waitingCond
.
L
.
Unlock
()
q
.
waitingCond
.
Wait
()
if
q
.
shuttingDown
{
// Add ready entries
return
readyEntries
:=
0
for
_
,
entry
:=
range
q
.
waitingForAdd
{
if
entry
.
readyAt
.
After
(
now
)
{
break
}
}
q
.
Add
(
entry
.
data
)
readyEntries
++
}
q
.
waitingForAdd
=
q
.
waitingForAdd
[
readyEntries
:
]
q
.
waitingLock
.
Lock
()
// Set up a wait for the first item's readyAt (if one exists)
defer
q
.
waitingLock
.
Unlock
()
nextReadyAt
:=
never
if
len
(
q
.
waitingForAdd
)
>
0
{
nextReadyAt
=
q
.
clock
.
After
(
q
.
waitingForAdd
[
0
]
.
readyAt
.
Sub
(
now
))
}
nextReadyCheck
:=
time
.
Time
{}
select
{
itemsAdded
:=
0
case
<-
q
.
stopCh
:
return
for
_
,
queuedItem
:=
range
q
.
waitingForAdd
{
case
<-
q
.
heartbeat
:
nextReadyCheck
=
queuedItem
.
readyAt
// continue the loop, which will add ready items
if
queuedItem
.
readyAt
.
After
(
q
.
clock
.
Now
())
{
break
}
q
.
Type
.
Add
(
queuedItem
.
data
)
itemsAdded
++
}
switch
itemsAdded
{
case
<-
nextReadyAt
:
case
0
:
// continue the loop, which will add ready items
// no change
case
len
(
q
.
waitingForAdd
)
:
// consumed everything
q
.
waitingForAdd
=
make
([]
waitFor
,
0
,
len
(
q
.
waitingForAdd
))
default
:
case
waitEntry
:=
<-
q
.
waitingForAddCh
:
// consumed some
if
waitEntry
.
readyAt
.
After
(
q
.
clock
.
Now
())
{
q
.
waitingForAdd
=
q
.
waitingForAdd
[
itemsAdded
:
]
q
.
waitingForAdd
=
insert
(
q
.
waitingForAdd
,
waitEntry
)
}
else
{
q
.
Add
(
waitEntry
.
data
)
}
if
len
(
q
.
waitingForAdd
)
>
0
{
drained
:=
false
q
.
notifyAt
(
nextReadyCheck
)
for
!
drained
{
select
{
case
waitEntry
:=
<-
q
.
waitingForAddCh
:
if
waitEntry
.
readyAt
.
After
(
q
.
clock
.
Now
())
{
q
.
waitingForAdd
=
insert
(
q
.
waitingForAdd
,
waitEntry
)
}
else
{
q
.
Add
(
waitEntry
.
data
)
}
default
:
drained
=
true
}
}
}
}
}()
}
}
// heartbeat forces a check every maxWait seconds
func
(
q
*
delayingType
)
heartbeat
()
{
defer
utilruntime
.
HandleCrash
()
for
{
if
q
.
shuttingDown
{
return
}
}
ch
:=
q
.
clock
.
After
(
maxWait
)
<-
ch
q
.
waitingCond
.
Broadcast
()
}
}
// clearNextCheckTimeIf resets the nextCheckTime if it matches the expected value to ensure that the subsequent notification will take effect.
func
(
q
*
delayingType
)
clearNextCheckTimeIf
(
nextReadyCheck
time
.
Time
)
{
q
.
nextCheckLock
.
Lock
()
defer
q
.
nextCheckLock
.
Unlock
()
if
q
.
nextCheckTime
!=
nil
&&
*
q
.
nextCheckTime
==
nextReadyCheck
{
q
.
nextCheckTime
=
nil
}
}
}
}
// notifyAt: if the requested nextReadyCheck is sooner than the current check, then a new go func is
// inserts the given entry into the sorted entries list
// spawned to notify the condition that the waitingLoop is waiting for after the time is up. The previous go func
// same semantics as append()... the given slice may be modified,
// is cancelled
// and the returned value should be used
func
(
q
*
delayingType
)
notifyAt
(
nextReadyCheck
time
.
Time
)
{
func
insert
(
entries
[]
waitFor
,
entry
waitFor
)
[]
waitFor
{
q
.
nextCheckLock
.
Lock
()
insertionIndex
:=
sort
.
Search
(
len
(
entries
),
func
(
i
int
)
bool
{
defer
q
.
nextCheckLock
.
Unlock
()
return
entry
.
readyAt
.
Before
(
entries
[
i
]
.
readyAt
)
})
now
:=
q
.
clock
.
Now
()
if
(
q
.
nextCheckTime
!=
nil
&&
(
nextReadyCheck
.
After
(
*
q
.
nextCheckTime
)
||
nextReadyCheck
==
*
q
.
nextCheckTime
))
||
nextReadyCheck
.
Before
(
now
)
{
return
}
duration
:=
nextReadyCheck
.
Sub
(
now
)
q
.
nextCheckTime
=
&
nextReadyCheck
ch
:=
q
.
clock
.
After
(
duration
)
newCancel
:=
make
(
chan
struct
{})
oldCancel
:=
q
.
nextCheckCancel
// always cancel the old notifier
if
oldCancel
!=
nil
{
close
(
oldCancel
)
}
q
.
nextCheckCancel
=
newCancel
go
func
()
{
// grow by 1
defer
utilruntime
.
HandleCrash
()
entries
=
append
(
entries
,
waitFor
{})
// shift items from the insertion point to the end
copy
(
entries
[
insertionIndex
+
1
:
],
entries
[
insertionIndex
:
])
// insert the record
entries
[
insertionIndex
]
=
entry
select
{
return
entries
case
<-
ch
:
// we only have one of these go funcs active at a time. If we hit our timer, then clear
// the check time so that the next add will win
q
.
clearNextCheckTimeIf
(
nextReadyCheck
)
q
.
waitingCond
.
Broadcast
()
case
<-
newCancel
:
// do nothing, cancelled
}
}()
}
}
pkg/util/workqueue/delaying_queue_test.go
View file @
290d9702
...
@@ -33,6 +33,9 @@ func TestSimpleQueue(t *testing.T) {
...
@@ -33,6 +33,9 @@ func TestSimpleQueue(t *testing.T) {
first
:=
"foo"
first
:=
"foo"
q
.
AddAfter
(
first
,
50
*
time
.
Millisecond
)
q
.
AddAfter
(
first
,
50
*
time
.
Millisecond
)
if
err
:=
waitForWaitingQueueToFill
(
q
);
err
!=
nil
{
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
}
if
q
.
Len
()
!=
0
{
if
q
.
Len
()
!=
0
{
t
.
Errorf
(
"should not have added"
)
t
.
Errorf
(
"should not have added"
)
...
@@ -40,14 +43,7 @@ func TestSimpleQueue(t *testing.T) {
...
@@ -40,14 +43,7 @@ func TestSimpleQueue(t *testing.T) {
fakeClock
.
Step
(
60
*
time
.
Millisecond
)
fakeClock
.
Step
(
60
*
time
.
Millisecond
)
err
:=
wait
.
Poll
(
1
*
time
.
Millisecond
,
30
*
time
.
Millisecond
,
func
()
(
done
bool
,
err
error
)
{
if
err
:=
waitForAdded
(
q
,
1
);
err
!=
nil
{
if
q
.
Len
()
==
1
{
return
true
,
nil
}
return
false
,
nil
})
if
err
!=
nil
{
t
.
Errorf
(
"should have added"
)
t
.
Errorf
(
"should have added"
)
}
}
item
,
_
:=
q
.
Get
()
item
,
_
:=
q
.
Get
()
...
@@ -56,7 +52,7 @@ func TestSimpleQueue(t *testing.T) {
...
@@ -56,7 +52,7 @@ func TestSimpleQueue(t *testing.T) {
// step past the next heartbeat
// step past the next heartbeat
fakeClock
.
Step
(
10
*
time
.
Second
)
fakeClock
.
Step
(
10
*
time
.
Second
)
err
=
wait
.
Poll
(
1
*
time
.
Millisecond
,
30
*
time
.
Millisecond
,
func
()
(
done
bool
,
err
error
)
{
err
:
=
wait
.
Poll
(
1
*
time
.
Millisecond
,
30
*
time
.
Millisecond
,
func
()
(
done
bool
,
err
error
)
{
if
q
.
Len
()
>
0
{
if
q
.
Len
()
>
0
{
return
false
,
fmt
.
Errorf
(
"added to queue"
)
return
false
,
fmt
.
Errorf
(
"added to queue"
)
}
}
...
@@ -82,21 +78,18 @@ func TestAddTwoFireEarly(t *testing.T) {
...
@@ -82,21 +78,18 @@ func TestAddTwoFireEarly(t *testing.T) {
q
.
AddAfter
(
first
,
1
*
time
.
Second
)
q
.
AddAfter
(
first
,
1
*
time
.
Second
)
q
.
AddAfter
(
second
,
50
*
time
.
Millisecond
)
q
.
AddAfter
(
second
,
50
*
time
.
Millisecond
)
if
err
:=
waitForWaitingQueueToFill
(
q
);
err
!=
nil
{
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
}
if
q
.
Len
()
!=
0
{
if
q
.
Len
()
!=
0
{
t
.
Errorf
(
"should not have added"
)
t
.
Errorf
(
"should not have added"
)
}
}
fakeClock
.
Step
(
60
*
time
.
Millisecond
)
fakeClock
.
Step
(
60
*
time
.
Millisecond
)
err
:=
wait
.
Poll
(
1
*
time
.
Millisecond
,
30
*
time
.
Millisecond
,
func
()
(
done
bool
,
err
error
)
{
if
q
.
Len
()
==
1
{
return
true
,
nil
}
return
false
,
nil
if
err
:=
waitForAdded
(
q
,
1
);
err
!=
nil
{
})
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
if
err
!=
nil
{
t
.
Fatalf
(
"should have added"
)
}
}
item
,
_
:=
q
.
Get
()
item
,
_
:=
q
.
Get
()
if
!
reflect
.
DeepEqual
(
item
,
second
)
{
if
!
reflect
.
DeepEqual
(
item
,
second
)
{
...
@@ -106,15 +99,8 @@ func TestAddTwoFireEarly(t *testing.T) {
...
@@ -106,15 +99,8 @@ func TestAddTwoFireEarly(t *testing.T) {
q
.
AddAfter
(
third
,
2
*
time
.
Second
)
q
.
AddAfter
(
third
,
2
*
time
.
Second
)
fakeClock
.
Step
(
1
*
time
.
Second
)
fakeClock
.
Step
(
1
*
time
.
Second
)
err
=
wait
.
Poll
(
1
*
time
.
Millisecond
,
30
*
time
.
Millisecond
,
func
()
(
done
bool
,
err
error
)
{
if
err
:=
waitForAdded
(
q
,
1
);
err
!=
nil
{
if
q
.
Len
()
==
1
{
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
return
true
,
nil
}
return
false
,
nil
})
if
err
!=
nil
{
t
.
Fatalf
(
"should have added"
)
}
}
item
,
_
=
q
.
Get
()
item
,
_
=
q
.
Get
()
if
!
reflect
.
DeepEqual
(
item
,
first
)
{
if
!
reflect
.
DeepEqual
(
item
,
first
)
{
...
@@ -122,15 +108,8 @@ func TestAddTwoFireEarly(t *testing.T) {
...
@@ -122,15 +108,8 @@ func TestAddTwoFireEarly(t *testing.T) {
}
}
fakeClock
.
Step
(
2
*
time
.
Second
)
fakeClock
.
Step
(
2
*
time
.
Second
)
err
=
wait
.
Poll
(
1
*
time
.
Millisecond
,
30
*
time
.
Millisecond
,
func
()
(
done
bool
,
err
error
)
{
if
err
:=
waitForAdded
(
q
,
1
);
err
!=
nil
{
if
q
.
Len
()
==
1
{
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
return
true
,
nil
}
return
false
,
nil
})
if
err
!=
nil
{
t
.
Fatalf
(
"should have added"
)
}
}
item
,
_
=
q
.
Get
()
item
,
_
=
q
.
Get
()
if
!
reflect
.
DeepEqual
(
item
,
third
)
{
if
!
reflect
.
DeepEqual
(
item
,
third
)
{
...
@@ -138,3 +117,61 @@ func TestAddTwoFireEarly(t *testing.T) {
...
@@ -138,3 +117,61 @@ func TestAddTwoFireEarly(t *testing.T) {
}
}
}
}
func
TestCopyShifting
(
t
*
testing
.
T
)
{
fakeClock
:=
util
.
NewFakeClock
(
time
.
Now
())
q
:=
newDelayingQueue
(
fakeClock
)
first
:=
"foo"
second
:=
"bar"
third
:=
"baz"
q
.
AddAfter
(
first
,
1
*
time
.
Second
)
q
.
AddAfter
(
second
,
500
*
time
.
Millisecond
)
q
.
AddAfter
(
third
,
250
*
time
.
Millisecond
)
if
err
:=
waitForWaitingQueueToFill
(
q
);
err
!=
nil
{
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
}
if
q
.
Len
()
!=
0
{
t
.
Errorf
(
"should not have added"
)
}
fakeClock
.
Step
(
2
*
time
.
Second
)
if
err
:=
waitForAdded
(
q
,
3
);
err
!=
nil
{
t
.
Fatalf
(
"unexpected err: %v"
,
err
)
}
actualFirst
,
_
:=
q
.
Get
()
if
!
reflect
.
DeepEqual
(
actualFirst
,
third
)
{
t
.
Errorf
(
"expected %v, got %v"
,
third
,
actualFirst
)
}
actualSecond
,
_
:=
q
.
Get
()
if
!
reflect
.
DeepEqual
(
actualSecond
,
second
)
{
t
.
Errorf
(
"expected %v, got %v"
,
second
,
actualSecond
)
}
actualThird
,
_
:=
q
.
Get
()
if
!
reflect
.
DeepEqual
(
actualThird
,
first
)
{
t
.
Errorf
(
"expected %v, got %v"
,
first
,
actualThird
)
}
}
func
waitForAdded
(
q
DelayingInterface
,
depth
int
)
error
{
return
wait
.
Poll
(
1
*
time
.
Millisecond
,
10
*
time
.
Second
,
func
()
(
done
bool
,
err
error
)
{
if
q
.
Len
()
==
depth
{
return
true
,
nil
}
return
false
,
nil
})
}
func
waitForWaitingQueueToFill
(
q
DelayingInterface
)
error
{
return
wait
.
Poll
(
1
*
time
.
Millisecond
,
10
*
time
.
Second
,
func
()
(
done
bool
,
err
error
)
{
if
len
(
q
.
(
*
delayingType
)
.
waitingForAddCh
)
==
0
{
return
true
,
nil
}
return
false
,
nil
})
}
pkg/util/workqueue/queue.go
View file @
290d9702
...
@@ -26,6 +26,7 @@ type Interface interface {
...
@@ -26,6 +26,7 @@ type Interface interface {
Get
()
(
item
interface
{},
shutdown
bool
)
Get
()
(
item
interface
{},
shutdown
bool
)
Done
(
item
interface
{})
Done
(
item
interface
{})
ShutDown
()
ShutDown
()
ShuttingDown
()
bool
}
}
// New constructs a new workqueue (see the package comment).
// New constructs a new workqueue (see the package comment).
...
@@ -143,3 +144,10 @@ func (q *Type) ShutDown() {
...
@@ -143,3 +144,10 @@ func (q *Type) ShutDown() {
q
.
shuttingDown
=
true
q
.
shuttingDown
=
true
q
.
cond
.
Broadcast
()
q
.
cond
.
Broadcast
()
}
}
func
(
q
*
Type
)
ShuttingDown
()
bool
{
q
.
cond
.
L
.
Lock
()
defer
q
.
cond
.
L
.
Unlock
()
return
q
.
shuttingDown
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment