@@ -5,6 +5,7 @@ package sqlbase
5
5
import (
6
6
"context"
7
7
"database/sql"
8
+ "sync"
8
9
9
10
"github.com/btcsuite/btcwallet/walletdb"
10
11
)
@@ -19,11 +20,28 @@ type readWriteTx struct {
19
20
20
21
// active is true if the transaction hasn't been committed yet.
21
22
active bool
23
+
24
+ // locker is a pointer to the global db lock.
25
+ locker sync.Locker
22
26
}
23
27
24
28
// newReadWriteTx creates an rw transaction using a connection from the
25
29
// specified pool.
26
30
func newReadWriteTx (db * db , readOnly bool ) (* readWriteTx , error ) {
31
+ locker := newNoopLocker ()
32
+ if db .cfg .WithTxLevelLock {
33
+ // Obtain the global lock instance. An alternative here is to
34
+ // obtain a database lock from Postgres. Unfortunately there is
35
+ // no database-level lock in Postgres, meaning that each table
36
+ // would need to be locked individually. Perhaps an advisory
37
+ // lock could perform this function too.
38
+ locker = & db .lock
39
+ if readOnly {
40
+ locker = db .lock .RLocker ()
41
+ }
42
+ }
43
+ locker .Lock ()
44
+
27
45
// Start the transaction. Don't use the timeout context because it would
28
46
// be applied to the transaction as a whole. If possible, mark the
29
47
// transaction as read-only to make sure that potential programming
@@ -36,13 +54,15 @@ func newReadWriteTx(db *db, readOnly bool) (*readWriteTx, error) {
36
54
},
37
55
)
38
56
if err != nil {
57
+ locker .Unlock ()
39
58
return nil , err
40
59
}
41
60
42
61
return & readWriteTx {
43
62
db : db ,
44
63
tx : tx ,
45
64
active : true ,
65
+ locker : locker ,
46
66
}, nil
47
67
}
48
68
@@ -74,6 +94,7 @@ func (tx *readWriteTx) Rollback() error {
74
94
75
95
// Unlock the transaction regardless of the error result.
76
96
tx .active = false
97
+ tx .locker .Unlock ()
77
98
return err
78
99
}
79
100
@@ -141,6 +162,7 @@ func (tx *readWriteTx) Commit() error {
141
162
142
163
// Unlock the transaction regardless of the error result.
143
164
tx .active = false
165
+ tx .locker .Unlock ()
144
166
145
167
return err
146
168
}
@@ -182,3 +204,25 @@ func (tx *readWriteTx) Exec(query string, args ...interface{}) (sql.Result,
182
204
183
205
return tx .tx .ExecContext (ctx , query , args ... )
184
206
}
207
+
208
+ // noopLocker is an implementation of a no-op sync.Locker.
209
+ type noopLocker struct {}
210
+
211
+ // newNoopLocker creates a new noopLocker.
212
+ func newNoopLocker () sync.Locker {
213
+ return & noopLocker {}
214
+ }
215
+
216
+ // Lock is a noop.
217
+ //
218
+ // NOTE: this is part of the sync.Locker interface.
219
+ func (n * noopLocker ) Lock () {
220
+ }
221
+
222
+ // Unlock is a noop.
223
+ //
224
+ // NOTE: this is part of the sync.Locker interface.
225
+ func (n * noopLocker ) Unlock () {
226
+ }
227
+
228
+ var _ sync.Locker = (* noopLocker )(nil )
0 commit comments