big changes to ClientManager, separate goroutine to handle cleanup of old Clients

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
This commit is contained in:
kim (grufwub) 2020-04-10 20:31:21 +01:00
parent 242343ddac
commit a11a718c4f
3 changed files with 125 additions and 83 deletions

View File

@ -11,18 +11,21 @@ import (
"strings"
)
type ClientMsg int
/* unused for now */
type Client struct {
Message chan ClientMsg
Socket net.Conn
}
const (
GopherMapFile = "/gophermap"
)
type Client struct {
Cmd chan Command
Socket net.Conn
}
func (client *Client) Init(conn *net.Conn) {
client.Cmd = make(chan Command)
client.Socket = *conn
client.Message = make(chan ClientMsg)
client.Socket = *conn
}
func (client *Client) Start() {
@ -30,7 +33,7 @@ func (client *Client) Start() {
defer func() {
/* Close-up shop */
client.Socket.Close()
close(client.Cmd)
close(client.Message)
}()
var count int
@ -88,7 +91,7 @@ func (client *Client) SendError(format string, args ...interface{}) {
/* Format error message and append to response */
message := fmt.Sprintf(format, args...)
response = append(response, []byte(message)...)
response = append(response, []byte(message + CrLf)...)
response = append(response, []byte(LastLine)...)
/* We're sending an error, if this fails then fuck it lol */

View File

@ -57,7 +57,7 @@ var (
ServerPort = flag.Int("port", 70, "server listening port")
ServerHostname = flag.String("hostname", "127.0.0.1", "server hostname")
ServerUid = flag.Int("uid", 1000, "UID to run server under")
ServerGid = flag.Int("gid", 1000, "GID to run server under")
ServerGid = flag.Int("gid", 100, "GID to run server under")
UseChroot = flag.Bool("use-chroot", true, "chroot into the server directory")
)
@ -87,59 +87,36 @@ func main() {
/* Set privileges, see function definition for better explanation */
setPrivileges()
/* Setup client manager */
/* Setup manager */
manager := new(ClientManager)
manager.Init()
manager.Start()
/* Handle signals so we can _actually_ shutdown */
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
/* Handle signals so we can _actually_ shutdowm */
signal.Notify(manager.Signals, syscall.SIGINT, syscall.SIGTERM)
/* Start the manager */
manager.Start()
/* listener.Accept() server loop, in its own go-routine */
go func() {
count := 0
for {
newConn, err := listener.Accept()
if err != nil {
log.Fatalf("Error accepting connection: %s\n", err.Error())
}
/* Create Client from newConn and register with the manager */
client := new(Client)
client.Init(&newConn)
manager.Register<-client // this starts the Client go-routine
/* Every 'NewConnsBeforeClean' connections, request that manager perform clean */
if count == NewConnsBeforeClean {
manager.Cmd<-Clean
count = 0
} else {
count += 1
}
go func() {
/* Create Client from newConn and register with the manager */
client := new(Client)
client.Init(&newConn)
manager.Register<-client
}()
}
}()
/* Main thread sits and listens for OS signals */
for {
sig := <-signals
log.Printf("Received signal %v, waiting to finish up... (hit CTRL-C to terminate without cleanup)\n", sig)
for {
select {
case manager.Cmd<-Stop:
/* wait on clean */
log.Printf("Clean-up finished, exiting now...\n")
os.Exit(0)
case sig = <-signals:
if sig == syscall.SIGTERM {
log.Fatalf("Stopping NOW.\n")
}
continue
}
}
}
/* When manager message channel closes, we all done */
<-manager.Message
os.Exit(0)
}
func enterServerDir() {

View File

@ -1,59 +1,121 @@
package main
import (
"os"
"log"
"time"
// "syscall"
)
type ManagerMsg int
const (
/* FROM manager */
Done ManagerMsg = iota
/* TO manager */
RemoveOld ManagerMsg = iota
/* Internal */
FinishUp ManagerMsg = iota
)
type ClientManager struct {
Cmd chan Command
Clients map[*Client]bool
Register chan *Client
Unregister chan *Client
Signals chan os.Signal // OS sends, manager receives
Register chan *Client // User sends, manager receives
Message chan ManagerMsg // User and manager, send / receive
SleepTime time.Duration
}
func (manager *ClientManager) Init() {
manager.Cmd = make(chan Command)
manager.Clients = make(map[*Client]bool)
manager.Signals = make(chan os.Signal)
manager.Register = make(chan *Client)
manager.Unregister = make(chan *Client)
manager.Message = make(chan ManagerMsg)
manager.SleepTime = time.Second
}
func (manager *ClientManager) Start() {
/* Internal channel to stop cleaner goroutine */
cleanup := make(chan bool)
/* Main manager goroutine */
go func() {
defer func() {
/* We should have exited before this, but :shrug: */
close(manager.Register)
close(manager.Signals)
close(manager.Message)
}()
for {
select {
case cmd := <-manager.Cmd:
/* Received manager command, handle! */
switch cmd {
case Stop:
/* Stop and delete all clients, then return */
for client := range manager.Clients {
client.Cmd<-Stop
delete(manager.Clients, client)
}
return
case Clean:
/* Delete all 'done' clients */
for client := range manager.Clients {
select {
case <-client.Cmd:
/* Channel closed, client done, delete! */
delete(manager.Clients, client)
default:
/* Don't lock :) */
}
}
}
/* New client received */
case client := <-manager.Register:
/* Received new client to register */
/* TODO: decrease SleepTime for when more connections added */
manager.Clients[client] = true
client.Start()
case client := <-manager.Unregister:
/* Received client id to unregister */
if _, ok := manager.Clients[client]; ok {
client.Cmd<-Stop
delete(manager.Clients, client)
/* Message receieved */
case msg := <-manager.Message:
switch msg {
default:
/* do nothing */
}
/* OS signal received */
case sig := <-manager.Signals:
log.Printf("SIGNAL RECEIVED: %v\n", sig)
log.Printf("Received %v, waiting on cleanup before exit... (ctrl-c again to stop NOW)\n", sig)
cleanup<-true
select {
case sig = <-manager.Signals:
log.Printf("Signal received again, exiting now.\n")
return
case <-cleanup:
return
}
}
}
}()
/* Start cleaner goroutine in background */
go manager.Cleaner(cleanup)
}
func (manager *ClientManager) Cleaner(cleanup chan bool) {
finishUp := false
for {
/* Check for cleanup signal */
select {
case _ = <-cleanup:
finishUp = true
default:
/* do nothing */
}
for client := range manager.Clients {
select {
case <-client.Message:
delete(manager.Clients, client)
default:
/* do nothing */
}
}
if finishUp {
break
} else {
time.Sleep(manager.SleepTime)
}
}
/* Final cleanup */
for client := range manager.Clients {
<-client.Message
delete(manager.Clients, client)
}
/* Close the cleanup channel -- indicates we're done! */
close(cleanup)
}