github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/proto/contractset.go (about)

     1  package proto
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"sync"
     8  
     9  	"github.com/Synthesix/Sia/build"
    10  	"github.com/Synthesix/Sia/modules"
    11  	"github.com/Synthesix/Sia/types"
    12  	"github.com/NebulousLabs/ratelimit"
    13  
    14  	"github.com/NebulousLabs/writeaheadlog"
    15  )
    16  
    17  // A ContractSet provides safe concurrent access to a set of contracts. Its
    18  // purpose is to serialize modifications to individual contracts, as well as
    19  // to provide operations on the set as a whole.
    20  type ContractSet struct {
    21  	contracts map[types.FileContractID]*SafeContract
    22  	deps      modules.Dependencies
    23  	dir       string
    24  	mu        sync.Mutex
    25  	rl        *ratelimit.RateLimit
    26  	wal       *writeaheadlog.WAL
    27  }
    28  
    29  // Acquire looks up the contract with the specified FileContractID and locks
    30  // it before returning it. If the contract is not present in the set, Acquire
    31  // returns false and a zero-valued RenterContract.
    32  func (cs *ContractSet) Acquire(id types.FileContractID) (*SafeContract, bool) {
    33  	cs.mu.Lock()
    34  	safeContract, ok := cs.contracts[id]
    35  	cs.mu.Unlock()
    36  	if !ok {
    37  		return nil, false
    38  	}
    39  	safeContract.mu.Lock()
    40  	// We need to check if the contract is still in the map or if it has been
    41  	// deleted in the meantime.
    42  	cs.mu.Lock()
    43  	_, ok = cs.contracts[id]
    44  	cs.mu.Unlock()
    45  	if !ok {
    46  		safeContract.mu.Unlock()
    47  		return nil, false
    48  	}
    49  	return safeContract, true
    50  }
    51  
    52  // Delete removes a contract from the set. The contract must have been
    53  // previously acquired by Acquire. If the contract is not present in the set,
    54  // Delete is a no-op.
    55  func (cs *ContractSet) Delete(c *SafeContract) {
    56  	cs.mu.Lock()
    57  	safeContract, ok := cs.contracts[c.header.ID()]
    58  	if !ok {
    59  		cs.mu.Unlock()
    60  		build.Critical("Delete called on already deleted contract")
    61  		return
    62  	}
    63  	delete(cs.contracts, c.header.ID())
    64  	cs.mu.Unlock()
    65  	safeContract.mu.Unlock()
    66  	// delete contract file
    67  	os.Remove(filepath.Join(cs.dir, c.header.ID().String()+contractExtension))
    68  }
    69  
    70  // IDs returns the FileContractID of each contract in the set. The contracts
    71  // are not locked.
    72  func (cs *ContractSet) IDs() []types.FileContractID {
    73  	cs.mu.Lock()
    74  	defer cs.mu.Unlock()
    75  	ids := make([]types.FileContractID, 0, len(cs.contracts))
    76  	for id := range cs.contracts {
    77  		ids = append(ids, id)
    78  	}
    79  	return ids
    80  }
    81  
    82  // Len returns the number of contracts in the set.
    83  func (cs *ContractSet) Len() int {
    84  	cs.mu.Lock()
    85  	defer cs.mu.Unlock()
    86  	return len(cs.contracts)
    87  }
    88  
    89  // Return returns a locked contract to the set and unlocks it. The contract
    90  // must have been previously acquired by Acquire. If the contract is not
    91  // present in the set, Return panics.
    92  func (cs *ContractSet) Return(c *SafeContract) {
    93  	cs.mu.Lock()
    94  	safeContract, ok := cs.contracts[c.header.ID()]
    95  	if !ok {
    96  		cs.mu.Unlock()
    97  		build.Critical("no contract with that id")
    98  	}
    99  	cs.mu.Unlock()
   100  	safeContract.mu.Unlock()
   101  }
   102  
   103  // SetRateLimits sets the bandwidth limits for connections created by the
   104  // contractSet.
   105  func (cs *ContractSet) SetRateLimits(readBPS, writeBPS int64, packetSize uint64) {
   106  	cs.rl.SetLimits(readBPS, writeBPS, packetSize)
   107  }
   108  
   109  // View returns a copy of the contract with the specified ID. The contracts is
   110  // not locked. Certain fields, including the MerkleRoots, are set to nil for
   111  // safety reasons. If the contract is not present in the set, View
   112  // returns false and a zero-valued RenterContract.
   113  func (cs *ContractSet) View(id types.FileContractID) (modules.RenterContract, bool) {
   114  	cs.mu.Lock()
   115  	defer cs.mu.Unlock()
   116  	safeContract, ok := cs.contracts[id]
   117  	if !ok {
   118  		return modules.RenterContract{}, false
   119  	}
   120  	return safeContract.Metadata(), true
   121  }
   122  
   123  // ViewAll returns the metadata of each contract in the set. The contracts are
   124  // not locked.
   125  func (cs *ContractSet) ViewAll() []modules.RenterContract {
   126  	cs.mu.Lock()
   127  	defer cs.mu.Unlock()
   128  	contracts := make([]modules.RenterContract, 0, len(cs.contracts))
   129  	for _, safeContract := range cs.contracts {
   130  		contracts = append(contracts, safeContract.Metadata())
   131  	}
   132  	return contracts
   133  }
   134  
   135  // Close closes all contracts in a contract set, this means rendering it unusable for I/O
   136  func (cs *ContractSet) Close() error {
   137  	for _, c := range cs.contracts {
   138  		c.f.Close()
   139  	}
   140  	_, err := cs.wal.CloseIncomplete()
   141  	return err
   142  }
   143  
   144  // NewContractSet returns a ContractSet storing its contracts in the specified
   145  // dir.
   146  func NewContractSet(dir string, deps modules.Dependencies) (*ContractSet, error) {
   147  	if err := os.MkdirAll(dir, 0700); err != nil {
   148  		return nil, err
   149  	}
   150  	d, err := os.Open(dir)
   151  	if err != nil {
   152  		return nil, err
   153  	} else if stat, err := d.Stat(); err != nil {
   154  		return nil, err
   155  	} else if !stat.IsDir() {
   156  		return nil, errors.New("not a directory")
   157  	}
   158  	defer d.Close()
   159  
   160  	// Load the WAL. Any recovered updates will be applied after loading
   161  	// contracts.
   162  	// COMPATv1.3.1RC2 Rename old wals to have the 'wal' extension if new file
   163  	// doesn't exist.
   164  	if err := v131RC2RenameWAL(dir); err != nil {
   165  		return nil, err
   166  	}
   167  	walTxns, wal, err := writeaheadlog.New(filepath.Join(dir, "contractset.wal"))
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	cs := &ContractSet{
   173  		contracts: make(map[types.FileContractID]*SafeContract),
   174  		deps:      deps,
   175  		dir:       dir,
   176  		wal:       wal,
   177  	}
   178  	// Set the initial rate limit to 'unlimited' bandwidth with 4kib packets.
   179  	cs.rl = ratelimit.NewRateLimit(0, 0, 0)
   180  
   181  	// Load the contract files.
   182  	dirNames, err := d.Readdirnames(-1)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	for _, filename := range dirNames {
   188  		if filepath.Ext(filename) != contractExtension {
   189  			continue
   190  		}
   191  		path := filepath.Join(dir, filename)
   192  		if err := cs.loadSafeContract(path, walTxns); err != nil {
   193  			return nil, err
   194  		}
   195  	}
   196  
   197  	return cs, nil
   198  }
   199  
   200  // v131RC2RenameWAL renames an existing old wal file from contractset.log to
   201  // contractset.wal
   202  func v131RC2RenameWAL(dir string) error {
   203  	oldPath := filepath.Join(dir, "contractset.log")
   204  	newPath := filepath.Join(dir, "contractset.wal")
   205  	_, errOld := os.Stat(oldPath)
   206  	_, errNew := os.Stat(newPath)
   207  	if !os.IsNotExist(errOld) && os.IsNotExist(errNew) {
   208  		return build.ExtendErr("failed to rename contractset.log to contractset.wal",
   209  			os.Rename(oldPath, newPath))
   210  	}
   211  	return nil
   212  }