retry.go raw

   1  //go:build js && wasm
   2  // +build js,wasm
   3  
   4  package idb
   5  
   6  import (
   7  	"context"
   8  	"strings"
   9  )
  10  
  11  /*
  12  RetryTxn retries the function with a new transaction if the txn finishes prematurely.
  13  
  14  IndexedDB transactions automatically commit when all outstanding requests have
  15  been satisfied. When a Goroutine is suspended due to a select statement or other
  16  context switching, the IndexedDB transation commits automatically, leading to
  17  errors with a suffix "The transaction has finished."
  18  
  19  See: https://github.com/w3c/IndexedDB/issues/34 for more details.
  20  
  21  RetryTxn is a mechanism that automatically re-creates the transaction and
  22  retries the operation whenever we encounter this specific error. This
  23  ensures that operations can continue even if the transaction has been
  24  automatically committed.
  25  */
  26  func RetryTxn(
  27  	ctx context.Context,
  28  	db *Database,
  29  	txnMode TransactionMode,
  30  	fn func(txn *Transaction) error,
  31  	objectStoreName string,
  32  	objectStoreNames ...string,
  33  ) error {
  34  	for {
  35  		txn, err := db.Transaction(txnMode, objectStoreName, objectStoreNames...)
  36  		if err != nil {
  37  			if IsTxnFinishedErr(err) {
  38  				continue
  39  			}
  40  			return err
  41  		}
  42  
  43  		// call the fn
  44  		err = fn(txn)
  45  
  46  		// if the fn returns txn finished, retry.
  47  		if IsTxnFinishedErr(err) {
  48  			continue
  49  		}
  50  
  51  		// check for error performing the operation
  52  		if err != nil {
  53  			_ = txn.Abort()
  54  			return err
  55  		}
  56  
  57  		// commit the txn
  58  		err = txn.Commit()
  59  		if IsTxnFinishedErr(err) {
  60  			// txn committed automatically already
  61  			err = nil
  62  		}
  63  
  64  		return err
  65  	}
  66  }
  67  
  68  // IsTxnFinishedErr checks if an error corresponds to a transaction finishing.
  69  // see RetryTxn for details
  70  func IsTxnFinishedErr(err error) bool {
  71  	if err == nil {
  72  		return false
  73  	}
  74  	errStr := err.Error()
  75  	switch {
  76  	case strings.HasSuffix(errStr, "The transaction has finished."):
  77  		return true
  78  	case strings.HasSuffix(errStr, "The database connection is closing."):
  79  		return true
  80  	// Firefox: transaction finished error.
  81  	case strings.HasSuffix(errStr, "A request was placed against a transaction which is currently not active, or which is finished."):
  82  		return true
  83  	default:
  84  		return false
  85  	}
  86  }
  87