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 }