Mercurial > gemma
comparison auth/connection.go @ 26:96a429c5f227
Fundamental connection pool based on tokens.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 25 Jun 2018 13:19:55 +0200 |
parents | |
children | c3e2cd7fa46f |
comparison
equal
deleted
inserted
replaced
25:a728610b3812 | 26:96a429c5f227 |
---|---|
1 package auth | |
2 | |
3 import ( | |
4 "database/sql" | |
5 "errors" | |
6 "time" | |
7 ) | |
8 | |
9 var ErrNoSuchToken = errors.New("No such token") | |
10 | |
11 var ConnPool = NewConnectionPool() | |
12 | |
13 const ( | |
14 maxOpen = 16 | |
15 maxDBIdle = time.Minute * 5 | |
16 maxTokenIdle = time.Minute * 20 | |
17 ) | |
18 | |
19 type Connection struct { | |
20 user string | |
21 password string | |
22 | |
23 access time.Time | |
24 db *sql.DB | |
25 } | |
26 | |
27 func (c *Connection) set(user, password string) { | |
28 c.user = user | |
29 c.password = password | |
30 c.access = time.Now() | |
31 } | |
32 | |
33 type ConnectionPool struct { | |
34 conns map[string]*Connection | |
35 cmds chan func(*ConnectionPool) | |
36 } | |
37 | |
38 func NewConnectionPool() *ConnectionPool { | |
39 cp := &ConnectionPool{ | |
40 conns: make(map[string]*Connection), | |
41 cmds: make(chan func(*ConnectionPool)), | |
42 } | |
43 go cp.run() | |
44 return cp | |
45 } | |
46 | |
47 func (cp *ConnectionPool) run() { | |
48 for { | |
49 select { | |
50 case cmd := <-cp.cmds: | |
51 cmd(cp) | |
52 case <-time.After(time.Minute): | |
53 cp.cleanDB() | |
54 case <-time.After(time.Minute * 5): | |
55 cp.cleanToken() | |
56 } | |
57 } | |
58 } | |
59 | |
60 func (cp *ConnectionPool) cleanDB() { | |
61 valid := time.Now().Add(-maxDBIdle) | |
62 for _, con := range cp.conns { | |
63 if con.access.Before(valid) { | |
64 con.db.Close() | |
65 con.db = nil | |
66 } | |
67 } | |
68 } | |
69 | |
70 func (cp *ConnectionPool) cleanToken() { | |
71 valid := time.Now().Add(-maxDBIdle) | |
72 for token, con := range cp.conns { | |
73 if con.access.Before(valid) { | |
74 if con.db != nil { | |
75 // TODO: Be more graceful here? | |
76 con.db.Close() | |
77 con.db = nil | |
78 } | |
79 delete(cp.conns, token) | |
80 } | |
81 } | |
82 } | |
83 | |
84 func (cp *ConnectionPool) Add(token, user, password string) *Connection { | |
85 res := make(chan *Connection) | |
86 | |
87 cp.cmds <- func(cp *ConnectionPool) { | |
88 | |
89 con := cp.conns[token] | |
90 if con == nil { | |
91 con = &Connection{} | |
92 cp.conns[token] = con | |
93 } | |
94 con.set(user, password) | |
95 res <- con | |
96 } | |
97 | |
98 con := <-res | |
99 return con | |
100 } | |
101 | |
102 func trim(cp *ConnectionPool) { | |
103 | |
104 least := time.Now() | |
105 var count int | |
106 var oldest *Connection | |
107 | |
108 for _, con := range cp.conns { | |
109 if con.db != nil { | |
110 if con.access.Before(least) { | |
111 least = con.access | |
112 oldest = con | |
113 } | |
114 count++ | |
115 } | |
116 } | |
117 if count > maxOpen { | |
118 oldest.db.Close() | |
119 oldest.db = nil | |
120 } | |
121 return | |
122 } | |
123 | |
124 func (cp *ConnectionPool) Do(token string, fn func(*sql.DB) error) error { | |
125 | |
126 type result struct { | |
127 con *Connection | |
128 err error | |
129 } | |
130 | |
131 res := make(chan result) | |
132 | |
133 cp.cmds <- func(cp *ConnectionPool) { | |
134 con := cp.conns[token] | |
135 if con == nil { | |
136 res <- result{err: ErrNoSuchToken} | |
137 return | |
138 } | |
139 con.access = time.Now() | |
140 if con.db != nil { | |
141 res <- result{con: con} | |
142 return | |
143 } | |
144 | |
145 db, err := opendb(con.user, con.password) | |
146 if err != nil { | |
147 res <- result{err: err} | |
148 return | |
149 } | |
150 con.db = db | |
151 } | |
152 | |
153 r := <-res | |
154 | |
155 if r.err != nil { | |
156 return r.err | |
157 } | |
158 | |
159 defer func() { cp.cmds <- trim }() | |
160 | |
161 return fn(r.con.db) | |
162 } |