diff auth/inmemory.go @ 148:0116aae1071b

Made ConnectionPool an interface and use current in-memory implementation.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 02 Jul 2018 11:00:19 +0200
parents
children 3349bfc2a047
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth/inmemory.go	Mon Jul 02 11:00:19 2018 +0200
@@ -0,0 +1,200 @@
+package auth
+
+import (
+	"database/sql"
+	"time"
+)
+
+type InMemoryConnectionPool struct {
+	conns map[string]*Connection
+	cmds  chan func(*InMemoryConnectionPool)
+}
+
+func NewInMemoryConnectionPool() *InMemoryConnectionPool {
+	cp := &InMemoryConnectionPool{
+		conns: make(map[string]*Connection),
+		cmds:  make(chan func(*InMemoryConnectionPool)),
+	}
+	go cp.run()
+	return cp
+}
+
+func (cp *InMemoryConnectionPool) 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 *InMemoryConnectionPool) cleanDB() {
+	valid := time.Now().Add(-maxDBIdle)
+	for _, con := range cp.conns {
+		if con.refCount <= 0 && con.last().Before(valid) {
+			con.close()
+		}
+	}
+}
+
+func (cp *InMemoryConnectionPool) cleanToken() {
+	now := time.Now()
+	for token, con := range cp.conns {
+		expires := time.Unix(con.session.ExpiresAt, 0)
+		if expires.Before(now) {
+			// TODO: Be more graceful here?
+			con.close()
+			delete(cp.conns, token)
+		}
+	}
+}
+
+func (cp *InMemoryConnectionPool) Delete(token string) bool {
+	res := make(chan bool)
+	cp.cmds <- func(cp *InMemoryConnectionPool) {
+		conn, found := cp.conns[token]
+		if !found {
+			res <- false
+			return
+		}
+		conn.close()
+		delete(cp.conns, token)
+		res <- true
+	}
+	return <-res
+}
+
+func (cp *InMemoryConnectionPool) Add(token string, session *Session) *Connection {
+	res := make(chan *Connection)
+
+	cp.cmds <- func(cp *InMemoryConnectionPool) {
+		con := cp.conns[token]
+		if con == nil {
+			con = &Connection{}
+			cp.conns[token] = con
+		}
+		con.set(session)
+		res <- con
+	}
+
+	con := <-res
+	return con
+}
+
+func (cp *InMemoryConnectionPool) Renew(token string) (string, error) {
+
+	type result struct {
+		newToken string
+		err      error
+	}
+
+	resCh := make(chan result)
+
+	cp.cmds <- func(cp *InMemoryConnectionPool) {
+		con := cp.conns[token]
+		if con == nil {
+			resCh <- result{err: ErrNoSuchToken}
+		} else {
+			delete(cp.conns, token)
+			newToken := GenerateSessionKey()
+			// TODO: Ensure that this is not racy!
+			con.session.ExpiresAt = time.Now().Add(maxTokenValid).Unix()
+			cp.conns[newToken] = con
+			resCh <- result{newToken: newToken}
+		}
+	}
+
+	r := <-resCh
+	return r.newToken, r.err
+}
+
+func (cp *InMemoryConnectionPool) 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 last := con.last(); last.Before(least) {
+					least = last
+					oldest = con
+				}
+				count++
+			}
+		}
+		if count <= maxOpen {
+			break
+		}
+		oldest.close()
+	}
+}
+
+func (cp *InMemoryConnectionPool) Do(token string, fn func(*sql.DB) error) error {
+
+	type result struct {
+		con *Connection
+		err error
+	}
+
+	res := make(chan result)
+
+	cp.cmds <- func(cp *InMemoryConnectionPool) {
+		con := cp.conns[token]
+		if con == nil {
+			res <- result{err: ErrNoSuchToken}
+			return
+		}
+		con.touch()
+		if con.db != nil {
+			con.refCount++
+			res <- result{con: con}
+			return
+		}
+
+		session := con.session
+		db, err := opendb(session.User, session.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 *InMemoryConnectionPool) {
+			cp.trim(r.con)
+		}
+	}()
+
+	return fn(r.con.db)
+}
+
+func (cp *InMemoryConnectionPool) Session(token string) *Session {
+	res := make(chan *Session)
+	cp.cmds <- func(cp *InMemoryConnectionPool) {
+		con := cp.conns[token]
+		if con == nil {
+			res <- nil
+		} else {
+			con.touch()
+			res <- con.session
+		}
+	}
+	return <-res
+}