This repository has been archived by the owner on Dec 1, 2022. It is now read-only.
/
volume.go
124 lines (113 loc) · 4.05 KB
/
volume.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// +build !partners
// Don't include this in the partners build: it's not needed
// in the partner apps, and the syscall.Stat* functions cause
// the build to fail on Windows.
package models
import (
"fmt"
"sync"
"syscall"
)
// Volume tracks the amount of available space on a volume (disk),
// as well as the amount of space claimed for pending operations.
// The purpose is to allow the bag processor to try to determine
// ahead of time whether the underlying disk has enough space to
// accommodate the file it just pulled off the queue. We want to
// avoid downloading 100GB files when we know ahead of time that
// we don't have enough space to process them.
type Volume struct {
mountPoint string
mutex *sync.Mutex
claimed uint64
reservations map[string]uint64
}
// Creates a new Volume object to track free and used space on
// a volume (disk). Param mountPoint is the point at which the
// volume is mounted. The volume itself can be a physical disk
// or a logical partition.
//
// On Mac and *nix systems, use posix.GetMountPointFromPath to
// get the mountpoint. If you're on Windows, Mr. T pities you,
// fool! This volume manager won't work for you. Upgrade to a
// more sensible OS.
func NewVolume(mountPoint string) *Volume {
volume := &Volume{}
volume.mountPoint = mountPoint
volume.claimed = uint64(0)
volume.mutex = &sync.Mutex{}
volume.reservations = make(map[string]uint64)
return volume
}
// Returns the mountPoint to the volume.
func (volume *Volume) MountPoint() string {
return volume.mountPoint
}
// Returns the number of bytes claimed but not yet written to disk.
func (volume *Volume) ClaimedSpace() uint64 {
return volume.claimed
}
// currentFreeSpace returns the number of bytes currently available
// to unprivileged users on the underlying volume. This number comes
// directly from the operating system's statfs call, and does not
// take into account the number of bytes reserved for pending operations.
func (volume *Volume) currentFreeSpace() (numBytes uint64, err error) {
stat := &syscall.Statfs_t{}
err = syscall.Statfs(volume.mountPoint, stat)
if err != nil {
return 0, err
}
freeBytes := uint64(stat.Bsize) * uint64(stat.Bavail)
return freeBytes, nil
}
// AvailableSpace returns an approximate number of free bytes currently
// available to unprivileged users on the underlying volume, minus the
// number of bytes reserved for pending processes. The value returned
// will never be 100% accurate, because other processes may be writing
// to the volume.
func (volume *Volume) AvailableSpace() (uint64, error) {
available, err := volume.currentFreeSpace()
if err != nil {
return uint64(0), err
}
numBytes := available - volume.claimed
return numBytes, nil
}
// Reserve requests that a number of bytes on disk be reserved for an
// upcoming operation, such as downloading and untarring a file.
// Reserving space does not have any effect on the file system. It
// simply allows the Volume struct to maintain some internal bookkeeping.
// Reserve will return an error if there is not enough free disk space to
// accommodate the requested number of bytes.
func (volume *Volume) Reserve(path string, numBytes uint64) error {
available, err := volume.AvailableSpace()
if err != nil {
return err
}
if numBytes >= available {
err = fmt.Errorf("Requested %d bytes on volume, "+
"but only %d are available", numBytes, available)
} else {
volume.mutex.Lock()
volume.reservations[path] = numBytes
volume.claimed += numBytes
volume.mutex.Unlock()
}
return err
}
// Release tells the Volume that the bytes no longer need to be
// reserved. This could be because they have already been written
// (and hence will show up in volume.currentFreeSpace()) or because
// the bytes will not be written at all.
func (volume *Volume) Release(path string) {
volume.mutex.Lock()
numBytes, ok := volume.reservations[path]
if ok {
volume.claimed -= numBytes
}
delete(volume.reservations, path)
volume.mutex.Unlock()
}
// This is for reporting and debugging.
func (volume *Volume) Reservations() map[string]uint64 {
return volume.reservations
}