@@ -17,10 +17,12 @@ limitations under the License.
17
17
package apiserver
18
18
19
19
import (
20
+ "math/rand"
20
21
"net/http"
21
22
"reflect"
22
23
"regexp"
23
24
"strings"
25
+ "time"
24
26
25
27
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
26
28
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@@ -32,19 +34,47 @@ import (
32
34
"golang.org/x/net/websocket"
33
35
)
34
36
35
- var connectionUpgradeRegex = regexp .MustCompile ("(^|.*,\\ s*)upgrade($|\\ s*,)" )
37
+ var (
38
+ connectionUpgradeRegex = regexp .MustCompile ("(^|.*,\\ s*)upgrade($|\\ s*,)" )
39
+
40
+ // nothing will ever be sent down this channel
41
+ neverExitWatch <- chan time.Time = make (chan time.Time )
42
+ )
36
43
37
44
func isWebsocketRequest (req * http.Request ) bool {
38
45
return connectionUpgradeRegex .MatchString (strings .ToLower (req .Header .Get ("Connection" ))) && strings .ToLower (req .Header .Get ("Upgrade" )) == "websocket"
39
46
}
40
47
48
+ // timeoutFactory abstracts watch timeout logic for testing
49
+ type timeoutFactory interface {
50
+ TimeoutCh () (<- chan time.Time , func () bool )
51
+ }
52
+
53
+ // realTimeoutFactory implements timeoutFactory
54
+ type realTimeoutFactory struct {
55
+ timeout time.Duration
56
+ }
57
+
58
+ // TimeoutChan returns a channel which will receive something when the watch times out,
59
+ // and a cleanup function to call when this happens.
60
+ func (w * realTimeoutFactory ) TimeoutCh () (<- chan time.Time , func () bool ) {
61
+ if w .timeout == 0 {
62
+ return neverExitWatch , func () bool { return false }
63
+ }
64
+ t := time .NewTimer (w .timeout )
65
+ return t .C , t .Stop
66
+ }
67
+
41
68
// serveWatch handles serving requests to the server
42
69
func serveWatch (watcher watch.Interface , scope RequestScope , w http.ResponseWriter , req * restful.Request ) {
70
+ // Each watch gets a random timeout to avoid thundering herds. Rand is seeded once in the api installer.
71
+ timeout := time .Duration (MinTimeoutSecs + rand .Intn (MaxTimeoutSecs - MinTimeoutSecs )) * time .Second
72
+
43
73
watchServer := & WatchServer {watcher , scope .Codec , func (obj runtime.Object ) {
44
74
if err := setSelfLink (obj , req , scope .Namer ); err != nil {
45
75
glog .V (5 ).Infof ("Failed to set self link for object %v: %v" , reflect .TypeOf (obj ), err )
46
76
}
47
- }}
77
+ }, & realTimeoutFactory { timeout } }
48
78
if isWebsocketRequest (req .Request ) {
49
79
websocket .Handler (watchServer .HandleWS ).ServeHTTP (httplog .Unlogged (w ), req .Request )
50
80
} else {
@@ -57,6 +87,7 @@ type WatchServer struct {
57
87
watching watch.Interface
58
88
codec runtime.Codec
59
89
fixup func (runtime.Object )
90
+ t timeoutFactory
60
91
}
61
92
62
93
// HandleWS implements a websocket handler.
@@ -100,6 +131,9 @@ func (w *WatchServer) HandleWS(ws *websocket.Conn) {
100
131
func (self * WatchServer ) ServeHTTP (w http.ResponseWriter , req * http.Request ) {
101
132
loggedW := httplog .LogOf (req , w )
102
133
w = httplog .Unlogged (w )
134
+ timeoutCh , cleanup := self .t .TimeoutCh ()
135
+ defer cleanup ()
136
+ defer self .watching .Stop ()
103
137
104
138
cn , ok := w .(http.CloseNotifier )
105
139
if ! ok {
@@ -113,16 +147,15 @@ func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
113
147
http .NotFound (w , req )
114
148
return
115
149
}
116
-
117
150
w .Header ().Set ("Transfer-Encoding" , "chunked" )
118
151
w .WriteHeader (http .StatusOK )
119
152
flusher .Flush ()
120
-
121
153
encoder := watchjson .NewEncoder (w , self .codec )
122
154
for {
123
155
select {
124
156
case <- cn .CloseNotify ():
125
- self .watching .Stop ()
157
+ return
158
+ case <- timeoutCh :
126
159
return
127
160
case event , ok := <- self .watching .ResultChan ():
128
161
if ! ok {
@@ -132,7 +165,6 @@ func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
132
165
self .fixup (event .Object )
133
166
if err := encoder .Encode (& event ); err != nil {
134
167
// Client disconnect.
135
- self .watching .Stop ()
136
168
return
137
169
}
138
170
flusher .Flush ()
0 commit comments