Skip to content

Commit 05ecf64

Browse files
authored
feat: only "touch" the session if no value updated (#91)
1 parent 150203e commit 05ecf64

File tree

21 files changed

+424
-94
lines changed

21 files changed

+424
-94
lines changed

.github/workflows/go.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
name: Test
3434
strategy:
3535
matrix:
36-
go-version: [ 1.19.x, 1.20.x ]
36+
go-version: [ 1.20.x, 1.21.x ]
3737
platform: [ ubuntu-latest, macos-latest, windows-latest ]
3838
runs-on: ${{ matrix.platform }}
3939
steps:
@@ -55,7 +55,7 @@ jobs:
5555
name: Postgres
5656
strategy:
5757
matrix:
58-
go-version: [ 1.19.x, 1.20.x ]
58+
go-version: [ 1.20.x, 1.21.x ]
5959
platform: [ ubuntu-latest ]
6060
runs-on: ${{ matrix.platform }}
6161
services:
@@ -95,7 +95,7 @@ jobs:
9595
name: Redis
9696
strategy:
9797
matrix:
98-
go-version: [ 1.19.x, 1.20.x ]
98+
go-version: [ 1.20.x, 1.21.x ]
9999
platform: [ ubuntu-latest ]
100100
runs-on: ${{ matrix.platform }}
101101
services:
@@ -130,7 +130,7 @@ jobs:
130130
name: MySQL
131131
strategy:
132132
matrix:
133-
go-version: [ 1.19.x, 1.20.x ]
133+
go-version: [ 1.20.x, 1.21.x ]
134134
platform: [ ubuntu-20.04 ]
135135
runs-on: ${{ matrix.platform }}
136136
steps:
@@ -159,7 +159,7 @@ jobs:
159159
name: Mongo
160160
strategy:
161161
matrix:
162-
go-version: [ 1.19.x, 1.20.x ]
162+
go-version: [ 1.20.x, 1.21.x ]
163163
platform: [ ubuntu-latest ]
164164
runs-on: ${{ matrix.platform }}
165165
services:
@@ -196,7 +196,7 @@ jobs:
196196
name: SQLite
197197
strategy:
198198
matrix:
199-
go-version: [ 1.20.x ]
199+
go-version: [ 1.20.x, 1.21.x ]
200200
platform: [ ubuntu-latest ]
201201
runs-on: ${{ matrix.platform }}
202202
steps:

.github/workflows/lsif.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
if: github.repository == 'flamego/session'
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v2
16+
- uses: actions/checkout@v3
1717
- name: Generate LSIF data
1818
uses: sourcegraph/lsif-go-action@master
1919
- name: Upload LSIF data to sourcegraph.com

file.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,19 @@ func (s *fileStore) Destroy(_ context.Context, sid string) error {
104104
return os.Remove(s.filename(sid))
105105
}
106106

107+
func (s *fileStore) Touch(_ context.Context, sid string) error {
108+
filename := s.filename(sid)
109+
if !isFile(filename) {
110+
return nil
111+
}
112+
113+
err := os.Chtimes(filename, s.nowFunc(), s.nowFunc())
114+
if err != nil {
115+
return errors.Wrap(err, "change times")
116+
}
117+
return nil
118+
}
119+
107120
func (s *fileStore) Save(_ context.Context, sess Session) error {
108121
if len(sess.ID()) < minimumSIDLength {
109122
return ErrMinimumSIDLength
@@ -114,10 +127,16 @@ func (s *fileStore) Save(_ context.Context, sess Session) error {
114127
return errors.Wrap(err, "encode")
115128
}
116129

117-
err = os.WriteFile(s.filename(sess.ID()), binary, 0600)
130+
filename := s.filename(sess.ID())
131+
err = os.WriteFile(filename, binary, 0600)
118132
if err != nil {
119133
return errors.Wrap(err, "write file")
120134
}
135+
136+
err = os.Chtimes(filename, s.nowFunc(), s.nowFunc())
137+
if err != nil {
138+
return errors.Wrap(err, "change times")
139+
}
121140
return nil
122141
}
123142

@@ -145,7 +164,7 @@ func (s *fileStore) GC(ctx context.Context) error {
145164
}
146165
return os.Remove(path)
147166
})
148-
if err != nil && err != ctx.Err() {
167+
if err != nil && !errors.Is(err, ctx.Err()) {
149168
return err
150169
}
151170
return nil

file_test.go

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616

1717
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
1819

1920
"github.com/flamego/flamego"
2021
)
@@ -57,7 +58,7 @@ func TestFileStore(t *testing.T) {
5758

5859
resp := httptest.NewRecorder()
5960
req, err := http.NewRequest("GET", "/set", nil)
60-
assert.Nil(t, err)
61+
require.Nil(t, err)
6162

6263
f.ServeHTTP(resp, req)
6364
assert.Equal(t, http.StatusOK, resp.Code)
@@ -66,15 +67,15 @@ func TestFileStore(t *testing.T) {
6667

6768
resp = httptest.NewRecorder()
6869
req, err = http.NewRequest("GET", "/get", nil)
69-
assert.Nil(t, err)
70+
require.Nil(t, err)
7071

7172
req.Header.Set("Cookie", cookie)
7273
f.ServeHTTP(resp, req)
7374
assert.Equal(t, http.StatusOK, resp.Code)
7475

7576
resp = httptest.NewRecorder()
7677
req, err = http.NewRequest("GET", "/destroy", nil)
77-
assert.Nil(t, err)
78+
require.Nil(t, err)
7879

7980
req.Header.Set("Cookie", cookie)
8081
f.ServeHTTP(resp, req)
@@ -91,48 +92,65 @@ func TestFileStore_GC(t *testing.T) {
9192
Lifetime: time.Second,
9293
},
9394
)
94-
assert.Nil(t, err)
95-
96-
setModTime := func(sid string) {
97-
t.Helper()
98-
99-
err := os.Chtimes(store.(*fileStore).filename(sid), now, now)
100-
assert.Nil(t, err)
101-
}
95+
require.Nil(t, err)
10296

10397
sess1, err := store.Read(ctx, "111")
104-
assert.Nil(t, err)
98+
require.Nil(t, err)
10599
err = store.Save(ctx, sess1)
106-
assert.Nil(t, err)
107-
setModTime("111")
100+
require.Nil(t, err)
108101

109102
now = now.Add(-2 * time.Second)
110103
sess2, err := store.Read(ctx, "222")
111-
assert.Nil(t, err)
104+
require.Nil(t, err)
112105

113106
sess2.Set("name", "flamego")
114107
err = store.Save(ctx, sess2)
115-
assert.Nil(t, err)
116-
setModTime("222")
108+
require.Nil(t, err)
117109

118110
// Read on an expired session should wipe data but preserve the record
119111
now = now.Add(2 * time.Second)
120112
tmp, err := store.Read(ctx, "222")
121-
assert.Nil(t, err)
113+
require.Nil(t, err)
122114
assert.Nil(t, tmp.Get("name"))
123115

124116
now = now.Add(-2 * time.Second)
125117
sess3, err := store.Read(ctx, "333")
126-
assert.Nil(t, err)
118+
require.Nil(t, err)
127119
err = store.Save(ctx, sess3)
128-
assert.Nil(t, err)
129-
setModTime("333")
120+
require.Nil(t, err)
130121

131122
now = now.Add(2 * time.Second)
132123
err = store.GC(ctx) // sess3 should be recycled
133-
assert.Nil(t, err)
124+
require.Nil(t, err)
134125

135126
assert.True(t, store.Exist(ctx, "111"))
136127
assert.False(t, store.Exist(ctx, "222"))
137128
assert.False(t, store.Exist(ctx, "333"))
138129
}
130+
131+
func TestFileStore_Touch(t *testing.T) {
132+
ctx := context.Background()
133+
now := time.Now()
134+
store, err := FileIniter()(ctx,
135+
FileConfig{
136+
nowFunc: func() time.Time { return now },
137+
RootDir: filepath.Join(os.TempDir(), "sessions"),
138+
Lifetime: time.Second,
139+
},
140+
)
141+
require.Nil(t, err)
142+
143+
sess, err := store.Read(ctx, "111")
144+
require.Nil(t, err)
145+
err = store.Save(ctx, sess)
146+
require.Nil(t, err)
147+
148+
now = now.Add(2 * time.Second)
149+
// Touch should keep the session alive
150+
err = store.Touch(ctx, sess.ID())
151+
require.Nil(t, err)
152+
153+
err = store.GC(ctx)
154+
require.Nil(t, err)
155+
assert.True(t, store.Exist(ctx, sess.ID()))
156+
}

