github.com/ZuluSpl0it/Sia@v1.3.7/modules/dependencies.go (about) 1 package modules 2 3 import ( 4 "errors" 5 "io" 6 "io/ioutil" 7 "net" 8 "os" 9 "sync" 10 "time" 11 12 "github.com/NebulousLabs/Sia/build" 13 "github.com/NebulousLabs/Sia/persist" 14 "github.com/NebulousLabs/fastrand" 15 ) 16 17 // ProdDependencies act as a global instance of the production dependencies to 18 // avoid having to instantiate new dependencies every time we want to pass 19 // production dependencies. 20 var ProdDependencies = new(ProductionDependencies) 21 22 // Dependencies defines dependencies used by all of Sia's modules. Custom 23 // dependencies can be created to inject certain behavior during testing. 24 type ( 25 Dependencies interface { 26 // AtLeastOne will return a value that is at least one. In production, 27 // the value should always be one. This function is used to test the 28 // idempotency of actions, so during testing sometimes the value 29 // returned will be higher, causing an idempotent action to be 30 // committed multiple times. If the action is truly idempotent, 31 // committing it multiple times should not cause any problems or 32 // changes. 33 AtLeastOne() uint64 34 35 // CreateFile gives the host the ability to create files on the 36 // operating system. 37 CreateFile(string) (File, error) 38 39 // Destruct will clean up the dependencies, panicking if there are 40 // unclosed resources. 41 Destruct() 42 43 // DialTimeout tries to create a tcp connection to the specified 44 // address with a certain timeout. 45 DialTimeout(NetAddress, time.Duration) (net.Conn, error) 46 47 // Disrupt can be inserted in the code as a way to inject problems, 48 // such as a network call that take 10 minutes or a disk write that 49 // never completes. disrupt will return true if the disruption is 50 // forcibly triggered. In production, disrupt will always return false. 51 Disrupt(string) bool 52 53 // Listen gives the host the ability to receive incoming connections. 54 Listen(string, string) (net.Listener, error) 55 56 // LoadFile allows the host to load a persistence structure form disk. 57 LoadFile(persist.Metadata, interface{}, string) error 58 59 // MkdirAll gives the host the ability to create chains of folders 60 // within the filesystem. 61 MkdirAll(string, os.FileMode) error 62 63 // NewLogger creates a logger that the host can use to log messages and 64 // write critical statements. 65 NewLogger(string) (*persist.Logger, error) 66 67 // OpenDatabase creates a database that the host can use to interact 68 // with large volumes of persistent data. 69 OpenDatabase(persist.Metadata, string) (*persist.BoltDatabase, error) 70 71 // OpenFile opens a file for the host. 72 OpenFile(string, int, os.FileMode) (File, error) 73 74 // RandRead fills the input bytes with random data. 75 RandRead([]byte) (int, error) 76 77 // ReadFile reads a file in full from the filesystem. 78 ReadFile(string) ([]byte, error) 79 80 // RemoveFile removes a file from file filesystem. 81 RemoveFile(string) error 82 83 // RenameFile renames a file on disk to another name. 84 RenameFile(string, string) error 85 86 // SaveFileSync writes JSON encoded data to disk and syncs the file 87 // afterwards. 88 SaveFileSync(persist.Metadata, interface{}, string) error 89 90 // Sleep blocks the calling thread for at least the specified duration. 91 Sleep(time.Duration) 92 93 // Symlink creates a sym link between a source and a destination. 94 Symlink(s1, s2 string) error 95 96 // WriteFile writes data to the filesystem using the provided filename. 97 WriteFile(string, []byte, os.FileMode) error 98 } 99 100 // File implements all of the methods that can be called on an os.File. 101 File interface { 102 io.ReadWriteCloser 103 Name() string 104 ReadAt([]byte, int64) (int, error) 105 Sync() error 106 Truncate(int64) error 107 WriteAt([]byte, int64) (int, error) 108 } 109 ) 110 111 type ( 112 // ProductionDependencies are the dependencies used in a Release or Debug 113 // production build. 114 ProductionDependencies struct { 115 shouldInit bool 116 openFiles map[string]int 117 mu sync.Mutex 118 } 119 120 // ProductionFile is the implementation of the File interface that is used 121 // in a Release or Debug production build. 122 ProductionFile struct { 123 pd *ProductionDependencies 124 *os.File 125 } 126 ) 127 128 // Close will close a file, checking whether the file handle is open somewhere 129 // else before closing completely. This check is performed on Windows but not 130 // Linux, therefore a mock is used to ensure that linux testing picks up 131 // potential problems that would be seen on Windows. 132 func (pf *ProductionFile) Close() error { 133 if !build.DEBUG { 134 return pf.File.Close() 135 } 136 137 pf.pd.mu.Lock() 138 if pf.pd.openFiles == nil { 139 pf.pd.openFiles = make(map[string]int) 140 } 141 v, exists := pf.pd.openFiles[pf.File.Name()] 142 if !exists { 143 panic("file not registered") 144 } 145 if v == 1 { 146 delete(pf.pd.openFiles, pf.File.Name()) 147 } else if v > 1 { 148 pf.pd.openFiles[pf.File.Name()] = v - 1 149 } else { 150 panic("inconsistent state") 151 } 152 pf.pd.mu.Unlock() 153 return pf.File.Close() 154 } 155 156 // AtLeastOne will return a value that is equal to 1 if debugging is disabled. 157 // If debugging is enabled, a higher value may be returned. 158 func (*ProductionDependencies) AtLeastOne() uint64 { 159 if !build.DEBUG { 160 return 1 161 } 162 163 // Probabilistically return a number greater than one. 164 val := uint64(1) 165 for fastrand.Intn(2) != 0 { 166 val++ 167 } 168 return val 169 } 170 171 // CreateFile gives the host the ability to create files on the operating 172 // system. 173 func (pd *ProductionDependencies) CreateFile(s string) (File, error) { 174 if !build.DEBUG { 175 return os.Create(s) 176 } 177 178 f, err := os.Create(s) 179 if err != nil { 180 return f, err 181 } 182 183 pd.mu.Lock() 184 if pd.openFiles == nil { 185 pd.openFiles = make(map[string]int) 186 } 187 v := pd.openFiles[s] 188 pd.openFiles[s] = v + 1 189 pd.mu.Unlock() 190 return &ProductionFile{ 191 pd: pd, 192 File: f, 193 }, nil 194 } 195 196 // Destruct checks that all resources have been cleaned up correctly. 197 func (pd *ProductionDependencies) Destruct() { 198 if !build.DEBUG { 199 return 200 } 201 202 pd.mu.Lock() 203 l := len(pd.openFiles) 204 pd.mu.Unlock() 205 if l != 0 { 206 panic("unclosed resources - most likely file handles") 207 } 208 } 209 210 // DialTimeout creates a tcp connection to a certain address with the specified 211 // timeout. 212 func (*ProductionDependencies) DialTimeout(addr NetAddress, timeout time.Duration) (net.Conn, error) { 213 return net.DialTimeout("tcp", string(addr), timeout) 214 } 215 216 // Disrupt can be used to inject specific behavior into a module by overwriting 217 // it using a custom dependency. 218 func (*ProductionDependencies) Disrupt(string) bool { 219 return false 220 } 221 222 // Listen gives the host the ability to receive incoming connections. 223 func (*ProductionDependencies) Listen(s1, s2 string) (net.Listener, error) { 224 return net.Listen(s1, s2) 225 } 226 227 // LoadFile loads JSON encoded data from a file. 228 func (*ProductionDependencies) LoadFile(meta persist.Metadata, data interface{}, filename string) error { 229 return persist.LoadJSON(meta, data, filename) 230 } 231 232 // SaveFileSync writes JSON encoded data to a file and syncs the file to disk 233 // afterwards. 234 func (*ProductionDependencies) SaveFileSync(meta persist.Metadata, data interface{}, filename string) error { 235 return persist.SaveJSON(meta, data, filename) 236 } 237 238 // MkdirAll gives the host the ability to create chains of folders within the 239 // filesystem. 240 func (*ProductionDependencies) MkdirAll(s string, fm os.FileMode) error { 241 return os.MkdirAll(s, fm) 242 } 243 244 // NewLogger creates a logger that the host can use to log messages and write 245 // critical statements. 246 func (*ProductionDependencies) NewLogger(s string) (*persist.Logger, error) { 247 return persist.NewFileLogger(s) 248 } 249 250 // OpenDatabase creates a database that the host can use to interact with large 251 // volumes of persistent data. 252 func (*ProductionDependencies) OpenDatabase(m persist.Metadata, s string) (*persist.BoltDatabase, error) { 253 return persist.OpenDatabase(m, s) 254 } 255 256 // OpenFile opens a file for the contract manager. 257 func (pd *ProductionDependencies) OpenFile(s string, i int, fm os.FileMode) (File, error) { 258 if !build.DEBUG { 259 return os.OpenFile(s, i, fm) 260 } 261 262 f, err := os.OpenFile(s, i, fm) 263 if err != nil { 264 return f, err 265 } 266 267 pd.mu.Lock() 268 if pd.openFiles == nil { 269 pd.openFiles = make(map[string]int) 270 } 271 v := pd.openFiles[s] 272 pd.openFiles[s] = v + 1 273 pd.mu.Unlock() 274 return &ProductionFile{ 275 pd: pd, 276 File: f, 277 }, nil 278 } 279 280 // RandRead fills the input bytes with random data. 281 func (*ProductionDependencies) RandRead(b []byte) (int, error) { 282 return fastrand.Reader.Read(b) 283 } 284 285 // ReadFile reads a file from the filesystem. 286 func (*ProductionDependencies) ReadFile(s string) ([]byte, error) { 287 return ioutil.ReadFile(s) 288 } 289 290 // RemoveFile will remove a file from disk. 291 func (pd *ProductionDependencies) RemoveFile(s string) error { 292 if !build.DEBUG { 293 return os.Remove(s) 294 } 295 296 pd.mu.Lock() 297 if pd.openFiles == nil { 298 pd.openFiles = make(map[string]int) 299 } 300 v, exists := pd.openFiles[s] 301 pd.mu.Unlock() 302 if exists && v > 0 { 303 return errors.New("cannot remove the file, it's open somewhere else right now") 304 } 305 return os.Remove(s) 306 } 307 308 // RenameFile renames a file on disk. 309 func (pd *ProductionDependencies) RenameFile(s1 string, s2 string) error { 310 if !build.DEBUG { 311 return os.Rename(s1, s2) 312 } 313 314 pd.mu.Lock() 315 if pd.openFiles == nil { 316 pd.openFiles = make(map[string]int) 317 } 318 v1, exists1 := pd.openFiles[s1] 319 v2, exists2 := pd.openFiles[s2] 320 pd.mu.Unlock() 321 if exists1 && v1 > 0 { 322 return errors.New("cannot remove the file, it's open somewhere else right now") 323 } 324 if exists2 && v2 > 0 { 325 return errors.New("cannot remove the file, it's open somewhere else right now") 326 } 327 return os.Rename(s1, s2) 328 } 329 330 // Sleep blocks the calling thread for a certain duration. 331 func (*ProductionDependencies) Sleep(d time.Duration) { 332 time.Sleep(d) 333 } 334 335 // Symlink creates a symlink between a source and a destination file. 336 func (*ProductionDependencies) Symlink(s1, s2 string) error { 337 return os.Symlink(s1, s2) 338 } 339 340 // WriteFile writes a file to the filesystem. 341 func (*ProductionDependencies) WriteFile(s string, b []byte, fm os.FileMode) error { 342 return ioutil.WriteFile(s, b, fm) 343 }