github.com/yandex/pandora@v0.5.32/core/engine/instance.go (about)

     1  package engine
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  
     7  	"github.com/pkg/errors"
     8  	"github.com/yandex/pandora/core"
     9  	"github.com/yandex/pandora/core/aggregator/netsample"
    10  	"github.com/yandex/pandora/core/coreutil"
    11  	"github.com/yandex/pandora/lib/tag"
    12  	"go.uber.org/zap"
    13  )
    14  
    15  type instance struct {
    16  	log      *zap.Logger
    17  	id       int
    18  	gun      core.Gun
    19  	schedule core.Schedule
    20  	instanceSharedDeps
    21  }
    22  
    23  func newInstance(ctx context.Context, log *zap.Logger, poolID string, id int, deps instanceDeps) (*instance, error) {
    24  	log = log.With(zap.Int("instance", id))
    25  	gunDeps := core.GunDeps{Ctx: ctx, Log: log, PoolID: poolID, InstanceID: id, Shared: deps.gunDeps}
    26  	sched, err := deps.newSchedule()
    27  	if err != nil {
    28  		return nil, err
    29  	}
    30  	gun, err := deps.newGun()
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	err = gun.Bind(deps.aggregator, gunDeps)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	inst := &instance{log: log, id: id, gun: gun, schedule: sched, instanceSharedDeps: deps.instanceSharedDeps}
    40  	return inst, nil
    41  }
    42  
    43  type instanceDeps struct {
    44  	newSchedule func() (core.Schedule, error)
    45  	newGun      func() (core.Gun, error)
    46  	instanceSharedDeps
    47  }
    48  
    49  type instanceSharedDeps struct {
    50  	provider        core.Provider
    51  	metrics         Metrics
    52  	gunDeps         any
    53  	aggregator      core.Aggregator
    54  	discardOverflow bool
    55  }
    56  
    57  // Run blocks until ammo finish, error or context cancel.
    58  // Expects, that gun is already bind.
    59  func (i *instance) Run(ctx context.Context) (recoverErr error) {
    60  	defer func() {
    61  		r := recover()
    62  		if r != nil {
    63  			recoverErr = errors.Errorf("shoot panic: %s", r)
    64  		}
    65  
    66  		i.log.Debug("Instance finished")
    67  		i.metrics.InstanceFinish.Add(1)
    68  	}()
    69  	i.log.Debug("Instance started")
    70  	i.metrics.InstanceStart.Add(1)
    71  
    72  	waiter := coreutil.NewWaiter(i.schedule)
    73  	// Checking, that schedule is not finished, required, to not consume extra ammo,
    74  	// on finish in case of per instance schedule.
    75  	for !waiter.IsFinished(ctx) {
    76  		err := func() error {
    77  			ammo, ok := i.provider.Acquire()
    78  			if !ok {
    79  				i.log.Debug("Out of ammo")
    80  				return outOfAmmoErr
    81  			}
    82  			defer i.provider.Release(ammo)
    83  			if tag.Debug {
    84  				i.log.Debug("Ammo acquired", zap.Any("ammo", ammo))
    85  			}
    86  			if !waiter.Wait(ctx) {
    87  				return nil
    88  			}
    89  			i.metrics.Request.Add(1)
    90  			if !i.discardOverflow || !waiter.IsSlowDown(ctx) {
    91  				i.metrics.BusyInstances.OnStart(i.id)
    92  				defer i.metrics.BusyInstances.OnFinish(i.id)
    93  				if tag.Debug {
    94  					i.log.Debug("Shooting", zap.Any("ammo", ammo))
    95  				}
    96  				i.gun.Shoot(ammo)
    97  			} else {
    98  				i.aggregator.Report(netsample.DiscardedShootSample())
    99  			}
   100  			i.metrics.Response.Add(1)
   101  			return nil
   102  		}()
   103  		if err != nil {
   104  			return err
   105  		}
   106  	}
   107  	return ctx.Err()
   108  }
   109  
   110  func (i *instance) Close() error {
   111  	gunCloser, ok := i.gun.(io.Closer)
   112  	if !ok {
   113  		return nil
   114  	}
   115  	err := gunCloser.Close()
   116  	if err != nil {
   117  		i.log.Warn("Gun close fail", zap.Error(err))
   118  	}
   119  	i.log.Debug("Gun closed")
   120  	return err
   121  }
   122  
   123  var outOfAmmoErr = errors.New("Out of ammo")