@@ -19,6 +19,8 @@ package pulsar
19
19
20
20
import (
21
21
"context"
22
+ "errors"
23
+ "fmt"
22
24
"sync"
23
25
"sync/atomic"
24
26
"time"
@@ -33,9 +35,9 @@ type subscription struct {
33
35
}
34
36
35
37
type transaction struct {
36
- sync.Mutex
38
+ mu sync.Mutex
37
39
txnID TxnID
38
- state TxnState
40
+ state atomic. Int32
39
41
tcClient * transactionCoordinatorClient
40
42
registerPartitions map [string ]bool
41
43
registerAckSubscriptions map [subscription ]bool
@@ -54,96 +56,106 @@ type transaction struct {
54
56
// 1. When the transaction is committed or aborted, a bool will be read from opsFlow chan.
55
57
// 2. When the opsCount increment from 0 to 1, a bool will be read from opsFlow chan.
56
58
opsFlow chan bool
57
- opsCount int32
59
+ opsCount atomic. Int32
58
60
opTimeout time.Duration
59
61
log log.Logger
60
62
}
61
63
62
64
func newTransaction (id TxnID , tcClient * transactionCoordinatorClient , timeout time.Duration ) * transaction {
63
65
transaction := & transaction {
64
66
txnID : id ,
65
- state : TxnOpen ,
66
67
registerPartitions : make (map [string ]bool ),
67
68
registerAckSubscriptions : make (map [subscription ]bool ),
68
69
opsFlow : make (chan bool , 1 ),
69
- opTimeout : 5 * time . Second ,
70
+ opTimeout : tcClient . client . operationTimeout ,
70
71
tcClient : tcClient ,
71
72
}
72
- //This means there are not pending requests with this transaction. The transaction can be committed or aborted.
73
+ transaction .state .Store (int32 (TxnOpen ))
74
+ // This means there are not pending requests with this transaction. The transaction can be committed or aborted.
73
75
transaction .opsFlow <- true
74
76
go func () {
75
- //Set the state of the transaction to timeout after timeout
77
+ // Set the state of the transaction to timeout after timeout
76
78
<- time .After (timeout )
77
- atomic . CompareAndSwapInt32 (( * int32 )( & transaction .state ), int32 (TxnOpen ), int32 (TxnTimeout ))
79
+ transaction .state . CompareAndSwap ( int32 (TxnOpen ), int32 (TxnTimeout ))
78
80
}()
79
81
transaction .log = tcClient .log .SubLogger (log.Fields {})
80
82
return transaction
81
83
}
82
84
83
85
func (txn * transaction ) GetState () TxnState {
84
- return txn .state
86
+ return TxnState ( txn .state . Load ())
85
87
}
86
88
87
- func (txn * transaction ) Commit (_ context.Context ) error {
88
- if ! (atomic . CompareAndSwapInt32 (( * int32 )( & txn .state ), int32 (TxnOpen ), int32 (TxnCommitting )) ||
89
- txn . state == TxnCommitting ) {
90
- return newError (InvalidStatus , "Expect transaction state is TxnOpen but " + txn . state . string ( ))
89
+ func (txn * transaction ) Commit (ctx context.Context ) error {
90
+ if ! (txn .state . CompareAndSwap ( int32 (TxnOpen ), int32 (TxnCommitting ))) {
91
+ txnState := txn . state . Load ()
92
+ return newError (InvalidStatus , txnStateErrorMessage ( TxnOpen , TxnState ( txnState ) ))
91
93
}
92
94
93
- //Wait for all operations to complete
95
+ // Wait for all operations to complete
94
96
select {
95
97
case <- txn .opsFlow :
98
+ case <- ctx .Done ():
99
+ txn .state .Store (int32 (TxnOpen ))
100
+ return ctx .Err ()
96
101
case <- time .After (txn .opTimeout ):
102
+ txn .state .Store (int32 (TxnTimeout ))
97
103
return newError (TimeoutError , "There are some operations that are not completed after the timeout." )
98
104
}
99
- //Send commit transaction command to transaction coordinator
105
+ // Send commit transaction command to transaction coordinator
100
106
err := txn .tcClient .endTxn (& txn .txnID , pb .TxnAction_COMMIT )
101
107
if err == nil {
102
- atomic . StoreInt32 (( * int32 )( & txn .state ), int32 (TxnCommitted ))
108
+ txn .state . Store ( int32 (TxnCommitted ))
103
109
} else {
104
- if e , ok := err .(* Error ); ok && (e .Result () == TransactionNoFoundError || e .Result () == InvalidStatus ) {
105
- atomic .StoreInt32 ((* int32 )(& txn .state ), int32 (TxnError ))
110
+ var e * Error
111
+ if errors .As (err , & e ) && (e .Result () == TransactionNoFoundError || e .Result () == InvalidStatus ) {
112
+ txn .state .Store (int32 (TxnError ))
106
113
return err
107
114
}
108
115
txn .opsFlow <- true
109
116
}
110
117
return err
111
118
}
112
119
113
- func (txn * transaction ) Abort (_ context.Context ) error {
114
- if ! (atomic . CompareAndSwapInt32 (( * int32 )( & txn .state ), int32 (TxnOpen ), int32 (TxnAborting )) ||
115
- txn . state == TxnAborting ) {
116
- return newError (InvalidStatus , "Expect transaction state is TxnOpen but " + txn . state . string ( ))
120
+ func (txn * transaction ) Abort (ctx context.Context ) error {
121
+ if ! (txn .state . CompareAndSwap ( int32 (TxnOpen ), int32 (TxnAborting ))) {
122
+ txnState := txn . state . Load ()
123
+ return newError (InvalidStatus , txnStateErrorMessage ( TxnOpen , TxnState ( txnState ) ))
117
124
}
118
125
119
- //Wait for all operations to complete
126
+ // Wait for all operations to complete
120
127
select {
121
128
case <- txn .opsFlow :
129
+ case <- ctx .Done ():
130
+ txn .state .Store (int32 (TxnOpen ))
131
+ return ctx .Err ()
122
132
case <- time .After (txn .opTimeout ):
133
+ txn .state .Store (int32 (TxnTimeout ))
123
134
return newError (TimeoutError , "There are some operations that are not completed after the timeout." )
124
135
}
125
- //Send abort transaction command to transaction coordinator
136
+ // Send abort transaction command to transaction coordinator
126
137
err := txn .tcClient .endTxn (& txn .txnID , pb .TxnAction_ABORT )
127
138
if err == nil {
128
- atomic . StoreInt32 (( * int32 )( & txn .state ), int32 (TxnAborted ))
139
+ txn .state . Store ( int32 (TxnAborted ))
129
140
} else {
130
- if e , ok := err .( * Error ); ok && ( e . Result () == TransactionNoFoundError || e . Result () == InvalidStatus ) {
131
- atomic . StoreInt32 (( * int32 )( & txn . state ), int32 ( TxnError ))
132
- } else {
133
- txn . opsFlow <- true
141
+ var e * Error
142
+ if errors . As ( err , & e ) && ( e . Result () == TransactionNoFoundError || e . Result () == InvalidStatus ) {
143
+ txn . state . Store ( int32 ( TxnError ))
144
+ return err
134
145
}
146
+ txn .opsFlow <- true
135
147
}
136
148
return err
137
149
}
138
150
139
151
func (txn * transaction ) registerSendOrAckOp () error {
140
- if atomic . AddInt32 ( & txn .opsCount , 1 ) == 1 {
141
- //There are new operations that not completed
152
+ if txn .opsCount . Add ( 1 ) == 1 {
153
+ // There are new operations that were not completed
142
154
select {
143
155
case <- txn .opsFlow :
144
156
return nil
145
157
case <- time .After (txn .opTimeout ):
146
- if _ , err := txn .checkIfOpen (); err != nil {
158
+ if err := txn .verifyOpen (); err != nil {
147
159
return err
148
160
}
149
161
return newError (TimeoutError , "Failed to get the semaphore to register the send/ack operation" )
@@ -154,23 +166,22 @@ func (txn *transaction) registerSendOrAckOp() error {
154
166
155
167
func (txn * transaction ) endSendOrAckOp (err error ) {
156
168
if err != nil {
157
- atomic . StoreInt32 (( * int32 )( & txn .state ), int32 (TxnError ))
169
+ txn .state . Store ( int32 (TxnError ))
158
170
}
159
- if atomic . AddInt32 ( & txn .opsCount , - 1 ) == 0 {
160
- //This means there are not pending send/ack requests
171
+ if txn .opsCount . Add ( - 1 ) == 0 {
172
+ // This means there are no pending send/ack requests
161
173
txn .opsFlow <- true
162
174
}
163
175
}
164
176
165
177
func (txn * transaction ) registerProducerTopic (topic string ) error {
166
- isOpen , err := txn .checkIfOpen ()
167
- if ! isOpen {
178
+ if err := txn .verifyOpen (); err != nil {
168
179
return err
169
180
}
170
181
_ , ok := txn .registerPartitions [topic ]
171
182
if ! ok {
172
- txn .Lock ()
173
- defer txn .Unlock ()
183
+ txn .mu . Lock ()
184
+ defer txn .mu . Unlock ()
174
185
if _ , ok = txn .registerPartitions [topic ]; ! ok {
175
186
err := txn .tcClient .addPublishPartitionToTxn (& txn .txnID , []string {topic })
176
187
if err != nil {
@@ -183,8 +194,7 @@ func (txn *transaction) registerProducerTopic(topic string) error {
183
194
}
184
195
185
196
func (txn * transaction ) registerAckTopic (topic string , subName string ) error {
186
- isOpen , err := txn .checkIfOpen ()
187
- if ! isOpen {
197
+ if err := txn .verifyOpen (); err != nil {
188
198
return err
189
199
}
190
200
sub := subscription {
@@ -193,8 +203,8 @@ func (txn *transaction) registerAckTopic(topic string, subName string) error {
193
203
}
194
204
_ , ok := txn .registerAckSubscriptions [sub ]
195
205
if ! ok {
196
- txn .Lock ()
197
- defer txn .Unlock ()
206
+ txn .mu . Lock ()
207
+ defer txn .mu . Unlock ()
198
208
if _ , ok = txn .registerAckSubscriptions [sub ]; ! ok {
199
209
err := txn .tcClient .addSubscriptionToTxn (& txn .txnID , topic , subName )
200
210
if err != nil {
@@ -210,14 +220,15 @@ func (txn *transaction) GetTxnID() TxnID {
210
220
return txn .txnID
211
221
}
212
222
213
- func (txn * transaction ) checkIfOpen () (bool , error ) {
214
- if txn .state == TxnOpen {
215
- return true , nil
223
+ func (txn * transaction ) verifyOpen () error {
224
+ txnState := txn .state .Load ()
225
+ if txnState != int32 (TxnOpen ) {
226
+ return newError (InvalidStatus , txnStateErrorMessage (TxnOpen , TxnState (txnState )))
216
227
}
217
- return false , newError ( InvalidStatus , "Expect transaction state is TxnOpen but " + txn . state . string ())
228
+ return nil
218
229
}
219
230
220
- func (state TxnState ) string () string {
231
+ func (state TxnState ) String () string {
221
232
switch state {
222
233
case TxnOpen :
223
234
return "TxnOpen"
@@ -237,3 +248,8 @@ func (state TxnState) string() string {
237
248
return "Unknown"
238
249
}
239
250
}
251
+
252
+ //nolint:unparam
253
+ func txnStateErrorMessage (expected , actual TxnState ) string {
254
+ return fmt .Sprintf ("Expected transaction state: %s, actual: %s" , expected , actual )
255
+ }
0 commit comments