diff --git a/cmd/root.go b/cmd/root.go index 03a0a7e..706906e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -229,7 +229,7 @@ func rootCmdRun(*cobra.Command, []string) { // is that it was running, but we see that the container process is not currently running. s.Log().Info("detected server is running, re-attaching to process...") - s.SetState(environment.ProcessRunningState) + s.Environment.SetState(environment.ProcessRunningState) if err := s.Environment.Attach(); err != nil { s.Log().WithField("error", errors.WithStack(err)).Warn("failed to attach to running server environment") } @@ -239,7 +239,7 @@ func rootCmdRun(*cobra.Command, []string) { // Addresses potentially invalid data in the stored file that can cause Wings to lose // track of what the actual server state is. - _ = s.SetState(environment.ProcessOfflineState) + s.Environment.SetState(environment.ProcessOfflineState) }) } @@ -277,34 +277,20 @@ func rootCmdRun(*cobra.Command, []string) { Handler: r, TLSConfig: &tls.Config{ - NextProtos: []string{ - "h2", // enable HTTP/2 - "http/1.1", - }, - - // https://blog.cloudflare.com/exposing-go-on-the-internet + NextProtos: []string{"h2", "http/1.1"}, + // @see https://blog.cloudflare.com/exposing-go-on-the-internet CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, }, - PreferServerCipherSuites: true, - - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS13, - - CurvePreferences: []tls.CurveID{ - tls.X25519, - tls.CurveP256, - }, - // END https://blog.cloudflare.com/exposing-go-on-the-internet + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, }, } diff --git a/environment/docker/container.go b/environment/docker/container.go index eb143d9..148180d 100644 --- a/environment/docker/container.go +++ b/environment/docker/container.go @@ -60,7 +60,7 @@ func (e *Environment) Attach() error { defer cancel() defer e.stream.Close() defer func() { - e.setState(environment.ProcessOfflineState) + e.SetState(environment.ProcessOfflineState) e.SetStream(nil) }() @@ -245,7 +245,7 @@ func (e *Environment) convertMounts() []mount.Mount { // it will be forcibly stopped by Docker. func (e *Environment) Destroy() error { // We set it to stopping than offline to prevent crash detection from being triggered. - e.setState(environment.ProcessStoppingState) + e.SetState(environment.ProcessStoppingState) err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{ RemoveVolumes: true, @@ -261,7 +261,7 @@ func (e *Environment) Destroy() error { return nil } - e.setState(environment.ProcessOfflineState) + e.SetState(environment.ProcessOfflineState) return err } diff --git a/environment/docker/environment.go b/environment/docker/environment.go index 1ab4b49..448cfef 100644 --- a/environment/docker/environment.go +++ b/environment/docker/environment.go @@ -48,7 +48,7 @@ type Environment struct { emitter *events.EventBus // Tracks the environment state. - State system.AtomicString + st system.AtomicString } // Creates a new base Docker environment. The ID passed through will be the ID that is used to @@ -67,7 +67,7 @@ func New(id string, m *Metadata, c *environment.Configuration) (*Environment, er client: cli, } - e.State.Store(environment.ProcessOfflineState) + e.st.Store(environment.ProcessOfflineState) return e, nil } diff --git a/environment/docker/power.go b/environment/docker/power.go index 56d26ec..6544fef 100644 --- a/environment/docker/power.go +++ b/environment/docker/power.go @@ -57,8 +57,8 @@ func (e *Environment) Start() error { // If we don't set it to stopping first, you'll trigger crash detection which // we don't want to do at this point since it'll just immediately try to do the // exact same action that lead to it crashing in the first place... - e.setState(environment.ProcessStoppingState) - e.setState(environment.ProcessOfflineState) + e.SetState(environment.ProcessStoppingState) + e.SetState(environment.ProcessOfflineState) } }() @@ -74,7 +74,7 @@ func (e *Environment) Start() error { } else { // If the server is running update our internal state and continue on with the attach. if c.State.Running { - e.setState(environment.ProcessRunningState) + e.SetState(environment.ProcessRunningState) return e.Attach() } @@ -89,7 +89,7 @@ func (e *Environment) Start() error { } } - e.setState(environment.ProcessStartingState) + e.SetState(environment.ProcessStartingState) // Set this to true for now, we will set it to false once we reach the // end of this chain. @@ -136,8 +136,8 @@ func (e *Environment) Stop() error { // If the process is already offline don't switch it back to stopping. Just leave it how // it is and continue through to the stop handling for the process. - if e.State.Load() != environment.ProcessOfflineState { - e.setState(environment.ProcessStoppingState) + if e.st.Load() != environment.ProcessOfflineState { + e.SetState(environment.ProcessStoppingState) } // Only attempt to send the stop command to the instance if we are actually attached to @@ -153,7 +153,7 @@ func (e *Environment) Stop() error { // an error. if client.IsErrNotFound(err) { e.SetStream(nil) - e.setState(environment.ProcessOfflineState) + e.SetState(environment.ProcessOfflineState) return nil } @@ -217,16 +217,16 @@ func (e *Environment) Terminate(signal os.Signal) error { // If the container is not running but we're not already in a stopped state go ahead // and update things to indicate we should be completely stopped now. Set to stopping // first so crash detection is not triggered. - if e.State.Load() != environment.ProcessOfflineState { - e.setState(environment.ProcessStoppingState) - e.setState(environment.ProcessOfflineState) + if e.st.Load() != environment.ProcessOfflineState { + e.SetState(environment.ProcessStoppingState) + e.SetState(environment.ProcessOfflineState) } return nil } // We set it to stopping than offline to prevent crash detection from being triggered. - e.setState(environment.ProcessStoppingState) + e.SetState(environment.ProcessStoppingState) sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed") @@ -234,7 +234,7 @@ func (e *Environment) Terminate(signal os.Signal) error { return err } - e.setState(environment.ProcessOfflineState) + e.SetState(environment.ProcessOfflineState) return nil } diff --git a/environment/docker/state.go b/environment/docker/state.go index 50d4c2f..240eec5 100644 --- a/environment/docker/state.go +++ b/environment/docker/state.go @@ -6,25 +6,24 @@ import ( "github.com/pterodactyl/wings/environment" ) +func (e *Environment) State() string { + return e.st.Load() +} + // Sets the state of the environment. This emits an event that server's can hook into to // take their own actions and track their own state based on the environment. -func (e *Environment) setState(state string) error { +func (e *Environment) SetState(state string) { if state != environment.ProcessOfflineState && state != environment.ProcessStartingState && state != environment.ProcessRunningState && state != environment.ProcessStoppingState { - return errors.New(fmt.Sprintf("invalid server state received: %s", state)) + panic(errors.New(fmt.Sprintf("invalid server state received: %s", state))) } - // Get the current state of the environment before changing it. - prevState := e.State.Load() - // Emit the event to any listeners that are currently registered. - if prevState != state { + if e.State() != state { // If the state changed make sure we update the internal tracking to note that. - e.State.Store(state) + e.st.Store(state) e.Events().Publish(environment.StateChangeEvent, state) } - - return nil } diff --git a/environment/docker/stats.go b/environment/docker/stats.go index 01fbd3c..ba4b367 100644 --- a/environment/docker/stats.go +++ b/environment/docker/stats.go @@ -20,7 +20,7 @@ func (e *Environment) pollResources(ctx context.Context) error { l.Debug("starting resource polling for container") defer l.Debug("stopped resource polling for container") - if e.State.Load() == environment.ProcessOfflineState { + if e.st.Load() == environment.ProcessOfflineState { return errors.New("cannot enable resource polling on a stopped server") } @@ -50,7 +50,7 @@ func (e *Environment) pollResources(ctx context.Context) error { } // Disable collection if the server is in an offline state and this process is still running. - if e.State.Load() == environment.ProcessOfflineState { + if e.st.Load() == environment.ProcessOfflineState { l.Debug("process in offline state while resource polling is still active; stopping poll") return nil } diff --git a/environment/docker/stream.go b/environment/docker/stream.go index 8b20c2a..af02362 100644 --- a/environment/docker/stream.go +++ b/environment/docker/stream.go @@ -37,7 +37,7 @@ func (e *Environment) SendCommand(c string) error { // the server as entering the stopping state otherwise the process will stop and Wings will think // it has crashed and attempt to restart it. if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value { - e.Events().Publish(environment.StateChangeEvent, environment.ProcessStoppingState) + e.SetState(environment.ProcessStoppingState) } _, err := e.stream.Conn.Write([]byte(c + "\n")) diff --git a/environment/environment.go b/environment/environment.go index e8352ab..364af2c 100644 --- a/environment/environment.go +++ b/environment/environment.go @@ -94,4 +94,12 @@ type ProcessEnvironment interface { // Reads the log file for the process from the end backwards until the provided // number of lines is met. Readlog(int) ([]string, error) + + // Returns the current state of the environment. + State() string + + // Sets the current state of the environment. In general you should let the environment + // handle this itself, but there are some scenarios where it is helpful for the server + // to update the state externally (e.g. starting -> started). + SetState(string) } diff --git a/server/install.go b/server/install.go index b0c982f..0186591 100644 --- a/server/install.go +++ b/server/install.go @@ -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. diff --git a/server/listeners.go b/server/listeners.go index 8612117..6284ae1 100644 --- a/server/listeners.go +++ b/server/listeners.go @@ -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) } } } diff --git a/server/loader.go b/server/loader.go index a4e0df6..6a8d1af 100644 --- a/server/loader.go +++ b/server/loader.go @@ -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()) diff --git a/server/resources.go b/server/resources.go index d3a3d2c..ca52ed3 100644 --- a/server/resources.go +++ b/server/resources.go @@ -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() -} diff --git a/server/state.go b/server/state.go index bef2d91..3d6ae19 100644 --- a/server/state.go +++ b/server/state.go @@ -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