github.com/bigcommerce/nomad@v0.9.3-bc/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  func (s *StreamFrame) Copy() *StreamFrame {
    59  	n := new(StreamFrame)
    60  	*n = *s
    61  	n.Data = make([]byte, len(s.Data))
    62  	copy(n.Data, s.Data)
    63  	return n
    64  }
    65  
    66  // StreamFramer is used to buffer and send frames as well as heartbeat.
    67  type StreamFramer struct {
    68  	// out is where frames are sent and is closed when no more frames will
    69  	// be sent.
    70  	out chan<- *StreamFrame
    71  
    72  	frameSize int
    73  
    74  	heartbeat *time.Ticker
    75  	flusher   *time.Ticker
    76  
    77  	// shutdown is true when a shutdown is triggered
    78  	shutdown bool
    79  
    80  	// shutdownCh is closed when no more Send()s will be called and run()
    81  	// should flush pending frames before closing exitCh
    82  	shutdownCh chan struct{}
    83  
    84  	// exitCh is closed when the run() goroutine exits and no more frames
    85  	// will be sent.
    86  	exitCh chan struct{}
    87  
    88  	// The mutex protects everything below
    89  	l sync.Mutex
    90  
    91  	// The current working frame
    92  	f    *StreamFrame
    93  	data *bytes.Buffer
    94  
    95  	// Captures whether the framer is running
    96  	running bool
    97  }
    98  
    99  // NewStreamFramer creates a new stream framer that will output StreamFrames to
   100  // the passed output channel.
   101  func NewStreamFramer(out chan<- *StreamFrame,
   102  	heartbeatRate, batchWindow time.Duration, frameSize int) *StreamFramer {
   103  
   104  	// Create the heartbeat and flush ticker
   105  	heartbeat := time.NewTicker(heartbeatRate)
   106  	flusher := time.NewTicker(batchWindow)
   107  
   108  	return &StreamFramer{
   109  		out:        out,
   110  		frameSize:  frameSize,
   111  		heartbeat:  heartbeat,
   112  		flusher:    flusher,
   113  		f:          new(StreamFrame),
   114  		data:       bytes.NewBuffer(make([]byte, 0, 2*frameSize)),
   115  		shutdownCh: make(chan struct{}),
   116  		exitCh:     make(chan struct{}),
   117  	}
   118  }
   119  
   120  // Destroy is used to cleanup the StreamFramer and flush any pending frames
   121  func (s *StreamFramer) Destroy() {
   122  	s.l.Lock()
   123  
   124  	wasShutdown := s.shutdown
   125  	s.shutdown = true
   126  
   127  	if !wasShutdown {
   128  		close(s.shutdownCh)
   129  	}
   130  
   131  	s.heartbeat.Stop()
   132  	s.flusher.Stop()
   133  	running := s.running
   134  	s.l.Unlock()
   135  
   136  	// Ensure things were flushed
   137  	if running {
   138  		<-s.exitCh
   139  	}
   140  
   141  	// Close out chan only after exitCh has exited
   142  	if !wasShutdown {
   143  		close(s.out)
   144  	}
   145  }
   146  
   147  // Run starts a long lived goroutine that handles sending data as well as
   148  // heartbeating
   149  func (s *StreamFramer) Run() {
   150  	s.l.Lock()
   151  	defer s.l.Unlock()
   152  	if s.running {
   153  		return
   154  	}
   155  
   156  	s.running = true
   157  	go s.run()
   158  }
   159  
   160  // ExitCh returns a channel that will be closed when the run loop terminates.
   161  func (s *StreamFramer) ExitCh() <-chan struct{} {
   162  	return s.exitCh
   163  }
   164  
   165  // run is the internal run method. It exits if Destroy is called or an error
   166  // occurs, in which case the exit channel is closed.
   167  func (s *StreamFramer) run() {
   168  	defer func() {
   169  		s.l.Lock()
   170  		s.running = false
   171  		s.l.Unlock()
   172  		close(s.exitCh)
   173  	}()
   174  
   175  OUTER:
   176  	for {
   177  		select {
   178  		case <-s.shutdownCh:
   179  			break OUTER
   180  		case <-s.flusher.C:
   181  			// Skip if there is nothing to flush
   182  			s.l.Lock()
   183  			if s.f.IsCleared() {
   184  				s.l.Unlock()
   185  				continue
   186  			}
   187  
   188  			// Read the data for the frame, and send it
   189  			s.send()
   190  			s.l.Unlock()
   191  		case <-s.heartbeat.C:
   192  			// Send a heartbeat frame
   193  			select {
   194  			case s.out <- HeartbeatStreamFrame:
   195  			case <-s.shutdownCh:
   196  			}
   197  		}
   198  	}
   199  
   200  	s.l.Lock()
   201  	// Send() may have left a partial frame. Send it now.
   202  	if !s.f.IsCleared() {
   203  		s.f.Data = s.readData()
   204  
   205  		// Only send if there's actually data left
   206  		if len(s.f.Data) > 0 {
   207  			// Cannot select on shutdownCh as it's already closed
   208  			// Cannot select on exitCh as it's only closed after this exits
   209  			s.out <- s.f.Copy()
   210  		}
   211  	}
   212  	s.l.Unlock()
   213  }
   214  
   215  // send takes a StreamFrame, encodes and sends it
   216  func (s *StreamFramer) send() {
   217  	// Ensure s.out has not already been closd by Destroy
   218  	select {
   219  	case <-s.exitCh:
   220  		return
   221  	default:
   222  	}
   223  
   224  	s.f.Data = s.readData()
   225  	select {
   226  	case s.out <- s.f.Copy():
   227  		s.f.Clear()
   228  	case <-s.exitCh:
   229  	}
   230  }
   231  
   232  // readData is a helper which reads the buffered data returning up to the frame
   233  // size of data. Must be called with the lock held. The returned value is
   234  // invalid on the next read or write into the StreamFramer buffer
   235  func (s *StreamFramer) readData() []byte {
   236  	// Compute the amount to read from the buffer
   237  	size := s.data.Len()
   238  	if size > s.frameSize {
   239  		size = s.frameSize
   240  	}
   241  	if size == 0 {
   242  		return nil
   243  	}
   244  	d := s.data.Next(size)
   245  	return d
   246  }
   247  
   248  // Send creates and sends a StreamFrame based on the passed parameters. An error
   249  // is returned if the run routine hasn't run or encountered an error. Send is
   250  // asynchronous and does not block for the data to be transferred.
   251  func (s *StreamFramer) Send(file, fileEvent string, data []byte, offset int64) error {
   252  	s.l.Lock()
   253  	defer s.l.Unlock()
   254  	// If we are not running, return the error that caused us to not run or
   255  	// indicated that it was never started.
   256  	if !s.running {
   257  		return fmt.Errorf("StreamFramer not running")
   258  	}
   259  
   260  	// Check if not mergeable
   261  	if !s.f.IsCleared() && (s.f.File != file || s.f.FileEvent != fileEvent) {
   262  		// Flush the old frame
   263  		s.send()
   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  		// Ensure s.out has not already been closed by Destroy
   289  		select {
   290  		case <-s.exitCh:
   291  			return nil
   292  		default:
   293  		}
   294  
   295  		// Create a new frame to send it
   296  		s.f.Data = s.readData()
   297  		select {
   298  		case s.out <- s.f.Copy():
   299  		case <-s.exitCh:
   300  			return nil
   301  		}
   302  
   303  		// Update the offset
   304  		s.f.Offset += int64(len(s.f.Data))
   305  	}
   306  
   307  	return nil
   308  }