github.phpd.cn/hashicorp/consul@v1.4.5/agent/consul/session_ttl.go (about)

     1  package consul
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/armon/go-metrics"
     8  	"github.com/hashicorp/consul/agent/structs"
     9  )
    10  
    11  const (
    12  	// maxInvalidateAttempts limits how many invalidate attempts are made
    13  	maxInvalidateAttempts = 6
    14  
    15  	// invalidateRetryBase is a baseline retry time
    16  	invalidateRetryBase = 10 * time.Second
    17  )
    18  
    19  // initializeSessionTimers is used when a leader is newly elected to create
    20  // a new map to track session expiration and to reset all the timers from
    21  // the previously known set of timers.
    22  func (s *Server) initializeSessionTimers() error {
    23  	// Scan all sessions and reset their timer
    24  	state := s.fsm.State()
    25  	_, sessions, err := state.SessionList(nil)
    26  	if err != nil {
    27  		return err
    28  	}
    29  	for _, session := range sessions {
    30  		if err := s.resetSessionTimer(session.ID, session); err != nil {
    31  			return err
    32  		}
    33  	}
    34  	return nil
    35  }
    36  
    37  // resetSessionTimer is used to renew the TTL of a session.
    38  // This can be used for new sessions and existing ones. A session
    39  // will be faulted in if not given.
    40  func (s *Server) resetSessionTimer(id string, session *structs.Session) error {
    41  	// Fault the session in if not given
    42  	if session == nil {
    43  		state := s.fsm.State()
    44  		_, s, err := state.SessionGet(nil, id)
    45  		if err != nil {
    46  			return err
    47  		}
    48  		if s == nil {
    49  			return fmt.Errorf("Session '%s' not found", id)
    50  		}
    51  		session = s
    52  	}
    53  
    54  	// Bail if the session has no TTL, fast-path some common inputs
    55  	switch session.TTL {
    56  	case "", "0", "0s", "0m", "0h":
    57  		return nil
    58  	}
    59  
    60  	// Parse the TTL, and skip if zero time
    61  	ttl, err := time.ParseDuration(session.TTL)
    62  	if err != nil {
    63  		return fmt.Errorf("Invalid Session TTL '%s': %v", session.TTL, err)
    64  	}
    65  	if ttl == 0 {
    66  		return nil
    67  	}
    68  
    69  	s.createSessionTimer(session.ID, ttl)
    70  	return nil
    71  }
    72  
    73  func (s *Server) createSessionTimer(id string, ttl time.Duration) {
    74  	// Reset the session timer
    75  	// Adjust the given TTL by the TTL multiplier. This is done
    76  	// to give a client a grace period and to compensate for network
    77  	// and processing delays. The contract is that a session is not expired
    78  	// before the TTL, but there is no explicit promise about the upper
    79  	// bound so this is allowable.
    80  	ttl = ttl * structs.SessionTTLMultiplier
    81  	s.sessionTimers.ResetOrCreate(id, ttl, func() { s.invalidateSession(id) })
    82  }
    83  
    84  // invalidateSession is invoked when a session TTL is reached and we
    85  // need to invalidate the session.
    86  func (s *Server) invalidateSession(id string) {
    87  	defer metrics.MeasureSince([]string{"session_ttl", "invalidate"}, time.Now())
    88  
    89  	// Clear the session timer
    90  	s.sessionTimers.Del(id)
    91  
    92  	// Create a session destroy request
    93  	args := structs.SessionRequest{
    94  		Datacenter: s.config.Datacenter,
    95  		Op:         structs.SessionDestroy,
    96  		Session: structs.Session{
    97  			ID: id,
    98  		},
    99  	}
   100  
   101  	// Retry with exponential backoff to invalidate the session
   102  	for attempt := uint(0); attempt < maxInvalidateAttempts; attempt++ {
   103  		_, err := s.raftApply(structs.SessionRequestType, args)
   104  		if err == nil {
   105  			s.logger.Printf("[DEBUG] consul.state: Session %s TTL expired", id)
   106  			return
   107  		}
   108  
   109  		s.logger.Printf("[ERR] consul.session: Invalidation failed: %v", err)
   110  		time.Sleep((1 << attempt) * invalidateRetryBase)
   111  	}
   112  	s.logger.Printf("[ERR] consul.session: maximum revoke attempts reached for session: %s", id)
   113  }
   114  
   115  // clearSessionTimer is used to clear the session time for
   116  // a single session. This is used when a session is destroyed
   117  // explicitly and no longer needed.
   118  func (s *Server) clearSessionTimer(id string) error {
   119  	s.sessionTimers.Stop(id)
   120  	return nil
   121  }
   122  
   123  // clearAllSessionTimers is used when a leader is stepping
   124  // down and we no longer need to track any session timers.
   125  func (s *Server) clearAllSessionTimers() error {
   126  	s.sessionTimers.StopAll()
   127  	return nil
   128  }
   129  
   130  // sessionStats is a long running routine used to capture
   131  // the number of active sessions being tracked
   132  func (s *Server) sessionStats() {
   133  	for {
   134  		select {
   135  		case <-time.After(5 * time.Second):
   136  			metrics.SetGauge([]string{"session_ttl", "active"}, float32(s.sessionTimers.Len()))
   137  
   138  		case <-s.shutdownCh:
   139  			return
   140  		}
   141  	}
   142  }