gitlab.com/SiaPrime/SiaPrime@v1.4.1/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 "gitlab.com/NebulousLabs/fastrand" 13 "gitlab.com/SiaPrime/SiaPrime/build" 14 "gitlab.com/SiaPrime/SiaPrime/persist" 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 // LookupIP resolves a hostname to a number of IP addresses. If an IP 60 // address is provided as an argument it will just return that IP. 61 LookupIP(string) ([]net.IP, error) 62 63 // MkdirAll gives the host the ability to create chains of folders 64 // within the filesystem. 65 MkdirAll(string, os.FileMode) error 66 67 // NewLogger creates a logger that the host can use to log messages and 68 // write critical statements. 69 NewLogger(string) (*persist.Logger, error) 70 71 // OpenDatabase creates a database that the host can use to interact 72 // with large volumes of persistent data. 73 OpenDatabase(persist.Metadata, string) (*persist.BoltDatabase, error) 74 75 // Open opens a file readonly. 76 Open(string) (File, error) 77 78 // OpenFile opens a file with the specified mode. 79 OpenFile(string, int, os.FileMode) (File, error) 80 81 // Resolver returns a Resolver which can resolve hostnames to IPs. 82 Resolver() Resolver 83 84 // RandRead fills the input bytes with random data. 85 RandRead([]byte) (int, error) 86 87 // ReadFile reads a file in full from the filesystem. 88 ReadFile(string) ([]byte, error) 89 90 // RemoveFile removes a file from file filesystem. 91 RemoveFile(string) error 92 93 // RenameFile renames a file on disk to another name. 94 RenameFile(string, string) error 95 96 // SaveFileSync writes JSON encoded data to disk and syncs the file 97 // afterwards. 98 SaveFileSync(persist.Metadata, interface{}, string) error 99 100 // Sleep blocks the calling thread for at least the specified duration. 101 Sleep(time.Duration) 102 103 // Symlink creates a sym link between a source and a destination. 104 Symlink(s1, s2 string) error 105 106 // WriteFile writes data to the filesystem using the provided filename. 107 WriteFile(string, []byte, os.FileMode) error 108 } 109 110 // File implements all of the methods that can be called on an os.File. 111 File interface { 112 io.ReadWriteCloser 113 Name() string 114 ReadAt([]byte, int64) (int, error) 115 Seek(int64, int) (int64, error) 116 Sync() error 117 Truncate(int64) error 118 WriteAt([]byte, int64) (int, error) 119 } 120 ) 121 122 type ( 123 // ProductionDependencies are the dependencies used in a Release or Debug 124 // production build. 125 ProductionDependencies struct { 126 shouldInit bool 127 openFiles map[string]int 128 mu sync.Mutex 129 } 130 131 // ProductionFile is the implementation of the File interface that is used 132 // in a Release or Debug production build. 133 ProductionFile struct { 134 pd *ProductionDependencies 135 *os.File 136 } 137 ) 138 139 // Close will close a file, checking whether the file handle is open somewhere 140 // else before closing completely. This check is performed on Windows but not 141 // Linux, therefore a mock is used to ensure that linux testing picks up 142 // potential problems that would be seen on Windows. 143 func (pf *ProductionFile) Close() error { 144 if !build.DEBUG { 145 return pf.File.Close() 146 } 147 148 pf.pd.mu.Lock() 149 if pf.pd.openFiles == nil { 150 pf.pd.openFiles = make(map[string]int) 151 } 152 v, exists := pf.pd.openFiles[pf.File.Name()] 153 if !exists { 154 panic("file not registered") 155 } 156 if v == 1 { 157 delete(pf.pd.openFiles, pf.File.Name()) 158 } else if v > 1 { 159 pf.pd.openFiles[pf.File.Name()] = v - 1 160 } else { 161 panic("inconsistent state") 162 } 163 pf.pd.mu.Unlock() 164 return pf.File.Close() 165 } 166 167 // AtLeastOne will return a value that is equal to 1 if debugging is disabled. 168 // If debugging is enabled, a higher value may be returned. 169 func (*ProductionDependencies) AtLeastOne() uint64 { 170 if !build.DEBUG { 171 return 1 172 } 173 174 // Probabilistically return a number greater than one. 175 val := uint64(1) 176 for fastrand.Intn(2) != 0 { 177 val++ 178 } 179 return val 180 } 181 182 // CreateFile gives the host the ability to create files on the operating 183 // system. 184 func (pd *ProductionDependencies) CreateFile(s string) (File, error) { 185 if !build.DEBUG { 186 return os.Create(s) 187 } 188 189 f, err := os.Create(s) 190 if err != nil { 191 return f, err 192 } 193 194 pd.mu.Lock() 195 if pd.openFiles == nil { 196 pd.openFiles = make(map[string]int) 197 } 198 v := pd.openFiles[s] 199 pd.openFiles[s] = v + 1 200 pd.mu.Unlock() 201 return &ProductionFile{ 202 pd: pd, 203 File: f, 204 }, nil 205 } 206 207 // Destruct checks that all resources have been cleaned up correctly. 208 func (pd *ProductionDependencies) Destruct() { 209 if !build.DEBUG { 210 return 211 } 212 213 pd.mu.Lock() 214 l := len(pd.openFiles) 215 pd.mu.Unlock() 216 if l != 0 { 217 panic("unclosed resources - most likely file handles") 218 } 219 } 220 221 // DialTimeout creates a tcp connection to a certain address with the specified 222 // timeout. 223 func (*ProductionDependencies) DialTimeout(addr NetAddress, timeout time.Duration) (net.Conn, error) { 224 return net.DialTimeout("tcp", string(addr), timeout) 225 } 226 227 // Disrupt can be used to inject specific behavior into a module by overwriting 228 // it using a custom dependency. 229 func (*ProductionDependencies) Disrupt(string) bool { 230 return false 231 } 232 233 // Listen gives the host the ability to receive incoming connections. 234 func (*ProductionDependencies) Listen(s1, s2 string) (net.Listener, error) { 235 return net.Listen(s1, s2) 236 } 237 238 // LoadFile loads JSON encoded data from a file. 239 func (*ProductionDependencies) LoadFile(meta persist.Metadata, data interface{}, filename string) error { 240 return persist.LoadJSON(meta, data, filename) 241 } 242 243 // LookupIP resolves a hostname to a number of IP addresses. If an IP address 244 // is provided as an argument it will just return that IP. 245 func (*ProductionDependencies) LookupIP(host string) ([]net.IP, error) { 246 return net.LookupIP(host) 247 } 248 249 // SaveFileSync writes JSON encoded data to a file and syncs the file to disk 250 // afterwards. 251 func (*ProductionDependencies) SaveFileSync(meta persist.Metadata, data interface{}, filename string) error { 252 return persist.SaveJSON(meta, data, filename) 253 } 254 255 // MkdirAll gives the host the ability to create chains of folders within the 256 // filesystem. 257 func (*ProductionDependencies) MkdirAll(s string, fm os.FileMode) error { 258 return os.MkdirAll(s, fm) 259 } 260 261 // NewLogger creates a logger that the host can use to log messages and write 262 // critical statements. 263 func (*ProductionDependencies) NewLogger(s string) (*persist.Logger, error) { 264 return persist.NewFileLogger(s) 265 } 266 267 // OpenDatabase creates a database that the host can use to interact with large 268 // volumes of persistent data. 269 func (*ProductionDependencies) OpenDatabase(m persist.Metadata, s string) (*persist.BoltDatabase, error) { 270 return persist.OpenDatabase(m, s) 271 } 272 273 // Open opens a file readonly. 274 func (pd *ProductionDependencies) Open(s string) (File, error) { 275 return pd.OpenFile(s, os.O_RDONLY, 0) 276 } 277 278 // OpenFile opens a file with the specified mode and permissions. 279 func (pd *ProductionDependencies) OpenFile(s string, i int, fm os.FileMode) (File, error) { 280 if !build.DEBUG { 281 return os.OpenFile(s, i, fm) 282 } 283 284 f, err := os.OpenFile(s, i, fm) 285 if err != nil { 286 return f, err 287 } 288 289 pd.mu.Lock() 290 if pd.openFiles == nil { 291 pd.openFiles = make(map[string]int) 292 } 293 v := pd.openFiles[s] 294 pd.openFiles[s] = v + 1 295 pd.mu.Unlock() 296 return &ProductionFile{ 297 pd: pd, 298 File: f, 299 }, nil 300 } 301 302 // RandRead fills the input bytes with random data. 303 func (*ProductionDependencies) RandRead(b []byte) (int, error) { 304 return fastrand.Reader.Read(b) 305 } 306 307 // ReadFile reads a file from the filesystem. 308 func (*ProductionDependencies) ReadFile(s string) ([]byte, error) { 309 return ioutil.ReadFile(s) 310 } 311 312 // RemoveFile will remove a file from disk. 313 func (pd *ProductionDependencies) RemoveFile(s string) error { 314 if !build.DEBUG { 315 return os.Remove(s) 316 } 317 318 pd.mu.Lock() 319 if pd.openFiles == nil { 320 pd.openFiles = make(map[string]int) 321 } 322 v, exists := pd.openFiles[s] 323 pd.mu.Unlock() 324 if exists && v > 0 { 325 return errors.New("cannot remove the file, it's open somewhere else right now") 326 } 327 return os.Remove(s) 328 } 329 330 // RenameFile renames a file on disk. 331 func (pd *ProductionDependencies) RenameFile(s1 string, s2 string) error { 332 if !build.DEBUG { 333 return os.Rename(s1, s2) 334 } 335 336 pd.mu.Lock() 337 if pd.openFiles == nil { 338 pd.openFiles = make(map[string]int) 339 } 340 v1, exists1 := pd.openFiles[s1] 341 v2, exists2 := pd.openFiles[s2] 342 pd.mu.Unlock() 343 if exists1 && v1 > 0 { 344 return errors.New("cannot remove the file, it's open somewhere else right now") 345 } 346 if exists2 && v2 > 0 { 347 return errors.New("cannot remove the file, it's open somewhere else right now") 348 } 349 return os.Rename(s1, s2) 350 } 351 352 // Sleep blocks the calling thread for a certain duration. 353 func (*ProductionDependencies) Sleep(d time.Duration) { 354 time.Sleep(d) 355 } 356 357 // Symlink creates a symlink between a source and a destination file. 358 func (*ProductionDependencies) Symlink(s1, s2 string) error { 359 return os.Symlink(s1, s2) 360 } 361 362 // WriteFile writes a file to the filesystem. 363 func (*ProductionDependencies) WriteFile(s string, b []byte, fm os.FileMode) error { 364 return ioutil.WriteFile(s, b, fm) 365 } 366 367 // Resolver is an interface that allows resolving a hostname into IP 368 // addresses. 369 type Resolver interface { 370 LookupIP(string) ([]net.IP, error) 371 } 372 373 // ProductionResolver is the hostname resolver used in production builds. 374 type ProductionResolver struct{} 375 376 // LookupIP is a passthrough function to net.LookupIP. In testing builds it 377 // returns a random IP. 378 func (ProductionResolver) LookupIP(host string) ([]net.IP, error) { 379 if build.Release == "testing" { 380 rawIP := make([]byte, 16) 381 fastrand.Read(rawIP) 382 return []net.IP{net.IP(rawIP)}, nil 383 } 384 return net.LookupIP(host) 385 } 386 387 // Resolver returns the ProductionResolver. 388 func (*ProductionDependencies) Resolver() Resolver { 389 return ProductionResolver{} 390 }