-
Notifications
You must be signed in to change notification settings - Fork 32
Feature/fuse improvements #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f2c7539
5e214f0
ef581a0
4082458
58dbe03
73714fc
3a729e9
aed7d55
c24b8d3
1ae191f
bbf29ae
1f4dded
7c83f7d
6b90be0
d0b3f64
45f264d
03e48e6
d3aad7a
984107a
ce915ce
c940779
05b5737
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
8000
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -228,6 +228,24 @@ func (l *Layer) Write(buf []byte) (int, error) { | |
return len(buf), nil | ||
} | ||
|
||
// Writes data from `buf` at offset `off` counted from the start (0 offset). | ||
// Mimics `WriteAt` from `io` package https://golang.org/pkg/io/#WriterAt | ||
func (l *Layer) WriteAt(buf []byte, off int64) (n int, err error) { | ||
// Copy the buffer, since we cannot rely on it being valid forever. | ||
modBuf := make([]byte, len(buf)) | ||
copy(modBuf, buf) | ||
|
||
l.index.Add(&Modification{off, modBuf}) | ||
end := off + int64(len(buf)) | ||
if l.limit >= 0 && end > l.limit { | ||
l.limit = end | ||
} | ||
|
||
n = len(buf) | ||
err = nil | ||
return n, nil | ||
} | ||
|
||
// hasGaps checks if overlays occludes all bytes between `start` and `end` | ||
func hasGaps(overlays []Interval, start, end int64) bool { | ||
diff := end - start | ||
|
@@ -342,6 +360,11 @@ func (l *Layer) Seek(offset int64, whence int) (int64, error) { | |
l.limit = newPos | ||
} | ||
|
||
if l.pos == newPos { | ||
// very likely it sequent read/write request | ||
// no need to seek since we already pointing to the right position | ||
return l.pos, nil | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
l.pos = newPos | ||
|
||
// Silence EOF: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ type File struct { | |
// Attr is called to get the stat(2) attributes of a file. | ||
func (fi *File) Attr(ctx context.Context, attr *fuse.Attr) error { | ||
defer logPanic("file: attr") | ||
log.Debugf("fuse-file-attr: %v", fi.path) | ||
|
||
info, err := fi.m.fs.Stat(fi.path) | ||
if err != nil { | ||
|
@@ -55,11 +56,7 @@ func (fi *File) Attr(ctx context.Context, attr *fuse.Attr) error { | |
attr.Mode = os.ModeSymlink | filePerm | ||
} | ||
} | ||
if fi.hd != nil && fi.hd.writers > 0 { | ||
attr.Size = uint64(len(fi.hd.data)) | ||
} else { | ||
attr.Size = info.Size | ||
} | ||
attr.Size = info.Size | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure, but # file should have a size of 1MB.
$ dd if=/dev/zero of=/fuse/file.txt bs=1M count=1
# file size should increase with each write happening.
# (probably easier to test in a unit test where you can
# control when the file is closed).
$ dd if=/dev/zero of=/fuse/file.txt bs=1M count=20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might also explains why your test produced that much metadata: Every fuse write called |
||
attr.Mtime = info.ModTime | ||
attr.Inode = info.Inode | ||
|
||
|
@@ -83,6 +80,7 @@ func (fi *File) Attr(ctx context.Context, attr *fuse.Attr) error { | |
func (fi *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { | ||
defer logPanic("file: open") | ||
debugLog("fuse-open: %s", fi.path) | ||
log.Debugf("fuse-file-open: %v with request %v", fi.path, req) | ||
|
||
// Check if the file is actually available locally. | ||
if fi.m.options.Offline { | ||
|
@@ -103,30 +101,13 @@ func (fi *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.Open | |
|
||
fi.mu.Lock() | ||
if fi.hd == nil { | ||
hd := Handle{fd: fd, m: fi.m, writers: 0, wasModified: false, currentFileReadOffset: -1} | ||
hd := Handle{fd: fd, m: fi.m, wasModified: false, currentFileReadOffset: -1} | ||
fi.hd = &hd | ||
} | ||
fi.hd.fd = fd | ||
fi.mu.Unlock() | ||
if req.Flags.IsReadOnly() { | ||
// we don't need to track read-only handles | ||
// and no need to set handle `data` | ||
return fi.hd, nil | ||
} | ||
|
||
// for writers we need to copy file data to the handle `data` | ||
fi.hd.mu.Lock() | ||
defer fi.hd.mu.Unlock() | ||
if fi.hd.writers == 0 { | ||
err = fi.hd.loadData(fi.path) | ||
if err != nil { | ||
return nil, errorize("file-open-loadData", err) | ||
} | ||
} | ||
if fi.hd.writers == (^uint(0)) { // checks against writers overflow | ||
return nil, errorize(fi.path, ErrTooManyWriters) | ||
} | ||
fi.hd.writers++ | ||
resp.Flags |= fuse.OpenKeepCache | ||
return fi.hd, nil | ||
} | ||
|
||
|
@@ -135,11 +116,11 @@ func (fi *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.Open | |
// file, the size change is noticed here before Open() is called. | ||
func (fi *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { | ||
defer logPanic("file: setattr") | ||
log.Debugf("fuse-file-setattr: request %v", req) | ||
|
||
// This is called when any attribute of the file changes, | ||
// most importantly the file size. For example it is called when truncating | ||
// the file to zero bytes with a size change of `0`. | ||
debugLog("exec file setattr") | ||
switch { | ||
case req.Valid&fuse.SetattrSize != 0: | ||
if err := fi.hd.truncate(req.Size); err != nil { | ||
|
@@ -158,17 +139,21 @@ func (fi *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fus | |
// Currently, fsync is completely ignored. | ||
func (fi *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { | ||
defer logPanic("file: fsync") | ||
log.Debugf("fuse-file-fsync: %v", fi.path) | ||
|
||
debugLog("exec file fsync") | ||
return nil | ||
} | ||
|
||
// Getxattr is called to get a single xattr (extended attribute) of a file. | ||
func (fi *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error { | ||
defer logPanic("file: getxattr") | ||
// log.Debugf("fuse-file-getxattr %v for atribute %v", fi.path, req.Name) | ||
|
||
// debugLog("exec file getxattr: %v: %v", fi.path, req.Name) | ||
|
||
debugLog("exec file getxattr: %v: %v", fi.path, req.Name) | ||
xattrs, err := getXattr(fi.m.fs, req.Name, fi.path, req.Size) | ||
// Do not worry about req.Size | ||
// fuse will cut it to allowed size and report to the caller that buffer need to be larger | ||
xattrs, err := getXattr(fi.m.fs, req.Name, fi.path) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -180,15 +165,18 @@ func (fi *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f | |
// Listxattr is called to list all xattrs of this file. | ||
func (fi *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error { | ||
defer logPanic("file: listxattr") | ||
log.Debugf("fuse-file-listxattr: %v", fi.path) | ||
|
||
debugLog("exec file listxattr") | ||
resp.Xattr = listXattr(req.Size) | ||
// Do not worry about req.Size | ||
// fuse will cut it to allowed size and report to the caller that buffer need to be larger | ||
resp.Xattr = listXattr() | ||
return nil | ||
} | ||
|
||
// Readlink reads a symbolic link. | ||
// This call is triggered when OS tries to see where symlink points | ||
func (fi *File) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { | ||
log.Debugf("fuse-file-readlink: %v", fi.path) | ||
info, err := fi.m.fs.Stat(fi.path) | ||
if err != nil { | ||
return "/brig/backend/ipfs/", err | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential improvement here: use a sync.Pool to save a few allocations when writing many buffers. This also applies to the regular
Write
method and is probably more relevant there. Care must be taken that the buffer frompool.Get()
has a suitable length.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that I follow here.
modBuf
is somewhat permanent (sits in theoverlay
until flashed). Pool implies, that the data would be soon deleted. Besides manual says "Any item stored in the Pool may be removed automatically at any time without notification.". This sounds very scary.I guess I do not know internals of
gc
to really see the difference.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That documentation is a little misleading. Nothing gets garbage collected while you still use it (i.e. you hold a reference to it).
modBuf
can bePut()
back to the pool after flush and on L235 above you can do apool.Get()
to fetch a buffer that was either used before or is newly allocated. Go is free to do whatever it likes in the time between Put and Get.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now. Can we move it to separate issue?
From quick reading,
pool
is not suited for different size objects. So iflen(buf)
different from result of aGet
, we would have to put it back. But I see no way, to iterate overpool
to see if there is a suitable candidate. Besides, it would require the full rethinking of howoverlay
is working.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You would just get an item from the pool and check if it's reasonably sized (if larger, cut it), if not allocate a new one the normal way and use that. But sure, we can do it once overlay is touched in general.