mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-23 01:27:04 +00:00
https://github.com/XTLS/Xray-core/pull/5414#issuecomment-3796734827 https://github.com/XTLS/Xray-core/pull/5581#issuecomment-3797134147 https://github.com/XTLS/Xray-core/pull/5645#issuecomment-3899873945 https://github.com/XTLS/Xray-core/pull/6745#issuecomment-5627420177 https://github.com/XTLS/Xray-core/pull/6748#issuecomment-5740443122 --------- Co-authored-by: Risaro <62798663+Risaro@users.noreply.github.com>
446 lines
7.8 KiB
Go
446 lines
7.8 KiB
Go
package xdrive
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/errors"
|
|
)
|
|
|
|
const (
|
|
maxCoalescedTicks = 8
|
|
|
|
segSuffix = ".seg"
|
|
endSuffix = ".end"
|
|
errSuffix = ".err"
|
|
)
|
|
|
|
func objectName(prefix string, seq int64, suffix string) string {
|
|
return fmt.Sprintf("%s/%09d%s", prefix, seq, suffix)
|
|
}
|
|
|
|
func parseEntry(name string) (int64, bool) {
|
|
dot := strings.LastIndexByte(name, '.')
|
|
if dot < 0 {
|
|
return 0, false
|
|
}
|
|
switch name[dot:] {
|
|
case segSuffix, endSuffix, errSuffix:
|
|
default:
|
|
return 0, false
|
|
}
|
|
seq, err := strconv.ParseInt(name[:dot], 10, 64)
|
|
if err != nil || seq < 0 {
|
|
return 0, false
|
|
}
|
|
return seq, true
|
|
}
|
|
|
|
type walWriter struct {
|
|
ctx context.Context
|
|
storage Storage
|
|
prefix string
|
|
params
|
|
sem chan struct{}
|
|
wg sync.WaitGroup
|
|
|
|
mu sync.Mutex
|
|
buf []byte
|
|
seq int64
|
|
lastSize int
|
|
held int
|
|
closed bool
|
|
err error
|
|
}
|
|
|
|
func newWALWriter(ctx context.Context, storage Storage, prefix string, p params) *walWriter {
|
|
w := &walWriter{
|
|
ctx: ctx,
|
|
storage: storage,
|
|
prefix: prefix,
|
|
params: p,
|
|
sem: make(chan struct{}, p.concurrency),
|
|
}
|
|
go w.flushLoop()
|
|
return w
|
|
}
|
|
|
|
func (w *walWriter) Write(p []byte) (int, error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
if w.err != nil {
|
|
return 0, w.err
|
|
}
|
|
if w.closed {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
|
|
w.buf = append(w.buf, p...)
|
|
for len(w.buf) >= w.segmentBytes {
|
|
if err := w.flushLocked(); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (w *walWriter) flushLoop() {
|
|
ticker := time.NewTicker(w.flushInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-w.ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
w.mu.Lock()
|
|
if !w.closed && w.err == nil && len(w.buf) > 0 && w.readyToFlush() {
|
|
w.flushLocked()
|
|
}
|
|
done := w.closed || w.err != nil
|
|
w.mu.Unlock()
|
|
if done {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *walWriter) readyToFlush() bool {
|
|
grew := len(w.buf) > w.lastSize
|
|
w.lastSize = len(w.buf)
|
|
|
|
if grew && w.held < maxCoalescedTicks {
|
|
w.held++
|
|
return false
|
|
}
|
|
w.held = 0
|
|
return true
|
|
}
|
|
|
|
func (w *walWriter) flushLocked() error {
|
|
if len(w.buf) == 0 {
|
|
return nil
|
|
}
|
|
if w.err != nil {
|
|
return w.err
|
|
}
|
|
|
|
n := len(w.buf)
|
|
if n > w.segmentBytes {
|
|
n = w.segmentBytes
|
|
}
|
|
|
|
chunk := make([]byte, n)
|
|
copy(chunk, w.buf[:n])
|
|
seq := w.seq
|
|
w.seq++
|
|
|
|
if n == len(w.buf) {
|
|
w.buf = w.buf[:0]
|
|
} else {
|
|
w.buf = append(w.buf[:0], w.buf[n:]...)
|
|
}
|
|
w.lastSize = len(w.buf)
|
|
w.held = 0
|
|
|
|
w.wg.Add(1)
|
|
go w.upload(seq, chunk)
|
|
return nil
|
|
}
|
|
|
|
func (w *walWriter) upload(seq int64, chunk []byte) {
|
|
defer w.wg.Done()
|
|
|
|
select {
|
|
case w.sem <- struct{}{}:
|
|
case <-w.ctx.Done():
|
|
return
|
|
}
|
|
defer func() { <-w.sem }()
|
|
|
|
if err := w.storage.Put(w.ctx, objectName(w.prefix, seq, segSuffix), chunk); err != nil {
|
|
w.mu.Lock()
|
|
if w.err == nil {
|
|
w.err = errors.New("failed to store segment").Base(err)
|
|
}
|
|
w.mu.Unlock()
|
|
w.storage.Put(w.ctx, objectName(w.prefix, seq, errSuffix), nil)
|
|
}
|
|
}
|
|
|
|
func (w *walWriter) Close() error {
|
|
w.mu.Lock()
|
|
if w.closed {
|
|
w.mu.Unlock()
|
|
return nil
|
|
}
|
|
w.closed = true
|
|
for len(w.buf) > 0 && w.err == nil {
|
|
w.flushLocked()
|
|
}
|
|
w.mu.Unlock()
|
|
|
|
w.wg.Wait()
|
|
|
|
w.mu.Lock()
|
|
err, seq := w.err, w.seq
|
|
w.mu.Unlock()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return w.storage.Put(w.ctx, objectName(w.prefix, seq, endSuffix), nil)
|
|
}
|
|
|
|
type walReader struct {
|
|
ctx context.Context
|
|
storage Storage
|
|
prefix string
|
|
params
|
|
seq int64
|
|
|
|
ch chan []byte
|
|
discards chan string
|
|
wake chan struct{}
|
|
holeSince time.Time
|
|
|
|
errMu sync.Mutex
|
|
err error
|
|
}
|
|
|
|
func newWALReader(ctx context.Context, storage Storage, prefix string, p params) *walReader {
|
|
r := &walReader{
|
|
ctx: ctx,
|
|
storage: storage,
|
|
prefix: prefix,
|
|
params: p,
|
|
ch: make(chan []byte, p.concurrency),
|
|
discards: make(chan string, 4*p.concurrency),
|
|
wake: make(chan struct{}, 1),
|
|
}
|
|
go r.run()
|
|
go r.discardLoop()
|
|
return r
|
|
}
|
|
|
|
func (r *walReader) Wake() {
|
|
select {
|
|
case r.wake <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (r *walReader) run() {
|
|
defer close(r.ch)
|
|
|
|
delay := r.minPollInterval
|
|
active := time.Now()
|
|
for {
|
|
polled := time.Now()
|
|
advanced, eof, err := r.poll()
|
|
if err != nil {
|
|
r.setErr(err)
|
|
return
|
|
}
|
|
if eof {
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case advanced:
|
|
active = time.Now()
|
|
delay = r.minPollInterval
|
|
case time.Since(active) < r.eagerWindow:
|
|
delay = r.minPollInterval
|
|
default:
|
|
delay *= 2
|
|
if delay > r.maxPollInterval {
|
|
delay = r.maxPollInterval
|
|
}
|
|
}
|
|
|
|
timer := time.NewTimer(delay)
|
|
select {
|
|
case <-r.ctx.Done():
|
|
timer.Stop()
|
|
return
|
|
case <-timer.C:
|
|
case <-r.wake:
|
|
timer.Stop()
|
|
active = time.Now()
|
|
delay = r.minPollInterval
|
|
if rest := r.minPollInterval - time.Since(polled); rest > 0 {
|
|
select {
|
|
case <-r.ctx.Done():
|
|
return
|
|
case <-time.After(rest):
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *walReader) poll() (advanced, eof bool, err error) {
|
|
listed, err := r.storage.List(r.ctx, r.prefix)
|
|
if err != nil {
|
|
return false, false, err
|
|
}
|
|
if len(listed) == 0 {
|
|
return false, false, nil
|
|
}
|
|
|
|
pending := make(map[int64]Entry, len(listed))
|
|
ahead := false
|
|
for _, entry := range listed {
|
|
seq, ok := parseEntry(entry.Name)
|
|
if !ok {
|
|
continue
|
|
}
|
|
pending[seq] = entry
|
|
if seq > r.seq {
|
|
ahead = true
|
|
}
|
|
}
|
|
|
|
if _, ok := pending[r.seq]; !ok && ahead {
|
|
if r.holeSince.IsZero() {
|
|
r.holeSince = time.Now()
|
|
} else if time.Since(r.holeSince) >= r.holeTimeout {
|
|
return false, false, errors.New("segment ", r.seq,
|
|
" never arrived while later ones did, the peer lost it")
|
|
}
|
|
} else {
|
|
r.holeSince = time.Time{}
|
|
}
|
|
|
|
for {
|
|
if entry, ok := pending[r.seq]; ok && strings.HasSuffix(entry.Name, errSuffix) {
|
|
r.discard(r.prefix + "/" + entry.Name)
|
|
return advanced, false, errors.New("the peer could not store segment ", r.seq)
|
|
}
|
|
|
|
batch, done := r.nextBatch(pending)
|
|
if done {
|
|
return advanced, true, nil
|
|
}
|
|
if len(batch) == 0 {
|
|
return advanced, false, nil
|
|
}
|
|
|
|
chunks, err := r.fetch(batch)
|
|
if err != nil {
|
|
if err == errNotFound {
|
|
return advanced, false, nil
|
|
}
|
|
return advanced, false, err
|
|
}
|
|
|
|
for i, chunk := range chunks {
|
|
select {
|
|
case r.ch <- chunk:
|
|
case <-r.ctx.Done():
|
|
return advanced, true, nil
|
|
}
|
|
r.seq++
|
|
advanced = true
|
|
r.discard(r.prefix + "/" + batch[i].Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *walReader) nextBatch(pending map[int64]Entry) (batch []Entry, done bool) {
|
|
for i := 0; i < r.concurrency; i++ {
|
|
entry, ok := pending[r.seq+int64(i)]
|
|
if !ok {
|
|
break
|
|
}
|
|
if !strings.HasSuffix(entry.Name, segSuffix) {
|
|
if i == 0 && strings.HasSuffix(entry.Name, endSuffix) {
|
|
r.discard(r.prefix + "/" + entry.Name)
|
|
return nil, true
|
|
}
|
|
break
|
|
}
|
|
batch = append(batch, entry)
|
|
}
|
|
return batch, false
|
|
}
|
|
|
|
func (r *walReader) fetch(batch []Entry) ([][]byte, error) {
|
|
chunks := make([][]byte, len(batch))
|
|
failures := make([]error, len(batch))
|
|
|
|
var wg sync.WaitGroup
|
|
for i, entry := range batch {
|
|
if entry.Inline != nil {
|
|
chunks[i] = entry.Inline
|
|
continue
|
|
}
|
|
wg.Add(1)
|
|
go func(i int, name string) {
|
|
defer wg.Done()
|
|
chunks[i], failures[i] = r.storage.Get(r.ctx, r.prefix+"/"+name)
|
|
}(i, entry.Name)
|
|
}
|
|
wg.Wait()
|
|
|
|
for i := range batch {
|
|
if failures[i] != nil {
|
|
return nil, failures[i]
|
|
}
|
|
}
|
|
return chunks, nil
|
|
}
|
|
|
|
func (r *walReader) discard(name string) {
|
|
select {
|
|
case r.discards <- name:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (r *walReader) discardLoop() {
|
|
var wg sync.WaitGroup
|
|
defer wg.Wait()
|
|
|
|
sem := make(chan struct{}, r.concurrency)
|
|
for {
|
|
select {
|
|
case <-r.ctx.Done():
|
|
return
|
|
case name := <-r.discards:
|
|
sem <- struct{}{}
|
|
wg.Add(1)
|
|
go func(name string) {
|
|
defer wg.Done()
|
|
defer func() { <-sem }()
|
|
r.storage.Delete(r.ctx, name)
|
|
}(name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *walReader) setErr(err error) {
|
|
r.errMu.Lock()
|
|
defer r.errMu.Unlock()
|
|
if r.err == nil {
|
|
r.err = err
|
|
}
|
|
}
|
|
|
|
func (r *walReader) Err() error {
|
|
r.errMu.Lock()
|
|
defer r.errMu.Unlock()
|
|
if r.err != nil {
|
|
return r.err
|
|
}
|
|
return io.EOF
|
|
}
|