diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 88d0aa43b..8e7057c48 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -34,6 +34,7 @@ import ( "github.com/minio/minio/pkg/lock" "github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/mimedb" + "github.com/minio/minio/pkg/mountinfo" "github.com/minio/minio/pkg/policy" ) @@ -64,6 +65,8 @@ type FSObjects struct { // ListObjects pool management. listPool *treeWalkPool + diskMount bool + appendFileMap map[string]*fsAppendFile appendFileMapMu sync.Mutex @@ -138,6 +141,7 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) { nsMutex: newNSLock(false), listPool: newTreeWalkPool(globalLookupTimeout), appendFileMap: make(map[string]*fsAppendFile), + diskMount: mountinfo.IsLikelyMountPoint(fsPath), } // Once the filesystem has initialized hold the read lock for @@ -156,7 +160,10 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) { return nil, uiErrUnableToReadFromBackend(err).Msg("Unable to initialize policy system") } - go fs.diskUsage(globalServiceDoneCh) + if !fs.diskMount { + go fs.diskUsage(globalServiceDoneCh) + } + go fs.cleanupStaleMultipartUploads(ctx, globalMultipartCleanupInterval, globalMultipartExpiry, globalServiceDoneCh) // Return successfully initialized object layer. @@ -177,17 +184,29 @@ func (fs *FSObjects) diskUsage(doneCh chan struct{}) { defer ticker.Stop() usageFn := func(ctx context.Context, entry string) error { - var fi os.FileInfo - var err error - if hasSuffix(entry, slashSeparator) { - fi, err = fsStatDir(ctx, entry) - } else { - fi, err = fsStatFile(ctx, entry) + if globalHTTPServer != nil { + // Any requests in progress, delay the usage. + for globalHTTPServer.GetRequestCount() > 0 { + time.Sleep(1 * time.Second) + } } - if err != nil { - return err + + select { + case <-doneCh: + return errWalkAbort + default: + var fi os.FileInfo + var err error + if hasSuffix(entry, slashSeparator) { + fi, err = fsStatDir(ctx, entry) + } else { + fi, err = fsStatFile(ctx, entry) + } + if err != nil { + return err + } + atomic.AddUint64(&fs.totalUsed, uint64(fi.Size())) } - atomic.AddUint64(&fs.totalUsed, uint64(fi.Size())) return nil } @@ -216,6 +235,13 @@ func (fs *FSObjects) diskUsage(doneCh chan struct{}) { var usage uint64 usageFn = func(ctx context.Context, entry string) error { + if globalHTTPServer != nil { + // Any requests in progress, delay the usage. + for globalHTTPServer.GetRequestCount() > 0 { + time.Sleep(1 * time.Second) + } + } + var fi os.FileInfo var err error if hasSuffix(entry, slashSeparator) { @@ -243,8 +269,16 @@ func (fs *FSObjects) diskUsage(doneCh chan struct{}) { // StorageInfo - returns underlying storage statistics. func (fs *FSObjects) StorageInfo(ctx context.Context) StorageInfo { + di, err := getDiskInfo(fs.fsPath) + if err != nil { + return StorageInfo{} + } + used := di.Total - di.Free + if !fs.diskMount { + used = atomic.LoadUint64(&fs.totalUsed) + } storageInfo := StorageInfo{ - Used: atomic.LoadUint64(&fs.totalUsed), + Used: used, } storageInfo.Backend.Type = FS return storageInfo diff --git a/cmd/http/server.go b/cmd/http/server.go index 2bff96ec4..4aa0749fe 100644 --- a/cmd/http/server.go +++ b/cmd/http/server.go @@ -59,7 +59,12 @@ type Server struct { listenerMutex *sync.Mutex // to guard 'listener' field. listener *httpListener // HTTP listener for all 'Addrs' field. inShutdown uint32 // indicates whether the server is in shutdown or not - requestCount int32 // counter holds no. of request in process. + requestCount int32 // counter holds no. of request in progress. +} + +// GetRequestCount - returns number of request in progress. +func (srv *Server) GetRequestCount() int32 { + return atomic.LoadInt32(&srv.requestCount) } // Start - start HTTP server diff --git a/cmd/posix.go b/cmd/posix.go index 7bf2ba40e..60eb9a59b 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -34,6 +34,7 @@ import ( humanize "github.com/dustin/go-humanize" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/disk" + "github.com/minio/minio/pkg/mountinfo" ) const ( @@ -69,6 +70,8 @@ type posix struct { pool sync.Pool connected bool + diskMount bool // indicates if the path is an actual mount. + // Disk usage metrics stopUsageCh chan struct{} } @@ -183,9 +186,12 @@ func newPosix(path string) (*posix, error) { }, }, stopUsageCh: make(chan struct{}), + diskMount: mountinfo.IsLikelyMountPoint(path), } - go p.diskUsage(globalServiceDoneCh) + if !p.diskMount { + go p.diskUsage(globalServiceDoneCh) + } // Success. return p, nil @@ -293,10 +299,14 @@ func (s *posix) DiskInfo() (info DiskInfo, err error) { if err != nil { return info, err } + used := di.Total - di.Free + if !s.diskMount { + used = atomic.LoadUint64(&s.totalUsed) + } return DiskInfo{ Total: di.Total, Free: di.Free, - Used: atomic.LoadUint64(&s.totalUsed), + Used: used, }, nil } @@ -336,7 +346,16 @@ func (s *posix) diskUsage(doneCh chan struct{}) { defer ticker.Stop() usageFn := func(ctx context.Context, entry string) error { + if globalHTTPServer != nil { + // Any requests in progress, delay the usage. + for globalHTTPServer.GetRequestCount() > 0 { + time.Sleep(1 * time.Second) + } + } + select { + case <-doneCh: + return errWalkAbort case <-s.stopUsageCh: return errWalkAbort default: @@ -377,6 +396,13 @@ func (s *posix) diskUsage(doneCh chan struct{}) { var usage uint64 usageFn = func(ctx context.Context, entry string) error { + if globalHTTPServer != nil { + // Any requests in progress, delay the usage. + for globalHTTPServer.GetRequestCount() > 0 { + time.Sleep(1 * time.Second) + } + } + select { case <-s.stopUsageCh: return errWalkAbort diff --git a/pkg/mountinfo/mountinfo_linux.go b/pkg/mountinfo/mountinfo_linux.go index 04a5904de..b3652449b 100644 --- a/pkg/mountinfo/mountinfo_linux.go +++ b/pkg/mountinfo/mountinfo_linux.go @@ -1,7 +1,7 @@ // +build linux /* - * Minio Cloud Storage, (C) 2017 Minio, Inc. + * Minio Cloud Storage, (C) 2017, 2018 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" ) const ( @@ -35,6 +36,27 @@ const ( procMountsPath = "/proc/mounts" ) +// IsLikelyMountPoint determines if a directory is a mountpoint. +// It is fast but not necessarily ALWAYS correct. If the path is in fact +// a bind mount from one part of a mount to another it will not be detected. +// mkdir /tmp/a /tmp/b; mount --bin /tmp/a /tmp/b; IsLikelyMountPoint("/tmp/b") +// will return false. When in fact /tmp/b is a mount point. If this situation +// if of interest to you, don't use this function... +func IsLikelyMountPoint(file string) bool { + stat, err := os.Stat(file) + if err != nil { + return false + } + + rootStat, err := os.Lstat(filepath.Dir(strings.TrimSuffix(file, "/"))) + if err != nil { + return false + } + + // If the directory has a different device as parent, then it is a mountpoint. + return stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev +} + // CheckCrossDevice - check if any list of paths has any sub-mounts at /proc/mounts. func CheckCrossDevice(absPaths []string) error { return checkCrossDevice(absPaths, procMountsPath) diff --git a/pkg/mountinfo/mountinfo_others.go b/pkg/mountinfo/mountinfo_others.go index 9b28d39c2..2ad8b1d3f 100644 --- a/pkg/mountinfo/mountinfo_others.go +++ b/pkg/mountinfo/mountinfo_others.go @@ -1,7 +1,7 @@ -// +build !linux +// +build !linux,!windows /* - * Minio Cloud Storage, (C) 2017 Minio, Inc. + * Minio Cloud Storage, (C) 2017, 2018 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,3 +23,8 @@ package mountinfo func CheckCrossDevice(paths []string) error { return nil } + +// IsLikelyMountPoint determines if a directory is a mountpoint. +func IsLikelyMountPoint(file string) bool { + return false +} diff --git a/pkg/mountinfo/mountinfo_windows.go b/pkg/mountinfo/mountinfo_windows.go new file mode 100644 index 000000000..8ab400ada --- /dev/null +++ b/pkg/mountinfo/mountinfo_windows.go @@ -0,0 +1,59 @@ +// +build windows + +/* + * Minio Cloud Storage, (C) 2018 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mountinfo + +import ( + "os" +) + +// CheckCrossDevice - check if any input path has multiple sub-mounts. +// this is a dummy function and returns nil for now. +func CheckCrossDevice(paths []string) error { + return nil +} + +// fileExists checks if specified file exists. +func fileExists(filename string) (bool, error) { + if _, err := os.Stat(filename); os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +// IsLikelyMountPoint determines if a directory is a mountpoint. +func IsLikelyMountPoint(file string) bool { + stat, err := os.Lstat(file) + if err != nil { + return false + } + + // If current file is a symlink, then it is a mountpoint. + if stat.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(file) + if err != nil { + return false + } + exists, _ := fileExists(target) + return exists + } + + return false +}