Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 262b361

Browse files
yndensteebchen
andauthored
fix(prisma): handle race condition when disconnecting (#1442)
Co-authored-by: Luca Steeb <[email protected]>
1 parent 129ffca commit 262b361

File tree

5 files changed

+86
-1
lines changed

5 files changed

+86
-1
lines changed

engine/lifecycle.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ func (e *QueryEngine) Connect() error {
6262
}
6363

6464
func (e *QueryEngine) Disconnect() error {
65+
e.mu.Lock()
6566
e.disconnected = true
67+
e.mu.Unlock()
6668
logger.Debug.Printf("disconnecting...")
6769

6870
if platform.Name() == "windows" {

engine/qe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type QueryEngine struct {
5454
// lastEngineError contains the last received error
5555
lastEngineError string
5656

57-
mu sync.Mutex
57+
mu sync.RWMutex
5858
}
5959

6060
func (e *QueryEngine) Name() string {

engine/request.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,13 @@ func (e *QueryEngine) Request(ctx context.Context, method string, path string, p
8787
return nil, fmt.Errorf("client is not connected yet")
8888
}
8989

90+
e.mu.RLock()
9091
if e.disconnected {
92+
e.mu.RUnlock()
9193
logger.Info.Printf("A query was executed after Disconnect() was called. Make sure to not send any queries after calling .Prisma.Disconnect() the client.")
9294
return nil, fmt.Errorf("client is already disconnected")
9395
}
96+
e.mu.RUnlock()
9497

9598
requestBody, err := json.Marshal(payload)
9699
if err != nil {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package db
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/steebchen/prisma-client-go/test"
9+
"github.com/steebchen/prisma-client-go/test/helpers/massert"
10+
)
11+
12+
type cx = context.Context
13+
type Func func(t *testing.T, client *PrismaClient, ctx cx)
14+
15+
// The purpose of this test is to have a goroutine continuously making queries (whether or not they succeed),
16+
// and detect race conditions, after disconnecting.
17+
func TestDisconnectConcurrent(t *testing.T) {
18+
test.RunSerial(t, []test.Database{test.MySQL, test.PostgreSQL}, func(t *testing.T, db test.Database, ctx context.Context) {
19+
client := NewClient()
20+
mockDBName := test.Start(t, db, client.Engine, []string{})
21+
22+
a := "a"
23+
b := "b"
24+
created, err := client.User.CreateOne(
25+
User.A.Set(a),
26+
User.B.Set(b),
27+
User.ID.Set("123"),
28+
).Exec(ctx)
29+
if err != nil {
30+
t.Fatalf("fail %s", err)
31+
}
32+
33+
expected := &UserModel{
34+
InnerUser: InnerUser{
35+
ID: "123",
36+
A: a,
37+
B: &b,
38+
},
39+
}
40+
41+
massert.Equal(t, expected, created)
42+
43+
// Query database concurrently
44+
closeCh := make(chan struct{})
45+
go func() {
46+
loop := true
47+
for loop {
48+
time.Sleep(time.Millisecond * 100)
49+
select {
50+
case <-closeCh:
51+
loop = false
52+
default:
53+
client.User.FindUnique(User.ID.Equals(created.ID)).Exec(ctx)
54+
}
55+
}
56+
}()
57+
58+
// Wait, and close the connection
59+
time.Sleep(time.Millisecond * 300)
60+
test.End(t, db, client.Engine, mockDBName)
61+
closeCh <- struct{}{}
62+
})
63+
}

test/misc/goroutines/schema.prisma

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
datasource db {
2+
provider = "postgresql"
3+
url = env("__REPLACE__")
4+
}
5+
6+
generator db {
7+
provider = "go run github.com/steebchen/prisma-client-go"
8+
output = "."
9+
disableGoBinaries = true
10+
package = "db"
11+
}
12+
13+
model User {
14+
id String @id @default(cuid()) @map("_id")
15+
a String @db.VarChar(5)
16+
b String? @db.VarChar(50)
17+
}

0 commit comments

Comments
 (0)