diff --git a/socketserver/server/backend.go b/socketserver/server/backend.go index e93afeab..55ccf9f1 100644 --- a/socketserver/server/backend.go +++ b/socketserver/server/backend.go @@ -93,7 +93,7 @@ func HTTPBackendUncachedPublish(w http.ResponseWriter, r *http.Request) { return } - cm := ClientMessage{MessageID: -1, Command: CommandPool.Intern(cmd), origArguments: json} + cm := ClientMessage{MessageID: -1, Command: CommandPool.InternCommand(cmd), origArguments: json} cm.parseOrigArguments() var count int diff --git a/socketserver/server/commands.go b/socketserver/server/commands.go index 8f77b2e4..6d868512 100644 --- a/socketserver/server/commands.go +++ b/socketserver/server/commands.go @@ -41,7 +41,10 @@ var commandHandlers = map[Command]CommandHandler{ "user_history": C2SHandleRemoteCommand, } -func internCommands() { +func setupInterning() { + PubSubChannelPool = NewStringPool() + TwitchChannelPool = NewStringPool() + CommandPool = NewStringPool() CommandPool._Intern_Setup(string(HelloCommand)) CommandPool._Intern_Setup("ping") @@ -114,7 +117,7 @@ func C2SHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg return } - client.VersionString = version + client.VersionString = copyString(version) client.Version = VersionFromString(version) client.ClientID = uuid.FromStringOrNil(clientID) @@ -154,6 +157,8 @@ func C2SSetUser(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rm return } + username = copyString(username) + client.Mutex.Lock() client.UsernameValidated = false client.TwitchUsername = username @@ -198,11 +203,12 @@ func C2SReady(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg func C2SSubscribe(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { channel, err := msg.ArgumentsAsString() - if err != nil { return } + channel = PubSubChannelPool.Intern(channel) + client.Mutex.Lock() AddToSliceS(&client.CurrentChannels, channel) if usePendingSubscrptionsBacklog { @@ -219,11 +225,12 @@ func C2SSubscribe(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) ( // It removes the channel from ClientInfo.CurrentChannels and calls UnsubscribeSingleChat. func C2SUnsubscribe(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { channel, err := msg.ArgumentsAsString() - if err != nil { return } + channel = PubSubChannelPool.Intern(channel) + client.Mutex.Lock() RemoveFromSliceS(&client.CurrentChannels, channel) client.Mutex.Unlock() @@ -261,6 +268,8 @@ func C2STrackFollow(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) } now := time.Now() + channel = TwitchChannelPool.Intern(channel) + followEventsLock.Lock() followEvents = append(followEvents, followEvent{User: client.TwitchUsername, Channel: channel, NowFollowing: following, Timestamp: now}) followEventsLock.Unlock() @@ -323,6 +332,7 @@ func C2SEmoticonUses(conn *websocket.Conn, client *ClientInfo, msg ClientMessage if count > 200 { count = 200 } + roomName = TwitchChannelPool.Intern(roomName) destMapInner[roomName] += count total += count } @@ -422,7 +432,7 @@ var bunchCacheCleanupSignal = sync.NewCond(&bunchCacheLock) var bunchCacheLastCleanup time.Time func bunchedRequestFromCM(msg *ClientMessage) bunchedRequest { - return bunchedRequest{Command: msg.Command, Param: msg.origArguments} + return bunchedRequest{Command: msg.Command, Param: copyString(msg.origArguments)} } // is_init_func @@ -563,7 +573,7 @@ const AuthorizationFailedErrorString = "Failed to verify your Twitch username." const AuthorizationNeededError = "You must be signed in to use that command." func doRemoteCommand(conn *websocket.Conn, msg ClientMessage, client *ClientInfo) { - resp, err := SendRemoteCommandCached(string(msg.Command), msg.origArguments, client.AuthInfo) + resp, err := SendRemoteCommandCached(string(msg.Command), copyString(msg.origArguments), client.AuthInfo) if err == ErrAuthorizationNeeded { if client.TwitchUsername == "" { diff --git a/socketserver/server/handlecore.go b/socketserver/server/handlecore.go index bdda501c..058c628e 100644 --- a/socketserver/server/handlecore.go +++ b/socketserver/server/handlecore.go @@ -61,6 +61,8 @@ var Configuration *ConfigFile var janitorsOnce sync.Once var CommandPool *StringPool +var PubSubChannelPool *StringPool +var TwitchChannelPool *StringPool // SetupServerAndHandle starts all background goroutines and registers HTTP listeners on the given ServeMux. // Essentially, this function completely preps the server for a http.ListenAndServe call. @@ -115,7 +117,7 @@ func SetupServerAndHandle(config *ConfigFile, serveMux *http.ServeMux) { } func init() { - internCommands() + setupInterning() } // startJanitors starts the 'is_init_func' goroutines @@ -514,11 +516,11 @@ func UnmarshalClientMessage(data []byte, payloadType int, v interface{}) (err er spaceIdx = strings.IndexRune(dataStr, ' ') if spaceIdx == -1 { - out.Command = CommandPool.Intern(dataStr) + out.Command = CommandPool.InternCommand(dataStr) out.Arguments = nil return nil } else { - out.Command = CommandPool.Intern(dataStr[:spaceIdx]) + out.Command = CommandPool.InternCommand(dataStr[:spaceIdx]) } dataStr = dataStr[spaceIdx+1:] argumentsJSON := string([]byte(dataStr)) diff --git a/socketserver/server/intern.go b/socketserver/server/intern.go index fb319d5a..2f9cf416 100644 --- a/socketserver/server/intern.go +++ b/socketserver/server/intern.go @@ -6,19 +6,23 @@ import ( type StringPool struct { sync.RWMutex - lookup map[string]Command + lookup map[string]string } func NewStringPool() *StringPool { - return &StringPool{lookup: make(map[string]Command)} + return &StringPool{lookup: make(map[string]string)} } // doesn't lock, doesn't check for dupes. func (p *StringPool) _Intern_Setup(s string) { - p.lookup[s] = Command(s) + p.lookup[s] = s } -func (p *StringPool) Intern(s string) Command { +func (p *StringPool) InternCommand(s string) Command { + return Command(p.Intern(s)) +} + +func (p *StringPool) Intern(s string) string { p.RLock() ss, exists := p.lookup[s] p.RUnlock() @@ -32,7 +36,7 @@ func (p *StringPool) Intern(s string) Command { if exists { return ss } - ss = Command(string([]byte(s))) // make a copy - p.lookup[s] = ss + ss = copyString(s) + p.lookup[ss] = ss return ss } diff --git a/socketserver/server/publisher.go b/socketserver/server/publisher.go index a11c471e..2cfd4f4d 100644 --- a/socketserver/server/publisher.go +++ b/socketserver/server/publisher.go @@ -198,7 +198,7 @@ func HTTPBackendCachedPublish(w http.ResponseWriter, r *http.Request) { return } - cmd := CommandPool.Intern(formData.Get("cmd")) + cmd := CommandPool.InternCommand(formData.Get("cmd")) json := formData.Get("args") channel := formData.Get("channel") deleteMode := formData.Get("delete") != "" diff --git a/socketserver/server/utils.go b/socketserver/server/utils.go index 2cfa6a37..d30ec2a0 100644 --- a/socketserver/server/utils.go +++ b/socketserver/server/utils.go @@ -27,6 +27,10 @@ func New4KByteBuffer() interface{} { return make([]byte, 0, 4096) } +func copyString(s string) string { + return string([]byte(s)) +} + func SealRequest(form url.Values) (url.Values, error) { var nonce [24]byte var err error