Skip to content

Commit 1911448

Browse files
authored
Fix concurrent access to libraries manager gRPC functions. (#2480)
* Removed libraries index fields from LibraryManager The presence of the Index* fileds inside LibraryManager didn't give any functional benefit. * Removed all references to LibraryManger.Index * Removed unused tmp file creation * downloadLibrary do not require a libmanager but just the downloadDir * Inline method LibrariesManager.Install * Move initializations closer to usage * Use LibraryManager.FindAllInstalled instead of direct access to field * Made LibraryManager.Libraries private * Simplified libraries resolver init * Made LibrariesManager.LibrariesDir private * Removed DownloadsDir field from LibraryManager * Removed librariesindex.Reference structure and related helpers It does make things more complicated without any actual benefit. * Removed state-altering methods from LibrariesManager The original LibrariresManager has been split into three objects with specific goals: * The Builder object is used to construct a new LibrariesManager. It has methods to add librarires directories and to build the LibrariesManager. * The Explorer object is used to query the LibrariesManager about installed libraries. * The Installer object is used to rescan installed libraries and to install and uninstall. * Fixed test
1 parent bba8f72 commit 1911448

File tree

24 files changed

+461
-396
lines changed

24 files changed

+461
-396
lines changed

commands/core/download.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func PlatformDownload(ctx context.Context, req *rpc.PlatformDownloadRequest, dow
3636
}
3737
defer release()
3838

39-
version, err := commands.ParseVersion(req)
39+
version, err := commands.ParseVersion(req.GetVersion())
4040
if err != nil {
4141
return nil, &cmderrors.InvalidVersionError{Cause: err}
4242
}

commands/core/install.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func PlatformInstall(ctx context.Context, req *rpc.PlatformInstallRequest, downl
3535
}
3636
defer release()
3737

38-
version, err := commands.ParseVersion(req)
38+
version, err := commands.ParseVersion(req.GetVersion())
3939
if err != nil {
4040
return &cmderrors.InvalidVersionError{Cause: err}
4141
}

commands/instances.go

+36-30
Original file line numberDiff line numberDiff line change
@@ -301,17 +301,13 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
301301
}
302302

303303
// Create library manager and add libraries directories
304-
lm := librariesmanager.NewLibraryManager(
305-
pme.IndexDir,
306-
pme.DownloadDir,
307-
)
308-
_ = instances.SetLibraryManager(instance, lm) // should never fail
304+
lmb := librariesmanager.NewBuilder()
309305

