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
4fed7530
Unverified
Commit
4fed7530
authored
May 28, 2019
by
Kubernetes Prow Robot
Committed by
GitHub
May 28, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #77434 from mikedanese/watching
NewIndexerInformerWatcher: fix goroutine leak
parents
aa25195a
cafc640b
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
167 additions
and
90 deletions
+167
-90
informerwatcher.go
staging/src/k8s.io/client-go/tools/watch/informerwatcher.go
+84
-51
informerwatcher_test.go
.../src/k8s.io/client-go/tools/watch/informerwatcher_test.go
+83
-39
No files found.
staging/src/k8s.io/client-go/tools/watch/informerwatcher.go
View file @
4fed7530
...
...
@@ -18,42 +18,86 @@ package watch
import
(
"sync"
"sync/atomic"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
)
func
newTicketer
()
*
ticketer
{
return
&
ticketer
{
func
newEventProcessor
(
out
chan
<-
watch
.
Event
)
*
eventProcessor
{
return
&
eventProcessor
{
out
:
out
,
cond
:
sync
.
NewCond
(
&
sync
.
Mutex
{}),
done
:
make
(
chan
struct
{}),
}
}
type
ticketer
struct
{
counter
uint64
// eventProcessor buffers events and writes them to an out chan when a reader
// is waiting. Because of the requirement to buffer events, it synchronizes
// input with a condition, and synchronizes output with a channels. It needs to
// be able to yield while both waiting on an input condition and while blocked
// on writing to the output channel.
type
eventProcessor
struct
{
out
chan
<-
watch
.
Event
cond
*
sync
.
Cond
current
uint64
cond
*
sync
.
Cond
buff
[]
watch
.
Event
done
chan
struct
{}
}
func
(
t
*
ticketer
)
GetTicket
()
uint64
{
// -1 to start from 0
return
atomic
.
AddUint64
(
&
t
.
counter
,
1
)
-
1
func
(
e
*
eventProcessor
)
run
()
{
for
{
batch
:=
e
.
takeBatch
()
e
.
writeBatch
(
batch
)
if
e
.
stopped
()
{
return
}
}
}
func
(
t
*
ticketer
)
WaitForTicket
(
ticket
uint64
,
f
func
())
{
t
.
cond
.
L
.
Lock
()
defer
t
.
cond
.
L
.
Unlock
()
for
ticket
!=
t
.
current
{
t
.
cond
.
Wait
()
func
(
e
*
eventProcessor
)
takeBatch
()
[]
watch
.
Event
{
e
.
cond
.
L
.
Lock
()
defer
e
.
cond
.
L
.
Unlock
()
for
len
(
e
.
buff
)
==
0
&&
!
e
.
stopped
()
{
e
.
cond
.
Wait
()
}
f
()
batch
:=
e
.
buff
e
.
buff
=
nil
return
batch
}
func
(
e
*
eventProcessor
)
writeBatch
(
events
[]
watch
.
Event
)
{
for
_
,
event
:=
range
events
{
select
{
case
e
.
out
<-
event
:
case
<-
e
.
done
:
return
}
}
}
t
.
current
++
t
.
cond
.
Broadcast
()
func
(
e
*
eventProcessor
)
push
(
event
watch
.
Event
)
{
e
.
cond
.
L
.
Lock
()
defer
e
.
cond
.
L
.
Unlock
()
defer
e
.
cond
.
Signal
()
e
.
buff
=
append
(
e
.
buff
,
event
)
}
func
(
e
*
eventProcessor
)
stopped
()
bool
{
select
{
case
<-
e
.
done
:
return
true
default
:
return
false
}
}
func
(
e
*
eventProcessor
)
stop
()
{
close
(
e
.
done
)
e
.
cond
.
Signal
()
}
// NewIndexerInformerWatcher will create an IndexerInformer and wrap it into watch.Interface
...
...
@@ -61,55 +105,44 @@ func (t *ticketer) WaitForTicket(ticket uint64, f func()) {
// it also returns a channel you can use to wait for the informers to fully shutdown.
func
NewIndexerInformerWatcher
(
lw
cache
.
ListerWatcher
,
objType
runtime
.
Object
)
(
cache
.
Indexer
,
cache
.
Controller
,
watch
.
Interface
,
<-
chan
struct
{})
{
ch
:=
make
(
chan
watch
.
Event
)
doneCh
:=
make
(
chan
struct
{})
w
:=
watch
.
NewProxyWatcher
(
ch
)
t
:=
newTicketer
(
)
e
:=
newEventProcessor
(
ch
)
indexer
,
informer
:=
cache
.
NewIndexerInformer
(
lw
,
objType
,
0
,
cache
.
ResourceEventHandlerFuncs
{
AddFunc
:
func
(
obj
interface
{})
{
go
t
.
WaitForTicket
(
t
.
GetTicket
(),
func
()
{
select
{
case
ch
<-
watch
.
Event
{
Type
:
watch
.
Added
,
Object
:
obj
.
(
runtime
.
Object
),
}
:
case
<-
w
.
StopChan
()
:
}
e
.
push
(
watch
.
Event
{
Type
:
watch
.
Added
,
Object
:
obj
.
(
runtime
.
Object
),
})
},
UpdateFunc
:
func
(
old
,
new
interface
{})
{
go
t
.
WaitForTicket
(
t
.
GetTicket
(),
func
()
{
select
{
case
ch
<-
watch
.
Event
{
Type
:
watch
.
Modified
,
Object
:
new
.
(
runtime
.
Object
),
}
:
case
<-
w
.
StopChan
()
:
}
e
.
push
(
watch
.
Event
{
Type
:
watch
.
Modified
,
Object
:
new
.
(
runtime
.
Object
),
})
},
DeleteFunc
:
func
(
obj
interface
{})
{
go
t
.
WaitForTicket
(
t
.
GetTicket
(),
func
()
{
staleObj
,
stale
:=
obj
.
(
cache
.
DeletedFinalStateUnknown
)
if
stale
{
// We have no means of passing the additional information down using watch API based on watch.Event
// but the caller can filter such objects by checking if metadata.deletionTimestamp is set
obj
=
staleObj
}
select
{
case
ch
<-
watch
.
Event
{
Type
:
watch
.
Deleted
,
Object
:
obj
.
(
runtime
.
Object
),
}
:
case
<-
w
.
StopChan
()
:
}
staleObj
,
stale
:=
obj
.
(
cache
.
DeletedFinalStateUnknown
)
if
stale
{
// We have no means of passing the additional information down using
// watch API based on watch.Event but the caller can filter such
// objects by checking if metadata.deletionTimestamp is set
obj
=
staleObj
}
e
.
push
(
watch
.
Event
{
Type
:
watch
.
Deleted
,
Object
:
obj
.
(
runtime
.
Object
),
})
},
},
cache
.
Indexers
{})
go
e
.
run
()
doneCh
:=
make
(
chan
struct
{})
go
func
()
{
defer
close
(
doneCh
)
defer
e
.
stop
()
informer
.
Run
(
w
.
StopChan
())
}()
...
...
staging/src/k8s.io/client-go/tools/watch/informerwatcher_test.go
View file @
4fed7530
...
...
@@ -17,8 +17,9 @@ limitations under the License.
package
watch
import
(
"
math/rand
"
"
context
"
"reflect"
goruntime
"runtime"
"sort"
"testing"
"time"
...
...
@@ -28,6 +29,7 @@ import (
corev1
"k8s.io/api/core/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/watch"
fakeclientset
"k8s.io/client-go/kubernetes/fake"
...
...
@@ -35,58 +37,100 @@ import (
"k8s.io/client-go/tools/cache"
)
type
byEventTypeAndName
[]
watch
.
Event
// TestEventProcessorExit is expected to timeout if the event processor fails
// to exit when stopped.
func
TestEventProcessorExit
(
t
*
testing
.
T
)
{
event
:=
watch
.
Event
{}
func
(
a
byEventTypeAndName
)
Len
()
int
{
return
len
(
a
)
}
func
(
a
byEventTypeAndName
)
Swap
(
i
,
j
int
)
{
a
[
i
],
a
[
j
]
=
a
[
j
],
a
[
i
]
}
func
(
a
byEventTypeAndName
)
Less
(
i
,
j
int
)
bool
{
if
a
[
i
]
.
Type
<
a
[
j
]
.
Type
{
return
true
tests
:=
[]
struct
{
name
string
write
func
(
e
*
eventProcessor
)
}{
{
name
:
"exit on blocked read"
,
write
:
func
(
e
*
eventProcessor
)
{
e
.
push
(
event
)
},
},
{
name
:
"exit on blocked write"
,
write
:
func
(
e
*
eventProcessor
)
{
e
.
push
(
event
)
e
.
push
(
event
)
},
},
}
for
_
,
test
:=
range
tests
{
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
out
:=
make
(
chan
watch
.
Event
)
e
:=
newEventProcessor
(
out
)
if
a
[
i
]
.
Type
>
a
[
j
]
.
Type
{
return
false
}
test
.
write
(
e
)
return
a
[
i
]
.
Object
.
(
*
corev1
.
Secret
)
.
Name
<
a
[
j
]
.
Object
.
(
*
corev1
.
Secret
)
.
Name
exited
:=
make
(
chan
struct
{})
go
func
()
{
e
.
run
()
close
(
exited
)
}()
<-
out
e
.
stop
()
goruntime
.
Gosched
()
<-
exited
})
}
}
func
TestTicketer
(
t
*
testing
.
T
)
{
tg
:=
newTicketer
()
type
apiInt
int
const
numTickets
=
100
// current golang limit for race detector is 8192 simultaneously alive goroutines
var
tickets
[]
uint64
for
i
:=
0
;
i
<
numTickets
;
i
++
{
ticket
:=
tg
.
GetTicket
()
tickets
=
append
(
tickets
,
ticket
)
func
(
apiInt
)
GetObjectKind
()
schema
.
ObjectKind
{
return
nil
}
func
(
apiInt
)
DeepCopyObject
()
runtime
.
Object
{
return
nil
}
exp
,
got
:=
uint64
(
i
),
ticket
if
got
!=
exp
{
t
.
Fatalf
(
"expected ticket %d, got %d"
,
exp
,
got
)
func
TestEventProcessorOrdersEvents
(
t
*
testing
.
T
)
{
out
:=
make
(
chan
watch
.
Event
)
e
:=
newEventProcessor
(
out
)
go
e
.
run
()
numProcessed
:=
0
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
go
func
()
{
for
i
:=
0
;
i
<
1000
;
i
++
{
e
:=
<-
out
if
got
,
want
:=
int
(
e
.
Object
.
(
apiInt
)),
i
;
got
!=
want
{
t
.
Errorf
(
"unexpected event: got=%d, want=%d"
,
got
,
want
)
}
numProcessed
++
}
cancel
()
}()
for
i
:=
0
;
i
<
1000
;
i
++
{
e
.
push
(
watch
.
Event
{
Object
:
apiInt
(
i
)})
}
// shuffle tickets
rand
.
Shuffle
(
len
(
tickets
),
func
(
i
,
j
int
)
{
tickets
[
i
],
tickets
[
j
]
=
tickets
[
j
],
tickets
[
i
]
})
res
:=
make
(
chan
uint64
,
len
(
tickets
))
for
_
,
ticket
:=
range
tickets
{
go
func
(
ticket
uint64
)
{
time
.
Sleep
(
time
.
Duration
(
rand
.
Intn
(
50
))
*
time
.
Millisecond
)
tg
.
WaitForTicket
(
ticket
,
func
()
{
res
<-
ticket
})
}(
ticket
)
<-
ctx
.
Done
()
e
.
stop
()
if
numProcessed
!=
1000
{
t
.
Errorf
(
"unexpected number of events processed: %d"
,
numProcessed
)
}
for
i
:=
0
;
i
<
numTickets
;
i
++
{
exp
,
got
:=
uint64
(
i
),
<-
res
if
got
!=
exp
{
t
.
Fatalf
(
"expected ticket %d, got %d"
,
exp
,
got
)
}
}
type
byEventTypeAndName
[]
watch
.
Event
func
(
a
byEventTypeAndName
)
Len
()
int
{
return
len
(
a
)
}
func
(
a
byEventTypeAndName
)
Swap
(
i
,
j
int
)
{
a
[
i
],
a
[
j
]
=
a
[
j
],
a
[
i
]
}
func
(
a
byEventTypeAndName
)
Less
(
i
,
j
int
)
bool
{
if
a
[
i
]
.
Type
<
a
[
j
]
.
Type
{
return
true
}
if
a
[
i
]
.
Type
>
a
[
j
]
.
Type
{
return
false
}
return
a
[
i
]
.
Object
.
(
*
corev1
.
Secret
)
.
Name
<
a
[
j
]
.
Object
.
(
*
corev1
.
Secret
)
.
Name
}
func
TestNewInformerWatcher
(
t
*
testing
.
T
)
{
...
...
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