gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/contractor/downloader.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "sync" 6 7 "github.com/NebulousLabs/Sia/crypto" 8 "github.com/NebulousLabs/Sia/modules" 9 "github.com/NebulousLabs/Sia/modules/renter/proto" 10 "github.com/NebulousLabs/Sia/types" 11 ) 12 13 var errInvalidDownloader = errors.New("downloader has been invalidated because its contract is being renewed") 14 15 // An Downloader retrieves sectors from with a host. It requests one sector at 16 // a time, and revises the file contract to transfer money to the host 17 // proportional to the data retrieved. 18 type Downloader interface { 19 // Sector retrieves the sector with the specified Merkle root, and revises 20 // the underlying contract to pay the host proportionally to the data 21 // retrieve. 22 Sector(root crypto.Hash) ([]byte, error) 23 24 // Close terminates the connection to the host. 25 Close() error 26 } 27 28 // A hostDownloader retrieves sectors by calling the download RPC on a host. 29 // It implements the Downloader interface. hostDownloaders are safe for use by 30 // multiple goroutines. 31 type hostDownloader struct { 32 clients int // safe to Close when 0 33 contractID types.FileContractID 34 contractor *Contractor 35 downloader *proto.Downloader 36 hostSettings modules.HostExternalSettings 37 invalid bool // true if invalidate has been called 38 speed uint64 // Bytes per second. 39 mu sync.Mutex 40 } 41 42 // invalidate sets the invalid flag and closes the underlying 43 // proto.Downloader. Once invalidate returns, the hostDownloader is guaranteed 44 // to not further revise its contract. This is used during contract renewal to 45 // prevent a Downloader from revising a contract mid-renewal. 46 func (hd *hostDownloader) invalidate() { 47 hd.mu.Lock() 48 defer hd.mu.Unlock() 49 if !hd.invalid { 50 hd.downloader.Close() 51 hd.invalid = true 52 } 53 hd.contractor.mu.Lock() 54 delete(hd.contractor.downloaders, hd.contractID) 55 delete(hd.contractor.revising, hd.contractID) 56 hd.contractor.mu.Unlock() 57 } 58 59 // Close cleanly terminates the download loop with the host and closes the 60 // connection. 61 func (hd *hostDownloader) Close() error { 62 hd.mu.Lock() 63 defer hd.mu.Unlock() 64 hd.clients-- 65 // Close is a no-op if invalidate has been called, or if there are other 66 // clients still using the hostDownloader. 67 if hd.invalid || hd.clients > 0 { 68 return nil 69 } 70 hd.invalid = true 71 hd.contractor.mu.Lock() 72 delete(hd.contractor.downloaders, hd.contractID) 73 delete(hd.contractor.revising, hd.contractID) 74 hd.contractor.mu.Unlock() 75 return hd.downloader.Close() 76 } 77 78 // HostSettings returns the settings of the host that the downloader connects 79 // to. 80 func (hd *hostDownloader) HostSettings() modules.HostExternalSettings { 81 hd.mu.Lock() 82 defer hd.mu.Unlock() 83 return hd.hostSettings 84 } 85 86 // Sector retrieves the sector with the specified Merkle root, and revises 87 // the underlying contract to pay the host proportionally to the data 88 // retrieve. 89 func (hd *hostDownloader) Sector(root crypto.Hash) ([]byte, error) { 90 hd.mu.Lock() 91 defer hd.mu.Unlock() 92 if hd.invalid { 93 return nil, errInvalidDownloader 94 } 95 96 // Download the sector. 97 _, sector, err := hd.downloader.Sector(root) 98 if err != nil { 99 return nil, err 100 } 101 return sector, nil 102 } 103 104 // Downloader returns a Downloader object that can be used to download sectors 105 // from a host. 106 func (c *Contractor) Downloader(id types.FileContractID, cancel <-chan struct{}) (_ Downloader, err error) { 107 id = c.ResolveID(id) 108 c.mu.RLock() 109 cachedDownloader, haveDownloader := c.downloaders[id] 110 height := c.blockHeight 111 renewing := c.renewing[id] 112 c.mu.RUnlock() 113 if renewing { 114 return nil, errors.New("currently renewing that contract") 115 } else if haveDownloader { 116 // increment number of clients and return 117 cachedDownloader.mu.Lock() 118 cachedDownloader.clients++ 119 cachedDownloader.mu.Unlock() 120 return cachedDownloader, nil 121 } 122 123 // Fetch the contract and host. 124 contract, haveContract := c.contracts.View(id) 125 if !haveContract { 126 return nil, errors.New("no record of that contract") 127 } 128 host, haveHost := c.hdb.Host(contract.HostPublicKey) 129 if height > contract.EndHeight { 130 return nil, errors.New("contract has already ended") 131 } else if !haveHost { 132 return nil, errors.New("no record of that host") 133 } else if host.DownloadBandwidthPrice.Cmp(maxDownloadPrice) > 0 { 134 return nil, errTooExpensive 135 } 136 137 // Acquire the revising lock for the contract, which excludes other threads 138 // from interacting with the contract. 139 // 140 // TODO: Because we have another layer of contract safety via the 141 // contractset, do we need the revising lock anymore? 142 c.mu.Lock() 143 alreadyRevising := c.revising[contract.ID] 144 if alreadyRevising { 145 c.mu.Unlock() 146 return nil, errors.New("already revising that contract") 147 } 148 c.revising[contract.ID] = true 149 c.mu.Unlock() 150 // release lock early if function returns an error 151 defer func() { 152 if err != nil { 153 c.mu.Lock() 154 delete(c.revising, contract.ID) 155 c.mu.Unlock() 156 } 157 }() 158 159 // create downloader 160 d, err := c.contracts.NewDownloader(host, contract.ID, c.hdb, cancel) 161 if err != nil { 162 return nil, err 163 } 164 165 // cache downloader 166 hd := &hostDownloader{ 167 clients: 1, 168 contractID: contract.ID, 169 contractor: c, 170 downloader: d, 171 hostSettings: host.HostExternalSettings, 172 } 173 c.mu.Lock() 174 c.downloaders[contract.ID] = hd 175 c.mu.Unlock() 176 177 return hd, nil 178 }