310306
// Load libraries
311307
for _, pack := range pme.GetPackages() {
312308
for _, platform := range pack.Platforms {
313309
if platformRelease := pme.GetInstalledPlatformRelease(platform); platformRelease != nil {
314-
lm.AddLibrariesDir(&librariesmanager.LibrariesDir{
310+
lmb.AddLibrariesDir(&librariesmanager.LibrariesDir{
315311
PlatformRelease: platformRelease,
316312
Path: platformRelease.GetLibrariesDir(),
317313
Location: libraries.PlatformBuiltIn,
@@ -320,22 +316,33 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
320316
}
321317
}
322318

323-
if err := lm.LoadIndex(); err != nil {
319+
indexFileName, err := globals.LibrariesIndexResource.IndexFileName()
320+
if err != nil {
321+
// should never happen
322+
panic("failed getting libraries index file name: " + err.Error())
323+
}
324+
indexFile := pme.IndexDir.Join(indexFileName)
325+
326+
logrus.WithField("index", indexFile).Info("Loading libraries index file")
327+
li, err := librariesindex.LoadIndex(indexFile)
328+
if err != nil {
324329
s := status.Newf(codes.FailedPrecondition, tr("Loading index file: %v"), err)
325330
responseError(s)
331+
li = librariesindex.EmptyIndex
326332
}
333+
instances.SetLibrariesIndex(instance, li)
327334

328335
if profile == nil {
329336
// Add directories of libraries bundled with IDE
330337
if bundledLibsDir := configuration.IDEBuiltinLibrariesDir(configuration.Settings); bundledLibsDir != nil {
331-
lm.AddLibrariesDir(&librariesmanager.LibrariesDir{
338+
lmb.AddLibrariesDir(&librariesmanager.LibrariesDir{
332339
Path: bundledLibsDir,
333340
Location: libraries.IDEBuiltIn,
334341
})
335342
}
336343

337344
// Add libraries directory from config file
338-
lm.AddLibrariesDir(&librariesmanager.LibrariesDir{
345+
lmb.AddLibrariesDir(&librariesmanager.LibrariesDir{
339346
Path: configuration.LibrariesDir(configuration.Settings),
340347
Location: libraries.User,
341348
})
@@ -349,17 +356,14 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
349356
if !libDir.IsDir() {
350357
// Download library
351358
taskCallback(&rpc.TaskProgress{Name: tr("Downloading library %s", libraryRef)})
352-
libRelease := lm.Index.FindRelease(&librariesindex.Reference{
353-
Name: libraryRef.Library,
354-
Version: libraryRef.Version,
355-
})
356-
if libRelease == nil {
359+
libRelease, err := li.FindRelease(libraryRef.Library, libraryRef.Version)
360+
if err != nil {
357361
taskCallback(&rpc.TaskProgress{Name: tr("Library %s not found", libraryRef)})
358362
err := &cmderrors.LibraryNotFoundError{Library: libraryRef.Library}
359363
responseError(err.ToRPCStatus())
360364
continue
361365
}
362-
if err := libRelease.Resource.Download(lm.DownloadsDir, nil, libRelease.String(), downloadCallback, ""); err != nil {
366+
if err := libRelease.Resource.Download(pme.DownloadDir, nil, libRelease.String(), downloadCallback, ""); err != nil {
363367
taskCallback(&rpc.TaskProgress{Name: tr("Error downloading library %s", libraryRef)})
364368
e := &cmderrors.FailedLibraryInstallError{Cause: err}
365369
responseError(e.ToRPCStatus())
@@ -369,7 +373,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
369373

370374
// Install library
371375
taskCallback(&rpc.TaskProgress{Name: tr("Installing library %s", libraryRef)})
372-
if err := libRelease.Resource.Install(lm.DownloadsDir, libRoot, libDir); err != nil {
376+
if err := libRelease.Resource.Install(pme.DownloadDir, libRoot, libDir); err != nil {
373377
taskCallback(&rpc.TaskProgress{Name: tr("Error installing library %s", libraryRef)})
374378
e := &cmderrors.FailedLibraryInstallError{Cause: err}
375379
responseError(e.ToRPCStatus())
@@ -378,16 +382,23 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
378382
taskCallback(&rpc.TaskProgress{Completed: true})
379383
}
380384

381-
lm.AddLibrariesDir(&librariesmanager.LibrariesDir{
385+
lmb.AddLibrariesDir(&librariesmanager.LibrariesDir{
382386
Path: libRoot,
383387
Location: libraries.User,
384388
})
385389
}
386390
}
387391

388-
for _, status := range lm.RescanLibraries() {
389-
logrus.WithError(status.Err()).Warnf("Error loading library")
390-
// TODO: report as warning: responseError(err)
392+
lm := lmb.Build()
393+
_ = instances.SetLibraryManager(instance, lm) // should never fail
394+
395+
{
396+
lmi, release := lm.NewInstaller()
397+
for _, status := range lmi.RescanLibraries() {
398+
logrus.WithError(status.Err()).Warnf("Error loading library")
399+
// TODO: report as warning: responseError(err)
400+
}
401+
release()
391402
}
392403

393404
// Refreshes the locale used, this will change the
@@ -409,23 +420,18 @@ func Destroy(ctx context.Context, req *rpc.DestroyRequest) (*rpc.DestroyResponse
409420
// UpdateLibrariesIndex updates the library_index.json
410421
func UpdateLibrariesIndex(ctx context.Context, req *rpc.UpdateLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB) error {
411422
logrus.Info("Updating libraries index")
412-
lm, err := instances.GetLibraryManager(req.GetInstance())
423+
pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
413424
if err != nil {
414425
return err
415426
}
427+
indexDir := pme.IndexDir
428+
release()
416429

417-
if err := lm.IndexFile.Parent().MkdirAll(); err != nil {
430+
if err := indexDir.MkdirAll(); err != nil {
418431
return &cmderrors.PermissionDeniedError{Message: tr("Could not create index directory"), Cause: err}
419432
}
420433

421-
// Create a temp dir to stage all downloads
422-
tmp, err := paths.MkTempDir("", "library_index_download")
423-
if err != nil {
424-
return &cmderrors.TempDirCreationFailedError{Cause: err}
425-
}
426-
defer tmp.RemoveAll()
427-
428-
if err := globals.LibrariesIndexResource.Download(lm.IndexFile.Parent(), downloadCB); err != nil {
434+
if err := globals.LibrariesIndexResource.Download(indexDir, downloadCB); err != nil {
429435
return err
430436
}
431437

commands/internal/instances/instances.go

+51-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/arduino/arduino-cli/commands/cmderrors"
77
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
8+
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
89
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesmanager"
910
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
1011
"github.com/arduino/arduino-cli/version"
@@ -17,6 +18,7 @@ import (
1718
type coreInstance struct {
1819
pm *packagemanager.PackageManager
1920
lm *librariesmanager.LibrariesManager
21+
li *librariesindex.Index
2022
}
2123

2224
// instances contains all the running Arduino Core Services instances
@@ -60,6 +62,49 @@ func GetLibraryManager(inst *rpc.Instance) (*librariesmanager.LibrariesManager,
6062
return i.lm, nil
6163
}
6264

65+
// GetLibraryManagerExplorer returns the library manager Explorer for the given instance.
66+
func GetLibraryManagerExplorer(inst *rpc.Instance) (*librariesmanager.Explorer, func(), error) {
67+
lm, err := GetLibraryManager(inst)
68+
if err != nil {
69+
return nil, nil, err
70+
}
71+
lmi, release := lm.NewExplorer()
72+
return lmi, release, nil
73+
}
74+
75+
// GetLibraryManagerInstaller returns the library manager Installer for the given instance.
76+
func GetLibraryManagerInstaller(inst *rpc.Instance) (*librariesmanager.Installer, func(), error) {
77+
lm, err := GetLibraryManager(inst)
78+
if err != nil {
79+
return nil, nil, err
80+
}
81+
lmi, release := lm.NewInstaller()
82+
return lmi, release, nil
83+
}
84+
85+
// GetLibrariesIndex returns the library index for the given instance.
86+
func GetLibrariesIndex(inst *rpc.Instance) (*librariesindex.Index, error) {
87+
instancesMux.Lock()
88+
defer instancesMux.Unlock()
89+
i := instances[inst.GetId()]
90+
if i == nil {
91+
return nil, &cmderrors.InvalidInstanceError{}
92+
}
93+
return i.li, nil
94+
}
95+
96+
// SetLibrariesIndex sets the library index for the given instance.
97+
func SetLibrariesIndex(inst *rpc.Instance, li *librariesindex.Index) error {
98+
instancesMux.Lock()
99+
defer instancesMux.Unlock()
100+
i := instances[inst.GetId()]
101+
if i == nil {
102+
return &cmderrors.InvalidInstanceError{}
103+
}
104+
i.li = li
105+
return nil
106+
}
107+
63108
// SetLibraryManager sets the library manager for the given instance.
64109
func SetLibraryManager(inst *rpc.Instance, lm *librariesmanager.LibrariesManager) bool {
65110
instancesMux.Lock()
@@ -74,16 +119,18 @@ func SetLibraryManager(inst *rpc.Instance, lm *librariesmanager.LibrariesManager
74119

75120
// Create a new *rpc.Instance ready to be initialized
76121
func Create(dataDir, packagesDir, downloadsDir *paths.Path, extraUserAgent ...string) (*rpc.Instance, error) {
77-
instance := &coreInstance{}
78-
79122
// Create package manager
80123
userAgent := "arduino-cli/" + version.VersionInfo.VersionString
81124
for _, ua := range extraUserAgent {
82125
userAgent += " " + ua
83126
}
84127
tempDir := dataDir.Join("tmp")
85-
instance.pm = packagemanager.NewBuilder(dataDir, packagesDir, downloadsDir, tempDir, userAgent).Build()
86-
instance.lm = librariesmanager.NewLibraryManager(dataDir, downloadsDir)
128+
129+
instance := &coreInstance{
130+
pm: packagemanager.NewBuilder(dataDir, packagesDir, downloadsDir, tempDir, userAgent).Build(),
131+
lm: librariesmanager.NewBuilder().Build(),
132+
li: librariesindex.EmptyIndex,
133+
}
87134

88135
// Save instance
89136
instancesMux.Lock()

commands/lib/download.go

+20-6
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ package lib
1818
import (
1919
"context"
2020

21+
"github.com/arduino/arduino-cli/commands"
2122
"github.com/arduino/arduino-cli/commands/cmderrors"
2223
"github.com/arduino/arduino-cli/commands/internal/instances"
2324
"github.com/arduino/arduino-cli/internal/arduino/httpclient"
2425
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
25-
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesmanager"
2626
"github.com/arduino/arduino-cli/internal/i18n"
2727
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
28+
"github.com/arduino/go-paths-helper"
2829
"github.com/sirupsen/logrus"
2930
)
3031

@@ -35,34 +36,47 @@ var tr = i18n.Tr
3536
func LibraryDownload(ctx context.Context, req *rpc.LibraryDownloadRequest, downloadCB rpc.DownloadProgressCB) (*rpc.LibraryDownloadResponse, error) {
3637
logrus.Info("Executing `arduino-cli lib download`")
3738

38-
lm, err := instances.GetLibraryManager(req.GetInstance())
39+
var downloadsDir *paths.Path
40+
if pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance()); err != nil {
41+
return nil, err
42+
} else {
43+
downloadsDir = pme.DownloadDir
44+
release()
45+
}
46+
47+
li, err := instances.GetLibrariesIndex(req.GetInstance())
3948
if err != nil {
4049
return nil, err
4150
}
4251

4352
logrus.Info("Preparing download")
4453

45-
lib, err := findLibraryIndexRelease(lm.Index, req)
54+
version, err := commands.ParseVersion(req.GetVersion())
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
lib, err := li.FindRelease(req.GetName(), version)
4660
if err != nil {
4761
return nil, err
4862
}
4963

50-
if err := downloadLibrary(lm, lib, downloadCB, func(*rpc.TaskProgress) {}, "download"); err != nil {
64+
if err := downloadLibrary(downloadsDir, lib, downloadCB, func(*rpc.TaskProgress) {}, "download"); err != nil {
5165
return nil, err
5266
}
5367

5468
return &rpc.LibraryDownloadResponse{}, nil
5569
}
5670

57-
func downloadLibrary(lm *librariesmanager.LibrariesManager, libRelease *librariesindex.Release,
71+
func downloadLibrary(downloadsDir *paths.Path, libRelease *librariesindex.Release,
5872
downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, queryParameter string) error {
5973

6074
taskCB(&rpc.TaskProgress{Name: tr("Downloading %s", libRelease)})
6175
config, err := httpclient.GetDownloaderConfig()
6276
if err != nil {
6377
return &cmderrors.FailedDownloadError{Message: tr("Can't download library"), Cause: err}
6478
}
65-
if err := libRelease.Resource.Download(lm.DownloadsDir, config, libRelease.String(), downloadCB, queryParameter); err != nil {
79+
if err := libRelease.Resource.Download(downloadsDir, config, libRelease.String(), downloadCB, queryParameter); err != nil {
6680
return &cmderrors.FailedDownloadError{Message: tr("Can't download library"), Cause: err}
6781
}
6882
taskCB(&rpc.TaskProgress{Completed: true})

0 commit comments

Comments
 (0)