123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
- // released under the MIT license
-
- package irc
-
- import (
- "fmt"
- "sync"
- "time"
-
- "github.com/oragono/oragono/irc/caps"
- )
-
- const (
- // RegisterTimeout is how long clients have to register before we disconnect them
- RegisterTimeout = time.Minute
- // IdleTimeout is how long without traffic before a registered client is considered idle.
- IdleTimeout = time.Minute + time.Second*30
- // IdleTimeoutWithResumeCap is how long without traffic before a registered client is considered idle, when they have the resume capability.
- IdleTimeoutWithResumeCap = time.Minute*2 + time.Second*30
- // QuitTimeout is how long without traffic before an idle client is disconnected
- QuitTimeout = time.Minute
- )
-
- // client idleness state machine
-
- type TimerState uint
-
- const (
- TimerUnregistered TimerState = iota // client is unregistered
- TimerActive // client is actively sending commands
- TimerIdle // client is idle, we sent PING and are waiting for PONG
- TimerDead // client was terminated
- )
-
- type IdleTimer struct {
- sync.Mutex // tier 1
-
- // immutable after construction
- registerTimeout time.Duration
- idleTimeout time.Duration
- idleTimeoutWithResume time.Duration
- quitTimeout time.Duration
- client *Client
-
- // mutable
- state TimerState
- timer *time.Timer
- }
-
- // NewIdleTimer sets up a new IdleTimer using constant timeouts.
- func NewIdleTimer(client *Client) *IdleTimer {
- it := IdleTimer{
- registerTimeout: RegisterTimeout,
- idleTimeout: IdleTimeout,
- idleTimeoutWithResume: IdleTimeoutWithResumeCap,
- quitTimeout: QuitTimeout,
- client: client,
- }
- return &it
- }
-
- // Start starts counting idle time; if there is no activity from the client,
- // it will eventually be stopped.
- func (it *IdleTimer) Start() {
- it.Lock()
- defer it.Unlock()
- it.state = TimerUnregistered
- it.resetTimeout()
- }
-
- func (it *IdleTimer) Touch() {
- // ignore touches from unregistered clients
- if !it.client.Registered() {
- return
- }
-
- it.Lock()
- defer it.Unlock()
- // a touch transitions TimerUnregistered or TimerIdle into TimerActive
- if it.state != TimerDead {
- it.state = TimerActive
- it.resetTimeout()
- }
- }
-
- func (it *IdleTimer) processTimeout() {
- var previousState TimerState
- func() {
- it.Lock()
- defer it.Unlock()
- previousState = it.state
- // TimerActive transitions to TimerIdle, all others to TimerDead
- if it.state == TimerActive {
- // send them a ping, give them time to respond
- it.state = TimerIdle
- it.resetTimeout()
- } else {
- it.state = TimerDead
- }
- }()
-
- if previousState == TimerActive {
- it.client.Ping()
- } else {
- it.client.Quit(it.quitMessage(previousState))
- it.client.destroy(false)
- }
- }
-
- // Stop stops counting idle time.
- func (it *IdleTimer) Stop() {
- it.Lock()
- defer it.Unlock()
- it.state = TimerDead
- it.resetTimeout()
- }
-
- func (it *IdleTimer) resetTimeout() {
- if it.timer != nil {
- it.timer.Stop()
- }
- var nextTimeout time.Duration
- switch it.state {
- case TimerUnregistered:
- nextTimeout = it.registerTimeout
- case TimerActive:
- // if they have the resume cap, wait longer before pinging them out
- // to give them a chance to resume their connection
- if it.client.capabilities.Has(caps.Resume) {
- nextTimeout = it.idleTimeoutWithResume
- } else {
- nextTimeout = it.idleTimeout
- }
- case TimerIdle:
- nextTimeout = it.quitTimeout
- case TimerDead:
- return
- }
- it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
- }
-
- func (it *IdleTimer) quitMessage(state TimerState) string {
- switch state {
- case TimerUnregistered:
- return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
- case TimerIdle:
- // how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
- return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
- default:
- // shouldn't happen
- return ""
- }
- }
|