Skip to content

Commit 10e53e3

Browse files
committed
pkg/lib: lock clean-up, and add `path->pkg'
Make the installed-package database lock reentrant, change some functions to take the lock, and fix the documentation on when a lock is expected to be taken outside of functions.
1 parent 267ac03 commit 10e53e3

File tree

4 files changed

+116
-44
lines changed

4 files changed

+116
-44
lines changed

collects/compiler/commands/test.rkt

+4-7
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,10 @@
108108
[l
109109
(for-each do-test l)])]
110110
[packages?
111-
(unless (for*/or ([scope (in-list '(installation user shared))])
112-
(define pd
113-
(parameterize ([current-pkg-scope scope])
114-
(with-handlers ([exn:fail? (λ (x) #f)])
115-
(pkg-directory e))))
116-
(and pd (do-test pd)))
117-
(error 'test "Package ~e is not installed" e))]
111+
(define pd (pkg-directory e))
112+
(if pd
113+
(do-test pd)
114+
(error 'test "Package ~e is not installed" e))]
118115
[else
119116
(do-test e)]))
120117

collects/pkg/lib.rkt

+77-27
Original file line numberDiff line numberDiff line change
@@ -232,24 +232,35 @@
232232
(equal? p s))))
233233
#t))
234234

235+
(define pkg-lock-held (make-parameter #f))
236+
235237
(define (with-pkg-lock* read-only? t)
236-
(define d (pkg-dir))
237-
(unless read-only? (make-directory* d))
238-
(if (directory-exists? d)
239-
;; If the directory exists, assume that a lock file is
240-
;; available or creatable:
241-
(call-with-file-lock/timeout
242-
#f (if read-only? 'shared 'exclusive)
243-
t
244-
(λ () (pkg-error (~a "could not acquire package lock\n"
245-
" lock file: ~a")
246-
(pkg-lock-file)))
247-
#:lock-file (pkg-lock-file))
248-
;; Directory does not exist; we must be in read-only mode.
249-
;; Run `t' under the claim that no database is available
250-
;; (in case the database is created concurrently):
251-
(parameterize ([current-no-pkg-db #t])
252-
(t))))
238+
(define mode (if read-only? 'shared 'exclusive))
239+
(define held-mode (pkg-lock-held))
240+
(if (or (eq? mode held-mode)
241+
(eq? 'exclusive held-mode))
242+
(t)
243+
(let ([d (pkg-dir)])
244+
(unless read-only? (make-directory* d))
245+
(if (directory-exists? d)
246+
;; If the directory exists, assume that a lock file is
247+
;; available or creatable:
248+
(call-with-file-lock/timeout
249+
#f
250+
mode
251+
(lambda ()
252+
(parameterize ([pkg-lock-held mode])
253+
(t)))
254+
(λ () (pkg-error (~a "could not acquire package lock\n"
255+
" lock file: ~a")
256+
(pkg-lock-file)))
257+
#:lock-file (pkg-lock-file))
258+
;; Directory does not exist; we must be in read-only mode.
259+
;; Run `t' under the claim that no database is available
260+
;; (in case the database is created concurrently):
261+
(parameterize ([current-no-pkg-db #t])
262+
(parameterize ([pkg-lock-held mode])
263+
(t)))))))
253264
(define-syntax-rule (with-pkg-lock e ...)
254265
(with-pkg-lock* #f (λ () e ...)))
255266
(define-syntax-rule (with-pkg-lock/read-only e ...)
@@ -269,7 +280,8 @@
269280
"https://planet-compat.racket-lang.org")]))))
270281

271282
(define (pkg-config-indexes)
272-
(read-pkg-cfg/def "indexes"))
283+
(with-pkg-lock/read-only
284+
(read-pkg-cfg/def "indexes")))
273285

274286
(define (pkg-indexes)
275287
(or (current-pkg-indexes)
@@ -449,8 +461,9 @@
449461
[else 'user]))
450462
(define (default-pkg-scope-as-string)
451463
(parameterize ([current-pkg-scope 'installation])
452-
(define cfg (read-pkg-cfg))
453-
(hash-ref cfg "default-scope" "user")))
464+
(with-pkg-lock/read-only
465+
(define cfg (read-pkg-cfg))
466+
(hash-ref cfg "default-scope" "user"))))
454467

455468
(struct pkg-info (orig-pkg checksum auto?) #:prefab)
456469
(struct install-info (name orig-pkg directory clean? checksum))
@@ -463,13 +476,48 @@
463476
[checksum op]))
464477

465478
(define (pkg-directory pkg-name)
466-
(match-define (pkg-info orig-pkg checksum _)
467-
(package-info pkg-name))
468-
(match orig-pkg
469-
[`(link ,orig-pkg-dir)
470-
orig-pkg-dir]
471-
[_
472-
(build-path (pkg-installed-dir) pkg-name)]))
479+
(for/or ([scope (in-list '(user shared installation))])
480+
(parameterize ([current-pkg-scope scope])
481+
(with-pkg-lock/read-only
482+
(define info (package-info pkg-name #f))
483+
(and info
484+
(let ()
485+
(match-define (pkg-info orig-pkg checksum _) info)
486+
(match orig-pkg
487+
[`(link ,orig-pkg-dir)
488+
orig-pkg-dir]
489+
[_
490+
(build-path (pkg-installed-dir) pkg-name)])))))))
491+
492+
(define (path->pkg given-p)
493+
(define (explode p)
494+
(explode-path
495+
(normal-case-path
496+
(simple-form-path p))))
497+
(define (sub-path? < p d)
498+
(and ((length d) . <= . (length p))
499+
(for/and ([de (in-list d)]
500+
[pe (in-list p)])
501+
(equal? de pe))))
502+
(define p (explode given-p))
503+
(for/or ([scope (in-list '(user shared installation))])
504+
(parameterize ([current-pkg-scope scope])
505+
(with-pkg-lock/read-only
506+
(define d (explode (pkg-installed-dir)))
507+
(cond
508+
[(sub-path? < p d)
509+
;; Under the installation mode's package directory.
510+
;; We assume that no one else writes there, so the
511+
;; next path element is the package name.
512+
(path-element->string (list-ref p (length d)))]
513+
[else
514+
;; Maybe it's a linked package
515+
(for/or ([(k v) (in-hash (read-pkg-db))])
516+
(match (pkg-info-orig-pkg v)
517+
[`(link ,orig-pkg-dir)
518+
(and (sub-path? <= p (explode orig-pkg-dir))
519+
k)]
520+
[else #f]))])))))
473521

474522
(define (remove-package pkg-name)
475523
(printf "Removing ~a\n" pkg-name)
@@ -1666,6 +1714,8 @@
16661714
(parameter/c (or/c #f (listof url?)))]
16671715
[pkg-directory
16681716
(-> string? path-string?)]
1717+
[path->pkg
1718+
(-> path-string? (or/c #f string?))]
16691719
[pkg-desc
16701720
(-> string?
16711721
(or/c #f 'file 'dir 'link 'file-url 'dir-url 'github 'name)

collects/pkg/scribblings/lib.scrbl

+29-10
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ commands are built.}
2121
)]{
2222

2323
Evaluates the @racket[body]s while holding a lock to prevent
24-
concurrent modification to the package database. Use the
25-
@racket[with-pkg-lock/read-only] form for read-only access.
24+
concurrent modification to the package database for the current
25+
@tech{package scope}. Use the @racket[with-pkg-lock/read-only] form
26+
for read-only access. The lock is reentrant but not upgradable from
27+
read-only.
2628

2729
Use these form to wrap uses of functions from @racketmodname[pkg/lib]
28-
that read or modify the package database.}
29-
30+
that are documented to require the lock. Other functions from
31+
@racketmodname[pkg/lib] take the lock as needed.}
3032

3133
@deftogether[(
3234
@defparam[current-pkg-scope scope (or/c 'installation 'user 'shared)]
@@ -76,7 +78,12 @@ A structure type that is used to report installed-package information.}
7678
@defproc[(pkg-directory [name string?]) path-string?]{
7779

7880
Returns the directory that holds the installation of the installed
79-
package @racket[name].}
81+
(in any scope) package @racket[name].}
82+
83+
84+
@defproc[(path->pkg [path path-string?]) (or/c string? #f)]{
85+
86+
Returns the installed package containing @racket[path], if any.}
8087

8188

8289
@defproc[(default-pkg-scope) (or/c 'installation 'user 'shared)]{
@@ -131,7 +138,10 @@ needed.}
131138
@defproc[(pkg-config [set? boolean?] [keys/vals list?])
132139
void?]{
133140

134-
Implements the @racket[config] command.}
141+
Implements the @racket[config] command.
142+
143+
The package lock must be held (allowing writes if @racket[set?] is true); see
144+
@racket[with-pkg-lock].}
135145

136146

137147
@defproc[(pkg-create [format (or/c 'zip 'tgz 'plt 'MANIFEST)]
@@ -151,7 +161,9 @@ Implements the @racket[create] command.}
151161

152162
Implements the @racket[install] command. The result indicates which
153163
collections should be setup via @exec{raco setup}: @racket[#f] means
154-
all, and a list means only the indicated collections.}
164+
all, and a list means only the indicated collections.
165+
166+
The package lock must be held; see @racket[with-pkg-lock].}
155167

156168

157169
@defproc[(pkg-update [names (listof string?)]
@@ -163,15 +175,19 @@ all, and a list means only the indicated collections.}
163175
(or/c #f (listof (or/c path-string? (non-empty-listof path-string?))))]{
164176

165177
Implements the @racket[update] command. The result is the same as for
166-
@racket[install-pkgs].}
178+
@racket[install-pkgs].
179+
180+
The package lock must be held; see @racket[with-pkg-lock].}
167181

168182

169183
@defproc[(pkg-remove [names (listof string?)]
170184
[#:auto? auto? boolean? #f]
171185
[#:force? force? boolean? #f])
172186
void?]{
173187

174-
Implements the @racket[remove] command.}
188+
Implements the @racket[remove] command.
189+
190+
The package lock must be held; see @racket[with-pkg-lock].}
175191

176192

177193
@defproc[(pkg-show [indent string?]
@@ -180,7 +196,10 @@ Implements the @racket[remove] command.}
180196

181197
Implements the @racket[show] command for a single package scope,
182198
printing to the current output port. See also
183-
@racket[installed-pkg-names] and @racket[installed-pkg-table].}
199+
@racket[installed-pkg-names] and @racket[installed-pkg-table].
200+
201+
The package lock must be held to allow reads; see
202+
@racket[with-pkg-lock/read-only].}
184203

185204

186205
@defproc[(pkg-index-show [names (listof string?)]

collects/tests/pkg/tests-install.rkt

+6
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@
8383
$ "raco pkg install --link test-pkgs/pkg-test1-linking"
8484
$ "racket -e '(require pkg-test1)'"
8585
$ "racket -e '(require pkg-test1/a)'" =exit> 1
86+
$ "racket -e '(require pkg/lib)' -e '(path->pkg \"test-pkgs/pkg-test1-linking\")'" =stdout> "\"pkg-test1-linking\"\n"
87+
$ "racket -e '(require pkg/lib)' -e '(path->pkg \"test-pkgs/pkg-test1-linking/README\")'" =stdout> "\"pkg-test1-linking\"\n"
88+
$ "racket -e '(require pkg/lib)' -e '(path->pkg \"test-pkgs\")'" =stdout> "#f\n"
8689
$ "cp test-pkgs/pkg-test1-staging/a.rkt test-pkgs/pkg-test1-linking/pkg-test1/a.rkt"
8790
$ "racket -e '(require pkg-test1/a)'"
8891
$ "rm -f test-pkgs/pkg-test1-linking/pkg-test1/a.rkt"
@@ -116,5 +119,8 @@
116119
$ "raco pkg install --deps search-auto pkg-test2-snd"
117120
$ "racket -e '(require pkg-test1)'"
118121
$ "racket -e '(require pkg-test2)'"
122+
$ "racket -e '(require pkg/lib)' -e '(path->pkg (pkg-directory \"pkg-test1\"))'" =stdout> "\"pkg-test1\"\n"
123+
$ "racket -e '(require pkg/lib)' -e '(path->pkg (build-path (pkg-directory \"pkg-test1\") \"pkg-test2\"))'"
124+
=stdout> "\"pkg-test1\"\n"
119125
$ "raco pkg remove pkg-test2-snd pkg-test1"
120126
$ "racket -e '(require pkg-test1)'" =exit> 1)))))

0 commit comments

Comments
 (0)