Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/unpackerr/folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,11 @@ func (u *Unpackerr) checkFolderStats(now time.Time) {
folder.config.Path, folder.retries, u.MaxRetries, elapsed.Round(time.Second))
case EXTRACTFAILED == folder.status && folder.retries < u.MaxRetries:
// This empty block is to avoid deleting an item that needs more retries.
case EXTRACTFAILED == folder.status && u.MaxRetries > 0 && folder.retries >= u.MaxRetries:
// Retries exhausted — clean up to prevent the item from staying in the map forever.
u.updateQueueStatus(&newStatus{Name: name, Status: DELETED, Resp: nil}, now, true)
delete(u.folders.Folders, name)
u.Printf("[Folder] Retries exhausted (%d/%d), giving up: %s", folder.retries, u.MaxRetries, name)
case folder.status > EXTRACTING && folder.config.DeleteAfter.Duration <= 0:
// if DeleteAfter is 0 we don't delete anything. we are done.
u.updateQueueStatus(&newStatus{Name: name, Status: DELETED, Resp: nil}, now, false)
Expand Down
12 changes: 12 additions & 0 deletions pkg/unpackerr/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ func (u *Unpackerr) checkExtractDone(now time.Time) {
item.Updated = now
u.Printf("[%s] Extract failed %v ago, triggering restart (%d/%d): %v",
item.App, elapsed.Round(time.Second), item.Retries, u.MaxRetries, name)
case item.Status == EXTRACTFAILED && u.MaxRetries > 0 && item.Retries >= u.MaxRetries:
// Retries exhausted — clean up to prevent the item from staying in the map forever.
u.updateQueueStatus(&newStatus{Name: name, Status: DELETED, Resp: item.Resp}, now, true)
u.Printf("[%s] Retries exhausted (%d/%d), giving up: %v",
item.App, item.Retries, u.MaxRetries, name)
case (item.Status == EXTRACTED || item.Status == EXTRACTING || item.Status == QUEUED) &&
elapsed >= staleItemTimeout:
// Safety net: items stuck at intermediate states for too long are cleaned up
// to prevent unbounded map growth (e.g. Starr app never imports the item).
u.updateQueueStatus(&newStatus{Name: name, Status: DELETED, Resp: item.Resp}, now, true)
u.Printf("[%s] Stale item removed after %v at status %s: %v",
item.App, elapsed.Round(time.Second), item.Status.Desc(), name)
case item.Status == IMPORTED && elapsed >= item.DeleteDelay:
var webhook bool

Expand Down
1 change: 1 addition & 0 deletions pkg/unpackerr/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
defaultStartDelay = time.Minute
minimumDeleteDelay = time.Second
defaultDeleteDelay = 5 * time.Minute
staleItemTimeout = 24 * time.Hour // Safety net: items stuck at intermediate states are cleaned up.
defaultHistory = 10 // items kept in history.
suffix = "_unpackerred" // suffix for unpacked folders.
updateChanBuf = 100 // Size of xtractr callback update channels.
Expand Down
26 changes: 26 additions & 0 deletions pkg/unpackerr/webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net"
"net/http"
"net/http/pprof"
"path"
"strings"

Expand All @@ -15,6 +16,7 @@ import (

type WebServer struct {
Metrics bool `json:"metrics" toml:"metrics" xml:"metrics" yaml:"metrics"`
Pprof bool `json:"pprof" toml:"pprof" xml:"pprof" yaml:"pprof"`
LogFiles int `json:"logFiles" toml:"log_files" xml:"log_files" yaml:"logFiles"`
LogFileMb int `json:"logFileMb" toml:"log_file_mb" xml:"log_file_mb" yaml:"logFileMb"`
ListenAddr string `json:"listenAddr" toml:"listen_addr" xml:"listen_addr" yaml:"listenAddr"`
Expand Down Expand Up @@ -89,6 +91,11 @@ func (u *Unpackerr) startWebServer() {
func (u *Unpackerr) webRoutes() {
u.Webserver.router.GET(path.Join(u.Webserver.URLBase, "/"), Index)

if u.Webserver.Pprof {
u.registerPprof()
u.Printf(" => WARNING: pprof debug endpoints enabled at /debug/pprof/")
}

if !u.Webserver.Metrics {
return
}
Expand All @@ -102,6 +109,25 @@ func (u *Unpackerr) webRoutes() {
}
}

// registerPprof adds Go's built-in pprof handlers for runtime profiling.
// Access heap profiles at /debug/pprof/heap, goroutine dumps at /debug/pprof/goroutine, etc.
func (u *Unpackerr) registerPprof() {
wrap := func(h http.HandlerFunc) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
h(w, r)
}
}

u.Webserver.router.GET("/debug/pprof/", wrap(pprof.Index))
u.Webserver.router.GET("/debug/pprof/cmdline", wrap(pprof.Cmdline))
u.Webserver.router.GET("/debug/pprof/profile", wrap(pprof.Profile))
u.Webserver.router.GET("/debug/pprof/symbol", wrap(pprof.Symbol))
u.Webserver.router.GET("/debug/pprof/trace", wrap(pprof.Trace))
u.Webserver.router.Handler(http.MethodGet, "/debug/pprof/heap", pprof.Handler("heap"))
u.Webserver.router.Handler(http.MethodGet, "/debug/pprof/goroutine", pprof.Handler("goroutine"))
u.Webserver.router.Handler(http.MethodGet, "/debug/pprof/allocs", pprof.Handler("allocs"))
}

// runWebServer starts the http or https listener.
func (u *Unpackerr) runWebServer() {
var err error
Expand Down