diff --git a/irc/getters.go b/irc/getters.go index 761ece18..485c2d7e 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 955a9337..823ab832 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 @@ -704,7 +703,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..7159373d --- /dev/null +++ b/irc/utils/config.go @@ -0,0 +1,32 @@ +// 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. Load and munge a config (this can be arbitrarily expensive) +2. Use Set() to install the config +3. Use Get() to access the config +4. As long as any individual config is not modified (by any goroutine) + after the initial call to Set(), this is free of data races, and Get() + is extremely cheap (on amd64 it compiles down to plain MOV instructions). +*/ + +type ConfigStore[T any] struct { + ptr unsafe.Pointer +} + +func (c *ConfigStore[T]) Get() *T { + return (*T)(atomic.LoadPointer(&c.ptr)) +} + +func (c *ConfigStore[T]) Set(ptr *T) { + atomic.StorePointer(&c.ptr, unsafe.Pointer(ptr)) +}