Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock EnumCodec.membersMap during reads and writes #2088

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions pgtype/enum_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@ package pgtype
import (
"database/sql/driver"
"fmt"
"sync"
)

// EnumCodec is a codec that caches the strings it decodes. If the same string is read multiple times only one copy is
// allocated. These strings are only garbage collected when the EnumCodec is garbage collected. EnumCodec can be used
// for any text type not only enums, but it should only be used when there are a small number of possible values.
type EnumCodec struct {
membersMap map[string]string // map to quickly lookup member and reuse string instead of allocating
membersMap map[string]string // map to quickly lookup member and reuse string instead of allocating
membersMutex sync.RWMutex
}

func (EnumCodec) FormatSupported(format int16) bool {
func (*EnumCodec) FormatSupported(format int16) bool {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to make these pointer receivers to avoid FormatSupported passes lock by value: github.com/jackc/pgx/v5/pgtype.EnumCodec contains sync.RWMutex go vet errors

return format == TextFormatCode || format == BinaryFormatCode
}

func (EnumCodec) PreferredFormat() int16 {
func (*EnumCodec) PreferredFormat() int16 {
return TextFormatCode
}

func (EnumCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
func (*EnumCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
switch format {
case TextFormatCode, BinaryFormatCode:
switch value.(type) {
Expand Down Expand Up @@ -67,15 +69,26 @@ func (c *EnumCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (a
// lookupAndCacheString looks for src in the members map. If it is not found it is added to the map.
func (c *EnumCodec) lookupAndCacheString(src []byte) string {
if c.membersMap == nil {
c.membersMap = make(map[string]string)
c.membersMutex.Lock()
// Re-do the nil check in case it's changed while we were waiting on
// the lock
if c.membersMap == nil {
c.membersMap = make(map[string]string)
}
c.membersMutex.Unlock()
}

c.membersMutex.RLock()
if s, found := c.membersMap[string(src)]; found {
c.membersMutex.RUnlock()
return s
}
c.membersMutex.RUnlock()

s := string(src)
c.membersMutex.Lock()
c.membersMap[s] = s
c.membersMutex.Unlock()
return s
}

Expand Down