Skip to content

Commit 3062963

Browse files
eofedorovЕвгений
andauthored
Fix #537: protect rutor state with RWMutex to avoid concurrent map writes (#653)
Co-authored-by: Евгений <you@example.com>
1 parent 366dce4 commit 3062963

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

server/rutor/race_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package rutor
2+
3+
import (
4+
"bytes"
5+
"compress/flate"
6+
"encoding/json"
7+
"os"
8+
"path/filepath"
9+
"strconv"
10+
"sync"
11+
"testing"
12+
"time"
13+
14+
"server/rutor/models"
15+
"server/settings"
16+
)
17+
18+
// TestConcurrentSearchAndLoadDB проверяет отсутствие гонки при одновременном
19+
// обновлении индекса (loadDB) и поиске (Search).
20+
// !Запускать с -count=3
21+
func TestConcurrentSearchAndLoadDB(t *testing.T) {
22+
if settings.BTsets == nil {
23+
settings.BTsets = &settings.BTSets{EnableRutorSearch: true}
24+
defer func() { settings.BTsets = nil }()
25+
} else {
26+
old := settings.BTsets.EnableRutorSearch
27+
settings.BTsets.EnableRutorSearch = true
28+
defer func() { settings.BTsets.EnableRutorSearch = old }()
29+
}
30+
31+
dir := t.TempDir()
32+
oldPath := settings.Path
33+
settings.Path = dir
34+
defer func() { settings.Path = oldPath }()
35+
36+
const numTorrents = 800
37+
seed := make([]*models.TorrentDetails, numTorrents)
38+
for i := 0; i < numTorrents; i++ {
39+
s := strconv.Itoa(i)
40+
seed[i] = &models.TorrentDetails{
41+
Title: "Test Film Number " + s + " Part One Two Three Year",
42+
Name: "Film " + s,
43+
Year: 2015 + i%10,
44+
}
45+
}
46+
data, err := json.Marshal(seed)
47+
if err != nil {
48+
t.Fatal(err)
49+
}
50+
var compressed bytes.Buffer
51+
w, _ := flate.NewWriter(&compressed, flate.DefaultCompression)
52+
_, _ = w.Write(data)
53+
_ = w.Close()
54+
if err := os.WriteFile(filepath.Join(dir, "rutor.ls"), compressed.Bytes(), 0o600); err != nil {
55+
t.Fatal(err)
56+
}
57+
58+
done := make(chan struct{})
59+
var wg sync.WaitGroup
60+
61+
// Горутина: многократно перезагружает БД (долгая перезапись индекса)
62+
wg.Add(1)
63+
go func() {
64+
defer wg.Done()
65+
for i := 0; i < 20; i++ {
66+
select {
67+
case <-done:
68+
return
69+
default:
70+
loadDB()
71+
time.Sleep(5 * time.Millisecond)
72+
}
73+
}
74+
}()
75+
76+
// Несколько горутин: постоянный поиск, пока идёт переиндексация
77+
for i := 0; i < 8; i++ {
78+
wg.Add(1)
79+
go func() {
80+
defer wg.Done()
81+
queries := []string{"Test", "Film", "Number", "Part", "Year", "xxx"}
82+
for j := 0; j < 200; j++ {
83+
select {
84+
case <-done:
85+
return
86+
default:
87+
_ = Search(queries[j%len(queries)])
88+
}
89+
}
90+
}()
91+
}
92+
93+
// Даём время на пересечение loadDB и Search
94+
time.Sleep(800 * time.Millisecond)
95+
close(done)
96+
wg.Wait()
97+
}

server/rutor/rutor.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sort"
1111
"strconv"
1212
"strings"
13+
"sync"
1314
"time"
1415

1516
"github.com/agnivade/levenshtein"
@@ -23,6 +24,7 @@ import (
2324
)
2425

2526
var (
27+
mu sync.RWMutex
2628
torrs []*models.TorrentDetails
2729
isStop bool
2830
)
@@ -48,9 +50,11 @@ func Start() {
4850
}
4951

5052
func Stop() {
53+
mu.Lock()
5154
isStop = true
5255
torrs = nil
5356
torrsearch.NewIndex(nil)
57+
mu.Unlock()
5458
utils2.FreeOSMemGC()
5559
time.Sleep(time.Millisecond * 1500)
5660
}
@@ -133,6 +137,8 @@ func loadDB() {
133137
ftorrs = append(ftorrs, torr)
134138
}
135139
}
140+
mu.Lock()
141+
defer mu.Unlock()
136142
torrs = ftorrs
137143
log.TLogln("Index rutor db")
138144
torrsearch.NewIndex(torrs)
@@ -149,14 +155,17 @@ func Search(query string) []*models.TorrentDetails {
149155
if !settings.BTsets.EnableRutorSearch {
150156
return nil
151157
}
158+
mu.RLock()
152159
matchedIDs := torrsearch.Search(query)
153160
if len(matchedIDs) == 0 {
161+
mu.RUnlock()
154162
return nil
155163
}
156164
var list []*models.TorrentDetails
157165
for _, id := range matchedIDs {
158166
list = append(list, torrs[id])
159167
}
168+
mu.RUnlock()
160169

161170
hash := utils.ClearStr(query)
162171

0 commit comments

Comments
 (0)