|
1 | 1 | // Copyright IBM Corp. All Rights Reserved.
|
2 | 2 | // SPDX-License-Identifier: Apache-2.0
|
| 3 | + |
3 | 4 | package bcdb
|
4 | 5 |
|
5 | 6 | import (
|
6 | 7 | "encoding/json"
|
7 |
| - "fmt" |
8 |
| - "sync" |
9 | 8 | "time"
|
10 | 9 |
|
11 | 10 | "github.com/google/uuid"
|
@@ -45,7 +44,6 @@ type transactionProcessor struct {
|
45 | 44 | blockStore *blockstore.Store
|
46 | 45 | pendingTxs *queue.PendingTxs
|
47 | 46 | logger *logger.SugarLogger
|
48 |
| - sync.Mutex |
49 | 47 | }
|
50 | 48 |
|
51 | 49 | type txProcessorConfig struct {
|
@@ -266,37 +264,47 @@ func (t *transactionProcessor) SubmitTransaction(tx interface{}, timeout time.Du
|
266 | 264 | return nil, err
|
267 | 265 | }
|
268 | 266 |
|
269 |
| - t.Lock() |
270 |
| - duplicate, err := t.isTxIDDuplicate(txID) |
271 |
| - if err != nil { |
272 |
| - t.Unlock() |
273 |
| - return nil, err |
274 |
| - } |
275 |
| - if duplicate { |
276 |
| - t.Unlock() |
| 267 | + // We attempt to insert the txID atomically. |
| 268 | + // If we succeed, then future TX fill fail at this point. |
| 269 | + // However, if the TX already exists in the block store, then we will fail the subsequent check. |
| 270 | + // Since a TX will be removed from the pending queue only after it is inserted to the block store, |
| 271 | + // then it is guaranteed that we won't use the same txID twice. |
| 272 | + // TODO: add limit on the number of pending sync tx |
| 273 | + promise := queue.NewCompletionPromise(timeout) |
| 274 | + if existed := t.pendingTxs.Add(txID, promise); existed { |
277 | 275 | return nil, &internalerror.DuplicateTxIDError{TxID: txID}
|
278 | 276 | }
|
279 | 277 |
|
280 |
| - if t.txQueue.IsFull() { |
281 |
| - t.Unlock() |
282 |
| - return nil, fmt.Errorf("transaction queue is full. It means the server load is high. Try after sometime") |
| 278 | + duplicate, err := t.blockStore.DoesTxIDExist(txID) |
| 279 | + if err != nil || duplicate { |
| 280 | + t.pendingTxs.DeleteWithNoAction(txID) |
| 281 | + if err == nil { |
| 282 | + err = &internalerror.DuplicateTxIDError{TxID: txID} |
| 283 | + } |
| 284 | + return nil, err |
283 | 285 | }
|
284 | 286 |
|
285 |
| - jsonBytes, err := json.MarshalIndent(tx, "", "\t") |
286 |
| - if err != nil { |
287 |
| - t.Unlock() |
288 |
| - return nil, fmt.Errorf("failed to marshal transaction: %v", err) |
| 287 | + // Avoids marshaling the TX in production mode |
| 288 | + if t.logger.IsDebug() { |
| 289 | + if jsonBytes, err := json.MarshalIndent(tx, "", "\t"); err != nil { |
| 290 | + t.logger.Debugf("failed to marshal transaction: %v", err) |
| 291 | + } else { |
| 292 | + t.logger.Debugf("enqueuing transaction %s\n", jsonBytes) |
| 293 | + } |
289 | 294 | }
|
290 |
| - t.logger.Debugf("enqueuing transaction %s\n", string(jsonBytes)) |
291 | 295 |
|
292 |
| - t.txQueue.Enqueue(tx) |
| 296 | + if timeout <= 0 { |
| 297 | + // Enqueue will block until the queue is not full |
| 298 | + t.txQueue.Enqueue(tx) |
| 299 | + } else { |
| 300 | + // EnqueueWithTimeout will block until the queue is not full or timeout occurs |
| 301 | + if success := t.txQueue.EnqueueWithTimeout(tx, timeout); !success { |
| 302 | + t.pendingTxs.DeleteWithNoAction(txID) |
| 303 | + return nil, &internalerror.TimeoutErr{ErrMsg: "timeout has occurred while inserting the transaction to the queue"} |
| 304 | + } |
| 305 | + } |
293 | 306 | t.logger.Debug("transaction is enqueued for re-ordering")
|
294 | 307 |
|
295 |
| - promise := queue.NewCompletionPromise(timeout) |
296 |
| - // TODO: add limit on the number of pending sync tx |
297 |
| - t.pendingTxs.Add(txID, promise) |
298 |
| - t.Unlock() |
299 |
| - |
300 | 308 | receipt, err := promise.Wait()
|
301 | 309 |
|
302 | 310 | if err != nil {
|
@@ -336,49 +344,31 @@ func (t *transactionProcessor) PostBlockCommitProcessing(block *types.Block) err
|
336 | 344 | return errors.Errorf("unexpected transaction envelope in the block")
|
337 | 345 | }
|
338 | 346 |
|
339 |
| - t.pendingTxs.DoneWithReceipt(txIDs, block.Header) |
| 347 | + go t.pendingTxs.DoneWithReceipt(txIDs, block.Header) |
340 | 348 |
|
341 | 349 | return nil
|
342 | 350 | }
|
343 | 351 |
|
344 |
| -func (t *transactionProcessor) isTxIDDuplicate(txID string) (bool, error) { |
345 |
| - if t.pendingTxs.Has(txID) { |
346 |
| - return true, nil |
347 |
| - } |
348 |
| - |
349 |
| - isTxIDAlreadyCommitted, err := t.blockStore.DoesTxIDExist(txID) |
350 |
| - if err != nil { |
351 |
| - return false, err |
352 |
| - } |
353 |
| - return isTxIDAlreadyCommitted, nil |
354 |
| -} |
355 |
| - |
356 | 352 | func (t *transactionProcessor) Close() error {
|
357 |
| - t.Lock() |
358 |
| - defer t.Unlock() |
359 |
| - |
| 353 | + // It is safe to use without locks because all the following calls are protected internally. |
360 | 354 | t.txReorderer.Stop()
|
361 | 355 | t.blockCreator.Stop()
|
362 |
| - t.blockReplicator.Close() |
| 356 | + _ = t.blockReplicator.Close() |
363 | 357 | t.peerTransport.Close()
|
364 | 358 | t.blockProcessor.Stop()
|
365 | 359 |
|
366 | 360 | return nil
|
367 | 361 | }
|
368 | 362 |
|
369 | 363 | func (t *transactionProcessor) IsLeader() *internalerror.NotLeaderError {
|
370 |
| - t.Lock() |
371 |
| - defer t.Unlock() |
372 |
| - |
| 364 | + // It is safe to use without locks because the following call is protected internally. |
373 | 365 | return t.blockReplicator.IsLeader()
|
374 | 366 | }
|
375 | 367 |
|
376 | 368 | // ClusterStatus returns the leader NodeID, and the active nodes NodeIDs.
|
377 | 369 | // Note: leader is always in active.
|
378 | 370 | func (t *transactionProcessor) ClusterStatus() (leader string, active []string) {
|
379 |
| - t.Lock() |
380 |
| - defer t.Unlock() |
381 |
| - |
| 371 | + // It is safe to use without locks because the following call is protected internally. |
382 | 372 | leaderID, activePeers := t.blockReplicator.GetClusterStatus()
|
383 | 373 | for _, peer := range activePeers {
|
384 | 374 | active = append(active, peer.NodeId)
|
|
0 commit comments