@@ -18,29 +18,30 @@ package connector
1818
1919import (
2020 "bufio"
21+ "context"
2122 "encoding/json"
2223 "fmt"
24+ "io"
25+ "net/http"
26+ "strconv"
27+ "strings"
28+ "sync"
29+ "time"
30+
2331 "github.com/devtron-labs/devtron/api/bean"
2432 "github.com/gogo/protobuf/proto"
2533 "github.com/grpc-ecosystem/grpc-gateway/runtime"
2634 "github.com/juju/errors"
2735 "go.uber.org/zap"
2836 "google.golang.org/grpc/codes"
2937 "google.golang.org/grpc/status"
30- "io"
31- "net/http"
32- "regexp"
33- "strconv"
34- "strings"
35- "sync"
36- "time"
3738)
3839
3940var delimiter = []byte ("\n \n " )
4041
4142type Pump interface {
4243 StartStreamWithTransformer (w http.ResponseWriter , recv func () (proto.Message , error ), err error , transformer func (interface {}) interface {})
43- StartK8sStreamWithHeartBeat (w http.ResponseWriter , isReconnect bool , stream io.ReadCloser , err error )
44+ StartK8sStreamWithHeartBeat (ctx context. Context , w http.ResponseWriter , isReconnect bool , stream io.ReadCloser , err error )
4445}
4546
4647type PumpImpl struct {
@@ -53,7 +54,7 @@ func NewPumpImpl(logger *zap.SugaredLogger) *PumpImpl {
5354 }
5455}
5556
56- func (impl PumpImpl ) StartK8sStreamWithHeartBeat (w http.ResponseWriter , isReconnect bool , stream io.ReadCloser , err error ) {
57+ func (impl PumpImpl ) StartK8sStreamWithHeartBeat (ctx context. Context , w http.ResponseWriter , isReconnect bool , stream io.ReadCloser , err error ) {
5758 f , ok := w .(http.Flusher )
5859 if ! ok {
5960 http .Error (w , "unexpected server doesnt support streaming" , http .StatusInternalServerError )
@@ -82,47 +83,69 @@ func (impl PumpImpl) StartK8sStreamWithHeartBeat(w http.ResponseWriter, isReconn
8283 }
8384 // heartbeat start
8485 ticker := time .NewTicker (30 * time .Second )
85- done := make (chan bool )
86+ done := make (chan struct {}) // close(done) never blocks, so no buffer needed
8687 var mux sync.Mutex
87- go func () error {
88+
89+ go func () {
8890 for {
8991 select {
9092 case <- done :
91- return nil
93+ return
94+ case <- ctx .Done ():
95+ stream .Close () // unblocks the blocking bufReader.ReadString below
96+ return
9297 case t := <- ticker .C :
9398 mux .Lock ()
9499 err := impl .sendEvent (nil , []byte ("PING" ), []byte (t .String ()), w )
100+ if err == nil {
101+ f .Flush ()
102+ }
95103 mux .Unlock ()
96104 if err != nil {
97105 impl .logger .Errorw ("error in writing PING over sse" , "err" , err )
98- return err
106+ return
99107 }
100- f .Flush ()
101108 }
102109 }
103110 }()
104111 defer func () {
105112 ticker .Stop ()
106- done <- true
113+ stream .Close () // idempotent: safe to call after goroutine already closed it
114+ close (done ) // signals goroutine to exit if still running
107115 }()
108116
109117 bufReader := bufio .NewReader (stream )
110118 eof := false
111119 for ! eof {
120+ // fast-exit: if ctx expired between reads, return immediately
121+ select {
122+ case <- ctx .Done ():
123+ return
124+ default :
125+ }
126+
112127 log , err := bufReader .ReadString ('\n' )
113128 if err == io .EOF {
114129 eof = true
115- // stop if we reached end of stream and the next line is empty
116130 if log == "" {
117131 return
118132 }
119- } else if err != nil && err != io .EOF {
133+ } else if err != nil {
134+ if ctx .Err () != nil {
135+ // stream was closed because ctx expired — not an application error
136+ return
137+ }
120138 impl .logger .Errorw ("error in reading buffer string, StartK8sStreamWithHeartBeat" , "err" , err )
121139 return
122140 }
123- log = strings .TrimSpace (log ) // Remove trailing line ending
124- a := regexp .MustCompile (" " )
125- splitLog := a .Split (log , 2 )
141+ log = strings .TrimSpace (log )
142+ if log == "" {
143+ continue // blank line mid-stream: skip without aborting
144+ }
145+ splitLog := strings .SplitN (log , " " , 2 )
146+ if len (splitLog ) < 2 {
147+ continue // no space separator: not a valid log line, skip
148+ }
126149 parsedTime , err := time .Parse (time .RFC3339 , splitLog [0 ])
127150 if err != nil {
128151 impl .logger .Errorw ("error in writing data over sse" , "err" , err )
@@ -133,12 +156,14 @@ func (impl PumpImpl) StartK8sStreamWithHeartBeat(w http.ResponseWriter, isReconn
133156 if len (splitLog ) == 2 {
134157 err = impl .sendEvent ([]byte (eventId ), nil , []byte (splitLog [1 ]), w )
135158 }
159+ if err == nil {
160+ f .Flush ()
161+ }
136162 mux .Unlock ()
137163 if err != nil {
138164 impl .logger .Errorw ("error in writing data over sse" , "err" , err )
139165 return
140166 }
141- f .Flush ()
142167 }
143168 // heartbeat end
144169}
0 commit comments