github.com/observiq/bindplane-agent@v1.51.0/internal/service/standalone.go (about)

     1  // Copyright  observIQ, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package service
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"sync"
    22  
    23  	"github.com/observiq/bindplane-agent/collector"
    24  )
    25  
    26  // StandaloneCollectorService is a RunnableService that runs the collector in standalone mode.
    27  type StandaloneCollectorService struct {
    28  	col      collector.Collector
    29  	doneChan chan struct{}
    30  	errChan  chan error
    31  	wg       *sync.WaitGroup
    32  }
    33  
    34  // NewStandaloneCollectorService creates a new StandaloneCollectorService
    35  func NewStandaloneCollectorService(c collector.Collector) StandaloneCollectorService {
    36  	return StandaloneCollectorService{
    37  		col:      c,
    38  		doneChan: make(chan struct{}, 1),
    39  		errChan:  make(chan error, 1),
    40  		wg:       &sync.WaitGroup{},
    41  	}
    42  }
    43  
    44  // Start starts the collector
    45  func (s StandaloneCollectorService) Start(ctx context.Context) error {
    46  	err := s.col.Run(ctx)
    47  	if err != nil {
    48  		return fmt.Errorf("failed while starting collector: %w", err)
    49  	}
    50  
    51  	// monitor status for errors, so we don't zombie the service
    52  	s.wg.Add(1)
    53  	go s.monitorStatus()
    54  	return nil
    55  }
    56  
    57  // monitorStatus monitors the collector's status for errors, and reports them
    58  // to the error channel to trigger a shutdown.
    59  func (s StandaloneCollectorService) monitorStatus() {
    60  	defer s.wg.Done()
    61  	statusChan := s.col.Status()
    62  	for {
    63  		select {
    64  		case status := <-statusChan:
    65  			// This will catch panics and errors
    66  			if status.Err != nil {
    67  				s.errChan <- status.Err
    68  			} else if !status.Running {
    69  				// If we aren't running, bail out. Otherwise the collector is effectively a "zombie" process.
    70  				s.errChan <- errors.New("collector unexpectedly stopped running")
    71  			}
    72  		case <-s.doneChan:
    73  			return
    74  		}
    75  	}
    76  }
    77  
    78  // Error returns a channel that can emit asynchronous, unrecoverable errors
    79  func (s StandaloneCollectorService) Error() <-chan error {
    80  	return s.errChan
    81  }
    82  
    83  // Stop shuts down the underlying collector
    84  func (s StandaloneCollectorService) Stop(ctx context.Context) error {
    85  	close(s.doneChan)
    86  
    87  	collectorStoppedChan := make(chan struct{})
    88  	go func() {
    89  		s.col.Stop(ctx)
    90  		s.wg.Wait()
    91  		close(collectorStoppedChan)
    92  	}()
    93  
    94  	select {
    95  	case <-collectorStoppedChan:
    96  		return nil
    97  	case <-ctx.Done():
    98  		return fmt.Errorf("failed while waiting for service shutdown: %w", ctx.Err())
    99  	}
   100  }