Improve server state logical handling; allow setting state directly on the environment

This commit is contained in:
Dane Everitt
2020-11-06 21:53:00 -08:00
parent 3fce1b98d5
commit 944d381778
13 changed files with 73 additions and 105 deletions

View File

@@ -63,7 +63,7 @@ func (s *Server) Install(sync bool) error {
// Ensure that the server is marked as offline at this point, otherwise you end up
// with a blank value which is a bit confusing.
s.SetState(environment.ProcessOfflineState)
s.Environment.SetState(environment.ProcessOfflineState)
// Push an event to the websocket so we can auto-refresh the information in the panel once
// the install is completed.

View File

@@ -63,8 +63,8 @@ func (s *Server) StartEventListeners() {
if err != nil {
// If the process is already stopping, just let it continue with that action rather than attempting
// to terminate again.
if s.GetState() != environment.ProcessStoppingState {
s.SetState(environment.ProcessStoppingState)
if s.Environment.State() != environment.ProcessStoppingState {
s.Environment.SetState(environment.ProcessStoppingState)
go func() {
s.Log().Warn("stopping server instance, violating throttle limits")
s.PublishConsoleOutputFromDaemon("Your server is being stopped for outputting too much data in a short period of time.")
@@ -73,8 +73,8 @@ func (s *Server) StartEventListeners() {
if err := s.Environment.WaitForStop(config.Get().Throttles.StopGracePeriod, true); err != nil {
// If there is an error set the process back to running so that this throttler is called
// again and hopefully kills the server.
if s.GetState() != environment.ProcessOfflineState {
s.SetState(environment.ProcessRunningState)
if s.Environment.State() != environment.ProcessOfflineState {
s.Environment.SetState(environment.ProcessRunningState)
}
s.Log().WithField("error", errors.WithStack(err)).Error("failed to terminate environment after triggering throttle")
@@ -100,7 +100,7 @@ func (s *Server) StartEventListeners() {
s.Throttler().Reset()
}
s.SetState(e.Data)
s.OnStateChange()
}
stats := func(e events.Event) {
@@ -173,7 +173,7 @@ func (s *Server) onConsoleOutput(data string) {
// If the specific line of output is one that would mark the server as started,
// set the server to that state. Only do this if the server is not currently stopped
// or stopping.
_ = s.SetState(environment.ProcessRunningState)
s.Environment.SetState(environment.ProcessRunningState)
break
}
}
@@ -185,7 +185,7 @@ func (s *Server) onConsoleOutput(data string) {
stop := processConfiguration.Stop
if stop.Type == api.ProcessStopCommand && data == stop.Value {
_ = s.SetState(environment.ProcessOfflineState)
s.Environment.SetState(environment.ProcessOfflineState)
}
}
}

View File

@@ -104,6 +104,7 @@ func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
s.resources = ResourceUsage{}
defaults.Set(&s.resources)
s.resources.State.Store(environment.ProcessOfflineState)
s.Archiver = Archiver{Server: s}
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace())

View File

@@ -3,7 +3,9 @@ package server
import (
"encoding/json"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
"sync"
"sync/atomic"
)
// Defines the current resource usage for a given server instance. If a server is offline you
@@ -16,7 +18,7 @@ type ResourceUsage struct {
environment.Stats
// The current server status.
State string `json:"state" default:"offline"`
State system.AtomicString `json:"state"`
// The current disk space being used by the server. This value is not guaranteed to be accurate
// at all times. It is "manually" set whenever server.Proc() is called. This is kind of just a
@@ -24,16 +26,16 @@ type ResourceUsage struct {
Disk int64 `json:"disk_bytes"`
}
// Alias the resource usage so that we don't infinitely recurse when marshaling the struct.
type IResourceUsage ResourceUsage
// Custom marshaler to ensure that the object is locked when we're converting it to JSON in
// order to avoid race conditions.
func (ru *ResourceUsage) MarshalJSON() ([]byte, error) {
ru.mu.Lock()
defer ru.mu.Unlock()
return json.Marshal(IResourceUsage(*ru))
// Alias the resource usage so that we don't infinitely recurse when marshaling the struct.
type alias ResourceUsage
return json.Marshal(alias(*ru))
}
// Returns the resource usage stats for the server instance. If the server is not running, only the
@@ -42,10 +44,10 @@ func (ru *ResourceUsage) MarshalJSON() ([]byte, error) {
//
// When a process is stopped all of the stats are zeroed out except for the disk.
func (s *Server) Proc() *ResourceUsage {
s.resources.SetDisk(s.Filesystem().CachedUsage())
// Store the updated disk usage when requesting process usage.
atomic.StoreInt64(&s.resources.Disk, s.Filesystem().CachedUsage())
// Get a read lock on the resources at this point. Don't do this before setting
// the disk, otherwise you'll cause a deadlock.
// Acquire a lock before attempting to return the value of resources.
s.resources.mu.RLock()
defer s.resources.mu.RUnlock()
@@ -57,24 +59,3 @@ func (s *Server) emitProcUsage() {
s.Log().WithField("error", err).Warn("error while emitting server resource usage to listeners")
}
}
// Returns the servers current state.
func (ru *ResourceUsage) getInternalState() string {
ru.mu.RLock()
defer ru.mu.RUnlock()
return ru.State
}
// Sets the new state for the server.
func (ru *ResourceUsage) setInternalState(state string) {
ru.mu.Lock()
ru.State = state
ru.mu.Unlock()
}
func (ru *ResourceUsage) SetDisk(i int64) {
ru.mu.Lock()
ru.Disk = i
ru.mu.Unlock()
}

View File

@@ -2,7 +2,6 @@ package server
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
@@ -63,23 +62,17 @@ func saveServerStates() error {
// Sets the state of the server internally. This function handles crash detection as
// well as reporting to event listeners for the server.
func (s *Server) SetState(state string) error {
if state != environment.ProcessOfflineState &&
state != environment.ProcessStartingState &&
state != environment.ProcessRunningState &&
state != environment.ProcessStoppingState {
return errors.New(fmt.Sprintf("invalid server state received: %s", state))
}
prevState := s.GetState()
func (s *Server) OnStateChange() {
prevState := s.Proc().State.Load()
st := s.Environment.State()
// Update the currently tracked state for the server.
s.Proc().setInternalState(state)
s.Proc().State.Store(st)
// Emit the event to any listeners that are currently registered.
if prevState != state {
s.Log().WithField("status", s.Proc().getInternalState()).Debug("saw server status change event")
s.Events().Publish(StatusEvent, s.Proc().getInternalState())
if prevState != s.Environment.State() {
s.Log().WithField("status", st).Debug("saw server status change event")
s.Events().Publish(StatusEvent, st)
}
// Persist this change to the disk immediately so that should the Daemon be stopped or
@@ -98,7 +91,7 @@ func (s *Server) SetState(state string) error {
// Reset the resource usage to 0 when the process fully stops so that all of the UI
// views in the Panel correctly display 0.
if state == environment.ProcessOfflineState {
if st == environment.ProcessOfflineState {
s.resources.mu.Lock()
s.resources.Empty()
s.resources.mu.Unlock()
@@ -127,13 +120,13 @@ func (s *Server) SetState(state string) error {
}
}(s)
}
return nil
}
// Returns the current state of the server in a race-safe manner.
// Deprecated
// use Environment.State()
func (s *Server) GetState() string {
return s.Proc().getInternalState()
return s.Environment.State()
}
// Determines if the server state is running or not. This is different than the