Mercurial > gemma
view auth/connection.go @ 130:13b82701b1fb
Take expiring time from serialized tokens to garbage collect them.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Thu, 28 Jun 2018 16:59:16 +0200 |
parents | ee5a3dd8e972 |
children | af114cf64822 |
line wrap: on
line source
package auth import ( "database/sql" "errors" "log" "time" ) var ErrNoSuchToken = errors.New("No such token") var ConnPool = NewConnectionPool() const ( maxOpen = 16 maxDBIdle = time.Minute * 5 ) type Connection struct { user string password string access time.Time db *sql.DB } func (c *Connection) set(user, password string) { c.user = user c.password = password c.access = time.Now() } func (c *Connection) close() { if c.db != nil { if err := c.db.Close(); err != nil { log.Printf("warn: %v\n", err) } c.db = nil } } type ConnectionPool struct { conns map[string]*Connection cmds chan func(*ConnectionPool) } func NewConnectionPool() *ConnectionPool { cp := &ConnectionPool{ conns: make(map[string]*Connection), cmds: make(chan func(*ConnectionPool)), } go cp.run() return cp } func (cp *ConnectionPool) run() { for { select { case cmd := <-cp.cmds: cmd(cp) case <-time.After(time.Minute): cp.cleanDB() case <-time.After(time.Minute * 5): cp.cleanToken() } } } func (cp *ConnectionPool) cleanDB() { valid := time.Now().Add(-maxDBIdle) for _, con := range cp.conns { if con.access.Before(valid) && con.db != nil { con.db.Close() con.db = nil } } } func (cp *ConnectionPool) cleanToken() { now := time.Now() for token, con := range cp.conns { claims, err := TokenToClaims(token) if err != nil { // Should not happen. log.Printf("error: %v\n", err) con.close() delete(cp.conns, token) continue } expires := time.Unix(claims.ExpiresAt, 0) if expires.Before(now) { // TODO: Be more graceful here? con.close() delete(cp.conns, token) } } } func (cp *ConnectionPool) Delete(token string) bool { res := make(chan bool) cp.cmds <- func(cp *ConnectionPool) { conn, found := cp.conns[token] if !found { res <- false return } conn.close() delete(cp.conns, token) res <- true } return <-res } func (cp *ConnectionPool) Replace( token string, replace func(string, string) (string, error)) (string, error) { type res struct { token string err error } resCh := make(chan res) cp.cmds <- func(cp *ConnectionPool) { conn, found := cp.conns[token] if !found { resCh <- res{err: ErrNoSuchToken} return } newToken, err := replace(conn.user, conn.password) if err == nil { delete(cp.conns, token) cp.conns[newToken] = conn } resCh <- res{token: newToken, err: err} } r := <-resCh return r.token, r.err } func (cp *ConnectionPool) Add(token, user, password string) *Connection { res := make(chan *Connection) cp.cmds <- func(cp *ConnectionPool) { con := cp.conns[token] if con == nil { con = &Connection{} cp.conns[token] = con } con.set(user, password) res <- con } con := <-res return con } func trim(cp *ConnectionPool) { least := time.Now() var count int var oldest *Connection for _, con := range cp.conns { if con.db != nil { if con.access.Before(least) { least = con.access oldest = con } count++ } } if count > maxOpen { oldest.db.Close() oldest.db = nil } return } func (cp *ConnectionPool) Do(token string, fn func(*sql.DB) error) error { type result struct { con *Connection err error } res := make(chan result) cp.cmds <- func(cp *ConnectionPool) { con := cp.conns[token] if con == nil { res <- result{err: ErrNoSuchToken} return } con.access = time.Now() if con.db != nil { res <- result{con: con} return } db, err := opendb(con.user, con.password) if err != nil { res <- result{err: err} return } con.db = db } r := <-res if r.err != nil { return r.err } defer func() { cp.cmds <- trim }() return fn(r.con.db) }