mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 05:15:54 +00:00
Close server on SIGUSR1, 'kickclients' for rebalancing
This commit is contained in:
parent
62c9659430
commit
38972364fb
3 changed files with 93 additions and 6 deletions
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func commandLineConsole() {
|
func commandLineConsole() {
|
||||||
|
@ -88,6 +89,41 @@ func commandLineConsole() {
|
||||||
return "Usage: authorizeeveryone [ on | off ]", nil
|
return "Usage: authorizeeveryone [ on | off ]", nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
shell.Register("kickclients", func(args ...string) (string, error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return "Please enter either a count or a fraction of clients to kick.", nil
|
||||||
|
}
|
||||||
|
input, err := strconv.ParseFloat(args[0], 64)
|
||||||
|
if err != nil {
|
||||||
|
return "Argument must be a number", err
|
||||||
|
}
|
||||||
|
var count int
|
||||||
|
if input >= 1 {
|
||||||
|
count = int(input)
|
||||||
|
} else {
|
||||||
|
server.GlobalSubscriptionLock.RLock()
|
||||||
|
count = int(float64(len(server.GlobalSubscriptionInfo)) * input)
|
||||||
|
server.GlobalSubscriptionLock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := server.ClientMessage{ Arguments: &server.CloseRebalance }
|
||||||
|
server.GlobalSubscriptionLock.RLock()
|
||||||
|
defer server.GlobalSubscriptionLock.RUnlock()
|
||||||
|
|
||||||
|
kickCount := 0
|
||||||
|
for i, cl := range server.GlobalSubscriptionInfo {
|
||||||
|
if i >= count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case cl.MessageChannel <- msg:
|
||||||
|
case <-cl.MsgChannelIsDone:
|
||||||
|
}
|
||||||
|
kickCount++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Kicked %d clients", kickCount), nil
|
||||||
|
})
|
||||||
|
|
||||||
shell.Register("panic", func(args ...string) (string, error) {
|
shell.Register("panic", func(args ...string) (string, error) {
|
||||||
go func() {
|
go func() {
|
||||||
panic("requested panic")
|
panic("requested panic")
|
||||||
|
|
|
@ -16,6 +16,9 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SuccessCommand is a Reply Command to indicate success in reply to a C2S Command.
|
// SuccessCommand is a Reply Command to indicate success in reply to a C2S Command.
|
||||||
|
@ -97,6 +100,20 @@ func SetupServerAndHandle(config *ConfigFile, serveMux *http.ServeMux) {
|
||||||
go aggregateDataSender()
|
go aggregateDataSender()
|
||||||
|
|
||||||
go ircConnection()
|
go ircConnection()
|
||||||
|
go shutdownHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func shutdownHandler() {
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, syscall.SIGUSR1)
|
||||||
|
<-ch
|
||||||
|
log.Println("Shutting down...")
|
||||||
|
|
||||||
|
StopAcceptingConnections = true
|
||||||
|
close(StopAcceptingConnectionsCh)
|
||||||
|
|
||||||
|
time.Sleep(1*time.Second)
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketUpgrader is the websocket.Upgrader currently in use.
|
// SocketUpgrader is the websocket.Upgrader currently in use.
|
||||||
|
@ -112,6 +129,10 @@ var SocketUpgrader = websocket.Upgrader{
|
||||||
// Memes go here.
|
// Memes go here.
|
||||||
var BannerHTML []byte
|
var BannerHTML []byte
|
||||||
|
|
||||||
|
// StopAcceptingConnections is closed while the server is shutting down.
|
||||||
|
var StopAcceptingConnectionsCh = make(chan struct{})
|
||||||
|
var StopAcceptingConnections = false
|
||||||
|
|
||||||
// HTTPHandleRootURL is the http.HandleFunc for requests on `/`.
|
// HTTPHandleRootURL is the http.HandleFunc for requests on `/`.
|
||||||
// It either uses the SocketUpgrader or writes out the BannerHTML.
|
// It either uses the SocketUpgrader or writes out the BannerHTML.
|
||||||
func HTTPHandleRootURL(w http.ResponseWriter, r *http.Request) {
|
func HTTPHandleRootURL(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -120,6 +141,14 @@ func HTTPHandleRootURL(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Println(404)
|
fmt.Println(404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// racy, but should be ok?
|
||||||
|
if StopAcceptingConnections {
|
||||||
|
w.WriteHeader(503)
|
||||||
|
fmt.Fprint(w, "server is shutting down")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if r.Header.Get("Connection") == "Upgrade" {
|
if r.Header.Get("Connection") == "Upgrade" {
|
||||||
updateSysMem()
|
updateSysMem()
|
||||||
|
|
||||||
|
@ -165,6 +194,12 @@ var ErrExpectedStringAndInt = errors.New("Error: Expected array of string, int a
|
||||||
// ErrExpectedStringAndIntGotFloat is sent in a ErrorCommand Reply when the Arguments are of the wrong type.
|
// ErrExpectedStringAndIntGotFloat is sent in a ErrorCommand Reply when the Arguments are of the wrong type.
|
||||||
var ErrExpectedStringAndIntGotFloat = errors.New("Error: Second argument was a float, expected an integer.")
|
var ErrExpectedStringAndIntGotFloat = errors.New("Error: Second argument was a float, expected an integer.")
|
||||||
|
|
||||||
|
// CloseGoingAway is sent when the server is restarting.
|
||||||
|
var CloseGoingAway = websocket.CloseError{Code: websocket.CloseGoingAway, Text: "server restarting"}
|
||||||
|
|
||||||
|
// CloseRebalance is sent when the server has too many clients and needs to shunt some to another server.
|
||||||
|
var CloseRebalance = websocket.CloseError{Code: websocket.CloseGoingAway, Text: "kicked for rebalancing, please select a new server"}
|
||||||
|
|
||||||
// CloseGotBinaryMessage is the termination reason when the client sends a binary websocket frame.
|
// CloseGotBinaryMessage is the termination reason when the client sends a binary websocket frame.
|
||||||
var CloseGotBinaryMessage = websocket.CloseError{Code: websocket.CloseUnsupportedData, Text: "got binary packet"}
|
var CloseGotBinaryMessage = websocket.CloseError{Code: websocket.CloseUnsupportedData, Text: "got binary packet"}
|
||||||
|
|
||||||
|
@ -232,6 +267,10 @@ func RunSocketConnection(conn *websocket.Conn) {
|
||||||
var messageType int
|
var messageType int
|
||||||
var packet []byte
|
var packet []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
defer close(errorChan)
|
||||||
|
defer close(clientChan)
|
||||||
|
|
||||||
for ; err == nil; messageType, packet, err = conn.ReadMessage() {
|
for ; err == nil; messageType, packet, err = conn.ReadMessage() {
|
||||||
if messageType == websocket.BinaryMessage {
|
if messageType == websocket.BinaryMessage {
|
||||||
err = &CloseGotBinaryMessage
|
err = &CloseGotBinaryMessage
|
||||||
|
@ -249,8 +288,6 @@ func RunSocketConnection(conn *websocket.Conn) {
|
||||||
select {
|
select {
|
||||||
case clientChan <- msg:
|
case clientChan <- msg:
|
||||||
case <-stoppedChan:
|
case <-stoppedChan:
|
||||||
close(errorChan)
|
|
||||||
close(clientChan)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,8 +296,6 @@ func RunSocketConnection(conn *websocket.Conn) {
|
||||||
case errorChan <- err:
|
case errorChan <- err:
|
||||||
case <-stoppedChan:
|
case <-stoppedChan:
|
||||||
}
|
}
|
||||||
close(errorChan)
|
|
||||||
close(clientChan)
|
|
||||||
// exit goroutine
|
// exit goroutine
|
||||||
}(_errorChan, _clientChan, stoppedChan)
|
}(_errorChan, _clientChan, stoppedChan)
|
||||||
|
|
||||||
|
@ -318,6 +353,10 @@ RunLoop:
|
||||||
closeReason = CloseTooManyBufferedMessages
|
closeReason = CloseTooManyBufferedMessages
|
||||||
break RunLoop
|
break RunLoop
|
||||||
}
|
}
|
||||||
|
if cls, ok := msg.Arguments.(*websocket.CloseError); ok {
|
||||||
|
closeReason = *cls
|
||||||
|
break RunLoop
|
||||||
|
}
|
||||||
SendMessage(conn, msg)
|
SendMessage(conn, msg)
|
||||||
|
|
||||||
case <-time.After(1 * time.Minute):
|
case <-time.After(1 * time.Minute):
|
||||||
|
@ -331,6 +370,10 @@ RunLoop:
|
||||||
} else {
|
} else {
|
||||||
conn.WriteControl(websocket.PingMessage, []byte(strconv.FormatInt(time.Now().Unix(), 10)), getDeadline())
|
conn.WriteControl(websocket.PingMessage, []byte(strconv.FormatInt(time.Now().Unix(), 10)), getDeadline())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case <-StopAcceptingConnectionsCh:
|
||||||
|
closeReason = CloseGoingAway
|
||||||
|
break RunLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,6 +386,7 @@ RunLoop:
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Closes client.MsgChannelIsDone and also stops the reader thread
|
||||||
close(stoppedChan)
|
close(stoppedChan)
|
||||||
|
|
||||||
// Stop getting messages...
|
// Stop getting messages...
|
||||||
|
@ -356,8 +400,11 @@ RunLoop:
|
||||||
// Close the channel so the draining goroutine can finish, too.
|
// Close the channel so the draining goroutine can finish, too.
|
||||||
close(_serverMessageChan)
|
close(_serverMessageChan)
|
||||||
|
|
||||||
atomic.AddUint64(&Statistics.ClientDisconnectsTotal, 1)
|
if !StopAcceptingConnections {
|
||||||
atomic.AddUint64(&Statistics.CurrentClientCount, ^uint64(0))
|
// Don't perform high contention operations when server is closing
|
||||||
|
atomic.AddUint64(&Statistics.ClientDisconnectsTotal, 1)
|
||||||
|
atomic.AddUint64(&Statistics.CurrentClientCount, ^uint64(0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeadline() time.Time {
|
func getDeadline() time.Time {
|
||||||
|
|
|
@ -105,6 +105,10 @@ func UnsubscribeSingleChat(client *ClientInfo, channelName string) {
|
||||||
// - write lock to SubscriptionInfos
|
// - write lock to SubscriptionInfos
|
||||||
// - write lock to ClientInfo
|
// - write lock to ClientInfo
|
||||||
func UnsubscribeAll(client *ClientInfo) {
|
func UnsubscribeAll(client *ClientInfo) {
|
||||||
|
if StopAcceptingConnections {
|
||||||
|
return // no need to remove from a high-contention list when the server is closing
|
||||||
|
}
|
||||||
|
|
||||||
client.Mutex.Lock()
|
client.Mutex.Lock()
|
||||||
client.PendingSubscriptionsBacklog = nil
|
client.PendingSubscriptionsBacklog = nil
|
||||||
client.PendingSubscriptionsBacklog = nil
|
client.PendingSubscriptionsBacklog = nil
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue