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
This commit is contained in:
Vincent Bernat
2015-09-07 13:44:01 +02:00
parent 16101b56fe
commit 7f6a52019f
4 changed files with 87 additions and 5 deletions

View File

@@ -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) {

View File

@@ -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")
{

View File

@@ -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

View File

@@ -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
.