mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-05 20:13:23 -05:00
refactor(application): tidy workspace file setup
initStudioFiles had a named result it never used, and the setup repeated the same strict-or-disable branch five times. Fold them into one local helper. workspaceStartupCleanup changed options.Context as a side effect and the cancel func of its derived context was only reachable through the cleanup. Return the context and the cleanup, and let the caller assign the context. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
4 files changed
+28
-33
No files matched your search
@@ -42,7 +42,8 @@ import (
|
||||
|
||||
func New(opts ...config.AppOption) (*Application, error) {
|
||||
options := config.NewApplicationConfig(opts...)
|
||||
cleanupWorkspace := workspaceStartupCleanup(options)
|
||||
ctx, cleanupWorkspace := workspaceStartupCleanup(options.Context, options)
|
||||
options.Context = ctx
|
||||
|
||||
// Store a copy of the startup config (env/CLI only, before file
|
||||
// loading): the settings endpoint uses it to tell env-provided API
|
||||
@@ -833,15 +834,16 @@ func ensureAuthDBDirs(options *config.ApplicationConfig) error {
|
||||
|
||||
// workspaceStartupCleanup owns startup resources when workspace initialization
|
||||
// can fail fatally: configured S3 (with or without auth), or PostgreSQL metadata.
|
||||
// Never cancel the caller's context or change legacy JSON/SQLite without S3.
|
||||
func workspaceStartupCleanup(options *config.ApplicationConfig) func(*Application) {
|
||||
// It returns the context that startup runs in and the cleanup. Without those
|
||||
// setups it returns ctx itself and a no-op: it never cancels the caller's
|
||||
// context or changes legacy JSON/SQLite without S3.
|
||||
func workspaceStartupCleanup(ctx context.Context, options *config.ApplicationConfig) (context.Context, func(*Application)) {
|
||||
postgres := options.Auth.Enabled && (strings.HasPrefix(options.Auth.DatabaseURL, "postgres://") || strings.HasPrefix(options.Auth.DatabaseURL, "postgresql://"))
|
||||
if options.Distributed.StorageURL == "" && !postgres {
|
||||
return func(*Application) {}
|
||||
return ctx, func(*Application) {}
|
||||
}
|
||||
ctx, cancel := context.WithCancel(options.Context)
|
||||
options.Context = ctx
|
||||
return func(a *Application) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return ctx, func(a *Application) {
|
||||
cancel() // stop background work before shutting down its dependencies
|
||||
_ = a.Shutdown()
|
||||
if a.authDB != nil {
|
||||
|
||||
@@ -53,7 +53,7 @@ func (a *Application) StudioFiles() *files.Service {
|
||||
// store; a build without cgo and without any build tag has it too. The bytes
|
||||
// live in configured S3 for every role, otherwise at DataPath/chats/blobs.
|
||||
// Moving files between backends is not supported automatically.
|
||||
func (a *Application) initStudioFiles() (result error) {
|
||||
func (a *Application) initStudioFiles() error {
|
||||
return a.initStudioFilesWithS3(func(ctx context.Context, cfg storage.S3Config) (storage.ObjectStore, error) {
|
||||
return storage.NewS3Store(ctx, cfg)
|
||||
})
|
||||
@@ -76,15 +76,21 @@ func (a *Application) initStudioFilesWithS3(newS3 func(context.Context, storage.
|
||||
a.studioFiles = files.Disabled("There is no data path, so files are off. Start LocalAI with a data path to keep files.")
|
||||
return nil
|
||||
}
|
||||
// stop ends the setup: a strict setup fails startup, otherwise files are
|
||||
// off with a reason the user can read.
|
||||
stop := func(what string, err error, reason string) error {
|
||||
if strict {
|
||||
return fmt.Errorf("%s: %w", what, err)
|
||||
}
|
||||
xlog.Warn("Files disabled: "+what, "error", err)
|
||||
a.studioFiles = files.Disabled(reason)
|
||||
return nil
|
||||
}
|
||||
var tmpCache string
|
||||
if dp == "" { // a cluster without a data path: the blob cache is a temporary folder
|
||||
dir, err := os.MkdirTemp("", "localai-files-*")
|
||||
if err != nil {
|
||||
if strict {
|
||||
return fmt.Errorf("workspace scratch: %w", err)
|
||||
}
|
||||
a.studioFiles = files.Disabled("Files are off: there is no folder for their cache.")
|
||||
return
|
||||
return stop("workspace scratch", err, "Files are off: there is no folder for their cache.")
|
||||
}
|
||||
a.studioFilesTemp, tmpCache = dir, dir
|
||||
}
|
||||
@@ -100,33 +106,18 @@ func (a *Application) initStudioFilesWithS3(newS3 func(context.Context, storage.
|
||||
blobs, err = blob.NewFS(filepath.Join(root, "blobs"))
|
||||
}
|
||||
if err != nil {
|
||||
if strict {
|
||||
return fmt.Errorf("workspace blob store: %w", err)
|
||||
}
|
||||
xlog.Warn("Files disabled: cannot open the blob store", "error", err)
|
||||
a.studioFiles = files.Disabled("Files are off: their storage could not be opened.")
|
||||
return
|
||||
return stop("workspace blob store", err, "Files are off: their storage could not be opened.")
|
||||
}
|
||||
ws, closer, err := a.openWorkspaceIndex(root, blobs)
|
||||
if err != nil {
|
||||
if strict {
|
||||
return fmt.Errorf("workspace index: %w", err)
|
||||
}
|
||||
xlog.Warn("Files disabled: cannot start the workspace", "error", err)
|
||||
a.studioFiles = files.Disabled("Files are off: the workspace could not be started.")
|
||||
return
|
||||
return stop("workspace index", err, "Files are off: the workspace could not be started.")
|
||||
}
|
||||
f := files.New(ws, files.WithBlobs(blobs), files.WithMaxFileSize(a.studioUploadLimit()))
|
||||
if err := f.EnsureShared(context.Background()); err != nil {
|
||||
xlog.Warn("Files disabled: cannot create the shared folders", "error", err)
|
||||
a.studioFiles = files.Disabled("Files are off: the workspace could not be started.")
|
||||
if closer != nil {
|
||||
_ = closer.Close()
|
||||
}
|
||||
if strict {
|
||||
return fmt.Errorf("workspace folders: %w", err)
|
||||
}
|
||||
return
|
||||
return stop("workspace folders", err, "Files are off: the workspace could not be started.")
|
||||
}
|
||||
// The office skills ship with the server. A copy that fails leaves the
|
||||
// chats as they are: the skills are help, not a need.
|
||||
|
||||
@@ -128,7 +128,8 @@ var _ = Describe("PostgreSQL shared filesystem startup cleanup", func() {
|
||||
caller, cancel := context.WithCancel(context.Background())
|
||||
DeferCleanup(cancel)
|
||||
cfg := config.NewApplicationConfig(config.WithContext(caller), config.WithDataPath(GinkgoT().TempDir()), config.WithAuthEnabled(true), config.WithAuthDatabaseURL(raw))
|
||||
cleanup := workspaceStartupCleanup(cfg)
|
||||
ctx, cleanup := workspaceStartupCleanup(cfg.Context, cfg)
|
||||
cfg.Context = ctx
|
||||
app := &Application{applicationConfig: cfg, authDB: db}
|
||||
if early {
|
||||
cfg.Distributed.StorageURL = "http://s3.invalid"
|
||||
|
||||
@@ -81,7 +81,8 @@ var _ = Describe("automatic workspace", func() {
|
||||
cfg.Distributed.StorageURL = "http://s3.invalid:9000" // no bucket: fail before any network access
|
||||
cfg.Auth.Enabled = database
|
||||
cfg.Auth.DatabaseURL = filepath.Join(GinkgoT().TempDir(), "auth.db")
|
||||
cleanup := workspaceStartupCleanup(cfg) // the same ownership scope used by New
|
||||
ctx, cleanup := workspaceStartupCleanup(cfg.Context, cfg) // the same ownership scope used by New
|
||||
cfg.Context = ctx
|
||||
app := &Application{applicationConfig: cfg}
|
||||
DeferCleanup(func() { Expect(app.Shutdown()).To(Succeed()) })
|
||||
if database {
|
||||
|
||||
Reference in new issue
Block a user