implemented priority preloading
This commit is contained in:
parent
86bd1cd1e5
commit
eb6272e411
7 changed files with 134 additions and 107 deletions
|
|
@ -46,7 +46,7 @@ func main() {
|
||||||
flag.StringVar(&listenHttp, "listen", "127.0.0.1:8080", "listen addr:port")
|
flag.StringVar(&listenHttp, "listen", "127.0.0.1:8080", "listen addr:port")
|
||||||
flag.StringVar(&listenWebdav, "webdav", "", "listen addr:port for webdav")
|
flag.StringVar(&listenWebdav, "webdav", "", "listen addr:port for webdav")
|
||||||
flag.StringVar(&listenCache, "cache", "", "listen addr:port for cache only")
|
flag.StringVar(&listenCache, "cache", "", "listen addr:port for cache only")
|
||||||
flag.IntVar(&max, "max", -1, "max parallel preloads")
|
flag.IntVar(&max, "max", 1, "max parallel preloads")
|
||||||
flag.IntVar(&bs, "bs", -1, "tune preload buffer size in bytes (default: 8192)")
|
flag.IntVar(&bs, "bs", -1, "tune preload buffer size in bytes (default: 8192)")
|
||||||
flag.Int64Var("a, "quota", 1, "max disk usage quota for the dst cache in GiB")
|
flag.Int64Var("a, "quota", 1, "max disk usage quota for the dst cache in GiB")
|
||||||
flag.BoolVar(&block, "block", true, "block until sftp is connected")
|
flag.BoolVar(&block, "block", true, "block until sftp is connected")
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,14 @@ func (p *preload) Read(data []byte) (n int, err error) {
|
||||||
func (f *File) Preload(ctx context.Context, unlock func()) {
|
func (f *File) Preload(ctx context.Context, unlock func()) {
|
||||||
log := f.log
|
log := f.log
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
defer unlock()
|
|
||||||
if f.offline {
|
if f.offline {
|
||||||
log.V(2).Error(errors.New("no preload in offline mode"), "error preloading file")
|
log.V(2).Error(errors.New("no preload in offline mode"), "error preloading file")
|
||||||
|
unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f.md.FullyCached() {
|
if f.md.FullyCached() {
|
||||||
log.V(2).Info("skipped preload for fully cached file")
|
log.V(2).Info("skipped preload for fully cached file")
|
||||||
|
unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,6 +66,7 @@ func (f *File) Preload(ctx context.Context, unlock func()) {
|
||||||
_, err := io.Copy(Discard, p)
|
_, err := io.Copy(Discard, p)
|
||||||
if err == context.Canceled {
|
if err == context.Canceled {
|
||||||
log.V(2).Info("preload canceled", "skipped", p.skipped, "written", p.written)
|
log.V(2).Info("preload canceled", "skipped", p.skipped, "written", p.written)
|
||||||
|
// do not call unlock() to keep preload in list
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
|
|
@ -75,6 +77,7 @@ func (f *File) Preload(ctx context.Context, unlock func()) {
|
||||||
log.Error(err, "error closing cache file")
|
log.Error(err, "error closing cache file")
|
||||||
}
|
}
|
||||||
log.V(2).Info("preload finished", "skipped", p.skipped, "written", p.written)
|
log.V(2).Info("preload finished", "skipped", p.skipped, "written", p.written)
|
||||||
|
unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Read(p []byte) (n int, err error) {
|
func (f *File) Read(p []byte) (n int, err error) {
|
||||||
|
|
|
||||||
|
|
@ -145,16 +145,16 @@ func (fs *FS) QuotaUsage() (cur, max int64) {
|
||||||
return fs.q.Usage()
|
return fs.q.Usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) CancelPreload(name string) {
|
func (fs *FS) RemovePreload(name string) {
|
||||||
fs.ph.CancelPreload(name)
|
fs.ph.RemovePreload(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) Preloads() Preloads {
|
func (fs *FS) Preloads() Preloads {
|
||||||
return fs.ph.Preloads()
|
return fs.ph.Preloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) Preload(name string) {
|
func (fs *FS) Preload(name string, prio int) {
|
||||||
fs.ph.Preload(name)
|
fs.ph.Preload(name, prio)
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipLog(name string) bool {
|
func skipLog(name string) bool {
|
||||||
|
|
|
||||||
|
|
@ -4,138 +4,160 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type queue []*Preload
|
||||||
|
|
||||||
|
func (queue) LessReverse(i, j *Preload) bool { return i.Prio > j.Prio }
|
||||||
|
|
||||||
|
func (q *queue) Head() (p *Preload) {
|
||||||
|
l := len(*q)
|
||||||
|
if l > 0 {
|
||||||
|
p = (*q)[l-1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Pop() (p *Preload) {
|
||||||
|
l := len(*q)
|
||||||
|
if l > 0 {
|
||||||
|
p = q.Head()
|
||||||
|
*q = (*q)[:l-1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Sort() {
|
||||||
|
slices.SortFunc(*q, q.LessReverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Add(ph *PreloadHandler, name string, prio int) {
|
||||||
|
for _, p := range *q {
|
||||||
|
if p.Name == name {
|
||||||
|
p.Prio += prio
|
||||||
|
q.Sort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q.Append(&Preload{Name: name, ph: ph})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Append(p *Preload) {
|
||||||
|
*q = append(*q, p)
|
||||||
|
q.Sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queue) Remove(name string) {
|
||||||
|
for i, p := range *q {
|
||||||
|
if p.Name == name {
|
||||||
|
p.stop()
|
||||||
|
*q = slices.Delete(*q, i, i+1)
|
||||||
|
q.Sort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Preload struct {
|
type Preload struct {
|
||||||
|
ph *PreloadHandler
|
||||||
Name string
|
Name string
|
||||||
cancel func()
|
cancel func()
|
||||||
|
Prio int
|
||||||
Status int
|
Status int
|
||||||
Running bool
|
Running bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Preloads []*Preload
|
type Preloads []Preload
|
||||||
|
|
||||||
func (Preloads) Less(i, j *Preload) bool { return i.Name < j.Name }
|
|
||||||
|
|
||||||
type PreloadHandler struct {
|
type PreloadHandler struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
fs *FS
|
fs *FS
|
||||||
pm map[string]*Preload
|
q queue
|
||||||
sched chan *Preload
|
|
||||||
fin chan string
|
fin chan string
|
||||||
pre int
|
|
||||||
max int
|
max int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPreloadHandler(ctx context.Context, fs *FS, max int, log logr.Logger) (ph *PreloadHandler) {
|
func NewPreloadHandler(ctx context.Context, fs *FS, max int, log logr.Logger) (ph *PreloadHandler) {
|
||||||
if max < -1 {
|
if max <= 0 {
|
||||||
max = -1
|
max = 1
|
||||||
}
|
}
|
||||||
ph = &PreloadHandler{
|
ph = &PreloadHandler{
|
||||||
log: log,
|
log: log,
|
||||||
fs: fs,
|
fs: fs,
|
||||||
pm: make(map[string]*Preload),
|
q: make(queue, 0),
|
||||||
sched: make(chan *Preload, 1),
|
|
||||||
fin: make(chan string, 1),
|
fin: make(chan string, 1),
|
||||||
max: max,
|
max: max,
|
||||||
}
|
}
|
||||||
go ph.preload(ctx)
|
|
||||||
go ph.preloadFinish(ctx)
|
go ph.preloadFinish(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PreloadHandler) CancelPreload(name string) {
|
func (ph *PreloadHandler) RemovePreload(name string) {
|
||||||
ph.mu.Lock()
|
ph.mu.RLock()
|
||||||
defer ph.mu.Unlock()
|
defer ph.mu.RUnlock()
|
||||||
p, ok := ph.pm[name]
|
ph.q.Remove(name)
|
||||||
if ok {
|
ph.schedule()
|
||||||
if p.Running {
|
|
||||||
p.cancel()
|
|
||||||
}
|
|
||||||
delete(ph.pm, name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PreloadHandler) Preloads() (ps Preloads) {
|
func (ph *PreloadHandler) Preloads() (ps Preloads) {
|
||||||
ph.mu.RLock()
|
ph.mu.RLock()
|
||||||
defer ph.mu.RUnlock()
|
defer ph.mu.RUnlock()
|
||||||
for _, p := range ph.pm {
|
ps = make(Preloads, len(ph.q))
|
||||||
ps = append(ps, &Preload{
|
for i, v := range ph.q {
|
||||||
Name: p.Name,
|
ps[i] = *v
|
||||||
Running: p.Running,
|
ps[i].Status = ph.fs.CacheStatus(v.Name)
|
||||||
Status: ph.fs.CacheStatus(p.Name),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
slices.SortFunc(ps, ps.Less)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PreloadHandler) Preload(name string) {
|
func (ph *PreloadHandler) Preload(name string, prio int) {
|
||||||
ph.mu.Lock()
|
ph.mu.Lock()
|
||||||
defer ph.mu.Unlock()
|
defer ph.mu.Unlock()
|
||||||
|
ph.q.Add(ph, name, prio)
|
||||||
p, ok := ph.pm[name]
|
ph.schedule()
|
||||||
if !ok {
|
|
||||||
p = &Preload{Name: name}
|
|
||||||
ph.pm[name] = p
|
|
||||||
if ph.max == -1 || ph.pre < ph.max {
|
|
||||||
ph.sched <- p
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Running {
|
|
||||||
ph.log.WithValues("file", name).Error(errors.New("preload is running"), "error starting preload")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PreloadHandler) nextPreload() {
|
func (ph *PreloadHandler) schedule() {
|
||||||
if ph.pre == ph.max {
|
for i := 0; i < len(ph.q); i++ {
|
||||||
return
|
if i < ph.max {
|
||||||
|
ph.q[i].start()
|
||||||
|
} else {
|
||||||
|
ph.q[i].stop()
|
||||||
}
|
}
|
||||||
for _, p := range ph.pm {
|
|
||||||
if p.Running {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ph.sched <- p
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PreloadHandler) preload(ctx context.Context) {
|
func (p *Preload) start() {
|
||||||
var p *Preload
|
if p.Running {
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
return
|
||||||
case p = <-ph.sched:
|
|
||||||
}
|
}
|
||||||
name := p.Name
|
name := p.Name
|
||||||
file, err := ph.fs.Open(name)
|
file, err := p.ph.fs.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ph.log.Error(err, "error staring next preload", "file", name)
|
p.ph.log.Error(err, "error staring next preload", "file", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, ok := file.(*File)
|
f, ok := file.(*File)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
ph.mu.Lock()
|
|
||||||
ph.pre++
|
|
||||||
p.Running = true
|
p.Running = true
|
||||||
p.cancel = cancel
|
p.cancel = cancel
|
||||||
ph.mu.Unlock()
|
|
||||||
go f.Preload(ctx, func() {
|
go f.Preload(ctx, func() {
|
||||||
ph.fin <- name
|
p.ph.fin <- name
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Preload) stop() {
|
||||||
|
if p.Running {
|
||||||
|
p.cancel()
|
||||||
|
p.Running = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,23 +169,19 @@ func (ph *PreloadHandler) preloadFinish(ctx context.Context) {
|
||||||
return
|
return
|
||||||
case name := <-ph.fin:
|
case name := <-ph.fin:
|
||||||
ph.mu.Lock()
|
ph.mu.Lock()
|
||||||
delete(ph.pm, name)
|
ph.q.Remove(name)
|
||||||
ph.pre--
|
ph.schedule()
|
||||||
ph.nextPreload()
|
|
||||||
ph.mu.Unlock()
|
ph.mu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph *PreloadHandler) close() {
|
func (ph *PreloadHandler) close() {
|
||||||
for _, p := range ph.pm {
|
for _, p := range ph.q {
|
||||||
if p.Running {
|
p.stop()
|
||||||
p.cancel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ph.sched:
|
|
||||||
case <-ph.fin:
|
case <-ph.fin:
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ type file struct {
|
||||||
URI template.HTML
|
URI template.HTML
|
||||||
Anchor string
|
Anchor string
|
||||||
Status int
|
Status int
|
||||||
|
Prio int
|
||||||
Running bool
|
Running bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,12 +137,10 @@ func getPreloads(path string, fs *fs.FS) (dc dirContents) {
|
||||||
Name: template.HTML(p.Name),
|
Name: template.HTML(p.Name),
|
||||||
URI: template.HTML(p.Name),
|
URI: template.HTML(p.Name),
|
||||||
Status: p.Status,
|
Status: p.Status,
|
||||||
|
Prio: p.Prio,
|
||||||
Running: p.Running,
|
Running: p.Running,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
slices.SortFunc(dc.Files, func(i, _ file) bool {
|
|
||||||
return i.Running
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,16 @@ func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.IsDir() {
|
if !d.IsDir() {
|
||||||
if option == "p" || option == "s" {
|
if option == "p" || option == "i" || option == "d" || option == "s" {
|
||||||
if option == "p" {
|
switch option {
|
||||||
fs.fs.Preload(p)
|
case "p":
|
||||||
} else if option == "s" {
|
fs.fs.Preload(p, 0)
|
||||||
fs.fs.CancelPreload(p)
|
case "i":
|
||||||
|
fs.fs.Preload(p, 1)
|
||||||
|
case "d":
|
||||||
|
fs.fs.Preload(p, -1)
|
||||||
|
case "s":
|
||||||
|
fs.fs.RemovePreload(p)
|
||||||
}
|
}
|
||||||
rdir := "/"
|
rdir := "/"
|
||||||
if r.FormValue("r") == "preloads" {
|
if r.FormValue("r") == "preloads" {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
{{define "body" -}}
|
{{define "body" -}}
|
||||||
<pre>[v]: show video
|
<pre>[v]: show video
|
||||||
|
[+]: increase priority
|
||||||
|
[-]: decrease priority
|
||||||
[s]: stop preloading
|
[s]: stop preloading
|
||||||
Quota: {{.QuotaCur}} / {{.QuotaMax}} GiB
|
Quota: {{.QuotaCur}} / {{.QuotaMax}} GiB
|
||||||
</pre>
|
</pre>
|
||||||
|
|
@ -12,7 +14,7 @@ Quota: {{.QuotaCur}} / {{.QuotaMax}} GiB
|
||||||
<tr class="listitem">
|
<tr class="listitem">
|
||||||
<td class="listpath"><a href="{{$s.URI}}">{{$s.Name}}</a></td>
|
<td class="listpath"><a href="{{$s.URI}}">{{$s.Name}}</a></td>
|
||||||
<td class="listoptions">
|
<td class="listoptions">
|
||||||
{{if ge $s.Status 0}}<span>{{$s.Status}}%</span>{{end}}<span class="status-{{if $s.Running}}green">[running{{else}}yellow">[queued{{end}}]</span><a href="{{$s.URI}}?o=v">[v]</a><a href="{{$s.Name}}?o=s&r=preloads">[s]</a>
|
{{if ge $s.Status 0}}<span>{{$s.Status}}%</span>{{end}}<span class="status-{{if $s.Running}}green">[running{{else}}yellow">[queued{{end}}]</span><span>{{$s.Prio}}</span><a href="{{$s.URI}}?o=v">[v]</a><a href="{{$s.Name}}?o=s&r=preloads">[s]</a><a href="{{$s.Name}}?o=i&r=preloads">[+]</a><a href="{{$s.Name}}?o=d&r=preloads">[-]</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="spacer"><td colspan="2">
|
<tr class="spacer"><td colspan="2">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue