github.com/emate/nomad@v0.8.2-wo-binpacking/client/lib/streamframer/framer.go (about) 1 package framer 2 3 import ( 4 "bytes" 5 "fmt" 6 "sync" 7 "time" 8 ) 9 10 var ( 11 // HeartbeatStreamFrame is the StreamFrame to send as a heartbeat, avoiding 12 // creating many instances of the empty StreamFrame 13 HeartbeatStreamFrame = &StreamFrame{} 14 ) 15 16 // StreamFrame is used to frame data of a file when streaming 17 type StreamFrame struct { 18 // Offset is the offset the data was read from 19 Offset int64 `json:",omitempty"` 20 21 // Data is the read data 22 Data []byte `json:",omitempty"` 23 24 // File is the file that the data was read from 25 File string `json:",omitempty"` 26 27 // FileEvent is the last file event that occurred that could cause the 28 // streams position to change or end 29 FileEvent string `json:",omitempty"` 30 } 31 32 // IsHeartbeat returns if the frame is a heartbeat frame 33 func (s *StreamFrame) IsHeartbeat() bool { 34 return s.Offset == 0 && len(s.Data) == 0 && s.File == "" && s.FileEvent == "" 35 } 36 37 func (s *StreamFrame) Clear() { 38 s.Offset = 0 39 s.Data = nil 40 s.File = "" 41 s.FileEvent = "" 42 } 43 44 func (s *StreamFrame) IsCleared() bool { 45 if s.Offset != 0 { 46 return false 47 } else if s.Data != nil { 48 return false 49 } else if s.File != "" { 50 return false 51 } else if s.FileEvent != "" { 52 return false 53 } else { 54 return true 55 } 56 } 57 58 // StreamFramer is used to buffer and send frames as well as heartbeat. 59 type StreamFramer struct { 60 out chan<- *StreamFrame 61 62 frameSize int 63 64 heartbeat *time.Ticker 65 flusher *time.Ticker 66 67 shutdown bool 68 shutdownCh chan struct{} 69 exitCh chan struct{} 70 71 // The mutex protects everything below 72 l sync.Mutex 73 74 // The current working frame 75 f StreamFrame 76 data *bytes.Buffer 77 78 // Captures whether the framer is running and any error that occurred to 79 // cause it to stop. 80 running bool 81 err error 82 } 83 84 // NewStreamFramer creates a new stream framer that will output StreamFrames to 85 // the passed output channel. 86 func NewStreamFramer(out chan<- *StreamFrame, 87 heartbeatRate, batchWindow time.Duration, frameSize int) *StreamFramer { 88 89 // Create the heartbeat and flush ticker 90 heartbeat := time.NewTicker(heartbeatRate) 91 flusher := time.NewTicker(batchWindow) 92 93 return &StreamFramer{ 94 out: out, 95 frameSize: frameSize, 96 heartbeat: heartbeat, 97 flusher: flusher, 98 data: bytes.NewBuffer(make([]byte, 0, 2*frameSize)), 99 shutdownCh: make(chan struct{}), 100 exitCh: make(chan struct{}), 101 } 102 } 103 104 // Destroy is used to cleanup the StreamFramer and flush any pending frames 105 func (s *StreamFramer) Destroy() { 106 s.l.Lock() 107 108 wasShutdown := s.shutdown 109 s.shutdown = true 110 111 if !wasShutdown { 112 close(s.shutdownCh) 113 } 114 115 s.heartbeat.Stop() 116 s.flusher.Stop() 117 running := s.running 118 s.l.Unlock() 119 120 // Ensure things were flushed 121 if running { 122 <-s.exitCh 123 } 124 if !wasShutdown { 125 close(s.out) 126 } 127 } 128 129 // Run starts a long lived goroutine that handles sending data as well as 130 // heartbeating 131 func (s *StreamFramer) Run() { 132 s.l.Lock() 133 defer s.l.Unlock() 134 if s.running { 135 return 136 } 137 138 s.running = true 139 go s.run() 140 } 141 142 // ExitCh returns a channel that will be closed when the run loop terminates. 143 func (s *StreamFramer) ExitCh() <-chan struct{} { 144 return s.exitCh 145 } 146 147 // Err returns the error that caused the StreamFramer to exit 148 func (s *StreamFramer) Err() error { 149 s.l.Lock() 150 defer s.l.Unlock() 151 return s.err 152 } 153 154 // run is the internal run method. It exits if Destroy is called or an error 155 // occurs, in which case the exit channel is closed. 156 func (s *StreamFramer) run() { 157 var err error 158 defer func() { 159 s.l.Lock() 160 s.running = false 161 s.err = err 162 s.l.Unlock() 163 close(s.exitCh) 164 }() 165 166 OUTER: 167 for { 168 select { 169 case <-s.shutdownCh: 170 break OUTER 171 case <-s.flusher.C: 172 // Skip if there is nothing to flush 173 s.l.Lock() 174 if s.f.IsCleared() { 175 s.l.Unlock() 176 continue 177 } 178 179 // Read the data for the frame, and send it 180 s.f.Data = s.readData() 181 err = s.send(&s.f) 182 s.f.Clear() 183 s.l.Unlock() 184 if err != nil { 185 return 186 } 187 case <-s.heartbeat.C: 188 // Send a heartbeat frame 189 if err = s.send(HeartbeatStreamFrame); err != nil { 190 return 191 } 192 } 193 } 194 195 s.l.Lock() 196 if !s.f.IsCleared() { 197 s.f.Data = s.readData() 198 err = s.send(&s.f) 199 s.f.Clear() 200 } 201 s.l.Unlock() 202 } 203 204 // send takes a StreamFrame, encodes and sends it 205 func (s *StreamFramer) send(f *StreamFrame) error { 206 sending := *f 207 f.Data = nil 208 209 select { 210 case s.out <- &sending: 211 return nil 212 case <-s.exitCh: 213 return nil 214 } 215 } 216 217 // readData is a helper which reads the buffered data returning up to the frame 218 // size of data. Must be called with the lock held. The returned value is 219 // invalid on the next read or write into the StreamFramer buffer 220 func (s *StreamFramer) readData() []byte { 221 // Compute the amount to read from the buffer 222 size := s.data.Len() 223 if size > s.frameSize { 224 size = s.frameSize 225 } 226 if size == 0 { 227 return nil 228 } 229 d := s.data.Next(size) 230 return d 231 } 232 233 // Send creates and sends a StreamFrame based on the passed parameters. An error 234 // is returned if the run routine hasn't run or encountered an error. Send is 235 // asynchronous and does not block for the data to be transferred. 236 func (s *StreamFramer) Send(file, fileEvent string, data []byte, offset int64) error { 237 s.l.Lock() 238 defer s.l.Unlock() 239 240 // If we are not running, return the error that caused us to not run or 241 // indicated that it was never started. 242 if !s.running { 243 if s.err != nil { 244 return s.err 245 } 246 247 return fmt.Errorf("StreamFramer not running") 248 } 249 250 // Check if not mergeable 251 if !s.f.IsCleared() && (s.f.File != file || s.f.FileEvent != fileEvent) { 252 // Flush the old frame 253 s.f.Data = s.readData() 254 select { 255 case <-s.exitCh: 256 return nil 257 default: 258 } 259 err := s.send(&s.f) 260 s.f.Clear() 261 if err != nil { 262 return err 263 } 264 } 265 266 // Store the new data as the current frame. 267 if s.f.IsCleared() { 268 s.f.Offset = offset 269 s.f.File = file 270 s.f.FileEvent = fileEvent 271 } 272 273 // Write the data to the buffer 274 s.data.Write(data) 275 276 // Handle the delete case in which there is no data 277 force := s.data.Len() == 0 && s.f.FileEvent != "" 278 279 // Flush till we are under the max frame size 280 for s.data.Len() >= s.frameSize || force { 281 // Clear since are flushing the frame and capturing the file event. 282 // Subsequent data frames will be flushed based on the data size alone 283 // since they share the same fileevent. 284 if force { 285 force = false 286 } 287 288 // Create a new frame to send it 289 s.f.Data = s.readData() 290 select { 291 case <-s.exitCh: 292 return nil 293 default: 294 } 295 296 if err := s.send(&s.f); err != nil { 297 return err 298 } 299 300 // Update the offset 301 s.f.Offset += int64(len(s.f.Data)) 302 } 303 304 if s.data.Len() == 0 { 305 s.f.Clear() 306 } 307 308 return nil 309 }