Fix use of atomics in codebase
This commit is contained in:
parent
3842f054a5
commit
59c30c2842
|
@ -47,7 +47,7 @@ type Environment struct {
|
||||||
emitter *events.EventBus
|
emitter *events.EventBus
|
||||||
|
|
||||||
// Tracks the environment state.
|
// Tracks the environment state.
|
||||||
st system.AtomicString
|
st *system.AtomicString
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new base Docker environment. The ID passed through will be the ID that is used to
|
// Creates a new base Docker environment. The ID passed through will be the ID that is used to
|
||||||
|
|
|
@ -26,7 +26,7 @@ type ConsoleThrottler struct {
|
||||||
|
|
||||||
// Wether or not the console output is being throttled. It is up to calling code to
|
// Wether or not the console output is being throttled. It is up to calling code to
|
||||||
// determine what to do if it is.
|
// determine what to do if it is.
|
||||||
isThrottled system.AtomicBool
|
isThrottled *system.AtomicBool
|
||||||
|
|
||||||
// The total number of lines processed so far during the given time period.
|
// The total number of lines processed so far during the given time period.
|
||||||
timerCancel *context.CancelFunc
|
timerCancel *context.CancelFunc
|
||||||
|
@ -36,7 +36,7 @@ type ConsoleThrottler struct {
|
||||||
func (ct *ConsoleThrottler) Reset() {
|
func (ct *ConsoleThrottler) Reset() {
|
||||||
atomic.StoreUint64(&ct.count, 0)
|
atomic.StoreUint64(&ct.count, 0)
|
||||||
atomic.StoreUint64(&ct.activations, 0)
|
atomic.StoreUint64(&ct.activations, 0)
|
||||||
ct.isThrottled.Set(false)
|
ct.isThrottled.Store(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triggers an activation for a server. You can also decrement the number of activations
|
// Triggers an activation for a server. You can also decrement the number of activations
|
||||||
|
@ -57,19 +57,19 @@ func (ct *ConsoleThrottler) markActivation(increment bool) uint64 {
|
||||||
// Determines if the console is currently being throttled. Calls to this function can be used to
|
// Determines if the console is currently being throttled. Calls to this function can be used to
|
||||||
// determine if output should be funneled along to the websocket processes.
|
// determine if output should be funneled along to the websocket processes.
|
||||||
func (ct *ConsoleThrottler) Throttled() bool {
|
func (ct *ConsoleThrottler) Throttled() bool {
|
||||||
return ct.isThrottled.Get()
|
return ct.isThrottled.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts a timer that runs in a seperate thread and will continually decrement the lines processed
|
// Starts a timer that runs in a seperate thread and will continually decrement the lines processed
|
||||||
// and number of activations, regardless of the current console message volume. All of the timers
|
// and number of activations, regardless of the current console message volume. All of the timers
|
||||||
// are canceled if the context passed through is canceled.
|
// are canceled if the context passed through is canceled.
|
||||||
func (ct *ConsoleThrottler) StartTimer(ctx context.Context) {
|
func (ct *ConsoleThrottler) StartTimer(ctx context.Context) {
|
||||||
system.Every(ctx, time.Duration(int64(ct.LineResetInterval)) * time.Millisecond, func(_ time.Time) {
|
system.Every(ctx, time.Duration(int64(ct.LineResetInterval))*time.Millisecond, func(_ time.Time) {
|
||||||
ct.isThrottled.Set(false)
|
ct.isThrottled.Store(false)
|
||||||
atomic.StoreUint64(&ct.count, 0)
|
atomic.StoreUint64(&ct.count, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
system.Every(ctx, time.Duration(int64(ct.DecayInterval)) * time.Millisecond, func(_ time.Time) {
|
system.Every(ctx, time.Duration(int64(ct.DecayInterval))*time.Millisecond, func(_ time.Time) {
|
||||||
ct.markActivation(false)
|
ct.markActivation(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ func (ct *ConsoleThrottler) Increment(onTrigger func()) error {
|
||||||
// activation. Once the throttle is triggered and has passed the kill at value we will trigger a server
|
// activation. Once the throttle is triggered and has passed the kill at value we will trigger a server
|
||||||
// stop automatically.
|
// stop automatically.
|
||||||
if atomic.AddUint64(&ct.count, 1) >= ct.Lines && !ct.Throttled() {
|
if atomic.AddUint64(&ct.count, 1) >= ct.Lines && !ct.Throttled() {
|
||||||
ct.isThrottled.Set(true)
|
ct.isThrottled.Store(true)
|
||||||
if ct.markActivation(true) >= ct.MaximumTriggerCount {
|
if ct.markActivation(true) >= ct.MaximumTriggerCount {
|
||||||
return ErrTooMuchConsoleData
|
return ErrTooMuchConsoleData
|
||||||
}
|
}
|
||||||
|
@ -112,15 +112,12 @@ func (ct *ConsoleThrottler) Increment(onTrigger func()) error {
|
||||||
|
|
||||||
// Returns the throttler instance for the server or creates a new one.
|
// Returns the throttler instance for the server or creates a new one.
|
||||||
func (s *Server) Throttler() *ConsoleThrottler {
|
func (s *Server) Throttler() *ConsoleThrottler {
|
||||||
s.throttleLock.Lock()
|
s.throttleOnce.Do(func() {
|
||||||
defer s.throttleLock.Unlock()
|
|
||||||
|
|
||||||
if s.throttler == nil {
|
|
||||||
s.throttler = &ConsoleThrottler{
|
s.throttler = &ConsoleThrottler{
|
||||||
|
isThrottled: system.NewAtomicBool(false),
|
||||||
ConsoleThrottles: config.Get().Throttles,
|
ConsoleThrottles: config.Get().Throttles,
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
return s.throttler
|
return s.throttler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ func (fs *Filesystem) DiskUsage(allowStaleValue bool) (int64, error) {
|
||||||
// value. This is a blocking operation to the calling process.
|
// value. This is a blocking operation to the calling process.
|
||||||
if !allowStaleValue {
|
if !allowStaleValue {
|
||||||
return fs.updateCachedDiskUsage()
|
return fs.updateCachedDiskUsage()
|
||||||
} else if !fs.lookupInProgress.Get() {
|
} else if !fs.lookupInProgress.Load() {
|
||||||
// Otherwise, if we allow a stale value and there isn't a valid item in the cache and we aren't
|
// Otherwise, if we allow a stale value and there isn't a valid item in the cache and we aren't
|
||||||
// currently performing a lookup, just do the disk usage calculation in the background.
|
// currently performing a lookup, just do the disk usage calculation in the background.
|
||||||
go func(fs *Filesystem) {
|
go func(fs *Filesystem) {
|
||||||
|
@ -133,8 +133,8 @@ func (fs *Filesystem) updateCachedDiskUsage() (int64, error) {
|
||||||
// Signal that we're currently updating the disk size so that other calls to the disk checking
|
// Signal that we're currently updating the disk size so that other calls to the disk checking
|
||||||
// functions can determine if they should queue up additional calls to this function. Ensure that
|
// functions can determine if they should queue up additional calls to this function. Ensure that
|
||||||
// we always set this back to "false" when this process is done executing.
|
// we always set this back to "false" when this process is done executing.
|
||||||
fs.lookupInProgress.Set(true)
|
fs.lookupInProgress.Store(true)
|
||||||
defer fs.lookupInProgress.Set(false)
|
defer fs.lookupInProgress.Store(false)
|
||||||
|
|
||||||
// If there is no size its either because there is no data (in which case running this function
|
// If there is no size its either because there is no data (in which case running this function
|
||||||
// will have effectively no impact), or there is nothing in the cache, in which case we need to
|
// will have effectively no impact), or there is nothing in the cache, in which case we need to
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
type Filesystem struct {
|
type Filesystem struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
lastLookupTime *usageLookupTime
|
lastLookupTime *usageLookupTime
|
||||||
lookupInProgress system.AtomicBool
|
lookupInProgress *system.AtomicBool
|
||||||
diskUsed int64
|
diskUsed int64
|
||||||
diskCheckInterval time.Duration
|
diskCheckInterval time.Duration
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ func New(root string, size int64) *Filesystem {
|
||||||
diskLimit: size,
|
diskLimit: size,
|
||||||
diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval),
|
diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval),
|
||||||
lastLookupTime: &usageLookupTime{},
|
lastLookupTime: &usageLookupTime{},
|
||||||
|
lookupInProgress: system.NewAtomicBool(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,13 @@ import (
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"golang.org/x/sync/semaphore"
|
"github.com/pterodactyl/wings/system"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Executes the installation stack for a server process. Bubbles any errors up to the calling
|
// Executes the installation stack for a server process. Bubbles any errors up to the calling
|
||||||
|
@ -137,56 +136,30 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
|
||||||
return proc, nil
|
return proc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to obtain an exclusive lock on the installation process for the server. Waits up to 10
|
|
||||||
// seconds before aborting with a context timeout.
|
|
||||||
func (s *Server) acquireInstallationLock() error {
|
|
||||||
if s.installer.sem == nil {
|
|
||||||
s.installer.sem = semaphore.NewWeighted(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return s.installer.sem.Acquire(ctx, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determines if the server is actively running the installation process by checking the status
|
// Determines if the server is actively running the installation process by checking the status
|
||||||
// of the semaphore lock.
|
// of the installer lock.
|
||||||
func (s *Server) IsInstalling() bool {
|
func (s *Server) IsInstalling() bool {
|
||||||
if s.installer.sem == nil {
|
return s.installing.Load()
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.installer.sem.TryAcquire(1) {
|
|
||||||
// If we made it into this block it means we were able to obtain an exclusive lock
|
|
||||||
// on the semaphore. In that case, go ahead and release that lock immediately, and
|
|
||||||
// return false.
|
|
||||||
s.installer.sem.Release(1)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) IsTransferring() bool {
|
func (s *Server) IsTransferring() bool {
|
||||||
return s.transferring.Get()
|
return s.transferring.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetTransferring(state bool) {
|
func (s *Server) SetTransferring(state bool) {
|
||||||
s.transferring.Set(state)
|
s.transferring.Store(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes the installer container for the server.
|
// Removes the installer container for the server.
|
||||||
func (ip *InstallationProcess) RemoveContainer() {
|
func (ip *InstallationProcess) RemoveContainer() error {
|
||||||
err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{
|
err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{
|
||||||
RemoveVolumes: true,
|
RemoveVolumes: true,
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !client.IsErrNotFound(err) {
|
if err != nil && !client.IsErrNotFound(err) {
|
||||||
ip.Server.Log().WithField("error", err).Warn("failed to delete server install container")
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runs the installation process, this is done as in a background thread. This will configure
|
// Runs the installation process, this is done as in a background thread. This will configure
|
||||||
|
@ -196,8 +169,8 @@ func (ip *InstallationProcess) RemoveContainer() {
|
||||||
// log in the server's configuration directory.
|
// log in the server's configuration directory.
|
||||||
func (ip *InstallationProcess) Run() error {
|
func (ip *InstallationProcess) Run() error {
|
||||||
ip.Server.Log().Debug("acquiring installation process lock")
|
ip.Server.Log().Debug("acquiring installation process lock")
|
||||||
if err := ip.Server.acquireInstallationLock(); err != nil {
|
if !ip.Server.installing.SwapIf(true) {
|
||||||
return err
|
return errors.New("install: cannot obtain installation lock")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We now have an exclusive lock on this installation process. Ensure that whenever this
|
// We now have an exclusive lock on this installation process. Ensure that whenever this
|
||||||
|
@ -205,7 +178,7 @@ func (ip *InstallationProcess) Run() error {
|
||||||
// without encountering a wait timeout.
|
// without encountering a wait timeout.
|
||||||
defer func() {
|
defer func() {
|
||||||
ip.Server.Log().Debug("releasing installation process lock")
|
ip.Server.Log().Debug("releasing installation process lock")
|
||||||
ip.Server.installer.sem.Release(1)
|
ip.Server.installing.Store(false)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := ip.BeforeExecute(); err != nil {
|
if err := ip.BeforeExecute(); err != nil {
|
||||||
|
@ -215,7 +188,6 @@ func (ip *InstallationProcess) Run() error {
|
||||||
cid, err := ip.Execute()
|
cid, err := ip.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ip.RemoveContainer()
|
ip.RemoveContainer()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,22 +314,12 @@ func (ip *InstallationProcess) BeforeExecute() error {
|
||||||
if err := ip.writeScriptToDisk(); err != nil {
|
if err := ip.writeScriptToDisk(); err != nil {
|
||||||
return errors.WithMessage(err, "failed to write installation script to disk")
|
return errors.WithMessage(err, "failed to write installation script to disk")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ip.pullInstallationImage(); err != nil {
|
if err := ip.pullInstallationImage(); err != nil {
|
||||||
return errors.WithMessage(err, "failed to pull updated installation container image for server")
|
return errors.WithMessage(err, "failed to pull updated installation container image for server")
|
||||||
}
|
}
|
||||||
|
if err := ip.RemoveContainer(); err != nil {
|
||||||
opts := types.ContainerRemoveOptions{
|
return errors.WithMessage(err, "failed to remove existing install container for server")
|
||||||
RemoveVolumes: true,
|
|
||||||
Force: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", opts); err != nil {
|
|
||||||
if !client.IsErrNotFound(err) {
|
|
||||||
return errors.WithMessage(err, "failed to remove existing install container for server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,6 +392,12 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
||||||
|
|
||||||
// Executes the installation process inside a specially created docker container.
|
// Executes the installation process inside a specially created docker container.
|
||||||
func (ip *InstallationProcess) Execute() (string, error) {
|
func (ip *InstallationProcess) Execute() (string, error) {
|
||||||
|
// Create a child context that is canceled once this function is done running. This
|
||||||
|
// will also be canceled if the parent context (from the Server struct) is canceled
|
||||||
|
// which occurs if the server is deleted.
|
||||||
|
ctx, cancel := context.WithCancel(ip.context)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
conf := &container.Config{
|
conf := &container.Config{
|
||||||
Hostname: "installer",
|
Hostname: "installer",
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
|
@ -488,28 +456,35 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
r, err := ip.client.ContainerCreate(ip.context, conf, hostConf, nil, ip.Server.Id()+"_installer")
|
r, err := ip.client.ContainerCreate(ctx, conf, hostConf, nil, ip.Server.Id()+"_installer")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
|
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
|
||||||
if err := ip.client.ContainerStart(ip.context, r.ID, types.ContainerStartOptions{}); err != nil {
|
if err := ip.client.ContainerStart(ctx, r.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the install event in the background by listening to the stream output until the
|
||||||
|
// container has stopped, at which point we'll disconnect from it.
|
||||||
|
//
|
||||||
|
// If there is an error during the streaming output just report it and do nothing else, the
|
||||||
|
// install can still run, the console just won't have any output.
|
||||||
go func(id string) {
|
go func(id string) {
|
||||||
ip.Server.Events().Publish(DaemonMessageEvent, "Starting installation process, this could take a few minutes...")
|
ip.Server.Events().Publish(DaemonMessageEvent, "Starting installation process, this could take a few minutes...")
|
||||||
if err := ip.StreamOutput(id); err != nil {
|
if err := ip.StreamOutput(ctx, id); err != nil {
|
||||||
ip.Server.Log().WithField("error", err).Error("error while handling output stream for server install process")
|
ip.Server.Log().WithField("error", err).Warn("error connecting to server install stream output")
|
||||||
}
|
}
|
||||||
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
|
|
||||||
}(r.ID)
|
}(r.ID)
|
||||||
|
|
||||||
sChan, eChan := ip.client.ContainerWait(ip.context, r.ID, container.WaitConditionNotRunning)
|
sChan, eChan := ip.client.ContainerWait(ctx, r.ID, container.WaitConditionNotRunning)
|
||||||
select {
|
select {
|
||||||
case err := <-eChan:
|
case err := <-eChan:
|
||||||
if err != nil {
|
// Once the container has stopped running we can mark the install process as being completed.
|
||||||
|
if err == nil {
|
||||||
|
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
|
||||||
|
} else {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
case <-sChan:
|
case <-sChan:
|
||||||
|
@ -521,8 +496,8 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
||||||
// Streams the output of the installation process to a log file in the server configuration
|
// Streams the output of the installation process to a log file in the server configuration
|
||||||
// directory, as well as to a websocket listener so that the process can be viewed in
|
// directory, as well as to a websocket listener so that the process can be viewed in
|
||||||
// the panel by administrators.
|
// the panel by administrators.
|
||||||
func (ip *InstallationProcess) StreamOutput(id string) error {
|
func (ip *InstallationProcess) StreamOutput(ctx context.Context, id string) error {
|
||||||
reader, err := ip.client.ContainerLogs(ip.context, id, types.ContainerLogsOptions{
|
reader, err := ip.client.ContainerLogs(ctx, id, types.ContainerLogsOptions{
|
||||||
ShowStdout: true,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
Follow: true,
|
Follow: true,
|
||||||
|
|
|
@ -17,7 +17,7 @@ type ResourceUsage struct {
|
||||||
environment.Stats
|
environment.Stats
|
||||||
|
|
||||||
// The current server status.
|
// The current server status.
|
||||||
State system.AtomicString `json:"state"`
|
State *system.AtomicString `json:"state"`
|
||||||
|
|
||||||
// The current disk space being used by the server. This value is not guaranteed to be accurate
|
// 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
|
// at all times. It is "manually" set whenever server.Proc() is called. This is kind of just a
|
||||||
|
|
|
@ -28,7 +28,7 @@ type Server struct {
|
||||||
|
|
||||||
emitterLock sync.Mutex
|
emitterLock sync.Mutex
|
||||||
powerLock *semaphore.Weighted
|
powerLock *semaphore.Weighted
|
||||||
throttleLock sync.Mutex
|
throttleOnce sync.Once
|
||||||
|
|
||||||
// Maintains the configuration for the server. This is the data that gets returned by the Panel
|
// Maintains the configuration for the server. This is the data that gets returned by the Panel
|
||||||
// such as build settings and container images.
|
// such as build settings and container images.
|
||||||
|
@ -55,9 +55,8 @@ type Server struct {
|
||||||
// two installer processes at the same time. This also allows us to cancel a running
|
// two installer processes at the same time. This also allows us to cancel a running
|
||||||
// installation process, for example when a server is deleted from the panel while the
|
// installation process, for example when a server is deleted from the panel while the
|
||||||
// installer process is still running.
|
// installer process is still running.
|
||||||
installer InstallerDetails
|
installing *system.AtomicBool
|
||||||
|
transferring *system.AtomicBool
|
||||||
transferring system.AtomicBool
|
|
||||||
|
|
||||||
// The console throttler instance used to control outputs.
|
// The console throttler instance used to control outputs.
|
||||||
throttler *ConsoleThrottler
|
throttler *ConsoleThrottler
|
||||||
|
@ -67,19 +66,15 @@ type Server struct {
|
||||||
wsBagLocker sync.Mutex
|
wsBagLocker sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstallerDetails struct {
|
|
||||||
// Installer lock. You should obtain an exclusive lock on this context while running
|
|
||||||
// the installation process and release it when finished.
|
|
||||||
sem *semaphore.Weighted
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new server instance with a context and all of the default values set on
|
// Returns a new server instance with a context and all of the default values set on
|
||||||
// the instance.
|
// the instance.
|
||||||
func New() (*Server, error) {
|
func New() (*Server, error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
s := Server{
|
s := Server{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
ctxCancel: &cancel,
|
ctxCancel: &cancel,
|
||||||
|
installing: system.NewAtomicBool(false),
|
||||||
|
transferring: system.NewAtomicBool(false),
|
||||||
}
|
}
|
||||||
if err := defaults.Set(&s); err != nil {
|
if err := defaults.Set(&s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -87,7 +82,7 @@ func New() (*Server, error) {
|
||||||
if err := defaults.Set(&s.cfg); err != nil {
|
if err := defaults.Set(&s.cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.resources.State.Store(environment.ProcessOfflineState)
|
s.resources.State = system.NewAtomicString(environment.ProcessOfflineState)
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,20 +41,47 @@ func FormatBytes(b int64) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AtomicBool struct {
|
type AtomicBool struct {
|
||||||
flag uint32
|
v bool
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *AtomicBool) Set(v bool) {
|
func NewAtomicBool(v bool) *AtomicBool {
|
||||||
i := 0
|
return &AtomicBool{v: v}
|
||||||
if v {
|
}
|
||||||
i = 1
|
|
||||||
|
func (ab *AtomicBool) Store(v bool) {
|
||||||
|
ab.mu.Lock()
|
||||||
|
ab.v = v
|
||||||
|
ab.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stores the value "v" if the current value stored in the AtomicBool is the opposite
|
||||||
|
// boolean value. If successfully swapped, the response is "true", otherwise "false"
|
||||||
|
// is returned.
|
||||||
|
func (ab *AtomicBool) SwapIf(v bool) bool {
|
||||||
|
ab.mu.Lock()
|
||||||
|
defer ab.mu.Unlock()
|
||||||
|
if ab.v != v {
|
||||||
|
ab.v = v
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
atomic.StoreUint32(&ab.flag, uint32(i))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ab *AtomicBool) Get() bool {
|
func (ab *AtomicBool) Load() bool {
|
||||||
return atomic.LoadUint32(&ab.flag) == 1
|
ab.mu.RLock()
|
||||||
|
defer ab.mu.RUnlock()
|
||||||
|
return ab.v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ab *AtomicBool) UnmarshalJSON(b []byte) error {
|
||||||
|
ab.mu.Lock()
|
||||||
|
defer ab.mu.Unlock()
|
||||||
|
return json.Unmarshal(b, &ab.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ab *AtomicBool) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(ab.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
// AtomicString allows for reading/writing to a given struct field without having to worry
|
// AtomicString allows for reading/writing to a given struct field without having to worry
|
||||||
|
@ -61,8 +92,8 @@ type AtomicString struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAtomicString(v string) AtomicString {
|
func NewAtomicString(v string) *AtomicString {
|
||||||
return AtomicString{v: v}
|
return &AtomicString{v: v}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores the string value passed atomically.
|
// Stores the string value passed atomically.
|
||||||
|
@ -79,12 +110,12 @@ func (as *AtomicString) Load() string {
|
||||||
return as.v
|
return as.v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *AtomicString) UnmarshalText(b []byte) error {
|
func (as *AtomicString) UnmarshalJSON(b []byte) error {
|
||||||
as.Store(string(b))
|
as.mu.Lock()
|
||||||
return nil
|
defer as.mu.Unlock()
|
||||||
|
return json.Unmarshal(b, &as.v)
|
||||||
}
|
}
|
||||||
|
|
||||||
//goland:noinspection GoVetCopyLock
|
func (as *AtomicString) MarshalJSON() ([]byte, error) {
|
||||||
func (as AtomicString) MarshalText() ([]byte, error) {
|
return json.Marshal(as.Load())
|
||||||
return []byte(as.Load()), nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user