package page_writer import ( "fmt" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/util" "github.com/chrislusf/seaweedfs/weed/util/mem" "sync" "sync/atomic" "time" ) type LogicChunkIndex int type UploadPipeline struct { filepath util.FullPath ChunkSize int64 writers *util.LimitedConcurrentExecutor writableChunks map[LogicChunkIndex]*MemChunk writableChunksLock sync.Mutex sealedChunks map[LogicChunkIndex]*SealedChunk sealedChunksLock sync.Mutex activeWriterCond *sync.Cond activeWriterCount int32 activeReadChunks map[LogicChunkIndex]int activeReadChunksLock sync.Mutex saveToStorageFn SaveToStorageFunc } type SealedChunk struct { chunk *MemChunk 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) mem.Free(sc.chunk.buf) } } func NewUploadPipeline(filepath util.FullPath, writers *util.LimitedConcurrentExecutor, chunkSize int64, saveToStorageFn SaveToStorageFunc) *UploadPipeline { return &UploadPipeline{ ChunkSize: chunkSize, writableChunks: make(map[LogicChunkIndex]*MemChunk), sealedChunks: make(map[LogicChunkIndex]*SealedChunk), writers: writers, activeWriterCond: sync.NewCond(&sync.Mutex{}), saveToStorageFn: saveToStorageFn, filepath: filepath, activeReadChunks: make(map[LogicChunkIndex]int), } } func (cw *UploadPipeline) SaveDataAt(p []byte, off int64) (n int) { cw.writableChunksLock.Lock() defer cw.writableChunksLock.Unlock() logicChunkIndex := LogicChunkIndex(off / cw.ChunkSize) offsetRemainder := off % cw.ChunkSize memChunk, found := cw.writableChunks[logicChunkIndex] if !found { memChunk = &MemChunk{ buf: mem.Allocate(int(cw.ChunkSize)), usage: newChunkWrittenIntervalList(), } cw.writableChunks[logicChunkIndex] = memChunk } n = copy(memChunk.buf[offsetRemainder:], p) memChunk.usage.MarkWritten(offsetRemainder, offsetRemainder+int64(n)) cw.maybeMoveToSealed(memChunk, logicChunkIndex) return } func (cw *UploadPipeline) MaybeReadDataAt(p []byte, off int64) (maxStop int64) { logicChunkIndex := LogicChunkIndex(off / cw.ChunkSize) // read from sealed chunks first cw.sealedChunksLock.Lock() sealedChunk, found := cw.sealedChunks[logicChunkIndex] if found { sealedChunk.referenceCounter++ } cw.sealedChunksLock.Unlock() if found { maxStop = readMemChunk(sealedChunk.chunk, p, off, logicChunkIndex, cw.ChunkSize) glog.V(4).Infof("%s read sealed memchunk [%d,%d)", cw.filepath, off, maxStop) sealedChunk.FreeReference(fmt.Sprintf("%s finish reading chunk %d", cw.filepath, logicChunkIndex)) } // read from writable chunks last cw.writableChunksLock.Lock() defer cw.writableChunksLock.Unlock() writableChunk, found := cw.writableChunks[logicChunkIndex] if !found { return } writableMaxStop := readMemChunk(writableChunk, p, off, logicChunkIndex, cw.ChunkSize) glog.V(4).Infof("%s read writable memchunk [%d,%d)", cw.filepath, off, writableMaxStop) maxStop = max(maxStop, writableMaxStop) return } func (cw *UploadPipeline) FlushAll() { cw.writableChunksLock.Lock() defer cw.writableChunksLock.Unlock() for logicChunkIndex, memChunk := range cw.writableChunks { cw.moveToSealed(memChunk, logicChunkIndex) } cw.waitForCurrentWritersToComplete() } func (cw *UploadPipeline) LockForRead(startOffset, stopOffset int64) { startLogicChunkIndex := LogicChunkIndex(startOffset / cw.ChunkSize) stopLogicChunkIndex := LogicChunkIndex(stopOffset / cw.ChunkSize) if stopOffset%cw.ChunkSize > 0 { stopLogicChunkIndex += 1 } cw.activeReadChunksLock.Lock() defer cw.activeReadChunksLock.Unlock() for i := startLogicChunkIndex; i < stopLogicChunkIndex; i++ { if count, found := cw.activeReadChunks[i]; found { cw.activeReadChunks[i] = count + 1 } else { cw.activeReadChunks[i] = 1 } } } func (cw *UploadPipeline) UnlockForRead(startOffset, stopOffset int64) { startLogicChunkIndex := LogicChunkIndex(startOffset / cw.ChunkSize) stopLogicChunkIndex := LogicChunkIndex(stopOffset / cw.ChunkSize) if stopOffset%cw.ChunkSize > 0 { stopLogicChunkIndex += 1 } cw.activeReadChunksLock.Lock() defer cw.activeReadChunksLock.Unlock() for i := startLogicChunkIndex; i < stopLogicChunkIndex; i++ { if count, found := cw.activeReadChunks[i]; found { if count == 1 { delete(cw.activeReadChunks, i) } else { cw.activeReadChunks[i] = count - 1 } } } } func (cw *UploadPipeline) IsLocked(logicChunkIndex LogicChunkIndex) bool { cw.activeReadChunksLock.Lock() defer cw.activeReadChunksLock.Unlock() if count, found := cw.activeReadChunks[logicChunkIndex]; found { return count > 0 } return false } func (cw *UploadPipeline) waitForCurrentWritersToComplete() { cw.activeWriterCond.L.Lock() t := int32(100) for { t = atomic.LoadInt32(&cw.activeWriterCount) if t <= 0 { break } glog.V(4).Infof("activeWriterCond is %d", t) cw.activeWriterCond.Wait() } cw.activeWriterCond.L.Unlock() } func (cw *UploadPipeline) maybeMoveToSealed(memChunk *MemChunk, logicChunkIndex LogicChunkIndex) { if memChunk.usage.IsComplete(cw.ChunkSize) { cw.moveToSealed(memChunk, logicChunkIndex) } } func (cw *UploadPipeline) moveToSealed(memChunk *MemChunk, logicChunkIndex LogicChunkIndex) { atomic.AddInt32(&cw.activeWriterCount, 1) glog.V(4).Infof("%s activeWriterCount %d ++> %d", cw.filepath, cw.activeWriterCount-1, cw.activeWriterCount) cw.sealedChunksLock.Lock() if oldMemChunk, found := cw.sealedChunks[logicChunkIndex]; found { oldMemChunk.FreeReference(fmt.Sprintf("%s replace chunk %d", cw.filepath, logicChunkIndex)) } sealedChunk := &SealedChunk{ chunk: memChunk, referenceCounter: 1, // default 1 is for uploading process } cw.sealedChunks[logicChunkIndex] = sealedChunk delete(cw.writableChunks, logicChunkIndex) cw.sealedChunksLock.Unlock() cw.writers.Execute(func() { // first add to the file chunks cw.saveOneChunk(sealedChunk.chunk, logicChunkIndex) // notify waiting process atomic.AddInt32(&cw.activeWriterCount, -1) glog.V(4).Infof("%s activeWriterCount %d --> %d", cw.filepath, cw.activeWriterCount+1, cw.activeWriterCount) // Lock and Unlock are not required, // but it may signal multiple times during one wakeup, // and the waiting goroutine may miss some of them! cw.activeWriterCond.L.Lock() cw.activeWriterCond.Broadcast() cw.activeWriterCond.L.Unlock() // wait for readers for cw.IsLocked(logicChunkIndex) { time.Sleep(59 * time.Millisecond) } // then remove from sealed chunks cw.sealedChunksLock.Lock() defer cw.sealedChunksLock.Unlock() delete(cw.sealedChunks, logicChunkIndex) sealedChunk.FreeReference(fmt.Sprintf("%s finished uploading chunk %d", cw.filepath, logicChunkIndex)) }) } func (cw *UploadPipeline) saveOneChunk(memChunk *MemChunk, logicChunkIndex LogicChunkIndex) { if cw.saveToStorageFn == nil { return } for t := memChunk.usage.head.next; t != memChunk.usage.tail; t = t.next { reader := util.NewBytesReader(memChunk.buf[t.StartOffset:t.stopOffset]) cw.saveToStorageFn(reader, int64(logicChunkIndex)*cw.ChunkSize+t.StartOffset, t.Size(), func() { }) } } func readMemChunk(memChunk *MemChunk, p []byte, off int64, logicChunkIndex LogicChunkIndex, chunkSize int64) (maxStop int64) { memChunkBaseOffset := int64(logicChunkIndex) * chunkSize for t := memChunk.usage.head.next; t != memChunk.usage.tail; t = t.next { logicStart := max(off, int64(logicChunkIndex)*chunkSize+t.StartOffset) logicStop := min(off+int64(len(p)), memChunkBaseOffset+t.stopOffset) if logicStart < logicStop { copy(p[logicStart-off:logicStop-off], memChunk.buf[logicStart-memChunkBaseOffset:logicStop-memChunkBaseOffset]) maxStop = max(maxStop, logicStop) } } return } func (p2 *UploadPipeline) Shutdown() { }