Mercurial > gemma
view auth/connection.go @ 143:abfac07bd82a vue-gettext
closing branch vue-gettext
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Mon, 02 Jul 2018 09:37:53 +0200 |
parents | f4523620ba5d |
children | 0c56c56a1c44 |
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 refCount int } 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.refCount <= 0 && con.access.Before(valid) { con.close() } } } 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 (cp *ConnectionPool) trim(conn *Connection) { conn.refCount-- for { least := time.Now() var count int var oldest *Connection for _, con := range cp.conns { if con.db != nil && con.refCount <= 0 { if con.access.Before(least) { least = con.access oldest = con } count++ } } if count <= maxOpen { break } oldest.close() } } 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 { con.refCount++ res <- result{con: con} return } db, err := opendb(con.user, con.password) if err != nil { res <- result{err: err} return } con.db = db con.refCount++ res <- result{con: con} } r := <-res if r.err != nil { return r.err } defer func() { cp.cmds <- func(cp *ConnectionPool) { cp.trim(r.con) } }() return fn(r.con.db) }