|
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