From 7f6a52019f812dd54c7cb224256d0387ec8d0204 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Mon, 7 Sep 2015 13:44:01 +0200 Subject: [PATCH] Add a flag to unlock database after each API request After the first API request, the database was locked as long as the API server is running. This prevents a user to also use the command-line client. This commit adds a new flag `-no-lock` that will close the database after each API request. Closes #234 --- api/api.go | 53 +++++++++++++++++++++++++++++++++++++++++++++--- api/router.go | 34 +++++++++++++++++++++++++++++-- cmd/api_serve.go | 1 + man/aptly.1 | 4 ++++ 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/api/api.go b/api/api.go index 30bddddf..6b8fc26a 100644 --- a/api/api.go +++ b/api/api.go @@ -22,17 +22,36 @@ func apiVersion(c *gin.Context) { c.JSON(200, gin.H{"Version": aptly.Version}) } -// Periodically flushes CollectionFactory to free up memory used by collections, -// flushing caches. +const ( + ACQUIREDB = iota + RELEASEDB +) + +// Periodically flushes CollectionFactory to free up memory used by +// collections, flushing caches. If the two channels are provided, +// they are used to acquire and release the database. // // Should be run in goroutine! -func cacheFlusher() { +func cacheFlusher(requests chan int, acks chan error) { ticker := time.Tick(15 * time.Minute) for { <-ticker func() { + // lock database if needed + if requests != nil { + requests <- ACQUIREDB + err := <-acks + if err != nil { + return + } + defer func() { + requests <- RELEASEDB + <-acks + }() + } + // lock everything to eliminate in-progress calls r := context.CollectionFactory().RemoteRepoCollection() r.Lock() @@ -56,6 +75,34 @@ func cacheFlusher() { } } +// Acquire database lock and release it when not needed anymore. Two +// channels must be provided. The first one is to receive requests to +// acquire/release the database and the second one is to send acks. +// +// Should be run in a goroutine! +func acquireDatabase(requests chan int, acks chan error) { + clients := 0 + for { + request := <-requests + switch request { + case ACQUIREDB: + if clients == 0 { + acks <- context.ReOpenDatabase() + } else { + acks <- nil + } + clients++ + case RELEASEDB: + clients-- + if clients == 0 { + acks <- context.CloseDatabase() + } else { + acks <- nil + } + } + } +} + // Common piece of code to show list of packages, // with searching & details if requested func showPackages(c *gin.Context, reflist *deb.PackageRefList) { diff --git a/api/router.go b/api/router.go index 8dcd8c6e..455cca80 100644 --- a/api/router.go +++ b/api/router.go @@ -12,11 +12,41 @@ var context *ctx.AptlyContext func Router(c *ctx.AptlyContext) http.Handler { context = c - go cacheFlusher() - router := gin.Default() router.Use(gin.ErrorLogger()) + if context.Flags().Lookup("no-lock").Value.Get().(bool) { + // We use a goroutine to count the number of + // concurrent requests. When no more requests are + // running, we close the database to free the lock. + requests := make(chan int) + acks := make(chan error) + + go acquireDatabase(requests, acks) + go cacheFlusher(requests, acks) + + router.Use(func(c *gin.Context) { + requests <- ACQUIREDB + err := <-acks + if err != nil { + c.Fail(500, err) + return + } + defer func() { + requests <- RELEASEDB + err = <-acks + if err != nil { + c.Fail(500, err) + return + } + }() + c.Next() + }) + + } else { + go cacheFlusher(nil, nil) + } + root := router.Group("/api") { diff --git a/cmd/api_serve.go b/cmd/api_serve.go index 6d62322e..fd4c765b 100644 --- a/cmd/api_serve.go +++ b/cmd/api_serve.go @@ -46,6 +46,7 @@ Example: } cmd.Flag.String("listen", ":8080", "host:port for HTTP listening") + cmd.Flag.Bool("no-lock", false, "don't lock the database") return cmd diff --git a/man/aptly.1 b/man/aptly.1 index 2fc2d03a..160772c3 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -1658,6 +1658,10 @@ Options: \-\fBlisten\fR=:8080 host:port for HTTP listening . +.TP +\-\fBno-lock\fR +don't lock the database and allow one to still use the command-line client +. .SH "RENDER GRAPH OF RELATIONSHIPS" \fBaptly\fR \fBgraph\fR .