github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/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/Synthesix/Sia/build"
    13  	"github.com/Synthesix/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  }