github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/sync/pipe.go (about) 1 package sync 2 3 import ( 4 "context" 5 "math/bits" 6 "strconv" 7 "strings" 8 "sync" 9 10 "github.com/aalpar/deheap" 11 "github.com/pkg/errors" 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/fserrors" 14 ) 15 16 // compare two items for order by 17 type lessFn func(a, b fs.ObjectPair) bool 18 19 // pipe provides an unbounded channel like experience 20 // 21 // Note unlike channels these aren't strictly ordered. 22 type pipe struct { 23 mu sync.Mutex 24 c chan struct{} 25 queue []fs.ObjectPair 26 closed bool 27 totalSize int64 28 stats func(items int, totalSize int64) 29 less lessFn 30 fraction int 31 } 32 33 func newPipe(orderBy string, stats func(items int, totalSize int64), maxBacklog int) (*pipe, error) { 34 if maxBacklog < 0 { 35 maxBacklog = (1 << (bits.UintSize - 1)) - 1 // largest posititive int 36 } 37 less, fraction, err := newLess(orderBy) 38 if err != nil { 39 return nil, fserrors.FatalError(err) 40 } 41 p := &pipe{ 42 c: make(chan struct{}, maxBacklog), 43 stats: stats, 44 less: less, 45 fraction: fraction, 46 } 47 if p.less != nil { 48 deheap.Init(p) 49 } 50 return p, nil 51 } 52 53 // Len satisfy heap.Interface - must be called with lock held 54 func (p *pipe) Len() int { 55 return len(p.queue) 56 } 57 58 // Len satisfy heap.Interface - must be called with lock held 59 func (p *pipe) Less(i, j int) bool { 60 return p.less(p.queue[i], p.queue[j]) 61 } 62 63 // Swap satisfy heap.Interface - must be called with lock held 64 func (p *pipe) Swap(i, j int) { 65 p.queue[i], p.queue[j] = p.queue[j], p.queue[i] 66 } 67 68 // Push satisfy heap.Interface - must be called with lock held 69 func (p *pipe) Push(item interface{}) { 70 p.queue = append(p.queue, item.(fs.ObjectPair)) 71 } 72 73 // Pop satisfy heap.Interface - must be called with lock held 74 func (p *pipe) Pop() interface{} { 75 old := p.queue 76 n := len(old) 77 item := old[n-1] 78 old[n-1] = fs.ObjectPair{} // avoid memory leak 79 p.queue = old[0 : n-1] 80 return item 81 } 82 83 // Put a pair into the pipe 84 // 85 // It returns ok = false if the context was cancelled 86 // 87 // It will panic if you call it after Close() 88 func (p *pipe) Put(ctx context.Context, pair fs.ObjectPair) (ok bool) { 89 if ctx.Err() != nil { 90 return false 91 } 92 p.mu.Lock() 93 if p.less == nil { 94 // no order-by 95 p.queue = append(p.queue, pair) 96 } else { 97 deheap.Push(p, pair) 98 } 99 size := pair.Src.Size() 100 if size > 0 { 101 p.totalSize += size 102 } 103 p.stats(len(p.queue), p.totalSize) 104 p.mu.Unlock() 105 select { 106 case <-ctx.Done(): 107 return false 108 case p.c <- struct{}{}: 109 } 110 return true 111 } 112 113 // Get a pair from the pipe 114 // 115 // If fraction is > the mixed fraction set in the pipe then it gets it 116 // from the other end of the heap if order-by is in effect 117 // 118 // It returns ok = false if the context was cancelled or Close() has 119 // been called. 120 func (p *pipe) GetMax(ctx context.Context, fraction int) (pair fs.ObjectPair, ok bool) { 121 if ctx.Err() != nil { 122 return 123 } 124 select { 125 case <-ctx.Done(): 126 return 127 case _, ok = <-p.c: 128 if !ok { 129 return 130 } 131 } 132 p.mu.Lock() 133 if p.less == nil { 134 // no order-by 135 pair = p.queue[0] 136 p.queue[0] = fs.ObjectPair{} // avoid memory leak 137 p.queue = p.queue[1:] 138 } else if p.fraction < 0 || fraction < p.fraction { 139 pair = deheap.Pop(p).(fs.ObjectPair) 140 } else { 141 pair = deheap.PopMax(p).(fs.ObjectPair) 142 } 143 size := pair.Src.Size() 144 if size > 0 { 145 p.totalSize -= size 146 } 147 if p.totalSize < 0 { 148 p.totalSize = 0 149 } 150 p.stats(len(p.queue), p.totalSize) 151 p.mu.Unlock() 152 return pair, true 153 } 154 155 // Get a pair from the pipe 156 // 157 // It returns ok = false if the context was cancelled or Close() has 158 // been called. 159 func (p *pipe) Get(ctx context.Context) (pair fs.ObjectPair, ok bool) { 160 return p.GetMax(ctx, -1) 161 } 162 163 // Stats reads the number of items in the queue and the totalSize 164 func (p *pipe) Stats() (items int, totalSize int64) { 165 p.mu.Lock() 166 items, totalSize = len(p.queue), p.totalSize 167 p.mu.Unlock() 168 return items, totalSize 169 } 170 171 // Close the pipe 172 // 173 // Writes to a closed pipe will panic as will double closing a pipe 174 func (p *pipe) Close() { 175 p.mu.Lock() 176 close(p.c) 177 p.closed = true 178 p.mu.Unlock() 179 } 180 181 // newLess returns a less function for the heap comparison or nil if 182 // one is not required 183 func newLess(orderBy string) (less lessFn, fraction int, err error) { 184 fraction = -1 185 if orderBy == "" { 186 return nil, fraction, nil 187 } 188 parts := strings.Split(strings.ToLower(orderBy), ",") 189 switch parts[0] { 190 case "name": 191 less = func(a, b fs.ObjectPair) bool { 192 return a.Src.Remote() < b.Src.Remote() 193 } 194 case "size": 195 less = func(a, b fs.ObjectPair) bool { 196 return a.Src.Size() < b.Src.Size() 197 } 198 case "modtime": 199 less = func(a, b fs.ObjectPair) bool { 200 ctx := context.Background() 201 return a.Src.ModTime(ctx).Before(b.Src.ModTime(ctx)) 202 } 203 default: 204 return nil, fraction, errors.Errorf("unknown --order-by comparison %q", parts[0]) 205 } 206 descending := false 207 if len(parts) > 1 { 208 switch parts[1] { 209 case "ascending", "asc": 210 case "descending", "desc": 211 descending = true 212 case "mixed": 213 fraction = 50 214 if len(parts) > 2 { 215 fraction, err = strconv.Atoi(parts[2]) 216 if err != nil { 217 return nil, fraction, errors.Errorf("bad mixed fraction --order-by %q", parts[2]) 218 } 219 } 220 221 default: 222 return nil, fraction, errors.Errorf("unknown --order-by sort direction %q", parts[1]) 223 } 224 } 225 if (fraction >= 0 && len(parts) > 3) || (fraction < 0 && len(parts) > 2) { 226 return nil, fraction, errors.Errorf("bad --order-by string %q", orderBy) 227 } 228 if descending { 229 oldLess := less 230 less = func(a, b fs.ObjectPair) bool { 231 return !oldLess(a, b) 232 } 233 } 234 return less, fraction, nil 235 }