diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index b1b8a4c..cd4dddf 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [1.12, 1.13, 1.14, ^1.14] + go-version: [1.12, 1.13, 1.14, 1.15, ^1.16] steps: - name: Set up Go 1.x @@ -29,7 +29,7 @@ jobs: go get -v -t -d ./... - name: Run Tests - run: go test -v -cover ./... + run: go test -v -timeout 300s -cover ./... - name: Run Benchmarks - run: go test -bench=. -benchmem + run: go test -timeout 300s -bench=. -benchmem diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..01e4fbb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## v1.3.1 + +- Fix `concurrent map read and map write` panic when accessing the map concurrently while getting an existing key, which is expired at the time. [#4] \ No newline at end of file diff --git a/timedmap.go b/timedmap.go index e4ff8aa..9f81b12 100644 --- a/timedmap.go +++ b/timedmap.go @@ -228,6 +228,8 @@ func (tm *TimedMap) get(key interface{}, sec int) *element { } if time.Now().After(v.expires) { + tm.mtx.Lock() + defer tm.mtx.Unlock() tm.expireElement(key, sec, v) return nil } diff --git a/timedmap_test.go b/timedmap_test.go index a15c202..45b4ad3 100644 --- a/timedmap_test.go +++ b/timedmap_test.go @@ -1,6 +1,7 @@ package timedmap import ( + "sync" "testing" "time" ) @@ -275,6 +276,27 @@ func TestConcurrentReadWrite(t *testing.T) { time.Sleep(1 * time.Second) } +func TestGetExpiredConcurrent(t *testing.T) { + tm := New(dCleanupTick) + + wg := sync.WaitGroup{} + for i := 0; i < 50000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + tm.Set(1, 1, 0) + }() + + wg.Add(1) + go func() { + defer wg.Done() + tm.GetValue(1) + }() + } + + wg.Wait() +} + func TestExternalTicker(t *testing.T) { const key = "tKeySet" const val = "tValSet"