manager.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type Store interface {
2424
Read(ctx context.Context, sid string) (Session, error)
2525
// Destroy deletes session with given ID from the session store completely.
2626
Destroy(ctx context.Context, sid string) error
27+
// Touch updates the expiry time of the session with given ID. It does nothing
28+
// if there is no session associated with the ID.
29+
Touch(ctx context.Context, sid string) error
2730
// Save persists session data to the session store.
2831
Save(ctx context.Context, session Session) error
2932
// GC performs a GC operation on the session store.

manager_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import (
1010
"time"
1111

1212
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
1314
)
1415

1516
func TestIsValidSessionID(t *testing.T) {
1617
for i := 0; i < 10; i++ {
1718
s, err := randomChars(16)
18-
assert.Nil(t, err)
19+
require.Nil(t, err)
1920
assert.True(t, isValidSessionID(s, 16))
2021
}
2122

memory.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,22 @@ func (s *memoryStore) Destroy(_ context.Context, sid string) error {
155155
return nil
156156
}
157157

158-
func (s *memoryStore) Save(context.Context, Session) error {
158+
func (s *memoryStore) Touch(_ context.Context, sid string) error {
159+
s.lock.Lock()
160+
defer s.lock.Unlock()
161+
162+
sess, ok := s.index[sid]
163+
if !ok {
164+
return nil
165+
}
166+
167+
sess.SetLastAccessedAt(s.nowFunc())
168+
heap.Fix(s, sess.index)
159169
return nil
160170
}
161171

172+
func (s *memoryStore) Save(context.Context, Session) error { return nil }
173+
162174
func (s *memoryStore) GC(ctx context.Context) error {
163175
// Removing expired sessions from top of the heap until there is no more expired
164176
// sessions found.

memory_test.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
1617

1718
"github.com/flamego/flamego"
1819
)
@@ -46,25 +47,25 @@ func TestMemoryStore(t *testing.T) {
4647
})
4748

4849
resp := httptest.NewRecorder()
49-
req, err := http.NewRequest("GET", "/set", nil)
50-
assert.Nil(t, err)
50+
req, err := http.NewRequest(http.MethodGet, "/set", nil)
51+
require.Nil(t, err)
5152

5253
f.ServeHTTP(resp, req)
5354
assert.Equal(t, http.StatusOK, resp.Code)
5455

5556
cookie := resp.Header().Get("Set-Cookie")
5657

5758
resp = httptest.NewRecorder()
58-
req, err = http.NewRequest("GET", "/get", nil)
59-
assert.Nil(t, err)
59+
req, err = http.NewRequest(http.MethodGet, "/get", nil)
60+
require.Nil(t, err)
6061

6162
req.Header.Set("Cookie", cookie)
6263
f.ServeHTTP(resp, req)
6364
assert.Equal(t, http.StatusOK, resp.Code)
6465

6566
resp = httptest.NewRecorder()
66-
req, err = http.NewRequest("GET", "/destroy", nil)
67-
assert.Nil(t, err)
67+
req, err = http.NewRequest(http.MethodGet, "/destroy", nil)
68+
require.Nil(t, err)
6869

6970
req.Header.Set("Cookie", cookie)
7071
f.ServeHTTP(resp, req)
@@ -82,29 +83,29 @@ func TestMemoryStore_GC(t *testing.T) {
8283
)
8384

8485
sess1, err := store.Read(ctx, "1")
85-
assert.Nil(t, err)
86+
require.Nil(t, err)
8687

8788
now = now.Add(-2 * time.Second)
8889
sess2, err := store.Read(ctx, "2")
89-
assert.Nil(t, err)
90+
require.Nil(t, err)
9091

9192
sess2.Set("name", "flamego")
9293
err = store.Save(ctx, sess2)
93-
assert.Nil(t, err)
94+
require.Nil(t, err)
9495

9596
// Read on an expired session should wipe data but preserve the record
9697
now = now.Add(2 * time.Second)
9798
tmp, err := store.Read(ctx, "2")
98-
assert.Nil(t, err)
99+
require.Nil(t, err)
99100
assert.Nil(t, tmp.Get("name"))
100101

101102
now = now.Add(-2 * time.Second)
102103
_, err = store.Read(ctx, "3")
103-
assert.Nil(t, err)
104+
require.Nil(t, err)
104105

105106
now = now.Add(2 * time.Second)
106107
err = store.GC(ctx) // sess3 should be recycled
107-
assert.Nil(t, err)
108+
require.Nil(t, err)
108109

109110
wantHeap := []*memorySession{sess2.(*memorySession), sess1.(*memorySession)}
110111
assert.Equal(t, wantHeap, store.heap)
@@ -115,3 +116,28 @@ func TestMemoryStore_GC(t *testing.T) {
115116
}
116117
assert.Equal(t, wantIndex, store.index)
117118
}
119+
120+
func TestMemoryStore_Touch(t *testing.T) {
121+
ctx := context.Background()
122+
now := time.Now()
123+
store := newMemoryStore(
124+
MemoryConfig{
125+
nowFunc: func() time.Time { return now },
126+
Lifetime: time.Second,
127+
},
128+
)
129+
130+
sess, err := store.Read(ctx, "1")
131+
require.Nil(t, err)
132+
133+
now = now.Add(2 * time.Second)
134+
// Touch should keep the session alive
135+
err = store.Touch(ctx, sess.ID())
136+
require.Nil(t, err)
137+
138+
err = store.GC(ctx)
139+
require.Nil(t, err)
140+
141+
wantHeap := []*memorySession{sess.(*memorySession)}
142+
assert.Equal(t, wantHeap, store.heap)
143+
}

0 commit comments

Comments
 (0)