diff --git a/irc/getters.go b/irc/getters.go index c50d8ab4..f4b86391 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -7,7 +7,6 @@ import ( "net" "sync/atomic" "time" - "unsafe" "github.com/ergochat/ergo/irc/caps" "github.com/ergochat/ergo/irc/languages" @@ -16,11 +15,7 @@ import ( ) func (server *Server) Config() (config *Config) { - return (*Config)(atomic.LoadPointer(&server.config)) -} - -func (server *Server) SetConfig(config *Config) { - atomic.StorePointer(&server.config, unsafe.Pointer(config)) + return server.config.Get() } func (server *Server) ChannelRegistrationEnabled() bool { diff --git a/irc/server.go b/irc/server.go index c2c63161..2097a393 100644 --- a/irc/server.go +++ b/irc/server.go @@ -17,7 +17,6 @@ import ( "sync" "syscall" "time" - "unsafe" "github.com/ergochat/irc-go/ircfmt" "github.com/okzk/sdnotify" @@ -66,7 +65,7 @@ type Server struct { channels ChannelManager channelRegistry ChannelRegistry clients ClientManager - config unsafe.Pointer + config utils.ConfigStore[Config] configFilename string connectionLimiter connection_limits.Limiter ctime time.Time @@ -706,7 +705,7 @@ func (server *Server) applyConfig(config *Config) (err error) { config.Server.Cloaks.SetSecret(LoadCloakSecret(server.store)) // activate the new config - server.SetConfig(config) + server.config.Set(config) // load [dk]-lines, registered users and channels, etc. if initial { diff --git a/irc/utils/config.go b/irc/utils/config.go new file mode 100644 index 00000000..73fd7165 --- /dev/null +++ b/irc/utils/config.go @@ -0,0 +1,33 @@ +// Copyright (c) 2022 Shivaram Lingamneni +// released under the MIT license + +package utils + +import ( + "sync/atomic" + "unsafe" +) + +/* +This can be used to implement the following pattern: + +1. Prepare a config object (this can be arbitrarily expensive) +2. Take a pointer to the config object and use Set() to install it +3. Use Get() to access the config from any goroutine +4. To update the config, call Set() again with a new prepared config object +5. As long as any individual config object is not modified (by any goroutine) + after it is installed with Set(), this is free of data races, and Get() + is extremely cheap (on amd64 it compiles down to plain MOV instructions). +*/ + +type ConfigStore[Config any] struct { + ptr unsafe.Pointer +} + +func (c *ConfigStore[Config]) Get() *Config { + return (*Config)(atomic.LoadPointer(&c.ptr)) +} + +func (c *ConfigStore[Config]) Set(ptr *Config) { + atomic.StorePointer(&c.ptr, unsafe.Pointer(ptr)) +}