chrislu
3 years ago
18 changed files with 1257 additions and 24 deletions
-
99weed/mount/dirty_pages_chunked.go
-
94weed/mount/filehandle.go
-
26weed/mount/filehandle_map.go
-
114weed/mount/filehandle_read.go
-
95weed/mount/page_writer.go
-
115weed/mount/page_writer/chunk_interval_list.go
-
49weed/mount/page_writer/chunk_interval_list_test.go
-
30weed/mount/page_writer/dirty_pages.go
-
16weed/mount/page_writer/page_chunk.go
-
69weed/mount/page_writer/page_chunk_mem.go
-
121weed/mount/page_writer/page_chunk_swapfile.go
-
182weed/mount/page_writer/upload_pipeline.go
-
63weed/mount/page_writer/upload_pipeline_lock.go
-
47weed/mount/page_writer/upload_pipeline_test.go
-
42weed/mount/weedfs.go
-
29weed/mount/weedfs_file_read.go
-
6weed/mount/weedfs_filehandle.go
-
84weed/mount/weedfs_write.go
@ -0,0 +1,99 @@ |
|||||
|
package mount |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"github.com/chrislusf/seaweedfs/weed/filesys/page_writer" |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
||||
|
"io" |
||||
|
"sync" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type ChunkedDirtyPages struct { |
||||
|
fh *FileHandle |
||||
|
writeWaitGroup sync.WaitGroup |
||||
|
lastErr error |
||||
|
collection string |
||||
|
replication string |
||||
|
uploadPipeline *page_writer.UploadPipeline |
||||
|
hasWrites bool |
||||
|
} |
||||
|
|
||||
|
var ( |
||||
|
_ = page_writer.DirtyPages(&ChunkedDirtyPages{}) |
||||
|
) |
||||
|
|
||||
|
func newMemoryChunkPages(fh *FileHandle, chunkSize int64) *ChunkedDirtyPages { |
||||
|
|
||||
|
dirtyPages := &ChunkedDirtyPages{ |
||||
|
fh: fh, |
||||
|
} |
||||
|
|
||||
|
dirtyPages.uploadPipeline = page_writer.NewUploadPipeline(fh.wfs.concurrentWriters, chunkSize, dirtyPages.saveChunkedFileIntevalToStorage, fh.wfs.option.ConcurrentWriters) |
||||
|
|
||||
|
return dirtyPages |
||||
|
} |
||||
|
|
||||
|
func (pages *ChunkedDirtyPages) AddPage(offset int64, data []byte) { |
||||
|
pages.hasWrites = true |
||||
|
|
||||
|
glog.V(4).Infof("%v memory AddPage [%d, %d)", pages.fh, offset, offset+int64(len(data))) |
||||
|
pages.uploadPipeline.SaveDataAt(data, offset) |
||||
|
|
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (pages *ChunkedDirtyPages) FlushData() error { |
||||
|
if !pages.hasWrites { |
||||
|
return nil |
||||
|
} |
||||
|
pages.uploadPipeline.FlushAll() |
||||
|
if pages.lastErr != nil { |
||||
|
return fmt.Errorf("flush data: %v", pages.lastErr) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (pages *ChunkedDirtyPages) ReadDirtyDataAt(data []byte, startOffset int64) (maxStop int64) { |
||||
|
if !pages.hasWrites { |
||||
|
return |
||||
|
} |
||||
|
return pages.uploadPipeline.MaybeReadDataAt(data, startOffset) |
||||
|
} |
||||
|
|
||||
|
func (pages *ChunkedDirtyPages) GetStorageOptions() (collection, replication string) { |
||||
|
return pages.collection, pages.replication |
||||
|
} |
||||
|
|
||||
|
func (pages *ChunkedDirtyPages) saveChunkedFileIntevalToStorage(reader io.Reader, offset int64, size int64, cleanupFn func()) { |
||||
|
|
||||
|
mtime := time.Now().UnixNano() |
||||
|
defer cleanupFn() |
||||
|
|
||||
|
fileFullPath := pages.fh.FullPath() |
||||
|
fileName := fileFullPath.Name() |
||||
|
chunk, collection, replication, err := pages.fh.wfs.saveDataAsChunk(fileFullPath)(reader, fileName, offset) |
||||
|
if err != nil { |
||||
|
glog.V(0).Infof("%v saveToStorage [%d,%d): %v", fileFullPath, offset, offset+size, err) |
||||
|
pages.lastErr = err |
||||
|
return |
||||
|
} |
||||
|
chunk.Mtime = mtime |
||||
|
pages.collection, pages.replication = collection, replication |
||||
|
pages.fh.addChunks([]*filer_pb.FileChunk{chunk}) |
||||
|
pages.fh.entryViewCache = nil |
||||
|
glog.V(3).Infof("%v saveToStorage %s [%d,%d)", fileFullPath, chunk.FileId, offset, offset+size) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
func (pages ChunkedDirtyPages) Destroy() { |
||||
|
pages.uploadPipeline.Shutdown() |
||||
|
} |
||||
|
|
||||
|
func (pages *ChunkedDirtyPages) LockForRead(startOffset, stopOffset int64) { |
||||
|
pages.uploadPipeline.LockForRead(startOffset, stopOffset) |
||||
|
} |
||||
|
func (pages *ChunkedDirtyPages) UnlockForRead(startOffset, stopOffset int64) { |
||||
|
pages.uploadPipeline.UnlockForRead(startOffset, stopOffset) |
||||
|
} |
@ -0,0 +1,94 @@ |
|||||
|
package mount |
||||
|
|
||||
|
import ( |
||||
|
"github.com/chrislusf/seaweedfs/weed/filer" |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
||||
|
"github.com/chrislusf/seaweedfs/weed/util" |
||||
|
"io" |
||||
|
"sort" |
||||
|
"sync" |
||||
|
) |
||||
|
|
||||
|
type FileHandleId uint64 |
||||
|
|
||||
|
type FileHandle struct { |
||||
|
fh FileHandleId |
||||
|
counter int64 |
||||
|
entry *filer_pb.Entry |
||||
|
chunkAddLock sync.Mutex |
||||
|
inode uint64 |
||||
|
wfs *WFS |
||||
|
|
||||
|
// cache file has been written to
|
||||
|
dirtyPages *PageWriter |
||||
|
entryViewCache []filer.VisibleInterval |
||||
|
reader io.ReaderAt |
||||
|
contentType string |
||||
|
handle uint64 |
||||
|
sync.Mutex |
||||
|
|
||||
|
isDeleted bool |
||||
|
} |
||||
|
|
||||
|
func newFileHandle(wfs *WFS, handleId FileHandleId, inode uint64, entry *filer_pb.Entry) *FileHandle { |
||||
|
fh := &FileHandle{ |
||||
|
fh: handleId, |
||||
|
counter: 1, |
||||
|
inode: inode, |
||||
|
wfs: wfs, |
||||
|
} |
||||
|
// dirtyPages: newContinuousDirtyPages(file, writeOnly),
|
||||
|
fh.dirtyPages = newPageWriter(fh, wfs.option.ChunkSizeLimit) |
||||
|
if entry != nil { |
||||
|
entry.Attributes.FileSize = filer.FileSize(entry) |
||||
|
} |
||||
|
|
||||
|
return fh |
||||
|
} |
||||
|
|
||||
|
func (fh *FileHandle) FullPath() util.FullPath { |
||||
|
return fh.wfs.inodeToPath.GetPath(fh.inode) |
||||
|
} |
||||
|
|
||||
|
func (fh *FileHandle) addChunks(chunks []*filer_pb.FileChunk) { |
||||
|
|
||||
|
// find the earliest incoming chunk
|
||||
|
newChunks := chunks |
||||
|
earliestChunk := newChunks[0] |
||||
|
for i := 1; i < len(newChunks); i++ { |
||||
|
if lessThan(earliestChunk, newChunks[i]) { |
||||
|
earliestChunk = newChunks[i] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if fh.entry == nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// pick out-of-order chunks from existing chunks
|
||||
|
for _, chunk := range fh.entry.Chunks { |
||||
|
if lessThan(earliestChunk, chunk) { |
||||
|
chunks = append(chunks, chunk) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// sort incoming chunks
|
||||
|
sort.Slice(chunks, func(i, j int) bool { |
||||
|
return lessThan(chunks[i], chunks[j]) |
||||
|
}) |
||||
|
|
||||
|
glog.V(4).Infof("%s existing %d chunks adds %d more", fh.FullPath(), len(fh.entry.Chunks), len(chunks)) |
||||
|
|
||||
|
fh.chunkAddLock.Lock() |
||||
|
fh.entry.Chunks = append(fh.entry.Chunks, newChunks...) |
||||
|
fh.entryViewCache = nil |
||||
|
fh.chunkAddLock.Unlock() |
||||
|
} |
||||
|
|
||||
|
func lessThan(a, b *filer_pb.FileChunk) bool { |
||||
|
if a.Mtime == b.Mtime { |
||||
|
return a.Fid.FileKey < b.Fid.FileKey |
||||
|
} |
||||
|
return a.Mtime < b.Mtime |
||||
|
} |
@ -0,0 +1,114 @@ |
|||||
|
package mount |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"github.com/chrislusf/seaweedfs/weed/filer" |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
||||
|
"io" |
||||
|
"math" |
||||
|
) |
||||
|
|
||||
|
func (fh *FileHandle) lockForRead(startOffset int64, size int) { |
||||
|
fh.dirtyPages.LockForRead(startOffset, startOffset+int64(size)) |
||||
|
} |
||||
|
func (fh *FileHandle) unlockForRead(startOffset int64, size int) { |
||||
|
fh.dirtyPages.UnlockForRead(startOffset, startOffset+int64(size)) |
||||
|
} |
||||
|
|
||||
|
func (fh *FileHandle) readFromDirtyPages(buff []byte, startOffset int64) (maxStop int64) { |
||||
|
maxStop = fh.dirtyPages.ReadDirtyDataAt(buff, startOffset) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, error) { |
||||
|
|
||||
|
fileFullPath := fh.FullPath() |
||||
|
|
||||
|
entry := fh.entry |
||||
|
if entry == nil { |
||||
|
return 0, io.EOF |
||||
|
} |
||||
|
|
||||
|
if entry.IsInRemoteOnly() { |
||||
|
glog.V(4).Infof("download remote entry %s", fileFullPath) |
||||
|
newEntry, err := fh.downloadRemoteEntry(entry) |
||||
|
if err != nil { |
||||
|
glog.V(1).Infof("download remote entry %s: %v", fileFullPath, err) |
||||
|
return 0, err |
||||
|
} |
||||
|
entry = newEntry |
||||
|
} |
||||
|
|
||||
|
fileSize := int64(filer.FileSize(entry)) |
||||
|
|
||||
|
if fileSize == 0 { |
||||
|
glog.V(1).Infof("empty fh %v", fileFullPath) |
||||
|
return 0, io.EOF |
||||
|
} |
||||
|
|
||||
|
if offset+int64(len(buff)) <= int64(len(entry.Content)) { |
||||
|
totalRead := copy(buff, entry.Content[offset:]) |
||||
|
glog.V(4).Infof("file handle read cached %s [%d,%d] %d", fileFullPath, offset, offset+int64(totalRead), totalRead) |
||||
|
return int64(totalRead), nil |
||||
|
} |
||||
|
|
||||
|
var chunkResolveErr error |
||||
|
if fh.entryViewCache == nil { |
||||
|
fh.entryViewCache, chunkResolveErr = filer.NonOverlappingVisibleIntervals(fh.wfs.LookupFn(), entry.Chunks, 0, math.MaxInt64) |
||||
|
if chunkResolveErr != nil { |
||||
|
return 0, fmt.Errorf("fail to resolve chunk manifest: %v", chunkResolveErr) |
||||
|
} |
||||
|
fh.reader = nil |
||||
|
} |
||||
|
|
||||
|
reader := fh.reader |
||||
|
if reader == nil { |
||||
|
chunkViews := filer.ViewFromVisibleIntervals(fh.entryViewCache, 0, math.MaxInt64) |
||||
|
glog.V(4).Infof("file handle read %s [%d,%d) from %d views", fileFullPath, offset, offset+int64(len(buff)), len(chunkViews)) |
||||
|
for _, chunkView := range chunkViews { |
||||
|
glog.V(4).Infof(" read %s [%d,%d) from chunk %+v", fileFullPath, chunkView.LogicOffset, chunkView.LogicOffset+int64(chunkView.Size), chunkView.FileId) |
||||
|
} |
||||
|
reader = filer.NewChunkReaderAtFromClient(fh.wfs.LookupFn(), chunkViews, fh.wfs.chunkCache, fileSize) |
||||
|
} |
||||
|
fh.reader = reader |
||||
|
|
||||
|
totalRead, err := reader.ReadAt(buff, offset) |
||||
|
|
||||
|
if err != nil && err != io.EOF { |
||||
|
glog.Errorf("file handle read %s: %v", fileFullPath, err) |
||||
|
} |
||||
|
|
||||
|
glog.V(4).Infof("file handle read %s [%d,%d] %d : %v", fileFullPath, offset, offset+int64(totalRead), totalRead, err) |
||||
|
|
||||
|
return int64(totalRead), err |
||||
|
} |
||||
|
|
||||
|
func (fh *FileHandle) downloadRemoteEntry(entry *filer_pb.Entry) (*filer_pb.Entry, error) { |
||||
|
|
||||
|
fileFullPath := fh.FullPath() |
||||
|
dir, _ := fileFullPath.DirAndName() |
||||
|
|
||||
|
err := fh.wfs.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { |
||||
|
|
||||
|
request := &filer_pb.CacheRemoteObjectToLocalClusterRequest{ |
||||
|
Directory: string(dir), |
||||
|
Name: entry.Name, |
||||
|
} |
||||
|
|
||||
|
glog.V(4).Infof("download entry: %v", request) |
||||
|
resp, err := client.CacheRemoteObjectToLocalCluster(context.Background(), request) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("CacheRemoteObjectToLocalCluster file %s: %v", fileFullPath, err) |
||||
|
} |
||||
|
|
||||
|
entry = resp.Entry |
||||
|
|
||||
|
fh.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, resp.Entry)) |
||||
|
|
||||
|
return nil |
||||
|
}) |
||||
|
|
||||
|
return entry, err |
||||
|
} |
@ -0,0 +1,95 @@ |
|||||
|
package mount |
||||
|
|
||||
|
import ( |
||||
|
"github.com/chrislusf/seaweedfs/weed/filesys/page_writer" |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
) |
||||
|
|
||||
|
type PageWriter struct { |
||||
|
fh *FileHandle |
||||
|
collection string |
||||
|
replication string |
||||
|
chunkSize int64 |
||||
|
|
||||
|
randomWriter page_writer.DirtyPages |
||||
|
} |
||||
|
|
||||
|
var ( |
||||
|
_ = page_writer.DirtyPages(&PageWriter{}) |
||||
|
) |
||||
|
|
||||
|
func newPageWriter(fh *FileHandle, chunkSize int64) *PageWriter { |
||||
|
pw := &PageWriter{ |
||||
|
fh: fh, |
||||
|
chunkSize: chunkSize, |
||||
|
randomWriter: newMemoryChunkPages(fh, chunkSize), |
||||
|
// randomWriter: newTempFileDirtyPages(fh.f, chunkSize),
|
||||
|
} |
||||
|
return pw |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) AddPage(offset int64, data []byte) { |
||||
|
|
||||
|
glog.V(4).Infof("%v AddPage [%d, %d)", pw.fh, offset, offset+int64(len(data))) |
||||
|
|
||||
|
chunkIndex := offset / pw.chunkSize |
||||
|
for i := chunkIndex; len(data) > 0; i++ { |
||||
|
writeSize := min(int64(len(data)), (i+1)*pw.chunkSize-offset) |
||||
|
pw.addToOneChunk(i, offset, data[:writeSize]) |
||||
|
offset += writeSize |
||||
|
data = data[writeSize:] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) addToOneChunk(chunkIndex, offset int64, data []byte) { |
||||
|
pw.randomWriter.AddPage(offset, data) |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) FlushData() error { |
||||
|
return pw.randomWriter.FlushData() |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) ReadDirtyDataAt(data []byte, offset int64) (maxStop int64) { |
||||
|
glog.V(4).Infof("ReadDirtyDataAt %v [%d, %d)", pw.fh, offset, offset+int64(len(data))) |
||||
|
|
||||
|
chunkIndex := offset / pw.chunkSize |
||||
|
for i := chunkIndex; len(data) > 0; i++ { |
||||
|
readSize := min(int64(len(data)), (i+1)*pw.chunkSize-offset) |
||||
|
|
||||
|
maxStop = pw.randomWriter.ReadDirtyDataAt(data[:readSize], offset) |
||||
|
|
||||
|
offset += readSize |
||||
|
data = data[readSize:] |
||||
|
} |
||||
|
|
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) GetStorageOptions() (collection, replication string) { |
||||
|
return pw.randomWriter.GetStorageOptions() |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) LockForRead(startOffset, stopOffset int64) { |
||||
|
pw.randomWriter.LockForRead(startOffset, stopOffset) |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) UnlockForRead(startOffset, stopOffset int64) { |
||||
|
pw.randomWriter.UnlockForRead(startOffset, stopOffset) |
||||
|
} |
||||
|
|
||||
|
func (pw *PageWriter) Destroy() { |
||||
|
pw.randomWriter.Destroy() |
||||
|
} |
||||
|
|
||||
|
func max(x, y int64) int64 { |
||||
|
if x > y { |
||||
|
return x |
||||
|
} |
||||
|
return y |
||||
|
} |
||||
|
func min(x, y int64) int64 { |
||||
|
if x < y { |
||||
|
return x |
||||
|
} |
||||
|
return y |
||||
|
} |
@ -0,0 +1,115 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import "math" |
||||
|
|
||||
|
// ChunkWrittenInterval mark one written interval within one page chunk
|
||||
|
type ChunkWrittenInterval struct { |
||||
|
StartOffset int64 |
||||
|
stopOffset int64 |
||||
|
prev *ChunkWrittenInterval |
||||
|
next *ChunkWrittenInterval |
||||
|
} |
||||
|
|
||||
|
func (interval *ChunkWrittenInterval) Size() int64 { |
||||
|
return interval.stopOffset - interval.StartOffset |
||||
|
} |
||||
|
|
||||
|
func (interval *ChunkWrittenInterval) isComplete(chunkSize int64) bool { |
||||
|
return interval.stopOffset-interval.StartOffset == chunkSize |
||||
|
} |
||||
|
|
||||
|
// ChunkWrittenIntervalList mark written intervals within one page chunk
|
||||
|
type ChunkWrittenIntervalList struct { |
||||
|
head *ChunkWrittenInterval |
||||
|
tail *ChunkWrittenInterval |
||||
|
} |
||||
|
|
||||
|
func newChunkWrittenIntervalList() *ChunkWrittenIntervalList { |
||||
|
list := &ChunkWrittenIntervalList{ |
||||
|
head: &ChunkWrittenInterval{ |
||||
|
StartOffset: -1, |
||||
|
stopOffset: -1, |
||||
|
}, |
||||
|
tail: &ChunkWrittenInterval{ |
||||
|
StartOffset: math.MaxInt64, |
||||
|
stopOffset: math.MaxInt64, |
||||
|
}, |
||||
|
} |
||||
|
list.head.next = list.tail |
||||
|
list.tail.prev = list.head |
||||
|
return list |
||||
|
} |
||||
|
|
||||
|
func (list *ChunkWrittenIntervalList) MarkWritten(startOffset, stopOffset int64) { |
||||
|
interval := &ChunkWrittenInterval{ |
||||
|
StartOffset: startOffset, |
||||
|
stopOffset: stopOffset, |
||||
|
} |
||||
|
list.addInterval(interval) |
||||
|
} |
||||
|
|
||||
|
func (list *ChunkWrittenIntervalList) IsComplete(chunkSize int64) bool { |
||||
|
return list.size() == 1 && list.head.next.isComplete(chunkSize) |
||||
|
} |
||||
|
func (list *ChunkWrittenIntervalList) WrittenSize() (writtenByteCount int64) { |
||||
|
for t := list.head; t != nil; t = t.next { |
||||
|
writtenByteCount += t.Size() |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (list *ChunkWrittenIntervalList) addInterval(interval *ChunkWrittenInterval) { |
||||
|
|
||||
|
p := list.head |
||||
|
for ; p.next != nil && p.next.StartOffset <= interval.StartOffset; p = p.next { |
||||
|
} |
||||
|
q := list.tail |
||||
|
for ; q.prev != nil && q.prev.stopOffset >= interval.stopOffset; q = q.prev { |
||||
|
} |
||||
|
|
||||
|
if interval.StartOffset <= p.stopOffset && q.StartOffset <= interval.stopOffset { |
||||
|
// merge p and q together
|
||||
|
p.stopOffset = q.stopOffset |
||||
|
unlinkNodesBetween(p, q.next) |
||||
|
return |
||||
|
} |
||||
|
if interval.StartOffset <= p.stopOffset { |
||||
|
// merge new interval into p
|
||||
|
p.stopOffset = interval.stopOffset |
||||
|
unlinkNodesBetween(p, q) |
||||
|
return |
||||
|
} |
||||
|
if q.StartOffset <= interval.stopOffset { |
||||
|
// merge new interval into q
|
||||
|
q.StartOffset = interval.StartOffset |
||||
|
unlinkNodesBetween(p, q) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// add the new interval between p and q
|
||||
|
unlinkNodesBetween(p, q) |
||||
|
p.next = interval |
||||
|
interval.prev = p |
||||
|
q.prev = interval |
||||
|
interval.next = q |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// unlinkNodesBetween remove all nodes after start and before stop, exclusive
|
||||
|
func unlinkNodesBetween(start *ChunkWrittenInterval, stop *ChunkWrittenInterval) { |
||||
|
if start.next == stop { |
||||
|
return |
||||
|
} |
||||
|
start.next.prev = nil |
||||
|
start.next = stop |
||||
|
stop.prev.next = nil |
||||
|
stop.prev = start |
||||
|
} |
||||
|
|
||||
|
func (list *ChunkWrittenIntervalList) size() int { |
||||
|
var count int |
||||
|
for t := list.head; t != nil; t = t.next { |
||||
|
count++ |
||||
|
} |
||||
|
return count - 2 |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"github.com/stretchr/testify/assert" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func Test_PageChunkWrittenIntervalList(t *testing.T) { |
||||
|
list := newChunkWrittenIntervalList() |
||||
|
|
||||
|
assert.Equal(t, 0, list.size(), "empty list") |
||||
|
|
||||
|
list.MarkWritten(0, 5) |
||||
|
assert.Equal(t, 1, list.size(), "one interval") |
||||
|
|
||||
|
list.MarkWritten(0, 5) |
||||
|
assert.Equal(t, 1, list.size(), "duplicated interval2") |
||||
|
|
||||
|
list.MarkWritten(95, 100) |
||||
|
assert.Equal(t, 2, list.size(), "two intervals") |
||||
|
|
||||
|
list.MarkWritten(50, 60) |
||||
|
assert.Equal(t, 3, list.size(), "three intervals") |
||||
|
|
||||
|
list.MarkWritten(50, 55) |
||||
|
assert.Equal(t, 3, list.size(), "three intervals merge") |
||||
|
|
||||
|
list.MarkWritten(40, 50) |
||||
|
assert.Equal(t, 3, list.size(), "three intervals grow forward") |
||||
|
|
||||
|
list.MarkWritten(50, 65) |
||||
|
assert.Equal(t, 3, list.size(), "three intervals grow backward") |
||||
|
|
||||
|
list.MarkWritten(70, 80) |
||||
|
assert.Equal(t, 4, list.size(), "four intervals") |
||||
|
|
||||
|
list.MarkWritten(60, 70) |
||||
|
assert.Equal(t, 3, list.size(), "three intervals merged") |
||||
|
|
||||
|
list.MarkWritten(59, 71) |
||||
|
assert.Equal(t, 3, list.size(), "covered three intervals") |
||||
|
|
||||
|
list.MarkWritten(5, 59) |
||||
|
assert.Equal(t, 2, list.size(), "covered two intervals") |
||||
|
|
||||
|
list.MarkWritten(70, 99) |
||||
|
assert.Equal(t, 1, list.size(), "covered one intervals") |
||||
|
|
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
type DirtyPages interface { |
||||
|
AddPage(offset int64, data []byte) |
||||
|
FlushData() error |
||||
|
ReadDirtyDataAt(data []byte, startOffset int64) (maxStop int64) |
||||
|
GetStorageOptions() (collection, replication string) |
||||
|
Destroy() |
||||
|
LockForRead(startOffset, stopOffset int64) |
||||
|
UnlockForRead(startOffset, stopOffset int64) |
||||
|
} |
||||
|
|
||||
|
func max(x, y int64) int64 { |
||||
|
if x > y { |
||||
|
return x |
||||
|
} |
||||
|
return y |
||||
|
} |
||||
|
func min(x, y int64) int64 { |
||||
|
if x < y { |
||||
|
return x |
||||
|
} |
||||
|
return y |
||||
|
} |
||||
|
func minInt(x, y int) int { |
||||
|
if x < y { |
||||
|
return x |
||||
|
} |
||||
|
return y |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"io" |
||||
|
) |
||||
|
|
||||
|
type SaveToStorageFunc func(reader io.Reader, offset int64, size int64, cleanupFn func()) |
||||
|
|
||||
|
type PageChunk interface { |
||||
|
FreeResource() |
||||
|
WriteDataAt(src []byte, offset int64) (n int) |
||||
|
ReadDataAt(p []byte, off int64) (maxStop int64) |
||||
|
IsComplete() bool |
||||
|
WrittenSize() int64 |
||||
|
SaveContent(saveFn SaveToStorageFunc) |
||||
|
} |
@ -0,0 +1,69 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"github.com/chrislusf/seaweedfs/weed/util" |
||||
|
"github.com/chrislusf/seaweedfs/weed/util/mem" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
_ = PageChunk(&MemChunk{}) |
||||
|
) |
||||
|
|
||||
|
type MemChunk struct { |
||||
|
buf []byte |
||||
|
usage *ChunkWrittenIntervalList |
||||
|
chunkSize int64 |
||||
|
logicChunkIndex LogicChunkIndex |
||||
|
} |
||||
|
|
||||
|
func NewMemChunk(logicChunkIndex LogicChunkIndex, chunkSize int64) *MemChunk { |
||||
|
return &MemChunk{ |
||||
|
logicChunkIndex: logicChunkIndex, |
||||
|
chunkSize: chunkSize, |
||||
|
buf: mem.Allocate(int(chunkSize)), |
||||
|
usage: newChunkWrittenIntervalList(), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (mc *MemChunk) FreeResource() { |
||||
|
mem.Free(mc.buf) |
||||
|
} |
||||
|
|
||||
|
func (mc *MemChunk) WriteDataAt(src []byte, offset int64) (n int) { |
||||
|
innerOffset := offset % mc.chunkSize |
||||
|
n = copy(mc.buf[innerOffset:], src) |
||||
|
mc.usage.MarkWritten(innerOffset, innerOffset+int64(n)) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (mc *MemChunk) ReadDataAt(p []byte, off int64) (maxStop int64) { |
||||
|
memChunkBaseOffset := int64(mc.logicChunkIndex) * mc.chunkSize |
||||
|
for t := mc.usage.head.next; t != mc.usage.tail; t = t.next { |
||||
|
logicStart := max(off, int64(mc.logicChunkIndex)*mc.chunkSize+t.StartOffset) |
||||
|
logicStop := min(off+int64(len(p)), memChunkBaseOffset+t.stopOffset) |
||||
|
if logicStart < logicStop { |
||||
|
copy(p[logicStart-off:logicStop-off], mc.buf[logicStart-memChunkBaseOffset:logicStop-memChunkBaseOffset]) |
||||
|
maxStop = max(maxStop, logicStop) |
||||
|
} |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (mc *MemChunk) IsComplete() bool { |
||||
|
return mc.usage.IsComplete(mc.chunkSize) |
||||
|
} |
||||
|
|
||||
|
func (mc *MemChunk) WrittenSize() int64 { |
||||
|
return mc.usage.WrittenSize() |
||||
|
} |
||||
|
|
||||
|
func (mc *MemChunk) SaveContent(saveFn SaveToStorageFunc) { |
||||
|
if saveFn == nil { |
||||
|
return |
||||
|
} |
||||
|
for t := mc.usage.head.next; t != mc.usage.tail; t = t.next { |
||||
|
reader := util.NewBytesReader(mc.buf[t.StartOffset:t.stopOffset]) |
||||
|
saveFn(reader, int64(mc.logicChunkIndex)*mc.chunkSize+t.StartOffset, t.Size(), func() { |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -0,0 +1,121 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
"github.com/chrislusf/seaweedfs/weed/util" |
||||
|
"github.com/chrislusf/seaweedfs/weed/util/mem" |
||||
|
"os" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
_ = PageChunk(&SwapFileChunk{}) |
||||
|
) |
||||
|
|
||||
|
type ActualChunkIndex int |
||||
|
|
||||
|
type SwapFile struct { |
||||
|
dir string |
||||
|
file *os.File |
||||
|
logicToActualChunkIndex map[LogicChunkIndex]ActualChunkIndex |
||||
|
chunkSize int64 |
||||
|
} |
||||
|
|
||||
|
type SwapFileChunk struct { |
||||
|
swapfile *SwapFile |
||||
|
usage *ChunkWrittenIntervalList |
||||
|
logicChunkIndex LogicChunkIndex |
||||
|
actualChunkIndex ActualChunkIndex |
||||
|
} |
||||
|
|
||||
|
func NewSwapFile(dir string, chunkSize int64) *SwapFile { |
||||
|
return &SwapFile{ |
||||
|
dir: dir, |
||||
|
file: nil, |
||||
|
logicToActualChunkIndex: make(map[LogicChunkIndex]ActualChunkIndex), |
||||
|
chunkSize: chunkSize, |
||||
|
} |
||||
|
} |
||||
|
func (sf *SwapFile) FreeResource() { |
||||
|
if sf.file != nil { |
||||
|
sf.file.Close() |
||||
|
os.Remove(sf.file.Name()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (sf *SwapFile) NewTempFileChunk(logicChunkIndex LogicChunkIndex) (tc *SwapFileChunk) { |
||||
|
if sf.file == nil { |
||||
|
var err error |
||||
|
sf.file, err = os.CreateTemp(sf.dir, "") |
||||
|
if err != nil { |
||||
|
glog.Errorf("create swap file: %v", err) |
||||
|
return nil |
||||
|
} |
||||
|
} |
||||
|
actualChunkIndex, found := sf.logicToActualChunkIndex[logicChunkIndex] |
||||
|
if !found { |
||||
|
actualChunkIndex = ActualChunkIndex(len(sf.logicToActualChunkIndex)) |
||||
|
sf.logicToActualChunkIndex[logicChunkIndex] = actualChunkIndex |
||||
|
} |
||||
|
|
||||
|
return &SwapFileChunk{ |
||||
|
swapfile: sf, |
||||
|
usage: newChunkWrittenIntervalList(), |
||||
|
logicChunkIndex: logicChunkIndex, |
||||
|
actualChunkIndex: actualChunkIndex, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (sc *SwapFileChunk) FreeResource() { |
||||
|
} |
||||
|
|
||||
|
func (sc *SwapFileChunk) WriteDataAt(src []byte, offset int64) (n int) { |
||||
|
innerOffset := offset % sc.swapfile.chunkSize |
||||
|
var err error |
||||
|
n, err = sc.swapfile.file.WriteAt(src, int64(sc.actualChunkIndex)*sc.swapfile.chunkSize+innerOffset) |
||||
|
if err == nil { |
||||
|
sc.usage.MarkWritten(innerOffset, innerOffset+int64(n)) |
||||
|
} else { |
||||
|
glog.Errorf("failed to write swap file %s: %v", sc.swapfile.file.Name(), err) |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (sc *SwapFileChunk) ReadDataAt(p []byte, off int64) (maxStop int64) { |
||||
|
chunkStartOffset := int64(sc.logicChunkIndex) * sc.swapfile.chunkSize |
||||
|
for t := sc.usage.head.next; t != sc.usage.tail; t = t.next { |
||||
|
logicStart := max(off, chunkStartOffset+t.StartOffset) |
||||
|
logicStop := min(off+int64(len(p)), chunkStartOffset+t.stopOffset) |
||||
|
if logicStart < logicStop { |
||||
|
actualStart := logicStart - chunkStartOffset + int64(sc.actualChunkIndex)*sc.swapfile.chunkSize |
||||
|
if _, err := sc.swapfile.file.ReadAt(p[logicStart-off:logicStop-off], actualStart); err != nil { |
||||
|
glog.Errorf("failed to reading swap file %s: %v", sc.swapfile.file.Name(), err) |
||||
|
break |
||||
|
} |
||||
|
maxStop = max(maxStop, logicStop) |
||||
|
} |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (sc *SwapFileChunk) IsComplete() bool { |
||||
|
return sc.usage.IsComplete(sc.swapfile.chunkSize) |
||||
|
} |
||||
|
|
||||
|
func (sc *SwapFileChunk) WrittenSize() int64 { |
||||
|
return sc.usage.WrittenSize() |
||||
|
} |
||||
|
|
||||
|
func (sc *SwapFileChunk) SaveContent(saveFn SaveToStorageFunc) { |
||||
|
if saveFn == nil { |
||||
|
return |
||||
|
} |
||||
|
for t := sc.usage.head.next; t != sc.usage.tail; t = t.next { |
||||
|
data := mem.Allocate(int(t.Size())) |
||||
|
sc.swapfile.file.ReadAt(data, t.StartOffset+int64(sc.actualChunkIndex)*sc.swapfile.chunkSize) |
||||
|
reader := util.NewBytesReader(data) |
||||
|
saveFn(reader, int64(sc.logicChunkIndex)*sc.swapfile.chunkSize+t.StartOffset, t.Size(), func() { |
||||
|
}) |
||||
|
mem.Free(data) |
||||
|
} |
||||
|
sc.usage = newChunkWrittenIntervalList() |
||||
|
} |
@ -0,0 +1,182 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
"github.com/chrislusf/seaweedfs/weed/util" |
||||
|
"sync" |
||||
|
"sync/atomic" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type LogicChunkIndex int |
||||
|
|
||||
|
type UploadPipeline struct { |
||||
|
filepath util.FullPath |
||||
|
ChunkSize int64 |
||||
|
writableChunks map[LogicChunkIndex]PageChunk |
||||
|
writableChunksLock sync.Mutex |
||||
|
sealedChunks map[LogicChunkIndex]*SealedChunk |
||||
|
sealedChunksLock sync.Mutex |
||||
|
uploaders *util.LimitedConcurrentExecutor |
||||
|
uploaderCount int32 |
||||
|
uploaderCountCond *sync.Cond |
||||
|
saveToStorageFn SaveToStorageFunc |
||||
|
activeReadChunks map[LogicChunkIndex]int |
||||
|
activeReadChunksLock sync.Mutex |
||||
|
bufferChunkLimit int |
||||
|
} |
||||
|
|
||||
|
type SealedChunk struct { |
||||
|
chunk PageChunk |
||||
|
referenceCounter int // track uploading or reading processes
|
||||
|
} |
||||
|
|
||||
|
func (sc *SealedChunk) FreeReference(messageOnFree string) { |
||||
|
sc.referenceCounter-- |
||||
|
if sc.referenceCounter == 0 { |
||||
|
glog.V(4).Infof("Free sealed chunk: %s", messageOnFree) |
||||
|
sc.chunk.FreeResource() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func NewUploadPipeline(writers *util.LimitedConcurrentExecutor, chunkSize int64, saveToStorageFn SaveToStorageFunc, bufferChunkLimit int) *UploadPipeline { |
||||
|
return &UploadPipeline{ |
||||
|
ChunkSize: chunkSize, |
||||
|
writableChunks: make(map[LogicChunkIndex]PageChunk), |
||||
|
sealedChunks: make(map[LogicChunkIndex]*SealedChunk), |
||||
|
uploaders: writers, |
||||
|
uploaderCountCond: sync.NewCond(&sync.Mutex{}), |
||||
|
saveToStorageFn: saveToStorageFn, |
||||
|
activeReadChunks: make(map[LogicChunkIndex]int), |
||||
|
bufferChunkLimit: bufferChunkLimit, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) SaveDataAt(p []byte, off int64) (n int) { |
||||
|
up.writableChunksLock.Lock() |
||||
|
defer up.writableChunksLock.Unlock() |
||||
|
|
||||
|
logicChunkIndex := LogicChunkIndex(off / up.ChunkSize) |
||||
|
|
||||
|
memChunk, found := up.writableChunks[logicChunkIndex] |
||||
|
if !found { |
||||
|
if len(up.writableChunks) < up.bufferChunkLimit { |
||||
|
memChunk = NewMemChunk(logicChunkIndex, up.ChunkSize) |
||||
|
} else { |
||||
|
fullestChunkIndex, fullness := LogicChunkIndex(-1), int64(0) |
||||
|
for lci, mc := range up.writableChunks { |
||||
|
chunkFullness := mc.WrittenSize() |
||||
|
if fullness < chunkFullness { |
||||
|
fullestChunkIndex = lci |
||||
|
fullness = chunkFullness |
||||
|
} |
||||
|
} |
||||
|
up.moveToSealed(up.writableChunks[fullestChunkIndex], fullestChunkIndex) |
||||
|
delete(up.writableChunks, fullestChunkIndex) |
||||
|
fmt.Printf("flush chunk %d with %d bytes written", logicChunkIndex, fullness) |
||||
|
memChunk = NewMemChunk(logicChunkIndex, up.ChunkSize) |
||||
|
} |
||||
|
up.writableChunks[logicChunkIndex] = memChunk |
||||
|
} |
||||
|
n = memChunk.WriteDataAt(p, off) |
||||
|
up.maybeMoveToSealed(memChunk, logicChunkIndex) |
||||
|
|
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) MaybeReadDataAt(p []byte, off int64) (maxStop int64) { |
||||
|
logicChunkIndex := LogicChunkIndex(off / up.ChunkSize) |
||||
|
|
||||
|
// read from sealed chunks first
|
||||
|
up.sealedChunksLock.Lock() |
||||
|
sealedChunk, found := up.sealedChunks[logicChunkIndex] |
||||
|
if found { |
||||
|
sealedChunk.referenceCounter++ |
||||
|
} |
||||
|
up.sealedChunksLock.Unlock() |
||||
|
if found { |
||||
|
maxStop = sealedChunk.chunk.ReadDataAt(p, off) |
||||
|
glog.V(4).Infof("%s read sealed memchunk [%d,%d)", up.filepath, off, maxStop) |
||||
|
sealedChunk.FreeReference(fmt.Sprintf("%s finish reading chunk %d", up.filepath, logicChunkIndex)) |
||||
|
} |
||||
|
|
||||
|
// read from writable chunks last
|
||||
|
up.writableChunksLock.Lock() |
||||
|
defer up.writableChunksLock.Unlock() |
||||
|
writableChunk, found := up.writableChunks[logicChunkIndex] |
||||
|
if !found { |
||||
|
return |
||||
|
} |
||||
|
writableMaxStop := writableChunk.ReadDataAt(p, off) |
||||
|
glog.V(4).Infof("%s read writable memchunk [%d,%d)", up.filepath, off, writableMaxStop) |
||||
|
maxStop = max(maxStop, writableMaxStop) |
||||
|
|
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) FlushAll() { |
||||
|
up.writableChunksLock.Lock() |
||||
|
defer up.writableChunksLock.Unlock() |
||||
|
|
||||
|
for logicChunkIndex, memChunk := range up.writableChunks { |
||||
|
up.moveToSealed(memChunk, logicChunkIndex) |
||||
|
} |
||||
|
|
||||
|
up.waitForCurrentWritersToComplete() |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) maybeMoveToSealed(memChunk PageChunk, logicChunkIndex LogicChunkIndex) { |
||||
|
if memChunk.IsComplete() { |
||||
|
up.moveToSealed(memChunk, logicChunkIndex) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) moveToSealed(memChunk PageChunk, logicChunkIndex LogicChunkIndex) { |
||||
|
atomic.AddInt32(&up.uploaderCount, 1) |
||||
|
glog.V(4).Infof("%s uploaderCount %d ++> %d", up.filepath, up.uploaderCount-1, up.uploaderCount) |
||||
|
|
||||
|
up.sealedChunksLock.Lock() |
||||
|
|
||||
|
if oldMemChunk, found := up.sealedChunks[logicChunkIndex]; found { |
||||
|
oldMemChunk.FreeReference(fmt.Sprintf("%s replace chunk %d", up.filepath, logicChunkIndex)) |
||||
|
} |
||||
|
sealedChunk := &SealedChunk{ |
||||
|
chunk: memChunk, |
||||
|
referenceCounter: 1, // default 1 is for uploading process
|
||||
|
} |
||||
|
up.sealedChunks[logicChunkIndex] = sealedChunk |
||||
|
delete(up.writableChunks, logicChunkIndex) |
||||
|
|
||||
|
up.sealedChunksLock.Unlock() |
||||
|
|
||||
|
up.uploaders.Execute(func() { |
||||
|
// first add to the file chunks
|
||||
|
sealedChunk.chunk.SaveContent(up.saveToStorageFn) |
||||
|
|
||||
|
// notify waiting process
|
||||
|
atomic.AddInt32(&up.uploaderCount, -1) |
||||
|
glog.V(4).Infof("%s uploaderCount %d --> %d", up.filepath, up.uploaderCount+1, up.uploaderCount) |
||||
|
// Lock and Unlock are not required,
|
||||
|
// but it may signal multiple times during one wakeup,
|
||||
|
// and the waiting goroutine may miss some of them!
|
||||
|
up.uploaderCountCond.L.Lock() |
||||
|
up.uploaderCountCond.Broadcast() |
||||
|
up.uploaderCountCond.L.Unlock() |
||||
|
|
||||
|
// wait for readers
|
||||
|
for up.IsLocked(logicChunkIndex) { |
||||
|
time.Sleep(59 * time.Millisecond) |
||||
|
} |
||||
|
|
||||
|
// then remove from sealed chunks
|
||||
|
up.sealedChunksLock.Lock() |
||||
|
defer up.sealedChunksLock.Unlock() |
||||
|
delete(up.sealedChunks, logicChunkIndex) |
||||
|
sealedChunk.FreeReference(fmt.Sprintf("%s finished uploading chunk %d", up.filepath, logicChunkIndex)) |
||||
|
|
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) Shutdown() { |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"sync/atomic" |
||||
|
) |
||||
|
|
||||
|
func (up *UploadPipeline) LockForRead(startOffset, stopOffset int64) { |
||||
|
startLogicChunkIndex := LogicChunkIndex(startOffset / up.ChunkSize) |
||||
|
stopLogicChunkIndex := LogicChunkIndex(stopOffset / up.ChunkSize) |
||||
|
if stopOffset%up.ChunkSize > 0 { |
||||
|
stopLogicChunkIndex += 1 |
||||
|
} |
||||
|
up.activeReadChunksLock.Lock() |
||||
|
defer up.activeReadChunksLock.Unlock() |
||||
|
for i := startLogicChunkIndex; i < stopLogicChunkIndex; i++ { |
||||
|
if count, found := up.activeReadChunks[i]; found { |
||||
|
up.activeReadChunks[i] = count + 1 |
||||
|
} else { |
||||
|
up.activeReadChunks[i] = 1 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) UnlockForRead(startOffset, stopOffset int64) { |
||||
|
startLogicChunkIndex := LogicChunkIndex(startOffset / up.ChunkSize) |
||||
|
stopLogicChunkIndex := LogicChunkIndex(stopOffset / up.ChunkSize) |
||||
|
if stopOffset%up.ChunkSize > 0 { |
||||
|
stopLogicChunkIndex += 1 |
||||
|
} |
||||
|
up.activeReadChunksLock.Lock() |
||||
|
defer up.activeReadChunksLock.Unlock() |
||||
|
for i := startLogicChunkIndex; i < stopLogicChunkIndex; i++ { |
||||
|
if count, found := up.activeReadChunks[i]; found { |
||||
|
if count == 1 { |
||||
|
delete(up.activeReadChunks, i) |
||||
|
} else { |
||||
|
up.activeReadChunks[i] = count - 1 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) IsLocked(logicChunkIndex LogicChunkIndex) bool { |
||||
|
up.activeReadChunksLock.Lock() |
||||
|
defer up.activeReadChunksLock.Unlock() |
||||
|
if count, found := up.activeReadChunks[logicChunkIndex]; found { |
||||
|
return count > 0 |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (up *UploadPipeline) waitForCurrentWritersToComplete() { |
||||
|
up.uploaderCountCond.L.Lock() |
||||
|
t := int32(100) |
||||
|
for { |
||||
|
t = atomic.LoadInt32(&up.uploaderCount) |
||||
|
if t <= 0 { |
||||
|
break |
||||
|
} |
||||
|
up.uploaderCountCond.Wait() |
||||
|
} |
||||
|
up.uploaderCountCond.L.Unlock() |
||||
|
} |
@ -0,0 +1,47 @@ |
|||||
|
package page_writer |
||||
|
|
||||
|
import ( |
||||
|
"github.com/chrislusf/seaweedfs/weed/util" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func TestUploadPipeline(t *testing.T) { |
||||
|
|
||||
|
uploadPipeline := NewUploadPipeline(nil, 2*1024*1024, nil, 16) |
||||
|
|
||||
|
writeRange(uploadPipeline, 0, 131072) |
||||
|
writeRange(uploadPipeline, 131072, 262144) |
||||
|
writeRange(uploadPipeline, 262144, 1025536) |
||||
|
|
||||
|
confirmRange(t, uploadPipeline, 0, 1025536) |
||||
|
|
||||
|
writeRange(uploadPipeline, 1025536, 1296896) |
||||
|
|
||||
|
confirmRange(t, uploadPipeline, 1025536, 1296896) |
||||
|
|
||||
|
writeRange(uploadPipeline, 1296896, 2162688) |
||||
|
|
||||
|
confirmRange(t, uploadPipeline, 1296896, 2162688) |
||||
|
|
||||
|
confirmRange(t, uploadPipeline, 1296896, 2162688) |
||||
|
} |
||||
|
|
||||
|
// startOff and stopOff must be divided by 4
|
||||
|
func writeRange(uploadPipeline *UploadPipeline, startOff, stopOff int64) { |
||||
|
p := make([]byte, 4) |
||||
|
for i := startOff / 4; i < stopOff/4; i += 4 { |
||||
|
util.Uint32toBytes(p, uint32(i)) |
||||
|
uploadPipeline.SaveDataAt(p, i) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func confirmRange(t *testing.T, uploadPipeline *UploadPipeline, startOff, stopOff int64) { |
||||
|
p := make([]byte, 4) |
||||
|
for i := startOff; i < stopOff/4; i += 4 { |
||||
|
uploadPipeline.MaybeReadDataAt(p, i) |
||||
|
x := util.BytesToUint32(p) |
||||
|
if x != uint32(i) { |
||||
|
t.Errorf("expecting %d found %d at offset [%d,%d)", i, x, i, i+4) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,84 @@ |
|||||
|
package mount |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
|
||||
|
"github.com/chrislusf/seaweedfs/weed/filer" |
||||
|
"github.com/chrislusf/seaweedfs/weed/glog" |
||||
|
"github.com/chrislusf/seaweedfs/weed/operation" |
||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
||||
|
"github.com/chrislusf/seaweedfs/weed/security" |
||||
|
"github.com/chrislusf/seaweedfs/weed/util" |
||||
|
) |
||||
|
|
||||
|
func (wfs *WFS) saveDataAsChunk(fullPath util.FullPath) filer.SaveDataAsChunkFunctionType { |
||||
|
|
||||
|
return func(reader io.Reader, filename string, offset int64) (chunk *filer_pb.FileChunk, collection, replication string, err error) { |
||||
|
var fileId, host string |
||||
|
var auth security.EncodedJwt |
||||
|
|
||||
|
if err := wfs.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { |
||||
|
return util.Retry("assignVolume", func() error { |
||||
|
request := &filer_pb.AssignVolumeRequest{ |
||||
|
Count: 1, |
||||
|
Replication: wfs.option.Replication, |
||||
|
Collection: wfs.option.Collection, |
||||
|
TtlSec: wfs.option.TtlSec, |
||||
|
DiskType: string(wfs.option.DiskType), |
||||
|
DataCenter: wfs.option.DataCenter, |
||||
|
Path: string(fullPath), |
||||
|
} |
||||
|
|
||||
|
resp, err := client.AssignVolume(context.Background(), request) |
||||
|
if err != nil { |
||||
|
glog.V(0).Infof("assign volume failure %v: %v", request, err) |
||||
|
return err |
||||
|
} |
||||
|
if resp.Error != "" { |
||||
|
return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) |
||||
|
} |
||||
|
|
||||
|
fileId, auth = resp.FileId, security.EncodedJwt(resp.Auth) |
||||
|
loc := resp.Location |
||||
|
host = wfs.AdjustedUrl(loc) |
||||
|
collection, replication = resp.Collection, resp.Replication |
||||
|
|
||||
|
return nil |
||||
|
}) |
||||
|
}); err != nil { |
||||
|
return nil, "", "", fmt.Errorf("filerGrpcAddress assign volume: %v", err) |
||||
|
} |
||||
|
|
||||
|
fileUrl := fmt.Sprintf("http://%s/%s", host, fileId) |
||||
|
if wfs.option.VolumeServerAccess == "filerProxy" { |
||||
|
fileUrl = fmt.Sprintf("http://%s/?proxyChunkId=%s", wfs.getCurrentFiler(), fileId) |
||||
|
} |
||||
|
uploadOption := &operation.UploadOption{ |
||||
|
UploadUrl: fileUrl, |
||||
|
Filename: filename, |
||||
|
Cipher: wfs.option.Cipher, |
||||
|
IsInputCompressed: false, |
||||
|
MimeType: "", |
||||
|
PairMap: nil, |
||||
|
Jwt: auth, |
||||
|
} |
||||
|
uploadResult, err, data := operation.Upload(reader, uploadOption) |
||||
|
if err != nil { |
||||
|
glog.V(0).Infof("upload data %v to %s: %v", filename, fileUrl, err) |
||||
|
return nil, "", "", fmt.Errorf("upload data: %v", err) |
||||
|
} |
||||
|
if uploadResult.Error != "" { |
||||
|
glog.V(0).Infof("upload failure %v to %s: %v", filename, fileUrl, err) |
||||
|
return nil, "", "", fmt.Errorf("upload result: %v", uploadResult.Error) |
||||
|
} |
||||
|
|
||||
|
if offset == 0 { |
||||
|
wfs.chunkCache.SetChunk(fileId, data) |
||||
|
} |
||||
|
|
||||
|
chunk = uploadResult.ToPbFileChunk(fileId, offset) |
||||
|
return chunk, collection, replication, nil |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue