1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package app
5
6import (
7	"encoding/json"
8	"fmt"
9	"sync"
10	"sync/atomic"
11	"time"
12
13	"github.com/mattermost/mattermost-server/v6/einterfaces"
14	"github.com/mattermost/mattermost-server/v6/model"
15)
16
17const (
18	TimestampFormat = "Mon Jan 2 15:04:05 -0700 MST 2006"
19)
20
21// Busy represents the busy state of the server. A server marked busy
22// will have non-critical services disabled. If a Cluster is provided
23// any changes will be propagated to each node.
24type Busy struct {
25	busy    int32 // protected via atomic for fast IsBusy calls
26	mux     sync.RWMutex
27	timer   *time.Timer
28	expires time.Time
29
30	cluster einterfaces.ClusterInterface
31}
32
33// NewBusy creates a new Busy instance with optional cluster which will
34// be notified of busy state changes.
35func NewBusy(cluster einterfaces.ClusterInterface) *Busy {
36	return &Busy{cluster: cluster}
37}
38
39// IsBusy returns true if the server has been marked as busy.
40func (b *Busy) IsBusy() bool {
41	if b == nil {
42		return false
43	}
44	return atomic.LoadInt32(&b.busy) != 0
45}
46
47// Set marks the server as busy for dur duration and notifies cluster nodes.
48func (b *Busy) Set(dur time.Duration) {
49	b.mux.Lock()
50	defer b.mux.Unlock()
51
52	// minimum 1 second
53	if dur < (time.Second * 1) {
54		dur = time.Second * 1
55	}
56
57	b.setWithoutNotify(dur)
58
59	if b.cluster != nil {
60		sbs := &model.ServerBusyState{Busy: true, Expires: b.expires.Unix(), ExpiresTS: b.expires.UTC().Format(TimestampFormat)}
61		b.notifyServerBusyChange(sbs)
62	}
63}
64
65// must hold mutex
66func (b *Busy) setWithoutNotify(dur time.Duration) {
67	b.clearWithoutNotify()
68	atomic.StoreInt32(&b.busy, 1)
69	b.expires = time.Now().Add(dur)
70	b.timer = time.AfterFunc(dur, func() {
71		b.mux.Lock()
72		b.clearWithoutNotify()
73		b.mux.Unlock()
74	})
75}
76
77// ClearBusy marks the server as not busy and notifies cluster nodes.
78func (b *Busy) Clear() {
79	b.mux.Lock()
80	defer b.mux.Unlock()
81
82	b.clearWithoutNotify()
83
84	if b.cluster != nil {
85		sbs := &model.ServerBusyState{Busy: false, Expires: time.Time{}.Unix(), ExpiresTS: ""}
86		b.notifyServerBusyChange(sbs)
87	}
88}
89
90// must hold mutex
91func (b *Busy) clearWithoutNotify() {
92	if b.timer != nil {
93		b.timer.Stop() // don't drain timer.C channel for AfterFunc timers.
94	}
95	b.timer = nil
96	b.expires = time.Time{}
97	atomic.StoreInt32(&b.busy, 0)
98}
99
100// Expires returns the expected time that the server
101// will be marked not busy. This expiry can be extended
102// via additional calls to SetBusy.
103func (b *Busy) Expires() time.Time {
104	b.mux.RLock()
105	defer b.mux.RUnlock()
106	return b.expires
107}
108
109// notifyServerBusyChange informs all cluster members of a server busy state change.
110func (b *Busy) notifyServerBusyChange(sbs *model.ServerBusyState) {
111	if b.cluster == nil {
112		return
113	}
114	buf, _ := json.Marshal(sbs)
115	msg := &model.ClusterMessage{
116		Event:            model.ClusterEventBusyStateChanged,
117		SendType:         model.ClusterSendReliable,
118		WaitForAllToSend: true,
119		Data:             buf,
120	}
121	b.cluster.SendClusterMessage(msg)
122}
123
124// ClusterEventChanged is called when a CLUSTER_EVENT_BUSY_STATE_CHANGED is received.
125func (b *Busy) ClusterEventChanged(sbs *model.ServerBusyState) {
126	b.mux.Lock()
127	defer b.mux.Unlock()
128
129	if sbs.Busy {
130		expires := time.Unix(sbs.Expires, 0)
131		dur := time.Until(expires)
132		if dur > 0 {
133			b.setWithoutNotify(dur)
134		}
135	} else {
136		b.clearWithoutNotify()
137	}
138}
139
140func (b *Busy) ToJSON() ([]byte, error) {
141	b.mux.RLock()
142	defer b.mux.RUnlock()
143
144	sbs := &model.ServerBusyState{
145		Busy:      atomic.LoadInt32(&b.busy) != 0,
146		Expires:   b.expires.Unix(),
147		ExpiresTS: b.expires.UTC().Format(TimestampFormat),
148	}
149	sbsJSON, jsonErr := json.Marshal(sbs)
150	if jsonErr != nil {
151		return []byte{}, fmt.Errorf("failed to encode server busy state to JSON: %w", jsonErr)
152	}
153
154	return sbsJSON, nil
155}
156