Imported Upstream version 0.0~git20150901.0.d8dbe4d
Tianon Gravi
8 years ago
0 | ||
1 | Apache License | |
2 | Version 2.0, January 2004 | |
3 | http://www.apache.org/licenses/ | |
4 | ||
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
6 | ||
7 | 1. Definitions. | |
8 | ||
9 | "License" shall mean the terms and conditions for use, reproduction, | |
10 | and distribution as defined by Sections 1 through 9 of this document. | |
11 | ||
12 | "Licensor" shall mean the copyright owner or entity authorized by | |
13 | the copyright owner that is granting the License. | |
14 | ||
15 | "Legal Entity" shall mean the union of the acting entity and all | |
16 | other entities that control, are controlled by, or are under common | |
17 | control with that entity. For the purposes of this definition, | |
18 | "control" means (i) the power, direct or indirect, to cause the | |
19 | direction or management of such entity, whether by contract or | |
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
21 | outstanding shares, or (iii) beneficial ownership of such entity. | |
22 | ||
23 | "You" (or "Your") shall mean an individual or Legal Entity | |
24 | exercising permissions granted by this License. | |
25 | ||
26 | "Source" form shall mean the preferred form for making modifications, | |
27 | including but not limited to software source code, documentation | |
28 | source, and configuration files. | |
29 | ||
30 | "Object" form shall mean any form resulting from mechanical | |
31 | transformation or translation of a Source form, including but | |
32 | not limited to compiled object code, generated documentation, | |
33 | and conversions to other media types. | |
34 | ||
35 | "Work" shall mean the work of authorship, whether in Source or | |
36 | Object form, made available under the License, as indicated by a | |
37 | copyright notice that is included in or attached to the work | |
38 | (an example is provided in the Appendix below). | |
39 | ||
40 | "Derivative Works" shall mean any work, whether in Source or Object | |
41 | form, that is based on (or derived from) the Work and for which the | |
42 | editorial revisions, annotations, elaborations, or other modifications | |
43 | represent, as a whole, an original work of authorship. For the purposes | |
44 | of this License, Derivative Works shall not include works that remain | |
45 | separable from, or merely link (or bind by name) to the interfaces of, | |
46 | the Work and Derivative Works thereof. | |
47 | ||
48 | "Contribution" shall mean any work of authorship, including | |
49 | the original version of the Work and any modifications or additions | |
50 | to that Work or Derivative Works thereof, that is intentionally | |
51 | submitted to Licensor for inclusion in the Work by the copyright owner | |
52 | or by an individual or Legal Entity authorized to submit on behalf of | |
53 | the copyright owner. For the purposes of this definition, "submitted" | |
54 | means any form of electronic, verbal, or written communication sent | |
55 | to the Licensor or its representatives, including but not limited to | |
56 | communication on electronic mailing lists, source code control systems, | |
57 | and issue tracking systems that are managed by, or on behalf of, the | |
58 | Licensor for the purpose of discussing and improving the Work, but | |
59 | excluding communication that is conspicuously marked or otherwise | |
60 | designated in writing by the copyright owner as "Not a Contribution." | |
61 | ||
62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
63 | on behalf of whom a Contribution has been received by Licensor and | |
64 | subsequently incorporated within the Work. | |
65 | ||
66 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
67 | this License, each Contributor hereby grants to You a perpetual, | |
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
69 | copyright license to reproduce, prepare Derivative Works of, | |
70 | publicly display, publicly perform, sublicense, and distribute the | |
71 | Work and such Derivative Works in Source or Object form. | |
72 | ||
73 | 3. Grant of Patent License. Subject to the terms and conditions of | |
74 | this License, each Contributor hereby grants to You a perpetual, | |
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
76 | (except as stated in this section) patent license to make, have made, | |
77 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
78 | where such license applies only to those patent claims licensable | |
79 | by such Contributor that are necessarily infringed by their | |
80 | Contribution(s) alone or by combination of their Contribution(s) | |
81 | with the Work to which such Contribution(s) was submitted. If You | |
82 | institute patent litigation against any entity (including a | |
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
84 | or a Contribution incorporated within the Work constitutes direct | |
85 | or contributory patent infringement, then any patent licenses | |
86 | granted to You under this License for that Work shall terminate | |
87 | as of the date such litigation is filed. | |
88 | ||
89 | 4. Redistribution. You may reproduce and distribute copies of the | |
90 | Work or Derivative Works thereof in any medium, with or without | |
91 | modifications, and in Source or Object form, provided that You | |
92 | meet the following conditions: | |
93 | ||
94 | (a) You must give any other recipients of the Work or | |
95 | Derivative Works a copy of this License; and | |
96 | ||
97 | (b) You must cause any modified files to carry prominent notices | |
98 | stating that You changed the files; and | |
99 | ||
100 | (c) You must retain, in the Source form of any Derivative Works | |
101 | that You distribute, all copyright, patent, trademark, and | |
102 | attribution notices from the Source form of the Work, | |
103 | excluding those notices that do not pertain to any part of | |
104 | the Derivative Works; and | |
105 | ||
106 | (d) If the Work includes a "NOTICE" text file as part of its | |
107 | distribution, then any Derivative Works that You distribute must | |
108 | include a readable copy of the attribution notices contained | |
109 | within such NOTICE file, excluding those notices that do not | |
110 | pertain to any part of the Derivative Works, in at least one | |
111 | of the following places: within a NOTICE text file distributed | |
112 | as part of the Derivative Works; within the Source form or | |
113 | documentation, if provided along with the Derivative Works; or, | |
114 | within a display generated by the Derivative Works, if and | |
115 | wherever such third-party notices normally appear. The contents | |
116 | of the NOTICE file are for informational purposes only and | |
117 | do not modify the License. You may add Your own attribution | |
118 | notices within Derivative Works that You distribute, alongside | |
119 | or as an addendum to the NOTICE text from the Work, provided | |
120 | that such additional attribution notices cannot be construed | |
121 | as modifying the License. | |
122 | ||
123 | You may add Your own copyright statement to Your modifications and | |
124 | may provide additional or different license terms and conditions | |
125 | for use, reproduction, or distribution of Your modifications, or | |
126 | for any such Derivative Works as a whole, provided Your use, | |
127 | reproduction, and distribution of the Work otherwise complies with | |
128 | the conditions stated in this License. | |
129 | ||
130 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
131 | any Contribution intentionally submitted for inclusion in the Work | |
132 | by You to the Licensor shall be under the terms and conditions of | |
133 | this License, without any additional terms or conditions. | |
134 | Notwithstanding the above, nothing herein shall supersede or modify | |
135 | the terms of any separate license agreement you may have executed | |
136 | with Licensor regarding such Contributions. | |
137 | ||
138 | 6. Trademarks. This License does not grant permission to use the trade | |
139 | names, trademarks, service marks, or product names of the Licensor, | |
140 | except as required for reasonable and customary use in describing the | |
141 | origin of the Work and reproducing the content of the NOTICE file. | |
142 | ||
143 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
144 | agreed to in writing, Licensor provides the Work (and each | |
145 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
147 | implied, including, without limitation, any warranties or conditions | |
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
149 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
150 | appropriateness of using or redistributing the Work and assume any | |
151 | risks associated with Your exercise of permissions under this License. | |
152 | ||
153 | 8. Limitation of Liability. In no event and under no legal theory, | |
154 | whether in tort (including negligence), contract, or otherwise, | |
155 | unless required by applicable law (such as deliberate and grossly | |
156 | negligent acts) or agreed to in writing, shall any Contributor be | |
157 | liable to You for damages, including any direct, indirect, special, | |
158 | incidental, or consequential damages of any character arising as a | |
159 | result of this License or out of the use or inability to use the | |
160 | Work (including but not limited to damages for loss of goodwill, | |
161 | work stoppage, computer failure or malfunction, or any and all | |
162 | other commercial damages or losses), even if such Contributor | |
163 | has been advised of the possibility of such damages. | |
164 | ||
165 | 9. Accepting Warranty or Additional Liability. While redistributing | |
166 | the Work or Derivative Works thereof, You may choose to offer, | |
167 | and charge a fee for, acceptance of support, warranty, indemnity, | |
168 | or other liability obligations and/or rights consistent with this | |
169 | License. However, in accepting such obligations, You may act only | |
170 | on Your own behalf and on Your sole responsibility, not on behalf | |
171 | of any other Contributor, and only if You agree to indemnify, | |
172 | defend, and hold each Contributor harmless for any liability | |
173 | incurred by, or claims asserted against, such Contributor by reason | |
174 | of your accepting any such warranty or additional liability. |
39 | 39 | "MONITOR": {Set: MonitorState}, |
40 | 40 | } |
41 | 41 | |
42 | func init() { | |
43 | for n, ci := range commandInfos { | |
44 | commandInfos[strings.ToLower(n)] = ci | |
45 | } | |
46 | } | |
47 | ||
42 | 48 | func LookupCommandInfo(commandName string) CommandInfo { |
49 | if ci, ok := commandInfos[commandName]; ok { | |
50 | return ci | |
51 | } | |
43 | 52 | return commandInfos[strings.ToUpper(commandName)] |
44 | 53 | } |
0 | package internal | |
1 | ||
2 | import "testing" | |
3 | ||
4 | func TestLookupCommandInfo(t *testing.T) { | |
5 | for _, n := range []string{"watch", "WATCH", "wAtch"} { | |
6 | if LookupCommandInfo(n) == (CommandInfo{}) { | |
7 | t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n) | |
8 | } | |
9 | } | |
10 | } | |
11 | ||
12 | func benchmarkLookupCommandInfo(b *testing.B, names ...string) { | |
13 | for i := 0; i < b.N; i++ { | |
14 | for _, c := range names { | |
15 | LookupCommandInfo(c) | |
16 | } | |
17 | } | |
18 | } | |
19 | ||
20 | func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) { | |
21 | benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR") | |
22 | } | |
23 | ||
24 | func BenchmarkLookupCommandInfoMixedCase(b *testing.B) { | |
25 | benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR") | |
26 | } |
48 | 48 | |
49 | 49 | _, err = c.Do("SELECT", "9") |
50 | 50 | if err != nil { |
51 | c.Close() | |
51 | 52 | return nil, err |
52 | 53 | } |
53 | 54 | |
54 | 55 | n, err := redis.Int(c.Do("DBSIZE")) |
55 | 56 | if err != nil { |
57 | c.Close() | |
56 | 58 | return nil, err |
57 | 59 | } |
58 | 60 | |
59 | 61 | if n != 0 { |
62 | c.Close() | |
60 | 63 | return nil, errors.New("database #9 is not empty, test can not continue") |
61 | 64 | } |
62 | 65 |
20 | 20 | "fmt" |
21 | 21 | "io" |
22 | 22 | "net" |
23 | "net/url" | |
24 | "regexp" | |
23 | 25 | "strconv" |
24 | 26 | "sync" |
25 | 27 | "time" |
50 | 52 | numScratch [40]byte |
51 | 53 | } |
52 | 54 | |
53 | // Dial connects to the Redis server at the given network and address. | |
54 | func Dial(network, address string) (Conn, error) { | |
55 | dialer := xDialer{} | |
56 | return dialer.Dial(network, address) | |
57 | } | |
58 | ||
59 | 55 | // DialTimeout acts like Dial but takes timeouts for establishing the |
60 | 56 | // connection to the server, writing a command and reading a reply. |
57 | // | |
58 | // DialTimeout is deprecated. | |
61 | 59 | func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { |
62 | netDialer := net.Dialer{Timeout: connectTimeout} | |
63 | dialer := xDialer{ | |
64 | NetDial: netDialer.Dial, | |
65 | ReadTimeout: readTimeout, | |
66 | WriteTimeout: writeTimeout, | |
67 | } | |
68 | return dialer.Dial(network, address) | |
69 | } | |
70 | ||
71 | // A Dialer specifies options for connecting to a Redis server. | |
72 | type xDialer struct { | |
73 | // NetDial specifies the dial function for creating TCP connections. If | |
74 | // NetDial is nil, then net.Dial is used. | |
75 | NetDial func(network, addr string) (net.Conn, error) | |
76 | ||
77 | // ReadTimeout specifies the timeout for reading a single command | |
78 | // reply. If ReadTimeout is zero, then no timeout is used. | |
79 | ReadTimeout time.Duration | |
80 | ||
81 | // WriteTimeout specifies the timeout for writing a single command. If | |
82 | // WriteTimeout is zero, then no timeout is used. | |
83 | WriteTimeout time.Duration | |
84 | } | |
85 | ||
86 | // Dial connects to the Redis server at address on the named network. | |
87 | func (d *xDialer) Dial(network, address string) (Conn, error) { | |
88 | dial := d.NetDial | |
89 | if dial == nil { | |
90 | dial = net.Dial | |
91 | } | |
92 | netConn, err := dial(network, address) | |
60 | return Dial(network, address, | |
61 | DialConnectTimeout(connectTimeout), | |
62 | DialReadTimeout(readTimeout), | |
63 | DialWriteTimeout(writeTimeout)) | |
64 | } | |
65 | ||
66 | // DialOption specifies an option for dialing a Redis server. | |
67 | type DialOption struct { | |
68 | f func(*dialOptions) | |
69 | } | |
70 | ||
71 | type dialOptions struct { | |
72 | readTimeout time.Duration | |
73 | writeTimeout time.Duration | |
74 | dial func(network, addr string) (net.Conn, error) | |
75 | db int | |
76 | password string | |
77 | } | |
78 | ||
79 | // DialReadTimeout specifies the timeout for reading a single command reply. | |
80 | func DialReadTimeout(d time.Duration) DialOption { | |
81 | return DialOption{func(do *dialOptions) { | |
82 | do.readTimeout = d | |
83 | }} | |
84 | } | |
85 | ||
86 | // DialWriteTimeout specifies the timeout for writing a single command. | |
87 | func DialWriteTimeout(d time.Duration) DialOption { | |
88 | return DialOption{func(do *dialOptions) { | |
89 | do.writeTimeout = d | |
90 | }} | |
91 | } | |
92 | ||
93 | // DialConnectTimeout specifies the timeout for connecting to the Redis server. | |
94 | func DialConnectTimeout(d time.Duration) DialOption { | |
95 | return DialOption{func(do *dialOptions) { | |
96 | dialer := net.Dialer{Timeout: d} | |
97 | do.dial = dialer.Dial | |
98 | }} | |
99 | } | |
100 | ||
101 | // DialNetDial specifies a custom dial function for creating TCP | |
102 | // connections. If this option is left out, then net.Dial is | |
103 | // used. DialNetDial overrides DialConnectTimeout. | |
104 | func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { | |
105 | return DialOption{func(do *dialOptions) { | |
106 | do.dial = dial | |
107 | }} | |
108 | } | |
109 | ||
110 | // DialDatabase specifies the database to select when dialing a connection. | |
111 | func DialDatabase(db int) DialOption { | |
112 | return DialOption{func(do *dialOptions) { | |
113 | do.db = db | |
114 | }} | |
115 | } | |
116 | ||
117 | // DialPassword specifies the password to use when connecting to | |
118 | // the Redis server. | |
119 | func DialPassword(password string) DialOption { | |
120 | return DialOption{func(do *dialOptions) { | |
121 | do.password = password | |
122 | }} | |
123 | } | |
124 | ||
125 | // Dial connects to the Redis server at the given network and | |
126 | // address using the specified options. | |
127 | func Dial(network, address string, options ...DialOption) (Conn, error) { | |
128 | do := dialOptions{ | |
129 | dial: net.Dial, | |
130 | } | |
131 | for _, option := range options { | |
132 | option.f(&do) | |
133 | } | |
134 | ||
135 | netConn, err := do.dial(network, address) | |
93 | 136 | if err != nil { |
94 | 137 | return nil, err |
95 | 138 | } |
96 | return &conn{ | |
139 | c := &conn{ | |
97 | 140 | conn: netConn, |
98 | 141 | bw: bufio.NewWriter(netConn), |
99 | 142 | br: bufio.NewReader(netConn), |
100 | readTimeout: d.ReadTimeout, | |
101 | writeTimeout: d.WriteTimeout, | |
102 | }, nil | |
143 | readTimeout: do.readTimeout, | |
144 | writeTimeout: do.writeTimeout, | |
145 | } | |
146 | ||
147 | if do.password != "" { | |
148 | if _, err := c.Do("AUTH", do.password); err != nil { | |
149 | netConn.Close() | |
150 | return nil, err | |
151 | } | |
152 | } | |
153 | ||
154 | if do.db != 0 { | |
155 | if _, err := c.Do("SELECT", do.db); err != nil { | |
156 | netConn.Close() | |
157 | return nil, err | |
158 | } | |
159 | } | |
160 | ||
161 | return c, nil | |
162 | } | |
163 | ||
164 | var pathDBRegexp = regexp.MustCompile(`/(\d)\z`) | |
165 | ||
166 | // DialURL connects to a Redis server at the given URL using the Redis | |
167 | // URI scheme. URLs should follow the draft IANA specification for the | |
168 | // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). | |
169 | func DialURL(rawurl string, options ...DialOption) (Conn, error) { | |
170 | u, err := url.Parse(rawurl) | |
171 | if err != nil { | |
172 | return nil, err | |
173 | } | |
174 | ||
175 | if u.Scheme != "redis" { | |
176 | return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) | |
177 | } | |
178 | ||
179 | // As per the IANA draft spec, the host defaults to localhost and | |
180 | // the port defaults to 6379. | |
181 | host, port, err := net.SplitHostPort(u.Host) | |
182 | if err != nil { | |
183 | // assume port is missing | |
184 | host = u.Host | |
185 | port = "6379" | |
186 | } | |
187 | if host == "" { | |
188 | host = "localhost" | |
189 | } | |
190 | address := net.JoinHostPort(host, port) | |
191 | ||
192 | if u.User != nil { | |
193 | password, isSet := u.User.Password() | |
194 | if isSet { | |
195 | options = append(options, DialPassword(password)) | |
196 | } | |
197 | } | |
198 | ||
199 | match := pathDBRegexp.FindStringSubmatch(u.Path) | |
200 | if len(match) == 2 { | |
201 | db, err := strconv.Atoi(match[1]) | |
202 | if err != nil { | |
203 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) | |
204 | } | |
205 | if db != 0 { | |
206 | options = append(options, DialDatabase(db)) | |
207 | } | |
208 | } else if u.Path != "" { | |
209 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) | |
210 | } | |
211 | ||
212 | return Dial("tcp", address, options...) | |
103 | 213 | } |
104 | 214 | |
105 | 215 | // NewConn returns a new Redigo connection for the given net connection. |
416 | 526 | } |
417 | 527 | |
418 | 528 | if cmd != "" { |
419 | c.writeCommand(cmd, args) | |
529 | if err := c.writeCommand(cmd, args); err != nil { | |
530 | return nil, c.fatal(err) | |
531 | } | |
420 | 532 | } |
421 | 533 | |
422 | 534 | if err := c.bw.Flush(); err != nil { |
18 | 18 | "bytes" |
19 | 19 | "math" |
20 | 20 | "net" |
21 | "os" | |
21 | 22 | "reflect" |
22 | 23 | "strings" |
23 | 24 | "testing" |
421 | 422 | } |
422 | 423 | } |
423 | 424 | |
425 | var dialErrors = []struct { | |
426 | rawurl string | |
427 | expectedError string | |
428 | }{ | |
429 | { | |
430 | "localhost", | |
431 | "invalid redis URL scheme", | |
432 | }, | |
433 | // The error message for invalid hosts is diffferent in different | |
434 | // versions of Go, so just check that there is an error message. | |
435 | { | |
436 | "redis://weird url", | |
437 | "", | |
438 | }, | |
439 | { | |
440 | "redis://foo:bar:baz", | |
441 | "", | |
442 | }, | |
443 | { | |
444 | "http://www.google.com", | |
445 | "invalid redis URL scheme: http", | |
446 | }, | |
447 | { | |
448 | "redis://x:abc123@localhost", | |
449 | "no password is set", | |
450 | }, | |
451 | { | |
452 | "redis://localhost:6379/abc123", | |
453 | "invalid database: abc123", | |
454 | }, | |
455 | } | |
456 | ||
457 | func TestDialURL(t *testing.T) { | |
458 | for _, d := range dialErrors { | |
459 | _, err := redis.DialURL(d.rawurl) | |
460 | if err == nil || !strings.Contains(err.Error(), d.expectedError) { | |
461 | t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError) | |
462 | } | |
463 | } | |
464 | ||
465 | checkPort := func(network, address string) (net.Conn, error) { | |
466 | if address != "localhost:6379" { | |
467 | t.Errorf("DialURL did not set port to 6379 by default (got %v)", address) | |
468 | } | |
469 | return net.Dial(network, address) | |
470 | } | |
471 | c, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort)) | |
472 | if err != nil { | |
473 | t.Error("dial error:", err) | |
474 | } | |
475 | c.Close() | |
476 | ||
477 | checkHost := func(network, address string) (net.Conn, error) { | |
478 | if address != "localhost:6379" { | |
479 | t.Errorf("DialURL did not set host to localhost by default (got %v)", address) | |
480 | } | |
481 | return net.Dial(network, address) | |
482 | } | |
483 | c, err = redis.DialURL("redis://:6379", redis.DialNetDial(checkHost)) | |
484 | if err != nil { | |
485 | t.Error("dial error:", err) | |
486 | } | |
487 | c.Close() | |
488 | ||
489 | // Check that the database is set correctly | |
490 | c1, err := redis.DialURL("redis://:6379/8") | |
491 | defer c1.Close() | |
492 | if err != nil { | |
493 | t.Error("Dial error:", err) | |
494 | } | |
495 | dbSize, _ := redis.Int(c1.Do("DBSIZE")) | |
496 | if dbSize > 0 { | |
497 | t.Fatal("DB 8 has existing keys; aborting test to avoid overwriting data") | |
498 | } | |
499 | c1.Do("SET", "var", "val") | |
500 | ||
501 | c2, err := redis.Dial("tcp", ":6379") | |
502 | defer c2.Close() | |
503 | if err != nil { | |
504 | t.Error("dial error:", err) | |
505 | } | |
506 | _, err = c2.Do("SELECT", "8") | |
507 | if err != nil { | |
508 | t.Error(err) | |
509 | } | |
510 | got, err := redis.String(c2.Do("GET", "var")) | |
511 | if err != nil { | |
512 | t.Error(err) | |
513 | } | |
514 | if got != "val" { | |
515 | t.Error("DialURL did not correctly set the db.") | |
516 | } | |
517 | _, err = c2.Do("DEL", "var") | |
518 | } | |
519 | ||
424 | 520 | // Connect to local instance of Redis running on the default port. |
425 | 521 | func ExampleDial(x int) { |
426 | 522 | c, err := redis.Dial("tcp", ":6379") |
427 | 523 | if err != nil { |
428 | 524 | // handle error |
525 | } | |
526 | defer c.Close() | |
527 | } | |
528 | ||
529 | // Connect to remote instance of Redis using a URL. | |
530 | func ExampleDialURL() { | |
531 | c, err := redis.DialURL(os.Getenv("REDIS_URL")) | |
532 | if err != nil { | |
533 | // handle connection error | |
429 | 534 | } |
430 | 535 | defer c.Close() |
431 | 536 | } |
31 | 31 | redis.Conn |
32 | 32 | } |
33 | 33 | |
34 | func (c *poolTestConn) Close() error { c.d.open -= 1; return nil } | |
35 | func (c *poolTestConn) Err() error { return c.err } | |
36 | ||
37 | func (c *poolTestConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | |
34 | func (c *poolTestConn) Close() error { | |
35 | c.d.mu.Lock() | |
36 | c.d.open -= 1 | |
37 | c.d.mu.Unlock() | |
38 | return c.Conn.Close() | |
39 | } | |
40 | ||
41 | func (c *poolTestConn) Err() error { return c.err } | |
42 | ||
43 | func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) { | |
38 | 44 | if commandName == "ERR" { |
39 | 45 | c.err = args[0].(error) |
40 | 46 | commandName = "PING" |
51 | 57 | } |
52 | 58 | |
53 | 59 | type poolDialer struct { |
60 | mu sync.Mutex | |
54 | 61 | t *testing.T |
55 | 62 | dialed int |
56 | 63 | open int |
59 | 66 | } |
60 | 67 | |
61 | 68 | func (d *poolDialer) dial() (redis.Conn, error) { |
69 | d.mu.Lock() | |
62 | 70 | d.dialed += 1 |
63 | if d.dialErr != nil { | |
71 | dialErr := d.dialErr | |
72 | d.mu.Unlock() | |
73 | if dialErr != nil { | |
64 | 74 | return nil, d.dialErr |
65 | 75 | } |
66 | 76 | c, err := redistest.Dial() |
67 | 77 | if err != nil { |
68 | 78 | return nil, err |
69 | 79 | } |
80 | d.mu.Lock() | |
70 | 81 | d.open += 1 |
82 | d.mu.Unlock() | |
71 | 83 | return &poolTestConn{d: d, Conn: c}, nil |
72 | 84 | } |
73 | 85 | |
74 | 86 | func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { |
87 | d.mu.Lock() | |
75 | 88 | if d.dialed != dialed { |
76 | 89 | d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) |
77 | 90 | } |
81 | 94 | if active := p.ActiveCount(); active != open { |
82 | 95 | d.t.Errorf("%s: active=%d, want %d", message, active, open) |
83 | 96 | } |
97 | d.mu.Unlock() | |
84 | 98 | } |
85 | 99 | |
86 | 100 | func TestPoolReuse(t *testing.T) { |
110 | 124 | MaxIdle: 2, |
111 | 125 | Dial: d.dial, |
112 | 126 | } |
127 | defer p.Close() | |
128 | ||
113 | 129 | for i := 0; i < 10; i++ { |
114 | 130 | c1 := p.Get() |
115 | 131 | c1.Do("PING") |
132 | 148 | MaxIdle: 2, |
133 | 149 | Dial: d.dial, |
134 | 150 | } |
151 | defer p.Close() | |
135 | 152 | |
136 | 153 | c := p.Get() |
137 | 154 | c.Do("ERR", io.EOF) |
153 | 170 | MaxIdle: 2, |
154 | 171 | Dial: d.dial, |
155 | 172 | } |
173 | defer p.Close() | |
156 | 174 | |
157 | 175 | c1 := p.Get() |
158 | 176 | c1.Do("PING") |
194 | 212 | IdleTimeout: 300 * time.Second, |
195 | 213 | Dial: d.dial, |
196 | 214 | } |
215 | defer p.Close() | |
197 | 216 | |
198 | 217 | now := time.Now() |
199 | 218 | redis.SetNowFunc(func() time.Time { return now }) |
212 | 231 | c.Close() |
213 | 232 | |
214 | 233 | d.check("2", p, 2, 1) |
215 | ||
216 | p.Close() | |
217 | 234 | } |
218 | 235 | |
219 | 236 | func TestPoolConcurrenSendReceive(t *testing.T) { |
220 | 237 | p := &redis.Pool{ |
221 | 238 | Dial: redistest.Dial, |
222 | 239 | } |
240 | defer p.Close() | |
241 | ||
223 | 242 | c := p.Get() |
224 | 243 | done := make(chan error, 1) |
225 | 244 | go func() { |
237 | 256 | t.Fatalf("Do() returned error %v", err) |
238 | 257 | } |
239 | 258 | c.Close() |
240 | p.Close() | |
241 | 259 | } |
242 | 260 | |
243 | 261 | func TestPoolBorrowCheck(t *testing.T) { |
247 | 265 | Dial: d.dial, |
248 | 266 | TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, |
249 | 267 | } |
268 | defer p.Close() | |
250 | 269 | |
251 | 270 | for i := 0; i < 10; i++ { |
252 | 271 | c := p.Get() |
254 | 273 | c.Close() |
255 | 274 | } |
256 | 275 | d.check("1", p, 10, 1) |
257 | p.Close() | |
258 | 276 | } |
259 | 277 | |
260 | 278 | func TestPoolMaxActive(t *testing.T) { |
264 | 282 | MaxActive: 2, |
265 | 283 | Dial: d.dial, |
266 | 284 | } |
285 | defer p.Close() | |
286 | ||
267 | 287 | c1 := p.Get() |
268 | 288 | c1.Do("PING") |
269 | 289 | c2 := p.Get() |
288 | 308 | c3.Close() |
289 | 309 | |
290 | 310 | d.check("4", p, 2, 2) |
291 | p.Close() | |
292 | 311 | } |
293 | 312 | |
294 | 313 | func TestPoolMonitorCleanup(t *testing.T) { |
298 | 317 | MaxActive: 2, |
299 | 318 | Dial: d.dial, |
300 | 319 | } |
320 | defer p.Close() | |
321 | ||
301 | 322 | c := p.Get() |
302 | 323 | c.Send("MONITOR") |
303 | 324 | c.Close() |
304 | 325 | |
305 | 326 | d.check("", p, 1, 0) |
306 | p.Close() | |
307 | 327 | } |
308 | 328 | |
309 | 329 | func TestPoolPubSubCleanup(t *testing.T) { |
313 | 333 | MaxActive: 2, |
314 | 334 | Dial: d.dial, |
315 | 335 | } |
336 | defer p.Close() | |
316 | 337 | |
317 | 338 | c := p.Get() |
318 | 339 | c.Send("SUBSCRIBE", "x") |
333 | 354 | t.Errorf("got commands %v, want %v", d.commands, want) |
334 | 355 | } |
335 | 356 | d.commands = nil |
336 | ||
337 | p.Close() | |
338 | 357 | } |
339 | 358 | |
340 | 359 | func TestPoolTransactionCleanup(t *testing.T) { |
344 | 363 | MaxActive: 2, |
345 | 364 | Dial: d.dial, |
346 | 365 | } |
366 | defer p.Close() | |
347 | 367 | |
348 | 368 | c := p.Get() |
349 | 369 | c.Do("WATCH", "key") |
405 | 425 | t.Errorf("got commands %v, want %v", d.commands, want) |
406 | 426 | } |
407 | 427 | d.commands = nil |
408 | ||
409 | p.Close() | |
410 | 428 | } |
411 | 429 | |
412 | 430 | func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { |
435 | 453 | Wait: true, |
436 | 454 | } |
437 | 455 | defer p.Close() |
456 | ||
438 | 457 | c := p.Get() |
439 | 458 | errs := startGoroutines(p, "PING") |
440 | 459 | d.check("before close", p, 1, 1) |
461 | 480 | Dial: d.dial, |
462 | 481 | Wait: true, |
463 | 482 | } |
483 | defer p.Close() | |
484 | ||
464 | 485 | c := p.Get() |
465 | 486 | if _, err := c.Do("PING"); err != nil { |
466 | 487 | t.Fatal(err) |
496 | 517 | Wait: true, |
497 | 518 | } |
498 | 519 | defer p.Close() |
520 | ||
499 | 521 | c := p.Get() |
500 | 522 | errs := startGoroutines(p, "ERR", testErr) |
501 | 523 | d.check("before close", p, 1, 1) |
524 | 546 | Wait: true, |
525 | 547 | } |
526 | 548 | defer p.Close() |
549 | ||
527 | 550 | c := p.Get() |
528 | 551 | errs := startGoroutines(p, "ERR", testErr) |
529 | 552 | d.check("before close", p, 1, 1) |
564 | 587 | // test ensures that iteration will work correctly if multiple threads are |
565 | 588 | // iterating simultaneously. |
566 | 589 | func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { |
567 | count := 100 | |
590 | const count = 100 | |
568 | 591 | |
569 | 592 | // First we'll Create a pool where the pilfering of idle connections fails. |
570 | 593 | d := poolDialer{t: t} |
579 | 602 | defer p.Close() |
580 | 603 | |
581 | 604 | // Fill the pool with idle connections. |
582 | b1 := sync.WaitGroup{} | |
583 | b1.Add(count) | |
584 | b2 := sync.WaitGroup{} | |
585 | b2.Add(count) | |
586 | for i := 0; i < count; i++ { | |
587 | go func() { | |
588 | c := p.Get() | |
589 | if c.Err() != nil { | |
590 | t.Errorf("pool get failed: %v", c.Err()) | |
591 | } | |
592 | b1.Done() | |
593 | b1.Wait() | |
594 | c.Close() | |
595 | b2.Done() | |
596 | }() | |
597 | } | |
598 | b2.Wait() | |
599 | if d.dialed != count { | |
600 | t.Errorf("Expected %d dials, got %d", count, d.dialed) | |
605 | conns := make([]redis.Conn, count) | |
606 | for i := range conns { | |
607 | conns[i] = p.Get() | |
608 | } | |
609 | for i := range conns { | |
610 | conns[i].Close() | |
601 | 611 | } |
602 | 612 | |
603 | 613 | // Spawn a bunch of goroutines to thrash the pool. |
604 | b2.Add(count) | |
614 | var wg sync.WaitGroup | |
615 | wg.Add(count) | |
605 | 616 | for i := 0; i < count; i++ { |
606 | 617 | go func() { |
607 | 618 | c := p.Get() |
609 | 620 | t.Errorf("pool get failed: %v", c.Err()) |
610 | 621 | } |
611 | 622 | c.Close() |
612 | b2.Done() | |
623 | wg.Done() | |
613 | 624 | }() |
614 | 625 | } |
615 | b2.Wait() | |
626 | wg.Wait() | |
616 | 627 | if d.dialed != count*2 { |
617 | 628 | t.Errorf("Expected %d dials, got %d", count*2, d.dialed) |
618 | 629 | } |
13 | 13 | |
14 | 14 | package redis |
15 | 15 | |
16 | import ( | |
17 | "errors" | |
18 | ) | |
16 | import "errors" | |
19 | 17 | |
20 | 18 | // Subscription represents a subscribe or unsubscribe notification. |
21 | 19 | type Subscription struct { |
51 | 49 | |
52 | 50 | // The message data. |
53 | 51 | Data []byte |
52 | } | |
53 | ||
54 | // Pong represents a pubsub pong notification. | |
55 | type Pong struct { | |
56 | Data string | |
54 | 57 | } |
55 | 58 | |
56 | 59 | // PubSubConn wraps a Conn with convenience methods for subscribers. |
89 | 92 | return c.Conn.Flush() |
90 | 93 | } |
91 | 94 | |
92 | // Receive returns a pushed message as a Subscription, Message, PMessage or | |
93 | // error. The return value is intended to be used directly in a type switch as | |
94 | // illustrated in the PubSubConn example. | |
95 | // Ping sends a PING to the server with the specified data. | |
96 | func (c PubSubConn) Ping(data string) error { | |
97 | c.Conn.Send("PING", data) | |
98 | return c.Conn.Flush() | |
99 | } | |
100 | ||
101 | // Receive returns a pushed message as a Subscription, Message, PMessage, Pong | |
102 | // or error. The return value is intended to be used directly in a type switch | |
103 | // as illustrated in the PubSubConn example. | |
95 | 104 | func (c PubSubConn) Receive() interface{} { |
96 | 105 | reply, err := Values(c.Conn.Receive()) |
97 | 106 | if err != nil { |
123 | 132 | return err |
124 | 133 | } |
125 | 134 | return s |
135 | case "pong": | |
136 | var p Pong | |
137 | if _, err := Scan(reply, &p.Data); err != nil { | |
138 | return err | |
139 | } | |
140 | return p | |
126 | 141 | } |
127 | 142 | return errors.New("redigo: unknown pubsub notification") |
128 | 143 | } |
139 | 139 | |
140 | 140 | pc.Do("PUBLISH", "c1", "hello") |
141 | 141 | expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")}) |
142 | ||
143 | c.Ping("hello") | |
144 | expectPushed(t, c, `Ping("hello")`, redis.Pong{"hello"}) | |
145 | ||
146 | c.Conn.Send("PING") | |
147 | c.Conn.Flush() | |
148 | expectPushed(t, c, `Send("PING")`, redis.Pong{}) | |
142 | 149 | } |
274 | 274 | // err is not equal to nil, then Ints returns nil, err. |
275 | 275 | func Ints(reply interface{}, err error) ([]int, error) { |
276 | 276 | var ints []int |
277 | if reply == nil { | |
278 | return ints, ErrNil | |
279 | } | |
280 | 277 | values, err := Values(reply, err) |
281 | 278 | if err != nil { |
282 | 279 | return ints, err |
309 | 306 | } |
310 | 307 | return m, nil |
311 | 308 | } |
309 | ||
310 | // IntMap is a helper that converts an array of strings (alternating key, value) | |
311 | // into a map[string]int. The HGETALL commands return replies in this format. | |
312 | // Requires an even number of values in result. | |
313 | func IntMap(result interface{}, err error) (map[string]int, error) { | |
314 | values, err := Values(result, err) | |
315 | if err != nil { | |
316 | return nil, err | |
317 | } | |
318 | if len(values)%2 != 0 { | |
319 | return nil, errors.New("redigo: IntMap expects even number of values result") | |
320 | } | |
321 | m := make(map[string]int, len(values)/2) | |
322 | for i := 0; i < len(values); i += 2 { | |
323 | key, ok := values[i].([]byte) | |
324 | if !ok { | |
325 | return nil, errors.New("redigo: ScanMap key not a bulk string value") | |
326 | } | |
327 | value, err := Int(values[i+1], nil) | |
328 | if err != nil { | |
329 | return nil, err | |
330 | } | |
331 | m[string(key)] = value | |
332 | } | |
333 | return m, nil | |
334 | } | |
335 | ||
336 | // Int64Map is a helper that converts an array of strings (alternating key, value) | |
337 | // into a map[string]int64. The HGETALL commands return replies in this format. | |
338 | // Requires an even number of values in result. | |
339 | func Int64Map(result interface{}, err error) (map[string]int64, error) { | |
340 | values, err := Values(result, err) | |
341 | if err != nil { | |
342 | return nil, err | |
343 | } | |
344 | if len(values)%2 != 0 { | |
345 | return nil, errors.New("redigo: Int64Map expects even number of values result") | |
346 | } | |
347 | m := make(map[string]int64, len(values)/2) | |
348 | for i := 0; i < len(values); i += 2 { | |
349 | key, ok := values[i].([]byte) | |
350 | if !ok { | |
351 | return nil, errors.New("redigo: ScanMap key not a bulk string value") | |
352 | } | |
353 | value, err := Int64(values[i+1], nil) | |
354 | if err != nil { | |
355 | return nil, err | |
356 | } | |
357 | m[string(key)] = value | |
358 | } | |
359 | return m, nil | |
360 | } |
31 | 31 | } |
32 | 32 | |
33 | 33 | func cannotConvert(d reflect.Value, s interface{}) error { |
34 | return fmt.Errorf("redigo: Scan cannot convert from %s to %s", | |
35 | reflect.TypeOf(s), d.Type()) | |
36 | } | |
37 | ||
38 | func convertAssignBytes(d reflect.Value, s []byte) (err error) { | |
34 | var sname string | |
35 | switch s.(type) { | |
36 | case string: | |
37 | sname = "Redis simple string" | |
38 | case Error: | |
39 | sname = "Redis error" | |
40 | case int64: | |
41 | sname = "Redis integer" | |
42 | case []byte: | |
43 | sname = "Redis bulk string" | |
44 | case []interface{}: | |
45 | sname = "Redis array" | |
46 | default: | |
47 | sname = reflect.TypeOf(s).String() | |
48 | } | |
49 | return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) | |
50 | } | |
51 | ||
52 | func convertAssignBulkString(d reflect.Value, s []byte) (err error) { | |
39 | 53 | switch d.Type().Kind() { |
40 | 54 | case reflect.Float32, reflect.Float64: |
41 | 55 | var x float64 |
97 | 111 | func convertAssignValue(d reflect.Value, s interface{}) (err error) { |
98 | 112 | switch s := s.(type) { |
99 | 113 | case []byte: |
100 | err = convertAssignBytes(d, s) | |
114 | err = convertAssignBulkString(d, s) | |
101 | 115 | case int64: |
102 | 116 | err = convertAssignInt(d, s) |
103 | 117 | default: |
106 | 120 | return err |
107 | 121 | } |
108 | 122 | |
109 | func convertAssignValues(d reflect.Value, s []interface{}) error { | |
123 | func convertAssignArray(d reflect.Value, s []interface{}) error { | |
110 | 124 | if d.Type().Kind() != reflect.Slice { |
111 | 125 | return cannotConvert(d, s) |
112 | 126 | } |
143 | 157 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { |
144 | 158 | err = cannotConvert(d, s) |
145 | 159 | } else { |
146 | err = convertAssignBytes(d.Elem(), s) | |
160 | err = convertAssignBulkString(d.Elem(), s) | |
147 | 161 | } |
148 | 162 | } |
149 | 163 | case int64: |
180 | 194 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { |
181 | 195 | err = cannotConvert(d, s) |
182 | 196 | } else { |
183 | err = convertAssignValues(d.Elem(), s) | |
197 | err = convertAssignArray(d.Elem(), s) | |
184 | 198 | } |
185 | 199 | } |
186 | 200 | case Error: |
205 | 219 | // following the copied values. |
206 | 220 | func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { |
207 | 221 | if len(src) < len(dest) { |
208 | return nil, errors.New("redigo: Scan array short") | |
222 | return nil, errors.New("redigo.Scan: array short") | |
209 | 223 | } |
210 | 224 | var err error |
211 | 225 | for i, d := range dest { |
212 | 226 | err = convertAssign(d, src[i]) |
213 | 227 | if err != nil { |
228 | err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) | |
214 | 229 | break |
215 | 230 | } |
216 | 231 | } |
260 | 275 | //case "omitempty": |
261 | 276 | // fs.omitempty = true |
262 | 277 | default: |
263 | panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name())) | |
278 | panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) | |
264 | 279 | } |
265 | 280 | } |
266 | 281 | } |
320 | 335 | return ss |
321 | 336 | } |
322 | 337 | |
323 | var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct") | |
338 | var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") | |
324 | 339 | |
325 | 340 | // ScanStruct scans alternating names and values from src to a struct. The |
326 | 341 | // HGETALL and CONFIG GET commands return replies in this format. |
349 | 364 | ss := structSpecForType(d.Type()) |
350 | 365 | |
351 | 366 | if len(src)%2 != 0 { |
352 | return errors.New("redigo: ScanStruct expects even number of values in values") | |
367 | return errors.New("redigo.ScanStruct: number of values not a multiple of 2") | |
353 | 368 | } |
354 | 369 | |
355 | 370 | for i := 0; i < len(src); i += 2 { |
359 | 374 | } |
360 | 375 | name, ok := src[i].([]byte) |
361 | 376 | if !ok { |
362 | return errors.New("redigo: ScanStruct key not a bulk string value") | |
377 | return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) | |
363 | 378 | } |
364 | 379 | fs := ss.fieldSpec(name) |
365 | 380 | if fs == nil { |
366 | 381 | continue |
367 | 382 | } |
368 | 383 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { |
369 | return err | |
384 | return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) | |
370 | 385 | } |
371 | 386 | } |
372 | 387 | return nil |
373 | 388 | } |
374 | 389 | |
375 | 390 | var ( |
376 | errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct") | |
391 | errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") | |
377 | 392 | ) |
378 | 393 | |
379 | 394 | // ScanSlice scans src to the slice pointed to by dest. The elements the dest |
406 | 421 | continue |
407 | 422 | } |
408 | 423 | if err := convertAssignValue(d.Index(i), s); err != nil { |
409 | return err | |
424 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) | |
410 | 425 | } |
411 | 426 | } |
412 | 427 | return nil |
419 | 434 | for i, name := range fieldNames { |
420 | 435 | fss[i] = ss.m[name] |
421 | 436 | if fss[i] == nil { |
422 | return errors.New("redigo: ScanSlice bad field name " + name) | |
437 | return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) | |
423 | 438 | } |
424 | 439 | } |
425 | 440 | } |
426 | 441 | |
427 | 442 | if len(fss) == 0 { |
428 | return errors.New("redigo: ScanSlice no struct fields") | |
443 | return errors.New("redigo.ScanSlice: no struct fields") | |
429 | 444 | } |
430 | 445 | |
431 | 446 | n := len(src) / len(fss) |
432 | 447 | if n*len(fss) != len(src) { |
433 | return errors.New("redigo: ScanSlice length not a multiple of struct field count") | |
448 | return errors.New("redigo.ScanSlice: length not a multiple of struct field count") | |
434 | 449 | } |
435 | 450 | |
436 | 451 | ensureLen(d, n) |
448 | 463 | continue |
449 | 464 | } |
450 | 465 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { |
451 | return err | |
466 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) | |
452 | 467 | } |
453 | 468 | } |
454 | 469 | } |