Compare commits
493
Commits
v2.0.0-rc1
...
2.2-alpha6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce260f736e | ||
|
|
c54afb6d0d | ||
|
|
b2cc45bfbc | ||
|
|
41945ba6ae | ||
|
|
ff4058183b | ||
|
|
d8d528e992 | ||
|
|
efcf948c1a | ||
|
|
5b761a3387 | ||
|
|
a2a69d5803 | ||
|
|
50d0e82d54 | ||
|
|
2612e0521f | ||
|
|
a4e48b417d | ||
|
|
bd70a5f588 | ||
|
|
9a68cf91f0 | ||
|
|
8a623a98c3 | ||
|
|
5397f2b596 | ||
|
|
c086b85afb | ||
|
|
9fd01051bf | ||
|
|
0c2f75c6d8 | ||
|
|
11fd0c422b | ||
|
|
d9d8ccab93 | ||
|
|
5402c4262e | ||
|
|
240f8dbf3f | ||
|
|
be98a33b51 | ||
|
|
97e7f8aec3 | ||
|
|
57c9babd81 | ||
|
|
abc3ff4d90 | ||
|
|
645e9962cb | ||
|
|
3b5e72d402 | ||
|
|
ecc72ca17f | ||
|
|
56fce7ce7d | ||
|
|
feecb608ed | ||
|
|
4ebfc45528 | ||
|
|
12ebe2ac17 | ||
|
|
f6433915fe | ||
|
|
26b3366993 | ||
|
|
62ec599c36 | ||
|
|
f4aa600b99 | ||
|
|
8146e31677 | ||
|
|
ec8f06675a | ||
|
|
afc156c2d8 | ||
|
|
4d7e125519 | ||
|
|
a9b18e54d4 | ||
|
|
8df3dcada5 | ||
|
|
e902b579b4 | ||
|
|
339b9dc2d2 | ||
|
|
cfcd5d6d43 | ||
|
|
7fc4ce13ed | ||
|
|
24f753a8b9 | ||
|
|
8d3e063a0a | ||
|
|
1b1f47c915 | ||
|
|
297e77c6ab | ||
|
|
0a546fc017 | ||
|
|
539fb43820 | ||
|
|
7d0966a6b7 | ||
|
|
3ce014c766 | ||
|
|
b5b22da8e6 | ||
|
|
ca734d17ad | ||
|
|
a3e60027e7 | ||
|
|
7d47ecd543 | ||
|
|
10c12171cc | ||
|
|
92e282288f | ||
|
|
21dbc6499a | ||
|
|
4794d88f15 | ||
|
|
a3a323e0e5 | ||
|
|
5e78edb350 | ||
|
|
73abd0a9d2 | ||
|
|
1de98301f6 | ||
|
|
e584d82fec | ||
|
|
244201f6ba | ||
|
|
d08fac3eb9 | ||
|
|
19e61097c5 | ||
|
|
d94ac406ba | ||
|
|
7cdc98b630 | ||
|
|
da47440d44 | ||
|
|
d8a717fb1a | ||
|
|
fc41345116 | ||
|
|
1a587ff843 | ||
|
|
13b3715925 | ||
|
|
0a802bd7a0 | ||
|
|
75fcab8c23 | ||
|
|
a375b077cc | ||
|
|
19408d83a4 | ||
|
|
33aba595b0 | ||
|
|
75b41de8ca | ||
|
|
b19c33d48a | ||
|
|
ea5b70924d | ||
|
|
13a49af44b | ||
|
|
1aa608fc68 | ||
|
|
9da6caac4e | ||
|
|
dc11daf3b5 | ||
|
|
5b12b47df9 | ||
|
|
00cf82c0bd | ||
|
|
cd8788f26d | ||
|
|
5a4f9f27e7 | ||
|
|
9f8ded8ced | ||
|
|
95506e4611 | ||
|
|
53eeeaff08 | ||
|
|
a36879293d | ||
|
|
670bf2fd36 | ||
|
|
b33ef40105 | ||
|
|
165346ca29 | ||
|
|
306c6a02e3 | ||
|
|
ef59a8bc9e | ||
|
|
7236fdb22f | ||
|
|
91504b6cbe | ||
|
|
26f3388d27 | ||
|
|
b04ce2a35c | ||
|
|
4fe83b554a | ||
|
|
ab17b909fe | ||
|
|
893819801d | ||
|
|
5d10923f7b | ||
|
|
704bd093be | ||
|
|
941c9fa285 | ||
|
|
9f1ae9abee | ||
|
|
b4f2e412d0 | ||
|
|
1dd10ca233 | ||
|
|
144a5e72f2 | ||
|
|
fdc0bde935 | ||
|
|
4610b0332c | ||
|
|
ef27ba988b | ||
|
|
1a06bf93c4 | ||
|
|
e43505d6e9 | ||
|
|
b0d605c1d6 | ||
|
|
30d31cc8bb | ||
|
|
963238f713 | ||
|
|
136cf53f22 | ||
|
|
56e52b69fe | ||
|
|
50a9fad5d5 | ||
|
|
beb7756dcb | ||
|
|
b882056c93 | ||
|
|
5ca2f0c498 | ||
|
|
5171777bf1 | ||
|
|
a4f3f93b90 | ||
|
|
34a719d250 | ||
|
|
d433ebc681 | ||
|
|
25bb8a4452 | ||
|
|
192fc3376a | ||
|
|
2b00385d51 | ||
|
|
4c2e506a39 | ||
|
|
83f39c7ab2 | ||
|
|
f335779240 | ||
|
|
89f9f83769 | ||
|
|
3856f14759 | ||
|
|
412e457c27 | ||
|
|
1d18f50458 | ||
|
|
0997b4119d | ||
|
|
e984050fb9 | ||
|
|
e13865033d | ||
|
|
3c23ee1ba2 | ||
|
|
bc63407be6 | ||
|
|
7f9a4db3c0 | ||
|
|
da14590bd9 | ||
|
|
155fb4b45e | ||
|
|
7f00cd2264 | ||
|
|
efc5d4cc0d | ||
|
|
106bd87a3c | ||
|
|
84403fe7c1 | ||
|
|
169d2ef1e0 | ||
|
|
abe18d0e00 | ||
|
|
9e83ac06ef | ||
|
|
556bdfbab9 | ||
|
|
b435f64510 | ||
|
|
49128f0b9d | ||
|
|
3ab203762f | ||
|
|
60361e5aac | ||
|
|
36c19d03e0 | ||
|
|
4a7893ca9c | ||
|
|
b70d355521 | ||
|
|
cd76bb651d | ||
|
|
2403fc9fde | ||
|
|
0537e7bf80 | ||
|
|
73db2acc37 | ||
|
|
eddb388ef9 | ||
|
|
a047bf52a4 | ||
|
|
dbebd395eb | ||
|
|
ec96ef47a6 | ||
|
|
f85202c3dc | ||
|
|
d320764706 | ||
|
|
f7f12a606c | ||
|
|
fc09b9f449 | ||
|
|
08f55b786b | ||
|
|
7b30cc3a7b | ||
|
|
1eb13e4913 | ||
|
|
57b0738011 | ||
|
|
b301c1fc2b | ||
|
|
834ef78e27 | ||
|
|
fb92ecece7 | ||
|
|
8079656a8e | ||
|
|
93b2a7718e | ||
|
|
e0e1c19520 | ||
|
|
8fedd04dcc | ||
|
|
ed0dd55402 | ||
|
|
36babc1e31 | ||
|
|
2f6b31c3bb | ||
|
|
e5f257c2b2 | ||
|
|
c1ae36aea8 | ||
|
|
09252fc4f3 | ||
|
|
357d36733d | ||
|
|
2df84b7269 | ||
|
|
ec7e138926 | ||
|
|
23c64fe50d | ||
|
|
588cd980e9 | ||
|
|
452ccf7a41 | ||
|
|
e4ecc93119 | ||
|
|
4e0d34089a | ||
|
|
94364d53b4 | ||
|
|
e59a64b8d3 | ||
|
|
2929ca9786 | ||
|
|
bad7d097e9 | ||
|
|
5f19e8a4a5 | ||
|
|
740eee1cc6 | ||
|
|
674492bceb | ||
|
|
acc75bfd4f | ||
|
|
f9d5c4e33c | ||
|
|
76864d5626 | ||
|
|
b4b62c34db | ||
|
|
f791d66e20 | ||
|
|
f9b252613b | ||
|
|
ae77016e57 | ||
|
|
5d15b5207d | ||
|
|
4b93e5e267 | ||
|
|
c0b3d42372 | ||
|
|
b37ca6edb1 | ||
|
|
695fe87456 | ||
|
|
b91d605a35 | ||
|
|
778b2210a9 | ||
|
|
e452436a07 | ||
|
|
01daeecee7 | ||
|
|
a679185aa5 | ||
|
|
e193873025 | ||
|
|
c91abdcd07 | ||
|
|
cb72d0f155 | ||
|
|
029e5577ff | ||
|
|
2b9a59471f | ||
|
|
ced6709cb9 | ||
|
|
5d4f3a8c85 | ||
|
|
a53ebb4c8e | ||
|
|
87c74dfaa8 | ||
|
|
aaada3f962 | ||
|
|
c470538142 | ||
|
|
cbce517145 | ||
|
|
abb731e5b8 | ||
|
|
cf0c6b78f1 | ||
|
|
3a51bff035 | ||
|
|
123a10f7a5 | ||
|
|
07242c0ccf | ||
|
|
0439d792c4 | ||
|
|
f2dd4769dd | ||
|
|
69ef89f2cf | ||
|
|
2159782b51 | ||
|
|
1fb4e8def7 | ||
|
|
a539d29ac5 | ||
|
|
6146329f1f | ||
|
|
a5639e7dd9 | ||
|
|
23d7298b15 | ||
|
|
2c572622fb | ||
|
|
0cf5b7b57c | ||
|
|
c25a5d3b10 | ||
|
|
bcf2995c98 | ||
|
|
7e91f971f7 | ||
|
|
c61e69257a | ||
|
|
6825491928 | ||
|
|
0c7a9dec65 | ||
|
|
673e1fb7e4 | ||
|
|
d9e28bcf00 | ||
|
|
715c801a07 | ||
|
|
86d392498b | ||
|
|
cbf7e1070a | ||
|
|
8c1420ff2a | ||
|
|
a0573260b0 | ||
|
|
6171250871 | ||
|
|
db0c43a70c | ||
|
|
dd3f505ff5 | ||
|
|
f99e660b44 | ||
|
|
80091bbaac | ||
|
|
e0be2289e9 | ||
|
|
b3aa6d712e | ||
|
|
399f2f401c | ||
|
|
d9dd352b36 | ||
|
|
b1e0bd4b9b | ||
|
|
e39c8b5047 | ||
|
|
230729617d | ||
|
|
c8a10631d1 | ||
|
|
2f996f0217 | ||
|
|
e002ec6801 | ||
|
|
cdbea20afb | ||
|
|
0e5441d816 | ||
|
|
1a71fb9669 | ||
|
|
2cffe2993b | ||
|
|
5b4bff9c17 | ||
|
|
e51a74aa40 | ||
|
|
99628c1af8 | ||
|
|
185cabda45 | ||
|
|
443d1e9efe | ||
|
|
acc0185493 | ||
|
|
f26dde8ca9 | ||
|
|
b056ca39f2 | ||
|
|
8b654e54c4 | ||
|
|
d0a4e24e32 | ||
|
|
70a214c46d | ||
|
|
d06a5b23c8 | ||
|
|
b67d234563 | ||
|
|
b7a8daef60 | ||
|
|
2767f1c0c6 | ||
|
|
96ffb2fe97 | ||
|
|
3688d7f308 | ||
|
|
d3b958c3fc | ||
|
|
5bd09cd4c5 | ||
|
|
daf2049d0d | ||
|
|
af4e866dbb | ||
|
|
0f49d6b049 | ||
|
|
bb8716b6dc | ||
|
|
2c24c22039 | ||
|
|
24110a4d7d | ||
|
|
fb829ec6a2 | ||
|
|
e2641e09cc | ||
|
|
b8b8501d70 | ||
|
|
f483ce5ffe | ||
|
|
c2ff0e90b8 | ||
|
|
4589a823fd | ||
|
|
676740a960 | ||
|
|
5eedc9c65e | ||
|
|
4774a53b24 | ||
|
|
184d74abc6 | ||
|
|
d5096a28d7 | ||
|
|
d52e588869 | ||
|
|
7e79de541a | ||
|
|
7d04fc7563 | ||
|
|
08b5920750 | ||
|
|
3042fb0537 | ||
|
|
7c4fc71c15 | ||
|
|
400aea2b13 | ||
|
|
273f616930 | ||
|
|
ab37269c38 | ||
|
|
bb039e853d | ||
|
|
70ff3511bc | ||
|
|
a8dca69bb3 | ||
|
|
b978abbf02 | ||
|
|
d0b58d5300 | ||
|
|
f6475c7250 | ||
|
|
ac9b8cfe57 | ||
|
|
e24d93762f | ||
|
|
35cabcb505 | ||
|
|
7d288d6547 | ||
|
|
23d3a5feef | ||
|
|
3ab98cef4e | ||
|
|
144b0094c3 | ||
|
|
70b4b320ae | ||
|
|
244b873b0c | ||
|
|
bcfb387694 | ||
|
|
1240552da9 | ||
|
|
279d7e67cf | ||
|
|
0e1684bcd0 | ||
|
|
dedff272f6 | ||
|
|
306974f5d7 | ||
|
|
4e16d8b312 | ||
|
|
829137b9a0 | ||
|
|
056c19c6f2 | ||
|
|
b785b2bf66 | ||
|
|
65cc766581 | ||
|
|
cd627d4e78 | ||
|
|
6b0253fabd | ||
|
|
a26452263f | ||
|
|
ab193fe452 | ||
|
|
d4507ec615 | ||
|
|
d1578a33ee | ||
|
|
422cf21f15 | ||
|
|
4dc1218c58 | ||
|
|
dda20542ab | ||
|
|
003f0840ff | ||
|
|
d0686e070d | ||
|
|
846d8b3ea5 | ||
|
|
22194a7ffe | ||
|
|
4c8f23700b | ||
|
|
44262c58a4 | ||
|
|
0924181364 | ||
|
|
178d690372 | ||
|
|
74e0f445a8 | ||
|
|
9fcfd6b651 | ||
|
|
a7159fe817 | ||
|
|
5a9fcb87ca | ||
|
|
afbf59145a | ||
|
|
f6fa411d6c | ||
|
|
6b6f101c27 | ||
|
|
5713f06b33 | ||
|
|
7a6ae0a2b2 | ||
|
|
7f7499eeac | ||
|
|
73bd6c583b | ||
|
|
6e0e5bedd9 | ||
|
|
9e5d2e8bd6 | ||
|
|
38273a9ed6 | ||
|
|
436f18b618 | ||
|
|
1edbae8678 | ||
|
|
a89b7013ff | ||
|
|
7e02fe32d3 | ||
|
|
169dd6b7d3 | ||
|
|
e1f93d4b2c | ||
|
|
ffc1585267 | ||
|
|
b84186ff2d | ||
|
|
612e4de8ce | ||
|
|
356f923913 | ||
|
|
dbc289aed1 | ||
|
|
4e538759c4 | ||
|
|
a4798f733d | ||
|
|
e4ed181d40 | ||
|
|
6ddc908ab6 | ||
|
|
9eaef89fbc | ||
|
|
dc845730a9 | ||
|
|
d71b98650f | ||
|
|
a03611e133 | ||
|
|
1cd92e7f04 | ||
|
|
7dd8e7cfdf | ||
|
|
23f964946b | ||
|
|
b6eb970394 | ||
|
|
2796f6da7b | ||
|
|
0f62e1775d | ||
|
|
be02a7c0d6 | ||
|
|
6a8e35ad92 | ||
|
|
8632fb3040 | ||
|
|
560db612e1 | ||
|
|
752203d86d | ||
|
|
3fbf9001ce | ||
|
|
bd8db0ada8 | ||
|
|
dbaa41c618 | ||
|
|
d2ee16abec | ||
|
|
9ae6b0be4a | ||
|
|
a6dd455b09 | ||
|
|
697bd5673f | ||
|
|
d72562f7ba | ||
|
|
c7d9d662a4 | ||
|
|
0f3dfa87bc | ||
|
|
033fb554be | ||
|
|
177a0a0b0d | ||
|
|
c03206fdf1 | ||
|
|
a24ba809d1 | ||
|
|
7b1f85c0a2 | ||
|
|
1ce81fa59b | ||
|
|
6435c76772 | ||
|
|
0c0d056412 | ||
|
|
bb57b965c4 | ||
|
|
03e52931dd | ||
|
|
c8d9e7f4c0 | ||
|
|
d593c48869 | ||
|
|
a5456b2cb6 | ||
|
|
dcb9cf4e82 | ||
|
|
fc2c0f7a6c | ||
|
|
4812cf2873 | ||
|
|
c4aace9003 | ||
|
|
6205b46387 | ||
|
|
aa549962a4 | ||
|
|
c7019f7019 | ||
|
|
c09c2c3b04 | ||
|
|
75d8978e7a | ||
|
|
37fff074a2 | ||
|
|
af5f66fb98 | ||
|
|
29b14d5fac | ||
|
|
f6eb17477a | ||
|
|
0f10458c4f | ||
|
|
924727d905 | ||
|
|
ba5b4bde21 | ||
|
|
779deb60f9 | ||
|
|
33c1269efc | ||
|
|
335d16bc0f | ||
|
|
08253bf42b | ||
|
|
11ac6ff613 | ||
|
|
3d04d29e7f | ||
|
|
d55d5c5dd3 | ||
|
|
1a132bbc9c | ||
|
|
3caf15e6da | ||
|
|
5fc9229c34 | ||
|
|
d5d23dabd7 | ||
|
|
b0bd87f60c | ||
|
|
38db91713c | ||
|
|
8a3b0d2d9a | ||
|
|
ea2c08e922 | ||
|
|
ca3f830b32 | ||
|
|
4ea93ad3ac | ||
|
|
a2f4f8711a | ||
|
|
37dc9e5abb | ||
|
|
90fdc82697 | ||
|
|
1ad4d31631 | ||
|
|
6531c94d28 | ||
|
|
bc000c1db0 | ||
|
|
9005896c0a | ||
|
|
b167f87705 | ||
|
|
cef34df02a | ||
|
|
9b30e1a207 | ||
|
|
c20c189db5 | ||
|
|
87c68815c3 | ||
|
|
37ab76c99d | ||
|
|
75a190ca72 | ||
|
|
fda32cb601 |
@@ -12,4 +12,11 @@ mkrelease.sh
|
||||
release
|
||||
myredis.conf
|
||||
misc/*
|
||||
src/release.h
|
||||
appendonly.aof
|
||||
SHORT_TERM_TODO
|
||||
redis.conf.*
|
||||
release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
src/redis-server.dSYM
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
This is a stable release, for beta testing make sure to download the latest source code from Git:
|
||||
|
||||
git clone git://github.com/antirez/redis.git
|
||||
|
||||
It's also possibe to download the latest source code as a tarball:
|
||||
|
||||
http://github.com/antirez/redis/tree/master
|
||||
|
||||
(use the download button)
|
||||
@@ -0,0 +1,13 @@
|
||||
1. Enter irc.freenode.org #redis and start talking with 'antirez' and/or 'pietern' to check if there is interest for such a feature and to understand the probability of it being merged. We'll try hard to keep Redis simple... so you'll likely encounter an high resistence.
|
||||
|
||||
2. Drop a message to the Redis Google Group with a proposal of semantics/API.
|
||||
|
||||
3. If steps 1 and 2 are ok, use the following procedure to submit a patch:
|
||||
|
||||
a. Fork Redis on github
|
||||
b. Create a topic branch (git checkout -b my_branch)
|
||||
c. Push to your branch (git push origin my_branch)
|
||||
d. Create an issue in the Redis google code site with a link to your patch
|
||||
e. Done :)
|
||||
|
||||
Thanks!
|
||||
@@ -1,9 +1,176 @@
|
||||
2010-07-01 gitignore modified (antirez)
|
||||
2010-06-22 redis.c split into many different C files. (antirez)
|
||||
2010-06-16 more pub/sub tests (Pieter Noordhuis)
|
||||
2010-06-15 initial basic pub/sub tests (Pieter Noordhuis)
|
||||
2010-06-15 fix BLPOP/BRPOP to use the wrapped function for list length (Pieter Noordhuis)
|
||||
2010-06-15 tests for BLPOP/BRPOP via an option in the tcl client that defers reading the reply (Pieter Noordhuis)
|
||||
2010-06-14 TODO updated (antirez)
|
||||
2010-06-14 Merge branch 'ltrim-tests' of git://github.com/pietern/redis (antirez)
|
||||
2010-06-14 rename "list" to "linkedlist" to be more verbose (Pieter Noordhuis)
|
||||
2010-06-14 allow running the test suite against an external Redis instance, without auto spawning (antirez)
|
||||
2010-06-14 change ltrim tests to cover all min/max cases and add stronger stresser (Pieter Noordhuis)
|
||||
2010-06-13 Fixed deps in makefile and mkreleasehdr.sh script to really take advantage of the new trick to avoid recompilation of redis.c on git sha1 or dirty status change (antirez)
|
||||
2010-06-13 hopefully faster recompiling with a trick (antirez)
|
||||
2010-06-13 fixed a bug in rdbLoadObject abount specially encoded objects (antirez)
|
||||
2010-06-13 use raw strings when loading a hash from the rdb into a zipmap (Pieter Noordhuis)
|
||||
2010-06-12 Merge branch 'expire' of git://github.com/pietern/redis (antirez)
|
||||
2010-06-11 Merge branch 'lists' of git://github.com/pietern/redis (antirez)
|
||||
2010-06-11 LPUSHX, RPUSHX, LINSERT only work on non-empty lists, so there are no clients waiting for a push (Pieter Noordhuis)
|
||||
2010-06-11 make LINSERT return -1 when the value could not be inserted (Pieter Noordhuis)
|
||||
2010-06-11 check if the list encoding needs to be changed on LPUSHX, RPUSHX, LINSERT (Pieter Noordhuis)
|
||||
2010-06-11 make sure the value to insert is string encoded (Pieter Noordhuis)
|
||||
2010-06-11 rename vars, move arguments, add comments (Pieter Noordhuis)
|
||||
2010-06-11 always iterate from head to tail on LINSERT (Pieter Noordhuis)
|
||||
2010-06-11 use REDIS_TAIL to insert AFTER an entry and REDIS_HEAD to insert BEFORE an entry (Pieter Noordhuis)
|
||||
2010-06-11 move listTypeInsert to be grouped with other wrapper functions (Pieter Noordhuis)
|
||||
2010-06-11 squashed merge from robey/twitter3: LINSERT BEFORE|AFTER, LPUSHX, RPUSHX (Pieter Noordhuis)
|
||||
2010-06-09 remove pop function and the sds dependency; can be implemented using get+delete (Pieter Noordhuis)
|
||||
2010-06-07 compute swappability for ziplist encoded lists (Pieter Noordhuis)
|
||||
2010-06-07 reuse the sds from the main dictionary in the expiration dictionary (Pieter Noordhuis)
|
||||
2010-06-07 TODO updated (antirez)
|
||||
2010-06-07 encode integers while loading an hash (antirez)
|
||||
2010-06-05 Merge branch 'lists' of git://github.com/pietern/redis (antirez)
|
||||
2010-06-05 fixed two leaks for the dual encoded lists (Pieter Noordhuis)
|
||||
2010-06-04 TODO updated (antirez)
|
||||
2010-06-04 DISCSARD now unwatches all keys, as it should (antirez)
|
||||
2010-06-04 generated tests for different encodings to avoid test code duplication (Pieter Noordhuis)
|
||||
2010-06-04 refactor list tests to test both encodings; implemented assert functions (Pieter Noordhuis)
|
||||
2010-06-04 renamed hash wrapper functions to match wrapper function naming convention: "<type>Type<func>" (Pieter Noordhuis)
|
||||
2010-06-04 Merge branch 'lists' of git://github.com/pietern/redis (antirez)
|
||||
2010-06-04 Merge branch 'smallkeys' (antirez)
|
||||
2010-06-04 safety assert in listTypeNext (Pieter Noordhuis)
|
||||
2010-06-04 renamed list wrapper functions to be more verbose (Pieter Noordhuis)
|
||||
2010-06-04 add thresholds for converting a ziplist to a real list (Pieter Noordhuis)
|
||||
2010-06-04 merge antirez/smallkeys (Pieter Noordhuis)
|
||||
2010-06-03 test restored (antirez)
|
||||
2010-06-03 memory leak introduced in the latest big changes fixed (antirez)
|
||||
2010-06-03 Fixed VM bugs introduced with the top level keys as sds strings changes (antirez)
|
||||
2010-06-03 top level keys are no longer redis objects but sds strings. There are still a few bugs to fix when VM is enabled (antirez)
|
||||
2010-06-03 update Makefile to include ziplist.o (Pieter Noordhuis)
|
||||
2010-06-03 use ziplists in SORT STORE until the thresholds are determined (Pieter Noordhuis)
|
||||
2010-06-03 Merge branch 'testsuite' of git://github.com/pietern/redis (antirez)
|
||||
2010-06-03 Merge branch 'testsuite' of git://github.com/pietern/redis into smallkeys (antirez)
|
||||
2010-06-03 tag memory leak check on kill server as "leaks" (Pieter Noordhuis)
|
||||
2010-06-03 tag test with sleep() as slow (Pieter Noordhuis)
|
||||
2010-06-03 make sure the config it returned when called without code (Pieter Noordhuis)
|
||||
2010-06-03 tag more slow tests (Pieter Noordhuis)
|
||||
2010-06-03 change how arguments are passed from the AOF tests (Pieter Noordhuis)
|
||||
2010-06-03 scope res variable outside test (Pieter Noordhuis)
|
||||
2010-06-02 tags for existing tests (Pieter Noordhuis)
|
||||
2010-06-02 pass tags to filter and match via arguments (Pieter Noordhuis)
|
||||
2010-06-02 basic support to tag tests (Pieter Noordhuis)
|
||||
2010-06-02 changed how server.tcl accepts options to support more directives without requiring more arguments to the proc (Pieter Noordhuis)
|
||||
2010-06-02 removed obsolete code (Pieter Noordhuis)
|
||||
2010-06-02 catch exceptions in the server proc, to be able to kill the entire chain of running servers (Pieter Noordhuis)
|
||||
2010-06-02 Merge branch 'master' into smallkeys (antirez)
|
||||
2010-06-02 smarter swapout policy on AOF too (antirez)
|
||||
2010-06-02 better swapout policy while loading RDB file (antirez)
|
||||
2010-06-02 minor code comment change (antirez)
|
||||
2010-06-01 use integer types from stdint.h to be more verbose on the size in bytes of encoded elements. update list length to use 2 bytes instead of 1. (Pieter Noordhuis)
|
||||
2010-06-01 added stress test for heavy i/o in ziplists (Pieter Noordhuis)
|
||||
2010-06-01 fix signedness errors in ziplist testing code (Pieter Noordhuis)
|
||||
2010-06-01 minor code movements and free object pull restored to 1 million (antirez)
|
||||
2010-06-01 TODO updated with syslog plans for 2.2 (antirez)
|
||||
2010-06-01 Debug message was printing stuff that are sometimes not initialized/valid (antirez)
|
||||
2010-06-01 Merge branch 'smallkeys' of github.com:antirez/redis into smallkeys (antirez)
|
||||
2010-06-01 fixed a few comments (antirez)
|
||||
2010-06-01 fixed bugs introduced in the rewrite of the new VM engine (antirez)
|
||||
2010-05-31 support rewriting the AOF with dual list encoding (Pieter Noordhuis)
|
||||
2010-05-31 small refactor of fwrite* commands for AOF rewrite to allow writing a bulk long long (Pieter Noordhuis)
|
||||
2010-05-31 use list wrapper functions in computing the dataset digest (Pieter Noordhuis)
|
||||
2010-05-31 ziplistNext should work as expected when called with a pointer to ZIP_END (Pieter Noordhuis)
|
||||
2010-05-31 update SORT to work with the dual list encoding (Pieter Noordhuis)
|
||||
2010-05-31 function to create a new ziplist encoded list (Pieter Noordhuis)
|
||||
2010-05-31 fixed missing incrRefCount (antirez)
|
||||
2010-05-31 support rdb saving/loading with dual list encoding (Pieter Noordhuis)
|
||||
2010-05-31 fixed signedness and disambiguate variable names (Pieter Noordhuis)
|
||||
2010-05-31 added rdb save function to directly save long long values (Pieter Noordhuis)
|
||||
2010-05-31 update RPOPLPUSH to support dual encoding (Pieter Noordhuis)
|
||||
2010-05-31 update list iteration semantic to work as expected (i.e. "while(lNext(..))") (Pieter Noordhuis)
|
||||
2010-05-31 ziplistDelete no longer needs a direction now ziplistPrev is fixed (Pieter Noordhuis)
|
||||
2010-05-31 ziplistPrev should return the tail when the argument is ZIP_END (Pieter Noordhuis)
|
||||
2010-05-31 first step of VM rewrite. blocking VM tests passing, more work needed in the async side (antirez)
|
||||
2010-05-31 Merge branch 'no-appendfsync-on-rewrite' (antirez)
|
||||
2010-05-30 fix LREM to remove *all* occurances when a zero argument is given (Pieter Noordhuis)
|
||||
2010-05-30 fixed LINDEX to always return bulk response (Pieter Noordhuis)
|
||||
2010-05-30 the tail offset must be an integer pointer to hold a 32-bit offset (Pieter Noordhuis)
|
||||
2010-05-30 update LREM to support dual encoding via extra iteration primitives (Pieter Noordhuis)
|
||||
2010-05-30 support dual encoding in LTRIM (Pieter Noordhuis)
|
||||
2010-05-30 update LRANGE to use basic iteration code to support dual encoding (Pieter Noordhuis)
|
||||
2010-05-30 inline support for dual encoding in the LINDEX and LSET commands (Pieter Noordhuis)
|
||||
2010-05-30 generic pop and length function for ziplist encoding (Pieter Noordhuis)
|
||||
2010-05-30 generic push function that supports the dual encoding (Pieter Noordhuis)
|
||||
2010-05-30 change delete function to accept a direction argument, so "p" can be properly updated (Pieter Noordhuis)
|
||||
2010-05-30 expose extra functionality from ziplist.c (Pieter Noordhuis)
|
||||
2010-05-30 code style consistency fixes (Pieter Noordhuis)
|
||||
2010-05-29 ziplistIndex now accepts negative indices (Pieter Noordhuis)
|
||||
2010-05-29 fix compile warnings (Pieter Noordhuis)
|
||||
2010-05-29 use simpler encoding for the length of the previous entry (Pieter Noordhuis)
|
||||
2010-05-29 replace functions to get pointers to head and tail by macros (Pieter Noordhuis)
|
||||
2010-05-29 function to insert an element at an arbitrary position in the list (Pieter Noordhuis)
|
||||
2010-05-29 extract a generic delete function that can be used in pop and delete(range) (Pieter Noordhuis)
|
||||
2010-05-29 use the entry struct in zipRawEntryLength (Pieter Noordhuis)
|
||||
2010-05-29 rename argument names to s* to disambiguate from e* (Pieter Noordhuis)
|
||||
2010-05-29 change ziplistRepr to use the entry struct (Pieter Noordhuis)
|
||||
2010-05-29 modify compare function to check if the encoding is equal before comparing (Pieter Noordhuis)
|
||||
2010-05-29 use a struct to retrieve all details for an entry (Pieter Noordhuis)
|
||||
2010-05-29 initial implementation for making the ziplist doubly linked (Pieter Noordhuis)
|
||||
2010-05-29 fix some warnings (Pieter Noordhuis)
|
||||
2010-05-29 add function to retrieve ziplist size in bytes (Pieter Noordhuis)
|
||||
2010-05-22 fix compare function of ziplist to only load integer from ziplist when it is encoded as integer (Pieter Noordhuis)
|
||||
2010-05-22 add function to retrieve length of ziplist (Pieter Noordhuis)
|
||||
2010-05-22 re-introduce ZIP_BIGLEN for clarity (Pieter Noordhuis)
|
||||
2010-05-22 added header ziplist.h (Pieter Noordhuis)
|
||||
2010-05-22 code to compare strings with entries in ziplist, regardless of their encoding (Pieter Noordhuis)
|
||||
2010-05-22 updated iteration code to work well with different encodings (Pieter Noordhuis)
|
||||
2010-05-22 move code from zip.c to ziplist.c (Pieter Noordhuis)
|
||||
2010-05-22 partial revert of c80df5 because ziplist functions are starting to divert too much from zipmap functions (Pieter Noordhuis)
|
||||
2010-05-22 initial work for integer encoding in ziplists (Pieter Noordhuis)
|
||||
2010-05-22 move length housekeeping to a macro (Pieter Noordhuis)
|
||||
2010-05-21 allow entries to be deleted in place when iterating over a ziplist (Pieter Noordhuis)
|
||||
2010-05-21 allow pointer to be stored to current element when iterating over ziplist (Pieter Noordhuis)
|
||||
2010-05-21 rename ziplistDelete to ziplistDeleteRange (Pieter Noordhuis)
|
||||
2010-05-21 code to delete an inner range from the ziplist (Pieter Noordhuis)
|
||||
2010-05-21 check if *value is non-NULL before setting it (Pieter Noordhuis)
|
||||
2010-05-21 change iteration code to avoid allocating a new sds for each traversed entry (Pieter Noordhuis)
|
||||
2010-05-21 code to iterate over a ziplist (Pieter Noordhuis)
|
||||
2010-05-21 implementation for a ziplist with push and pop support (Pieter Noordhuis)
|
||||
2010-05-21 extracted general methods to zip.c for reuse in other zip* structures (Pieter Noordhuis)
|
||||
2010-05-28 command table size calculated with sizeof (antirez)
|
||||
2010-05-28 use qsort and bsearch to lookup commands in O(log(N)) instead of O(N) (Pieter Noordhuis)
|
||||
2010-05-28 Merge branch 'cli-stdin' of git://github.com/pietern/redis (antirez)
|
||||
2010-05-28 Fixed ZINCR Nan bugs leading to server crash and added tests (antirez)
|
||||
2010-05-28 redis.conf new features the new option, a minor typo preventing the compilation fixed (antirez)
|
||||
2010-05-28 don't fsync after a rewrite if appendfsync is set to no. use aof_fsycn instead of fsync where appropriate (antirez)
|
||||
2010-05-28 added new option no-appendfsync-on-rewrite to avoid blocking on fsync() in the main thread while a background process is doing big I/O (antirez)
|
||||
2010-05-28 Added Git sha1 and dirty status in redis-server -v output (antirez)
|
||||
2010-05-28 changed the message in the Makefile with the new command like to run the test suite (antirez)
|
||||
2010-05-27 Fixed typo. (Vincent Palmer)
|
||||
2010-05-27 new multi/exec tests (antirez)
|
||||
2010-05-26 build command outside while loop (Pieter Noordhuis)
|
||||
2010-05-26 require the flag "-c" to be used for redis-cli to read the last argument from stdin (Pieter Noordhuis)
|
||||
2010-05-26 Merge branch 'master' into nested-multi (antirez)
|
||||
2010-05-26 Fix EXEC bug that was leaving the client in dirty status when used with WATCH (antirez)
|
||||
2010-05-26 raise error on nested MULTI and WATCH inside multi (antirez)
|
||||
2010-05-25 allow regular sets to be passed to zunionstore/zinterstore (Pieter Noordhuis)
|
||||
2010-05-25 Version is now 2.1.1 (antirez)
|
||||
2010-05-25 RENAME is now WATCH-aware (antirez)
|
||||
2010-05-25 TODO updated (antirez)
|
||||
2010-05-25 WATCH is now able to detect keys removed by FLUSHALL and FLUSHDB (antirez)
|
||||
2010-05-25 WATCH tests (antirez)
|
||||
2010-05-25 minor bug fixed in WATCH (antirez)
|
||||
2010-05-25 WATCH for MULTI/EXEC (CAS alike concurrency) (antirez)
|
||||
2010-05-25 gitignore updated (antirez)
|
||||
2010-05-21 Master is now already unfreezed, unstable, and ready to hacking sessions! (antirez)
|
||||
2010-05-21 Merge branch 'solaris' of git://github.com/pietern/redis (antirez)
|
||||
2010-05-21 Changelog updated (antirez)
|
||||
2010-05-21 redis version is now 1.3.14 (aka 2.0.0 RC1) (antirez)
|
||||
2010-05-21 html doc updated (antirez)
|
||||
2010-05-21 by default test with valgrind does not show full leak info (antirez)
|
||||
2010-05-21 minor fix for the skiplist code, resulting in a false positive with valgrind, and in general into a useless small allocation (antirez)
|
||||
2010-05-21 Merge branch 'master' of git@github.com:antirez/redis (antirez)
|
||||
2010-05-21 tests suite initial support for valgrind, fixed the old test suite until the new one is able to target a specific host/port (antirez)
|
||||
2010-05-21 include solaris fixes in sha1.c (Pieter Noordhuis)
|
||||
2010-05-20 Don't exit with error in tests temp file cleanup if there are no files to clean (antirez)
|
||||
2010-05-20 fix memory leak on 32-bit builds (Pieter Noordhuis)
|
||||
2010-05-20 Merge branch 'master' of github.com:antirez/redis (antirez)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
To compile Redis, do the following:
|
||||
|
||||
cd src; make
|
||||
|
||||
The compilation will produce a redis-server binary.
|
||||
|
||||
To install Redis, use
|
||||
|
||||
make install
|
||||
|
||||
and all the binaries will be installed on /usr/local/bin.
|
||||
|
||||
Alternatively:
|
||||
|
||||
make PREFIX=/some/other/directory
|
||||
|
||||
to have the binaries in /some/other/directory/bin.
|
||||
|
||||
Run the server using the following command line:
|
||||
|
||||
/path/to/redis-server
|
||||
|
||||
This will start a Redis server with the default configuration.
|
||||
|
||||
Otherwise if you want to provide your configuration use:
|
||||
|
||||
/path/to/redis-server /path/to/redis.conf
|
||||
|
||||
You can find an example redis.conf file in the root directory
|
||||
of this source distribution.
|
||||
@@ -1,115 +1,22 @@
|
||||
# Redis Makefile
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
# Top level makefile, the real shit is at src/Makefile
|
||||
|
||||
release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
|
||||
else
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
TARGETS=32bit noopt test
|
||||
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o
|
||||
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
|
||||
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
|
||||
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
|
||||
CHECKAOFOBJ = redis-check-aof.o
|
||||
all:
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
PRGNAME = redis-server
|
||||
BENCHPRGNAME = redis-benchmark
|
||||
CLIPRGNAME = redis-cli
|
||||
CHECKDUMPPRGNAME = redis-check-dump
|
||||
CHECKAOFPRGNAME = redis-check-aof
|
||||
|
||||
all: redis-server redis-benchmark redis-cli redis-check-dump redis-check-aof
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
|
||||
ae_epoll.o: ae_epoll.c
|
||||
ae_kqueue.o: ae_kqueue.c
|
||||
ae_select.o: ae_select.c
|
||||
anet.o: anet.c fmacros.h anet.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
dict2.o: dict2.c fmacros.h dict2.h zmalloc.h
|
||||
linenoise.o: linenoise.c fmacros.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
pqsort.o: pqsort.c
|
||||
printraw.o: printraw.c
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
|
||||
zmalloc.h
|
||||
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h
|
||||
redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h \
|
||||
linenoise.h
|
||||
redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
|
||||
adlist.h zmalloc.h lzf.h pqsort.h zipmap.h staticsymbols.h sha1.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
zipmap.o: zipmap.c zmalloc.h
|
||||
zmalloc.o: zmalloc.c config.h
|
||||
|
||||
redis-server: $(OBJ)
|
||||
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
|
||||
@echo ""
|
||||
@echo "Hint: To run the test-redis.tcl script is a good idea."
|
||||
@echo "Launch the redis server with ./redis-server, then in another"
|
||||
@echo "terminal window enter this directory and run 'make test'."
|
||||
@echo ""
|
||||
|
||||
redis-benchmark: $(BENCHOBJ)
|
||||
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ)
|
||||
|
||||
redis-cli: $(CLIOBJ)
|
||||
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ)
|
||||
|
||||
redis-check-dump: $(CHECKDUMPOBJ)
|
||||
$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ)
|
||||
|
||||
redis-check-aof: $(CHECKAOFOBJ)
|
||||
$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
install: dummy
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
cd src && $(MAKE) $@
|
||||
cd deps/hiredis && $(MAKE) $@
|
||||
cd deps/linenoise && $(MAKE) $@
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
$(TARGETS):
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
staticsymbols:
|
||||
tclsh utils/build-static-symbols.tcl > staticsymbols.h
|
||||
src/help.h:
|
||||
@./utils/generate-command-help.rb > $@
|
||||
|
||||
test:
|
||||
tclsh8.5 tests/test_helper.tcl
|
||||
|
||||
bench:
|
||||
./redis-benchmark
|
||||
|
||||
log:
|
||||
git log '--pretty=format:%ad %s (%cn)' --date=short > Changelog
|
||||
|
||||
32bit:
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
make ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
make PROF="-pg"
|
||||
|
||||
gcov:
|
||||
make PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
make OPTIMIZATION=""
|
||||
|
||||
32bitgprof:
|
||||
make PROF="-pg" ARCH="-arch i386"
|
||||
dummy:
|
||||
|
||||
@@ -1 +1,86 @@
|
||||
Check the 'doc' directory. doc/README.html is a good starting point :)
|
||||
Where to find complete Redis documentation?
|
||||
-------------------------------------------
|
||||
|
||||
This README is just a fast "quick start" document. You can find more detailed
|
||||
documentation here:
|
||||
|
||||
1) http://code.google.com/p/redis
|
||||
2) Check the 'doc' directory. doc/README.html is a good starting point :)
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
|
||||
It is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
Redis is just a single binary, but if you want to install it you can use
|
||||
the "make install" target that will copy the binary in /usr/local/bin
|
||||
for default. You can also use "make PREFIX=/some/other/directory install"
|
||||
if you wish to use a different destination.
|
||||
|
||||
You can run a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
|
||||
After you build Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
Buliding using tcmalloc
|
||||
-----------------------
|
||||
|
||||
tcmalloc is a fast and space efficient implementation (for little objects)
|
||||
of malloc(). Compiling Redis with it can improve performances and memeory
|
||||
usage. You can read more about it here:
|
||||
|
||||
http://goog-perftools.sourceforge.net/doc/tcmalloc.html
|
||||
|
||||
In order to compile Redis with tcmalloc support install tcmalloc on your system
|
||||
and then use:
|
||||
|
||||
% make USE_TCMALLOC=yes
|
||||
|
||||
Note that you can pass any other target to make, as long as you append
|
||||
USE_TCMALLOC=yes at the end.
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
|
||||
To run Redis with the default configuration just type:
|
||||
|
||||
% cd src
|
||||
% ./redis-server
|
||||
|
||||
If you want to provide your redis.conf, you have to run it using an additional
|
||||
parameter (the path of the configuration file):
|
||||
|
||||
% cd src
|
||||
% ./redis-server /path/to/redis.conf
|
||||
|
||||
Playing with Redis
|
||||
------------------
|
||||
|
||||
You can use redis-cli to play with Redis. Start a redis-server instance,
|
||||
then in another terminal try the following:
|
||||
|
||||
% cd src
|
||||
% ./redis-cli
|
||||
redis> ping
|
||||
PONG
|
||||
redis> set foo bar
|
||||
OK
|
||||
redis> get foo
|
||||
"bar"
|
||||
redis> incr mycounter
|
||||
(integer) 1
|
||||
redis> incr mycounter
|
||||
(integer) 2
|
||||
redis>
|
||||
|
||||
You can find the list of all the available commands here:
|
||||
|
||||
http://code.google.com/p/redis/wiki/CommandReference
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
@@ -4,30 +4,28 @@ Redis TODO and Roadmap
|
||||
VERSION 2.2 TODO (Optimizations and latency)
|
||||
============================================
|
||||
|
||||
* Lower the CPU usage.
|
||||
* Lower the RAM usage everywhere possible.
|
||||
* Specially encoded Sets (like Hashes).
|
||||
* Implement an UDP interface for low-latency operations.
|
||||
* What about a special coding that is about storing the "rdb" serialized format instead of the actual value? This can be used when we have LRU in order to super-compress data into memory, for data not accessed frequetly. It's a VM-alike strategy but fully in memory, may reduce the space to hold some dataset in an impressive way. Trivial to implement.
|
||||
* Another idea: LRU does not need to be super precise right? Maybe it's a good idea to just put into the skiplist implementing the LRU just the pointer to the key without evne incr/decr business, nor the need to remove the pointer when the key is deleted. There is to think more about that.
|
||||
|
||||
VERSION 2.x TODO
|
||||
================
|
||||
|
||||
* BRPOPLPUSH
|
||||
* Save dataset / fsync() on SIGTERM
|
||||
* Support for syslog(3).
|
||||
* Change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster.
|
||||
* Add an explicit test for MULTI/EXEC reloaded in the AOF.
|
||||
* Command table -> hash table, with support for command renaming
|
||||
|
||||
VM TODO
|
||||
=======
|
||||
|
||||
Virtual Memory optimizations:
|
||||
* Use multiple open FDs against the VM file, one for thread.
|
||||
* Check what happens performance-wise if instead of creating threads again and again the same threads are reused forever. Note: this requires a way to disable this clients in the child, but waiting for empty new jobs queue can be enough.
|
||||
* Implement LEN, PEEK, POKE, SETBIT, GETBIT
|
||||
|
||||
STRING COMMANDS
|
||||
===============
|
||||
|
||||
* Implement STRLEN, PEEK, POKE, SETBIT, GETBIT
|
||||
|
||||
OTHER IMPORTANT THINGS THAT WILL BE ADDED BUT I'M NOT SURE WHEN
|
||||
===============================================================
|
||||
|
||||
BIG ONES:
|
||||
|
||||
* BRPOPLPUSH
|
||||
* Specially encoded memory-saving integer sets.
|
||||
* A command to export a JSON dump (there should be mostly working patch needing major reworking).
|
||||
* Specially encoded sets of integers (this includes a big refactoring providing an higher level layer for Sets manipulation)
|
||||
@@ -49,18 +47,20 @@ THE "MAYBE" TODO LIST: things that may or may not get implemented
|
||||
Most of this can be seen just as proposals, the fact they are in this list
|
||||
it's not a guarantee they'll ever get implemented ;)
|
||||
|
||||
* Move dict.c from hash table to skip list, in order to avoid the blocking resize operation needed for the hash table.
|
||||
* FORK command (fork()s executing the commands received by the current
|
||||
client in the new process). Hint: large SORTs can use more cores,
|
||||
copy-on-write will avoid memory problems.
|
||||
* DUP command? DUP srckey dstkey, creates an exact clone of srckey value in dstkey.
|
||||
* SORT: Don't copy the list into a vector when BY argument is constant.
|
||||
* Write the hash table size of every db in the dump, so that Redis can resize the hash table just one time when loading a big DB.
|
||||
* LOCK / TRYLOCK / UNLOCK as described many times in the google group
|
||||
* Replication automated tests
|
||||
* Byte Array type (BA prefixed commands): BASETBIT BAGETBIT BASETU8 U16 U32 U64 S8 S16 S32 S64, ability to atomically INCRBY all the base types. BARANGE to get a range of bytes as a bulk value, BASETRANGE to set a range of bytes.
|
||||
* zmalloc() should avoid to add a private header for archs where there is some other kind of libc-specific way to get the size of a malloced block. Already done for Mac OS X.
|
||||
* Read-only mode.
|
||||
* Pattern-matching replication.
|
||||
* Add an option to relax the delete-expiring-keys-on-write semantic *denying* replication and AOF when this is on? Can be handy sometimes, when using Redis for non persistent state, but can create problems. For instance should rename and move also "move" the timeouts? How does this affect other commands?
|
||||
* Kill the delete-on-write behavior of expires, replicating DELs
|
||||
* Multiple BY in SORT.
|
||||
|
||||
KNOWN BUGS
|
||||
==========
|
||||
|
||||
* LRANGE and other commands are using 32 bit integers for ranges, and overflows are not detected. So LRANGE mylist 0 23498204823094823904823904 will have random effects.
|
||||
|
||||
REDIS CLI TODO
|
||||
==============
|
||||
|
||||
* Computer parsable output generation
|
||||
* Memoize return values so that they can be used later as arguments, like $1
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/hiredis-test
|
||||
/hiredis-example*
|
||||
/*.o
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.a
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
Copyright (c) 2006-2009, Salvatore Sanfilippo
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+102
@@ -0,0 +1,102 @@
|
||||
# Hiredis Makefile
|
||||
# Copyright (C) 2010 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ = net.o hiredis.o sds.o async.o
|
||||
BINS = hiredis-example hiredis-test
|
||||
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
|
||||
DYLIBNAME?=libhiredis.so
|
||||
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
OBJARCH?= -arch i386 -arch x86_64
|
||||
DYLIBNAME?=libhiredis.dylib
|
||||
DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ}
|
||||
else
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
DYLIBNAME?=libhiredis.so
|
||||
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -ggdb
|
||||
|
||||
PREFIX?= /usr/local
|
||||
INSTALL_INC= $(PREFIX)/include/hiredis
|
||||
INSTALL_LIB= $(PREFIX)/lib
|
||||
INSTALL= cp -a
|
||||
|
||||
all: ${DYLIBNAME} ${BINS}
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
net.o: net.c fmacros.h net.h
|
||||
async.o: async.c async.h hiredis.h sds.h util.h
|
||||
example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
|
||||
example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
|
||||
example.o: example.c hiredis.h
|
||||
hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
|
||||
sds.o: sds.c sds.h
|
||||
test.o: test.c hiredis.h
|
||||
|
||||
${DYLIBNAME}: ${OBJ}
|
||||
${DYLIB_MAKE_CMD}
|
||||
|
||||
${STLIBNAME}: ${OBJ}
|
||||
${STLIB_MAKE_CMD}
|
||||
|
||||
dynamic: ${DYLIBNAME}
|
||||
static: ${STLIBNAME}
|
||||
|
||||
# Binaries:
|
||||
hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
|
||||
|
||||
hiredis-example-libev: example-libev.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
|
||||
|
||||
hiredis-%: %.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
|
||||
|
||||
test: hiredis-test
|
||||
./hiredis-test
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
clean:
|
||||
rm -rf ${DYLIBNAME} ${STLIBNAME} $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
install: ${DYLIBNAME} ${STLIBNAME}
|
||||
mkdir -p $(INSTALL_INC) $(INSTALL_LIB)
|
||||
$(INSTALL) hiredis.h async.h adapters $(INSTALL_INC)
|
||||
$(INSTALL) ${DYLIBNAME} ${STLIBNAME} $(INSTALL_LIB)
|
||||
|
||||
32bit:
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
$(MAKE) ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
$(MAKE) PROF="-pg"
|
||||
|
||||
gcov:
|
||||
$(MAKE) PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
Vendored
+284
@@ -0,0 +1,284 @@
|
||||
# HIREDIS
|
||||
|
||||
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
|
||||
|
||||
It is minimalistic because it just adds minimal support for the protocol, but
|
||||
at the same time it uses an high level printf-alike API in order to make it
|
||||
much higher level than otherwise suggested by its minimal code base and the
|
||||
lack of explicit bindings for every Redis command.
|
||||
|
||||
Apart from supporting sending commands and receiving replies, it comes with
|
||||
a reply parser that is decoupled from the I/O layer. It
|
||||
is a stream parser designed for easy reusability, which can for instance be used
|
||||
in higher level language bindings for efficient reply parsing.
|
||||
|
||||
Hiredis only supports the binary-safe Redis protocol, so you can use it with any
|
||||
Redis version >= 1.2.0.
|
||||
|
||||
The library comes with multiple APIs. There is the
|
||||
*synchronous API*, the *asynchronous API* and the *reply parsing API*.
|
||||
|
||||
## UPGRADING
|
||||
|
||||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
|
||||
code using hiredis should not be a big pain. The key thing to keep in mind when
|
||||
upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to
|
||||
the stateless 0.0.1 that only has a file descriptor to work with.
|
||||
|
||||
## Synchronous API
|
||||
|
||||
To consume the synchronous API, there are only a few function calls that need to be introduced:
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void freeReplyObject(void *reply);
|
||||
|
||||
### Connecting
|
||||
|
||||
The function `redisConnect` is used to create a so-called `redisContext`. The context is where
|
||||
Hiredis holds state for a connection. The `redisContext` struct has an `error` field that is
|
||||
non-NULL when the connection is in an error state. It contains a string with a textual
|
||||
representation of the error. After trying to connect to Redis using `redisConnect` you should
|
||||
check the `error` field to see if establishing the connection was successful:
|
||||
|
||||
redisContext *c = redisConnect("127.0.0.1", 6379);
|
||||
if (c->error != NULL) {
|
||||
printf("Error: %s\n", c->error);
|
||||
// handle error
|
||||
}
|
||||
|
||||
### Sending commands
|
||||
|
||||
There are several ways to issue commands to Redis. The first that will be introduced is
|
||||
`redisCommand`. This function takes a format similar to printf. In the simplest form,
|
||||
it is used like this:
|
||||
|
||||
reply = redisCommand(context, "SET foo bar");
|
||||
|
||||
The specifier `%s` interpolates a string in the command, and uses `strlen` to
|
||||
determine the length of the string:
|
||||
|
||||
reply = redisCommand(context, "SET foo %s", value);
|
||||
|
||||
When you need to pass binary safe strings in a command, the `%b` specifier can be
|
||||
used. Together with a pointer to the string, it requires a `size_t` length argument
|
||||
of the string:
|
||||
|
||||
reply = redisCommand(context, "SET foo %b", value, valuelen);
|
||||
|
||||
Internally, Hiredis splits the command in different arguments and will
|
||||
convert it to the protocol used to communicate with Redis.
|
||||
One or more spaces separates arguments, so you can use the specifiers
|
||||
anywhere in an argument:
|
||||
|
||||
reply = redisCommand("SET key:%s %s", myid, value);
|
||||
|
||||
### Using replies
|
||||
|
||||
The return value of `redisCommand` holds a reply when the command was
|
||||
successfully executed. When the return value is `NULL`, the `error` field
|
||||
in the context can be used to find out what was the cause of failure.
|
||||
Once an error is returned the context cannot be reused and you should set up
|
||||
a new connection.
|
||||
|
||||
The standard replies that `redisCommand` are of the type `redisReply`. The
|
||||
`type` field in the `redisReply` should be used to test what kind of reply
|
||||
was received:
|
||||
|
||||
* **`REDIS_REPLY_STATUS`**:
|
||||
* The command replied with a status reply. The status string can be accessed using `reply->str`.
|
||||
The length of this string can be accessed using `reply->len`.
|
||||
|
||||
* **`REDIS_REPLY_ERROR`**:
|
||||
* The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
|
||||
|
||||
* **`REDIS_REPLY_INTEGER`**:
|
||||
* The command replied with an integer. The integer value can be accessed using the
|
||||
`reply->integer` field of type `long long`.
|
||||
|
||||
* **`REDIS_REPLY_NIL`**:
|
||||
* The command replied with a **nil** object. There is no data to access.
|
||||
|
||||
* **`REDIS_REPLY_STRING`**:
|
||||
* A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
|
||||
The length of this string can be accessed using `reply->len`.
|
||||
|
||||
* **`REDIS_REPLY_ARRAY`**:
|
||||
* A multi bulk reply. The number of elements in the multi bulk reply is stored in
|
||||
`reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
|
||||
and can be accessed via `reply->elements[..index..]`.
|
||||
Redis may reply with nested arrays but this is fully supported.
|
||||
|
||||
Replies should be freed using the `freeReplyObject()` function.
|
||||
Note that this function will take care of freeing sub-replies objects
|
||||
contained in arrays and nested arrays, so there is no need for the user to
|
||||
free the sub replies (it is actually harmful and will corrupt the memory).
|
||||
|
||||
### Cleaning up
|
||||
|
||||
To disconnect and free the context the following function can be used:
|
||||
|
||||
void redisFree(redisContext *c);
|
||||
|
||||
This function immediately closes the socket and then free's the allocations done in
|
||||
creating the context.
|
||||
|
||||
### Sending commands (cont'd)
|
||||
|
||||
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
|
||||
It has the following prototype:
|
||||
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
|
||||
arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
|
||||
use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
|
||||
need to be binary safe, the entire array of lengths `argvlen` should be provided.
|
||||
|
||||
The return value has the same semantic as `redisCommand`.
|
||||
|
||||
### Pipelining
|
||||
|
||||
To explain how Hiredis supports pipelining in a blocking connection, there needs to be
|
||||
understanding of the internal execution flow.
|
||||
|
||||
When any of the functions in the `redisCommand` family is called, Hiredis first formats the
|
||||
command according to the Redis protocol. The formatted command is then put in the output buffer
|
||||
of the context. This output buffer is dynamic, so it can hold any number of commands.
|
||||
After the command is put in the output buffer, `redisGetReply` is called. This function has the
|
||||
following two execution paths:
|
||||
|
||||
1. The input buffer is non-empty:
|
||||
* Try to parse a single reply from the input buffer and return it
|
||||
* If no reply could be parsed, continue at *2*
|
||||
2. The input buffer is empty:
|
||||
* Write the **entire** output buffer to the socket
|
||||
* Read from the socket until a single reply could be parsed
|
||||
|
||||
The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
|
||||
is expected on the socket. To pipeline commands, the only things that needs to be done is
|
||||
filling up the output buffer. For this cause, two commands can be used that are identical
|
||||
to the `redisCommand` family, apart from not returning a reply:
|
||||
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...);
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
After calling either function one or more times, `redisGetReply` can be used to receive the
|
||||
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
|
||||
the latter means an error occurred while reading a reply. Just as with the other commands,
|
||||
the `error` field in the context can be used to find out what the cause of this error is.
|
||||
|
||||
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
|
||||
a single call to `write(2)`):
|
||||
|
||||
redisReply *reply;
|
||||
redisAppendCommand(context,"SET foo bar");
|
||||
redisAppendCommand(context,"GET foo");
|
||||
redisGetReply(context,&reply); // reply for SET
|
||||
freeReplyObject(reply);
|
||||
redisGetReply(context,&reply); // reply for GET
|
||||
freeReplyObject(reply);
|
||||
|
||||
This API can also be used to implement a blocking subscriber:
|
||||
|
||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||
freeReplyObject(reply);
|
||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
||||
// consume message
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
## Asynchronous API
|
||||
|
||||
Hiredis comes with an asynchronous API that works easily with any event library.
|
||||
Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
|
||||
and [libevent](http://monkey.org/~provos/libevent/).
|
||||
|
||||
### Connecting
|
||||
|
||||
The function `redisAsyncConnect` can be used to establish a non-blocking connection to
|
||||
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `error` field
|
||||
should be checked after creation to see if there were errors creating the connection.
|
||||
Because the connection that will be created is non-blocking, the kernel is not able to
|
||||
instantly return if the specified host and port is able to accept a connection.
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->error != NULL) {
|
||||
printf("Error: %s\n", c->error);
|
||||
// handle error
|
||||
}
|
||||
|
||||
The asynchronous context can hold a disconnect callback function that is called when the
|
||||
connection is disconnected (either because of an error or per user request). This function should
|
||||
have the following prototype:
|
||||
|
||||
void(const redisAsyncContext *c, int status);
|
||||
|
||||
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
|
||||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `error`
|
||||
field in the context can be accessed to find out the cause of the error.
|
||||
|
||||
The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
|
||||
the disconnect callback is a good point to do so.
|
||||
|
||||
Setting the disconnect callback can only be done once per context. For subsequent calls it will
|
||||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
|
||||
### Sending commands and their callbacks
|
||||
|
||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||
Therefore, unlike the synchronous API, there is only a single way to send commands.
|
||||
Because commands are sent to Redis asynchronously, issuing a command requires a callback function
|
||||
that is called when the reply is received. Reply callbacks should have the following prototype:
|
||||
|
||||
void(redisAsyncContext *c, void *reply, void *privdata);
|
||||
|
||||
The `privdata` argument can be used to curry arbitrary data to the callback from the point where
|
||||
the command is initially queued for execution.
|
||||
|
||||
The functions that can be used to issue commands in an asynchronous context are:
|
||||
|
||||
int redisAsyncCommand(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
const char *format, ...);
|
||||
int redisAsyncCommandArgv(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
|
||||
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
|
||||
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
|
||||
returned on calls to the `redisAsyncCommand` family.
|
||||
|
||||
If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback
|
||||
for a command is non-`NULL`, it is responsible for cleaning up the reply.
|
||||
|
||||
All pending callbacks are called with a `NULL` reply when the context encountered an error.
|
||||
|
||||
### Disconnecting
|
||||
|
||||
An asynchronous connection can be terminated using:
|
||||
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
|
||||
When this function is called, the connection is **not** immediately terminated. Instead, new
|
||||
commands are no longer accepted and the connection is only terminated when all pending commands
|
||||
have been written to the socket, their respective replies have been read and their respective
|
||||
callbacks have been executed. After this, the disconnection callback is executed with the
|
||||
`REDIS_OK` status and the context object is free'd.
|
||||
|
||||
### Hooking it up to event library *X*
|
||||
|
||||
There are a few hooks that need to be set on the context object after it is created.
|
||||
See the `adapters/` directory for bindings to *libev* and *libevent*.
|
||||
|
||||
## Reply parsing API
|
||||
|
||||
To be done.
|
||||
|
||||
## AUTHORS
|
||||
|
||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
- add redisCommandVector()
|
||||
- add support for pipelining
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
#include <sys/types.h>
|
||||
#include <ev.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisLibevEvents {
|
||||
redisAsyncContext *context;
|
||||
struct ev_loop *loop;
|
||||
int reading, writing;
|
||||
ev_io rev, wev;
|
||||
} redisLibevEvents;
|
||||
|
||||
void redisLibevReadEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
((void)loop); ((void)revents);
|
||||
redisLibevEvents *e = watcher->data;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisLibevWriteEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
((void)loop); ((void)revents);
|
||||
redisLibevEvents *e = watcher->data;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisLibevAddRead(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
if (!e->reading) {
|
||||
e->reading = 1;
|
||||
ev_io_start(e->loop,&e->rev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevDelRead(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
if (e->reading) {
|
||||
e->reading = 0;
|
||||
ev_io_stop(e->loop,&e->rev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevAddWrite(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
if (!e->writing) {
|
||||
e->writing = 1;
|
||||
ev_io_start(e->loop,&e->wev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevDelWrite(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
if (e->writing) {
|
||||
e->writing = 0;
|
||||
ev_io_stop(e->loop,&e->wev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevCleanup(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
redisLibevDelRead(privdata);
|
||||
redisLibevDelWrite(privdata);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibevEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
e->loop = loop;
|
||||
e->reading = e->writing = 0;
|
||||
e->rev.data = e;
|
||||
e->wev.data = e;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->evAddRead = redisLibevAddRead;
|
||||
ac->evDelRead = redisLibevDelRead;
|
||||
ac->evAddWrite = redisLibevAddWrite;
|
||||
ac->evDelWrite = redisLibevDelWrite;
|
||||
ac->evCleanup = redisLibevCleanup;
|
||||
ac->data = e;
|
||||
|
||||
/* Initialize read/write events */
|
||||
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
|
||||
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
|
||||
return REDIS_OK;
|
||||
}
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
#include <sys/types.h>
|
||||
#include <event.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisLibeventEvents {
|
||||
redisAsyncContext *context;
|
||||
struct event rev, wev;
|
||||
} redisLibeventEvents;
|
||||
|
||||
void redisLibeventReadEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = arg;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = arg;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisLibeventAddRead(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
event_add(&e->rev,NULL);
|
||||
}
|
||||
|
||||
void redisLibeventDelRead(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
event_del(&e->rev);
|
||||
}
|
||||
|
||||
void redisLibeventAddWrite(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
event_add(&e->wev,NULL);
|
||||
}
|
||||
|
||||
void redisLibeventDelWrite(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
event_del(&e->wev);
|
||||
}
|
||||
|
||||
void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
event_del(&e->rev);
|
||||
event_del(&e->wev);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibeventEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->evAddRead = redisLibeventAddRead;
|
||||
ac->evDelRead = redisLibeventDelRead;
|
||||
ac->evAddWrite = redisLibeventAddWrite;
|
||||
ac->evDelWrite = redisLibeventDelWrite;
|
||||
ac->evCleanup = redisLibeventCleanup;
|
||||
ac->data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
|
||||
event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
|
||||
event_base_set(base,&e->rev);
|
||||
event_base_set(base,&e->wev);
|
||||
return REDIS_OK;
|
||||
}
|
||||
Vendored
+284
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "async.h"
|
||||
#include "sds.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Forward declaration of function in hiredis.c */
|
||||
void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
|
||||
|
||||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
|
||||
/* Set all bytes in the async part of the context to 0 */
|
||||
memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
|
||||
return ac;
|
||||
}
|
||||
|
||||
/* We want the error field to be accessible directly instead of requiring
|
||||
* an indirection to the redisContext struct. */
|
||||
static void __redisAsyncCopyError(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
ac->err = c->err;
|
||||
ac->errstr = c->errstr;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
||||
redisContext *c = redisConnectNonBlock(ip,port);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||
redisContext *c = redisConnectUnixNonBlock(path);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn) {
|
||||
redisContext *c = &(ac->c);
|
||||
return redisSetReplyObjectFunctions(c,fn);
|
||||
}
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
|
||||
if (ac->onDisconnect == NULL) {
|
||||
ac->onDisconnect = fn;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Helper functions to push/shift callbacks */
|
||||
static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
|
||||
redisCallback *cb;
|
||||
|
||||
/* Copy callback from stack to heap */
|
||||
cb = calloc(1,sizeof(*cb));
|
||||
if (!cb) redisOOM();
|
||||
if (source != NULL) {
|
||||
cb->fn = source->fn;
|
||||
cb->privdata = source->privdata;
|
||||
}
|
||||
|
||||
/* Store callback in list */
|
||||
if (list->head == NULL)
|
||||
list->head = cb;
|
||||
if (list->tail != NULL)
|
||||
list->tail->next = cb;
|
||||
list->tail = cb;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
|
||||
redisCallback *cb = list->head;
|
||||
if (cb != NULL) {
|
||||
list->head = cb->next;
|
||||
if (cb == list->tail)
|
||||
list->tail = NULL;
|
||||
|
||||
/* Copy callback from heap to stack */
|
||||
if (target != NULL)
|
||||
memcpy(target,cb,sizeof(*cb));
|
||||
free(cb);
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
||||
* from being issued, but tries to flush the output buffer and execute
|
||||
* callbacks for all remaining replies.
|
||||
*
|
||||
* This functions is generally called from within a callback, so the
|
||||
* processCallbacks function will pick up the flag when there are no
|
||||
* more replies. */
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
}
|
||||
|
||||
/* Helper function to make the disconnect happen and clean up. */
|
||||
static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
int status;
|
||||
|
||||
/* Make sure error is accessible if there is any */
|
||||
__redisAsyncCopyError(ac);
|
||||
status = (ac->err == 0) ? REDIS_OK : REDIS_ERR;
|
||||
|
||||
if (status == REDIS_OK) {
|
||||
/* When the connection is cleanly disconnected, there should not
|
||||
* be pending callbacks. */
|
||||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
|
||||
} else {
|
||||
/* Callbacks should not be able to issue new commands. */
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
|
||||
/* Execute pending callbacks with NULL reply. */
|
||||
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) {
|
||||
if (cb.fn != NULL)
|
||||
cb.fn(ac,NULL,cb.privdata);
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal event lib to clean up */
|
||||
if (ac->evCleanup) ac->evCleanup(ac->data);
|
||||
|
||||
/* Execute callback with proper status */
|
||||
if (ac->onDisconnect) ac->onDisconnect(ac,status);
|
||||
|
||||
/* Cleanup self */
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
void *reply = NULL;
|
||||
int status;
|
||||
|
||||
while((status = redisGetReply(c,&reply)) == REDIS_OK) {
|
||||
if (reply == NULL) {
|
||||
/* When the connection is being disconnected and there are
|
||||
* no more replies, this is the cue to really disconnect. */
|
||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
}
|
||||
|
||||
/* When the connection is not being disconnected, simply stop
|
||||
* trying to get replies and wait for the next loop tick. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Shift callback and execute it */
|
||||
assert(__redisShiftCallback(&ac->replies,&cb) == REDIS_OK);
|
||||
if (cb.fn != NULL) {
|
||||
cb.fn(ac,reply,cb.privdata);
|
||||
} else {
|
||||
c->fn->freeObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
/* Disconnect when there was an error reading the reply */
|
||||
if (status != REDIS_OK)
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
* It processes all replies that can be read and executes their callbacks.
|
||||
*/
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (redisBufferRead(c) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->data);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
int done = 0;
|
||||
|
||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Continue writing when not done, stop writing otherwise */
|
||||
if (!done) {
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->data);
|
||||
} else {
|
||||
if (ac->evDelWrite) ac->evDelWrite(ac->data);
|
||||
}
|
||||
|
||||
/* Always schedule reads when something was written */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for the redisAsyncCommand* family of functions.
|
||||
*
|
||||
* Write a formatted command to the output buffer and register the provided
|
||||
* callback function with the context.
|
||||
*/
|
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
|
||||
/* Don't accept new commands when the connection is lazily closed. */
|
||||
if (c->flags & REDIS_DISCONNECTING) return REDIS_ERR;
|
||||
__redisAppendCommand(c,cmd,len);
|
||||
|
||||
/* Store callback */
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
__redisPushCallback(&ac->replies,&cb);
|
||||
|
||||
/* Always schedule a write when the write buffer is non-empty */
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->data);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
|
||||
char *cmd;
|
||||
int len;
|
||||
int status;
|
||||
len = redisvFormatCommand(&cmd,format,ap);
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
return status;
|
||||
}
|
||||
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
|
||||
va_list ap;
|
||||
int status;
|
||||
va_start(ap,format);
|
||||
status = redisvAsyncCommand(ac,fn,privdata,format,ap);
|
||||
va_end(ap);
|
||||
return status;
|
||||
}
|
||||
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd;
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
return status;
|
||||
}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_ASYNC_H
|
||||
#define __HIREDIS_ASYNC_H
|
||||
#include "hiredis.h"
|
||||
|
||||
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
|
||||
|
||||
/* Reply callback prototype and container */
|
||||
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
|
||||
typedef struct redisCallback {
|
||||
struct redisCallback *next; /* simple singly linked list */
|
||||
redisCallbackFn *fn;
|
||||
void *privdata;
|
||||
} redisCallback;
|
||||
|
||||
/* List of callbacks for either regular replies or pub/sub */
|
||||
typedef struct redisCallbackList {
|
||||
redisCallback *head, *tail;
|
||||
} redisCallbackList;
|
||||
|
||||
/* Disconnect callback prototype */
|
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||
|
||||
/* Context for an async connection to Redis */
|
||||
typedef struct redisAsyncContext {
|
||||
/* Hold the regular context, so it can be realloc'ed. */
|
||||
redisContext c;
|
||||
|
||||
/* Setup error flags so they can be used directly. */
|
||||
int err;
|
||||
char *errstr;
|
||||
|
||||
/* Called when the library expects to start reading/writing.
|
||||
* The supplied functions should be idempotent. */
|
||||
void (*evAddRead)(void *privdata);
|
||||
void (*evDelRead)(void *privdata);
|
||||
void (*evAddWrite)(void *privdata);
|
||||
void (*evDelWrite)(void *privdata);
|
||||
void (*evCleanup)(void *privdata);
|
||||
void *data;
|
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
|
||||
redisDisconnectCallback *onDisconnect;
|
||||
|
||||
/* Reply callbacks */
|
||||
redisCallbackList replies;
|
||||
} redisAsyncContext;
|
||||
|
||||
/* Functions that proxy to hiredis */
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
|
||||
/* Handle read/write events */
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac);
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||
|
||||
/* Command functions for an async context. Write the command to the
|
||||
* output buffer and register the provided callback. */
|
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#endif
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/libev.h"
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct ev_loop *loop = ev_default_loop(0);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisLibevAttach(c,loop);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
ev_loop(loop, 0);
|
||||
return 0;
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/libevent.h"
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct event_base *base = event_base_new();
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisLibeventAttach(c,base);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
event_base_dispatch(base);
|
||||
return 0;
|
||||
}
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
int main(void) {
|
||||
unsigned int j;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
|
||||
c = redisConnect((char*)"127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* PING server */
|
||||
reply = redisCommand(c,"PING");
|
||||
printf("PONG: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key */
|
||||
reply = redisCommand(c,"SET %s %s", "foo", "hello world");
|
||||
printf("SET: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key using binary safe API */
|
||||
reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5);
|
||||
printf("SET (binary API): %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Try a GET and two INCR */
|
||||
reply = redisCommand(c,"GET foo");
|
||||
printf("GET foo: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
/* again ... */
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Create a list of numbers, from 0 to 9 */
|
||||
reply = redisCommand(c,"DEL mylist");
|
||||
freeReplyObject(reply);
|
||||
for (j = 0; j < 10; j++) {
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf,64,"%d",j);
|
||||
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
/* Let's check what we have inside the list */
|
||||
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
||||
if (reply->type == REDIS_REPLY_ARRAY) {
|
||||
for (j = 0; j < reply->elements; j++) {
|
||||
printf("%u) %s\n", j, reply->element[j]->str);
|
||||
}
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Vendored
+927
@@ -0,0 +1,927 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
#include "util.h"
|
||||
|
||||
typedef struct redisReader {
|
||||
struct redisReplyObjectFunctions *fn;
|
||||
sds error; /* holds optional error */
|
||||
void *reply; /* holds temporary reply */
|
||||
|
||||
sds buf; /* read buffer */
|
||||
unsigned int pos; /* buffer cursor */
|
||||
|
||||
redisReadTask rstack[3]; /* stack of read tasks */
|
||||
int ridx; /* index of stack */
|
||||
} redisReader;
|
||||
|
||||
static redisReply *createReplyObject(int type);
|
||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
||||
static void *createArrayObject(const redisReadTask *task, int elements);
|
||||
static void *createIntegerObject(const redisReadTask *task, long long value);
|
||||
static void *createNilObject(const redisReadTask *task);
|
||||
static void redisSetReplyReaderError(redisReader *r, sds err);
|
||||
|
||||
/* Default set of functions to build the reply. */
|
||||
static redisReplyObjectFunctions defaultFunctions = {
|
||||
createStringObject,
|
||||
createArrayObject,
|
||||
createIntegerObject,
|
||||
createNilObject,
|
||||
freeReplyObject
|
||||
};
|
||||
|
||||
/* Create a reply object */
|
||||
static redisReply *createReplyObject(int type) {
|
||||
redisReply *r = calloc(sizeof(*r),1);
|
||||
|
||||
if (!r) redisOOM();
|
||||
r->type = type;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Free a reply object */
|
||||
void freeReplyObject(void *reply) {
|
||||
redisReply *r = reply;
|
||||
size_t j;
|
||||
|
||||
switch(r->type) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
break; /* Nothing to free */
|
||||
case REDIS_REPLY_ARRAY:
|
||||
for (j = 0; j < r->elements; j++)
|
||||
if (r->element[j]) freeReplyObject(r->element[j]);
|
||||
free(r->element);
|
||||
break;
|
||||
default:
|
||||
if (r->str != NULL)
|
||||
free(r->str);
|
||||
break;
|
||||
}
|
||||
free(r);
|
||||
}
|
||||
|
||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
|
||||
redisReply *r = createReplyObject(task->type);
|
||||
char *value = malloc(len+1);
|
||||
if (!value) redisOOM();
|
||||
assert(task->type == REDIS_REPLY_ERROR ||
|
||||
task->type == REDIS_REPLY_STATUS ||
|
||||
task->type == REDIS_REPLY_STRING);
|
||||
|
||||
/* Copy string value */
|
||||
memcpy(value,str,len);
|
||||
value[len] = '\0';
|
||||
r->str = value;
|
||||
r->len = len;
|
||||
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
redisReply *r = createReplyObject(REDIS_REPLY_ARRAY);
|
||||
r->elements = elements;
|
||||
if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
|
||||
redisOOM();
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
|
||||
r->integer = value;
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createNilObject(const redisReadTask *task) {
|
||||
redisReply *r = createReplyObject(REDIS_REPLY_NIL);
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||
char *p;
|
||||
if (sdslen(r->buf)-r->pos >= bytes) {
|
||||
p = r->buf+r->pos;
|
||||
r->pos += bytes;
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *seekNewline(char *s) {
|
||||
/* Find pointer to \r\n without strstr */
|
||||
while (s != NULL) {
|
||||
s = strchr(s,'\r');
|
||||
if (s != NULL) {
|
||||
if (s[1] == '\n')
|
||||
break;
|
||||
else
|
||||
s++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static char *readLine(redisReader *r, int *_len) {
|
||||
char *p, *s;
|
||||
int len;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p);
|
||||
if (s != NULL) {
|
||||
len = s-(r->buf+r->pos);
|
||||
r->pos += len+2; /* skip \r\n */
|
||||
if (_len) *_len = len;
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void moveToNextTask(redisReader *r) {
|
||||
redisReadTask *cur, *prv;
|
||||
while (r->ridx >= 0) {
|
||||
/* Return a.s.a.p. when the stack is now empty. */
|
||||
if (r->ridx == 0) {
|
||||
r->ridx--;
|
||||
return;
|
||||
}
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
/* Reset the type because the next item can be anything */
|
||||
assert(cur->idx < prv->elements);
|
||||
cur->type = -1;
|
||||
cur->elements = -1;
|
||||
cur->idx++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int processLineItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
int len;
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (r->fn) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
|
||||
} else {
|
||||
obj = r->fn->createString(cur,p,len);
|
||||
}
|
||||
} else {
|
||||
obj = (void*)(size_t)(cur->type);
|
||||
}
|
||||
|
||||
/* If there is no root yet, register this object as root. */
|
||||
if (r->reply == NULL)
|
||||
r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long len;
|
||||
unsigned long bytelen;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p);
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = strtol(p,NULL,10);
|
||||
|
||||
if (len < 0) {
|
||||
/* The nil object can always be created. */
|
||||
obj = r->fn ? r->fn->createNil(cur) :
|
||||
(void*)REDIS_REPLY_NIL;
|
||||
} else {
|
||||
/* Only continue when the buffer contains the entire bulk item. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
if (r->pos+bytelen <= sdslen(r->buf)) {
|
||||
obj = r->fn ? r->fn->createString(cur,s+2,len) :
|
||||
(void*)REDIS_REPLY_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
/* Proceed when obj was created. */
|
||||
if (obj != NULL) {
|
||||
r->pos += bytelen;
|
||||
if (r->reply == NULL)
|
||||
r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int processMultiBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
long elements;
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = strtol(p,NULL,10);
|
||||
if (elements == -1) {
|
||||
obj = r->fn ? r->fn->createNil(cur) :
|
||||
(void*)REDIS_REPLY_NIL;
|
||||
moveToNextTask(r);
|
||||
} else {
|
||||
obj = r->fn ? r->fn->createArray(cur,elements) :
|
||||
(void*)REDIS_REPLY_ARRAY;
|
||||
|
||||
/* Modify task stack when there are more than 0 elements. */
|
||||
if (elements > 0) {
|
||||
cur->elements = elements;
|
||||
r->ridx++;
|
||||
r->rstack[r->ridx].type = -1;
|
||||
r->rstack[r->ridx].elements = -1;
|
||||
r->rstack[r->ridx].parent = obj;
|
||||
r->rstack[r->ridx].idx = 0;
|
||||
} else {
|
||||
moveToNextTask(r);
|
||||
}
|
||||
}
|
||||
|
||||
/* Object was created, so we can always continue. */
|
||||
if (r->reply == NULL)
|
||||
r->reply = obj;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int processItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
char *p;
|
||||
sds byte;
|
||||
|
||||
/* check if we need to read type */
|
||||
if (cur->type < 0) {
|
||||
if ((p = readBytes(r,1)) != NULL) {
|
||||
switch (p[0]) {
|
||||
case '-':
|
||||
cur->type = REDIS_REPLY_ERROR;
|
||||
break;
|
||||
case '+':
|
||||
cur->type = REDIS_REPLY_STATUS;
|
||||
break;
|
||||
case ':':
|
||||
cur->type = REDIS_REPLY_INTEGER;
|
||||
break;
|
||||
case '$':
|
||||
cur->type = REDIS_REPLY_STRING;
|
||||
break;
|
||||
case '*':
|
||||
cur->type = REDIS_REPLY_ARRAY;
|
||||
break;
|
||||
default:
|
||||
byte = sdscatrepr(sdsempty(),p,1);
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"protocol error, got %s as reply type byte", byte));
|
||||
sdsfree(byte);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/* could not consume 1 byte */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* process typed item */
|
||||
switch(cur->type) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_INTEGER:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
default:
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"unknown item type '%d'", cur->type));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void *redisReplyReaderCreate() {
|
||||
redisReader *r = calloc(sizeof(redisReader),1);
|
||||
r->error = NULL;
|
||||
r->fn = &defaultFunctions;
|
||||
r->buf = sdsempty();
|
||||
r->ridx = -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Set the function set to build the reply. Returns REDIS_OK when there
|
||||
* is no temporary object and it can be set, REDIS_ERR otherwise. */
|
||||
int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn) {
|
||||
redisReader *r = reader;
|
||||
if (r->reply == NULL) {
|
||||
r->fn = fn;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* External libraries wrapping hiredis might need access to the temporary
|
||||
* variable while the reply is built up. When the reader contains an
|
||||
* object in between receiving some bytes to parse, this object might
|
||||
* otherwise be free'd by garbage collection. */
|
||||
void *redisReplyReaderGetObject(void *reader) {
|
||||
redisReader *r = reader;
|
||||
return r->reply;
|
||||
}
|
||||
|
||||
void redisReplyReaderFree(void *reader) {
|
||||
redisReader *r = reader;
|
||||
if (r->error != NULL)
|
||||
sdsfree(r->error);
|
||||
if (r->reply != NULL && r->fn)
|
||||
r->fn->freeObject(r->reply);
|
||||
if (r->buf != NULL)
|
||||
sdsfree(r->buf);
|
||||
free(r);
|
||||
}
|
||||
|
||||
static void redisSetReplyReaderError(redisReader *r, sds err) {
|
||||
if (r->reply != NULL)
|
||||
r->fn->freeObject(r->reply);
|
||||
|
||||
/* Clear remaining buffer when we see a protocol error. */
|
||||
if (r->buf != NULL) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
r->pos = 0;
|
||||
}
|
||||
r->ridx = -1;
|
||||
r->error = err;
|
||||
}
|
||||
|
||||
char *redisReplyReaderGetError(void *reader) {
|
||||
redisReader *r = reader;
|
||||
return r->error;
|
||||
}
|
||||
|
||||
void redisReplyReaderFeed(void *reader, char *buf, size_t len) {
|
||||
redisReader *r = reader;
|
||||
|
||||
/* Copy the provided buffer. */
|
||||
if (buf != NULL && len >= 1)
|
||||
r->buf = sdscatlen(r->buf,buf,len);
|
||||
}
|
||||
|
||||
int redisReplyReaderGetReply(void *reader, void **reply) {
|
||||
redisReader *r = reader;
|
||||
if (reply != NULL) *reply = NULL;
|
||||
|
||||
/* When the buffer is empty, there will never be a reply. */
|
||||
if (sdslen(r->buf) == 0)
|
||||
return REDIS_OK;
|
||||
|
||||
/* Set first item to process when the stack is empty. */
|
||||
if (r->ridx == -1) {
|
||||
r->rstack[0].type = -1;
|
||||
r->rstack[0].elements = -1;
|
||||
r->rstack[0].parent = NULL;
|
||||
r->rstack[0].idx = -1;
|
||||
r->ridx = 0;
|
||||
}
|
||||
|
||||
/* Process items in reply. */
|
||||
while (r->ridx >= 0)
|
||||
if (processItem(r) < 0)
|
||||
break;
|
||||
|
||||
/* Discard the consumed part of the buffer. */
|
||||
if (r->pos > 0) {
|
||||
if (r->pos == sdslen(r->buf)) {
|
||||
/* sdsrange has a quirck on this edge case. */
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
} else {
|
||||
r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
|
||||
}
|
||||
r->pos = 0;
|
||||
}
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
if (r->ridx == -1) {
|
||||
void *aux = r->reply;
|
||||
r->reply = NULL;
|
||||
|
||||
/* Destroy the buffer when it is empty and is quite large. */
|
||||
if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
r->pos = 0;
|
||||
}
|
||||
|
||||
/* Check if there actually *is* a reply. */
|
||||
if (r->error != NULL) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
if (reply != NULL) *reply = aux;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Calculate the number of bytes needed to represent an integer as string. */
|
||||
static int intlen(int i) {
|
||||
int len = 0;
|
||||
if (i < 0) {
|
||||
len++;
|
||||
i = -i;
|
||||
}
|
||||
do {
|
||||
len++;
|
||||
i /= 10;
|
||||
} while(i);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Helper function for redisvFormatCommand(). */
|
||||
static void addArgument(sds a, char ***argv, int *argc, int *totlen) {
|
||||
(*argc)++;
|
||||
if ((*argv = realloc(*argv, sizeof(char*)*(*argc))) == NULL) redisOOM();
|
||||
if (totlen) *totlen = *totlen+1+intlen(sdslen(a))+2+sdslen(a)+2;
|
||||
(*argv)[(*argc)-1] = a;
|
||||
}
|
||||
|
||||
int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
size_t size;
|
||||
const char *arg, *c = format;
|
||||
char *cmd = NULL; /* final command */
|
||||
int pos; /* position in final command */
|
||||
sds current; /* current argument */
|
||||
char **argv = NULL;
|
||||
int argc = 0, j;
|
||||
int totlen = 0;
|
||||
|
||||
/* Abort if there is not target to set */
|
||||
if (target == NULL)
|
||||
return -1;
|
||||
|
||||
/* Build the command string accordingly to protocol */
|
||||
current = sdsempty();
|
||||
while(*c != '\0') {
|
||||
if (*c != '%' || c[1] == '\0') {
|
||||
if (*c == ' ') {
|
||||
if (sdslen(current) != 0) {
|
||||
addArgument(current, &argv, &argc, &totlen);
|
||||
current = sdsempty();
|
||||
}
|
||||
} else {
|
||||
current = sdscatlen(current,c,1);
|
||||
}
|
||||
} else {
|
||||
switch(c[1]) {
|
||||
case 's':
|
||||
arg = va_arg(ap,char*);
|
||||
current = sdscat(current,arg);
|
||||
break;
|
||||
case 'b':
|
||||
arg = va_arg(ap,char*);
|
||||
size = va_arg(ap,size_t);
|
||||
current = sdscatlen(current,arg,size);
|
||||
break;
|
||||
case '%':
|
||||
cmd = sdscat(cmd,"%");
|
||||
break;
|
||||
}
|
||||
c++;
|
||||
}
|
||||
c++;
|
||||
}
|
||||
|
||||
/* Add the last argument if needed */
|
||||
if (sdslen(current) != 0) {
|
||||
addArgument(current, &argv, &argc, &totlen);
|
||||
} else {
|
||||
sdsfree(current);
|
||||
}
|
||||
|
||||
/* Add bytes needed to hold multi bulk count */
|
||||
totlen += 1+intlen(argc)+2;
|
||||
|
||||
/* Build the command at protocol level */
|
||||
cmd = malloc(totlen+1);
|
||||
if (!cmd) redisOOM();
|
||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(argv[j]));
|
||||
memcpy(cmd+pos,argv[j],sdslen(argv[j]));
|
||||
pos += sdslen(argv[j]);
|
||||
sdsfree(argv[j]);
|
||||
cmd[pos++] = '\r';
|
||||
cmd[pos++] = '\n';
|
||||
}
|
||||
assert(pos == totlen);
|
||||
free(argv);
|
||||
cmd[totlen] = '\0';
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol. This function
|
||||
* takes a format similar to printf:
|
||||
*
|
||||
* %s represents a C null terminated string you want to interpolate
|
||||
* %b represents a binary safe string
|
||||
*
|
||||
* When using %b you need to provide both the pointer to the string
|
||||
* and the length in bytes. Examples:
|
||||
*
|
||||
* len = redisFormatCommand(target, "GET %s", mykey);
|
||||
* len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
|
||||
*/
|
||||
int redisFormatCommand(char **target, const char *format, ...) {
|
||||
va_list ap;
|
||||
int len;
|
||||
va_start(ap,format);
|
||||
len = redisvFormatCommand(target,format,ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol. This function takes the
|
||||
* number of arguments, an array with arguments and an array with their
|
||||
* lengths. If the latter is set to NULL, strlen will be used to compute the
|
||||
* argument lengths.
|
||||
*/
|
||||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd = NULL; /* final command */
|
||||
int pos; /* position in final command */
|
||||
size_t len;
|
||||
int totlen, j;
|
||||
|
||||
/* Calculate number of bytes needed for the command */
|
||||
totlen = 1+intlen(argc)+2;
|
||||
for (j = 0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
totlen += 1+intlen(len)+2+len+2;
|
||||
}
|
||||
|
||||
/* Build the command at protocol level */
|
||||
cmd = malloc(totlen+1);
|
||||
if (!cmd) redisOOM();
|
||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
pos += sprintf(cmd+pos,"$%zu\r\n",len);
|
||||
memcpy(cmd+pos,argv[j],len);
|
||||
pos += len;
|
||||
cmd[pos++] = '\r';
|
||||
cmd[pos++] = '\n';
|
||||
}
|
||||
assert(pos == totlen);
|
||||
cmd[totlen] = '\0';
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
}
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const sds errstr) {
|
||||
c->err = type;
|
||||
if (errstr != NULL) {
|
||||
c->errstr = errstr;
|
||||
} else {
|
||||
/* Only REDIS_ERR_IO may lack a description! */
|
||||
assert(type == REDIS_ERR_IO);
|
||||
c->errstr = sdsnew(strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static redisContext *redisContextInit() {
|
||||
redisContext *c = calloc(sizeof(redisContext),1);
|
||||
c->fd = -1; /* quick fix for a bug that should be addressed differently */
|
||||
c->err = 0;
|
||||
c->errstr = NULL;
|
||||
c->obuf = sdsempty();
|
||||
c->fn = &defaultFunctions;
|
||||
c->reader = NULL;
|
||||
return c;
|
||||
}
|
||||
|
||||
void redisFree(redisContext *c) {
|
||||
/* Disconnect before free'ing if not yet disconnected. */
|
||||
if (c->flags & REDIS_CONNECTED)
|
||||
close(c->fd);
|
||||
if (c->errstr != NULL)
|
||||
sdsfree(c->errstr);
|
||||
if (c->obuf != NULL)
|
||||
sdsfree(c->obuf);
|
||||
if (c->reader != NULL)
|
||||
redisReplyReaderFree(c->reader);
|
||||
free(c);
|
||||
}
|
||||
|
||||
/* Connect to a Redis instance. On error the field error in the returned
|
||||
* context will be set to the return value of the error function.
|
||||
* When no set of reply functions is given, the default set will be used. */
|
||||
redisContext *redisConnect(const char *ip, int port) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags |= REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectTcp(c,ip,port);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectTcp(c,ip,port);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnix(const char *path) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags |= REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectUnix(c,path);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnixNonBlock(const char *path) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectUnix(c,path);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Set the replyObjectFunctions to use. Returns REDIS_ERR when the reader
|
||||
* was already initialized and the function set could not be re-set.
|
||||
* Return REDIS_OK when they could be set. */
|
||||
int redisSetReplyObjectFunctions(redisContext *c, redisReplyObjectFunctions *fn) {
|
||||
if (c->reader != NULL)
|
||||
return REDIS_ERR;
|
||||
c->fn = fn;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Helper function to lazily create a reply reader. */
|
||||
static void __redisCreateReplyReader(redisContext *c) {
|
||||
if (c->reader == NULL) {
|
||||
c->reader = redisReplyReaderCreate();
|
||||
assert(redisReplyReaderSetReplyObjectFunctions(c->reader,c->fn) == REDIS_OK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use this function to handle a read event on the descriptor. It will try
|
||||
* and read some bytes from the socket and feed them to the reply parser.
|
||||
*
|
||||
* After this function is called, you may use redisContextReadReply to
|
||||
* see if there is a reply available. */
|
||||
int redisBufferRead(redisContext *c) {
|
||||
char buf[2048];
|
||||
int nread = read(c->fd,buf,sizeof(buf));
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c,REDIS_ERR_EOF,
|
||||
sdsnew("Server closed the connection"));
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
__redisCreateReplyReader(c);
|
||||
redisReplyReaderFeed(c->reader,buf,nread);
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Write the output buffer to the socket.
|
||||
*
|
||||
* Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
|
||||
* succesfully written to the socket. When the buffer is empty after the
|
||||
* write operation, "wdone" is set to 1 (if given).
|
||||
*
|
||||
* Returns REDIS_ERR if an error occured trying to write and sets
|
||||
* c->error to hold the appropriate error string.
|
||||
*/
|
||||
int redisBufferWrite(redisContext *c, int *done) {
|
||||
int nwritten;
|
||||
if (sdslen(c->obuf) > 0) {
|
||||
nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
|
||||
if (nwritten == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else if (nwritten > 0) {
|
||||
if (nwritten == (signed)sdslen(c->obuf)) {
|
||||
sdsfree(c->obuf);
|
||||
c->obuf = sdsempty();
|
||||
} else {
|
||||
c->obuf = sdsrange(c->obuf,nwritten,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (done != NULL) *done = (sdslen(c->obuf) == 0);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Internal helper function to try and get a reply from the reader,
|
||||
* or set an error in the context otherwise. */
|
||||
int redisGetReplyFromReader(redisContext *c, void **reply) {
|
||||
__redisCreateReplyReader(c);
|
||||
if (redisReplyReaderGetReply(c->reader,reply) == REDIS_ERR) {
|
||||
__redisSetError(c,REDIS_ERR_PROTOCOL,
|
||||
sdsnew(((redisReader*)c->reader)->error));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisGetReply(redisContext *c, void **reply) {
|
||||
int wdone = 0;
|
||||
void *aux = NULL;
|
||||
|
||||
/* Try to read pending replies */
|
||||
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* For the blocking context, flush output buffer and read reply */
|
||||
if (aux == NULL && c->flags & REDIS_BLOCK) {
|
||||
/* Write until done */
|
||||
do {
|
||||
if (redisBufferWrite(c,&wdone) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
} while (!wdone);
|
||||
|
||||
/* Read until there is a reply */
|
||||
do {
|
||||
if (redisBufferRead(c) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
} while (aux == NULL);
|
||||
}
|
||||
|
||||
/* Set reply object */
|
||||
if (reply != NULL) *reply = aux;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Helper function for the redisAppendCommand* family of functions.
|
||||
*
|
||||
* Write a formatted command to the output buffer. When this family
|
||||
* is used, you need to call redisGetReply yourself to retrieve
|
||||
* the reply (or replies in pub/sub).
|
||||
*/
|
||||
void __redisAppendCommand(redisContext *c, char *cmd, size_t len) {
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
}
|
||||
|
||||
void redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
|
||||
char *cmd;
|
||||
int len;
|
||||
len = redisvFormatCommand(&cmd,format,ap);
|
||||
__redisAppendCommand(c,cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap,format);
|
||||
redisvAppendCommand(c,format,ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd;
|
||||
int len;
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
|
||||
__redisAppendCommand(c,cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
/* Helper function for the redisCommand* family of functions.
|
||||
*
|
||||
* Write a formatted command to the output buffer. If the given context is
|
||||
* blocking, immediately read the reply into the "reply" pointer. When the
|
||||
* context is non-blocking, the "reply" pointer will not be used and the
|
||||
* command is simply appended to the write buffer.
|
||||
*
|
||||
* Returns the reply when a reply was succesfully retrieved. Returns NULL
|
||||
* otherwise. When NULL is returned in a blocking context, the error field
|
||||
* in the context will be set.
|
||||
*/
|
||||
static void *__redisCommand(redisContext *c, char *cmd, size_t len) {
|
||||
void *aux = NULL;
|
||||
__redisAppendCommand(c,cmd,len);
|
||||
|
||||
if (c->flags & REDIS_BLOCK) {
|
||||
if (redisGetReply(c,&aux) == REDIS_OK)
|
||||
return aux;
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *redisvCommand(redisContext *c, const char *format, va_list ap) {
|
||||
char *cmd;
|
||||
int len;
|
||||
void *reply = NULL;
|
||||
len = redisvFormatCommand(&cmd,format,ap);
|
||||
reply = __redisCommand(c,cmd,len);
|
||||
free(cmd);
|
||||
return reply;
|
||||
}
|
||||
|
||||
void *redisCommand(redisContext *c, const char *format, ...) {
|
||||
va_list ap;
|
||||
void *reply = NULL;
|
||||
va_start(ap,format);
|
||||
reply = redisvCommand(c,format,ap);
|
||||
va_end(ap);
|
||||
return reply;
|
||||
}
|
||||
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd;
|
||||
int len;
|
||||
void *reply = NULL;
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
|
||||
reply = __redisCommand(c,cmd,len);
|
||||
free(cmd);
|
||||
return reply;
|
||||
}
|
||||
Vendored
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_H
|
||||
#define __HIREDIS_H
|
||||
#include <stdio.h> /* for size_t */
|
||||
#include <stdarg.h> /* for va_list */
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 9
|
||||
#define HIREDIS_PATCH 0
|
||||
|
||||
#define REDIS_ERR -1
|
||||
#define REDIS_OK 0
|
||||
|
||||
/* When an error occurs, the err flag in a context is set to hold the type of
|
||||
* error that occured. REDIS_ERR_IO means there was an I/O error and you
|
||||
* should use the "errno" variable to find out what is wrong.
|
||||
* For other values, the "errstr" field will hold a description. */
|
||||
#define REDIS_ERR_IO 1 /* error in read or write */
|
||||
#define REDIS_ERR_EOF 3 /* eof */
|
||||
#define REDIS_ERR_PROTOCOL 4 /* protocol error */
|
||||
#define REDIS_ERR_OTHER 2 /* something else */
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
#define REDIS_BLOCK 0x1
|
||||
|
||||
/* Connection may be disconnected before being free'd. The second bit
|
||||
* in the flags field is set when the context is connected. */
|
||||
#define REDIS_CONNECTED 0x2
|
||||
|
||||
/* The async API might try to disconnect cleanly and flush the output
|
||||
* buffer and read all subsequent replies before disconnecting.
|
||||
* This flag means no new commands can come in and the connection
|
||||
* should be terminated once all replies have been read. */
|
||||
#define REDIS_DISCONNECTING 0x4
|
||||
|
||||
#define REDIS_REPLY_ERROR 0
|
||||
#define REDIS_REPLY_STRING 1
|
||||
#define REDIS_REPLY_ARRAY 2
|
||||
#define REDIS_REPLY_INTEGER 3
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
|
||||
/* This is the reply object returned by redisCommand() */
|
||||
typedef struct redisReply {
|
||||
int type; /* REDIS_REPLY_* */
|
||||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
|
||||
int len; /* Length of string */
|
||||
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
|
||||
typedef struct redisReadTask {
|
||||
int type;
|
||||
int elements; /* number of elements in multibulk container */
|
||||
void *parent; /* optional pointer to parent object */
|
||||
int idx; /* index in parent (array) object */
|
||||
} redisReadTask;
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
void (*freeObject)(void*);
|
||||
} redisReplyObjectFunctions;
|
||||
|
||||
struct redisContext; /* need forward declaration of redisContext */
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
typedef struct redisContext {
|
||||
int fd;
|
||||
int flags;
|
||||
char *obuf; /* Write buffer */
|
||||
int err; /* Error flags, 0 when there is no error */
|
||||
char *errstr; /* String representation of error when applicable */
|
||||
|
||||
/* Function set for reply buildup and reply reader */
|
||||
redisReplyObjectFunctions *fn;
|
||||
void *reader;
|
||||
} redisContext;
|
||||
|
||||
void freeReplyObject(void *reply);
|
||||
void *redisReplyReaderCreate();
|
||||
int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
|
||||
void *redisReplyReaderGetObject(void *reader);
|
||||
char *redisReplyReaderGetError(void *reader);
|
||||
void redisReplyReaderFree(void *ptr);
|
||||
void redisReplyReaderFeed(void *reader, char *buf, size_t len);
|
||||
int redisReplyReaderGetReply(void *reader, void **reply);
|
||||
|
||||
/* Functions to format a command according to the protocol. */
|
||||
int redisvFormatCommand(char **target, const char *format, va_list ap);
|
||||
int redisFormatCommand(char **target, const char *format, ...);
|
||||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||
redisContext *redisConnectUnix(const char *path);
|
||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||
int redisSetReplyObjectFunctions(redisContext *c, redisReplyObjectFunctions *fn);
|
||||
void redisFree(redisContext *c);
|
||||
int redisBufferRead(redisContext *c);
|
||||
int redisBufferWrite(redisContext *c, int *done);
|
||||
|
||||
/* In a blocking context, this function first checks if there are unconsumed
|
||||
* replies to return and returns one if so. Otherwise, it flushes the output
|
||||
* buffer to the socket and reads until it has a reply. In a non-blocking
|
||||
* context, it will return unconsumed replies until there are no more. */
|
||||
int redisGetReply(redisContext *c, void **reply);
|
||||
int redisGetReplyFromReader(redisContext *c, void **reply);
|
||||
|
||||
/* Write a command to the output buffer. Use these functions in blocking mode
|
||||
* to get a pipeline of commands. */
|
||||
void redisvAppendCommand(redisContext *c, const char *format, va_list ap);
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...);
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
/* Issue a command to Redis. In a blocking context, it is identical to calling
|
||||
* redisAppendCommand, followed by redisGetReply. The function will return
|
||||
* NULL if there was an error in performing the request, otherwise it will
|
||||
* return the reply. In a non-blocking context, it is identical to calling
|
||||
* only redisAppendCommand and will always return NULL. */
|
||||
void *redisvCommand(redisContext *c, const char *format, va_list ap);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#endif
|
||||
Vendored
+167
@@ -0,0 +1,167 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
|
||||
/* Forward declaration */
|
||||
void __redisSetError(redisContext *c, int type, sds err);
|
||||
|
||||
static int redisCreateSocket(redisContext *c, int type) {
|
||||
int s, on = 1;
|
||||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (type == AF_INET) {
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static int redisSetNonBlock(redisContext *c, int fd) {
|
||||
int flags;
|
||||
|
||||
/* Set the socket nonblocking.
|
||||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(fd, F_GETFL)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,
|
||||
sdscatprintf(sdsempty(), "fcntl(F_GETFL): %s", strerror(errno)));
|
||||
close(fd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,
|
||||
sdscatprintf(sdsempty(), "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)));
|
||||
close(fd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redisSetTcpNoDelay(redisContext *c, int fd) {
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,
|
||||
sdscatprintf(sdsempty(), "setsockopt(TCP_NODELAY): %s", strerror(errno)));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
|
||||
int s;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = redisCreateSocket(c,AF_INET)) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (!blocking && redisSetNonBlock(c,s) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
if (inet_aton(addr, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
|
||||
he = gethostbyname(addr);
|
||||
if (he == NULL) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,
|
||||
sdscatprintf(sdsempty(),"can't resolve: %s",addr));
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
}
|
||||
|
||||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (redisSetTcpNoDelay(c,s) != REDIS_OK) {
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
c->fd = s;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextConnectUnix(redisContext *c, const char *path) {
|
||||
int s;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if ((s = redisCreateSocket(c,AF_LOCAL)) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (!blocking && redisSetNonBlock(c,s) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
c->fd = s;
|
||||
return REDIS_OK;
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __NET_H
|
||||
#define __NET_H
|
||||
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port);
|
||||
int redisContextConnectUnix(redisContext *c, const char *path);
|
||||
|
||||
#endif
|
||||
Vendored
+140
-20
@@ -33,10 +33,8 @@
|
||||
#include "sds.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "zmalloc.h"
|
||||
|
||||
static void sdsOomAbort(void) {
|
||||
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
|
||||
@@ -46,7 +44,7 @@ static void sdsOomAbort(void) {
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
sh = malloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
@@ -82,7 +80,7 @@ sds sdsdup(const sds s) {
|
||||
|
||||
void sdsfree(sds s) {
|
||||
if (s == NULL) return;
|
||||
zfree(s-sizeof(struct sdshdr));
|
||||
free(s-sizeof(struct sdshdr));
|
||||
}
|
||||
|
||||
size_t sdsavail(sds s) {
|
||||
@@ -106,7 +104,7 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
len = sdslen(s);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
newlen = (len+addlen)*2;
|
||||
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (newsh == NULL) sdsOomAbort();
|
||||
#else
|
||||
@@ -117,7 +115,7 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
return newsh->buf;
|
||||
}
|
||||
|
||||
sds sdscatlen(sds s, void *t, size_t len) {
|
||||
sds sdscatlen(sds s, const void *t, size_t len) {
|
||||
struct sdshdr *sh;
|
||||
size_t curlen = sdslen(s);
|
||||
|
||||
@@ -131,7 +129,7 @@ sds sdscatlen(sds s, void *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdscat(sds s, char *t) {
|
||||
sds sdscat(sds s, const char *t) {
|
||||
return sdscatlen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
@@ -156,31 +154,39 @@ sds sdscpy(sds s, char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
|
||||
while(1) {
|
||||
buf = zmalloc(buflen);
|
||||
buf = malloc(buflen);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (buf == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (buf == NULL) return NULL;
|
||||
#endif
|
||||
buf[buflen-2] = '\0';
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, buflen, fmt, ap);
|
||||
va_end(ap);
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
zfree(buf);
|
||||
free(buf);
|
||||
buflen *= 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
t = sdscat(s, buf);
|
||||
zfree(buf);
|
||||
free(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char *t;
|
||||
va_start(ap, fmt);
|
||||
t = sdscatvprintf(s,fmt,ap);
|
||||
va_end(ap);
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -201,7 +207,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdsrange(sds s, long start, long end) {
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
@@ -272,7 +278,7 @@ int sdscmp(sds s1, sds s2) {
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5, start = 0, j;
|
||||
|
||||
sds *tokens = zmalloc(sizeof(sds)*slots);
|
||||
sds *tokens = malloc(sizeof(sds)*slots);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (tokens == NULL) sdsOomAbort();
|
||||
#endif
|
||||
@@ -287,7 +293,7 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
sds *newtokens;
|
||||
|
||||
slots *= 2;
|
||||
newtokens = zrealloc(tokens,sizeof(sds)*slots);
|
||||
newtokens = realloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
@@ -330,7 +336,7 @@ cleanup:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
|
||||
zfree(tokens);
|
||||
free(tokens);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
@@ -340,7 +346,7 @@ void sdsfreesplitres(sds *tokens, int count) {
|
||||
if (!tokens) return;
|
||||
while(count--)
|
||||
sdsfree(tokens[count]);
|
||||
zfree(tokens);
|
||||
free(tokens);
|
||||
}
|
||||
|
||||
sds sdsfromlonglong(long long value) {
|
||||
@@ -357,3 +363,117 @@ sds sdsfromlonglong(long long value) {
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
sds sdscatrepr(sds s, char *p, size_t len) {
|
||||
s = sdscatlen(s,"\"",1);
|
||||
while(len--) {
|
||||
switch(*p) {
|
||||
case '\\':
|
||||
case '"':
|
||||
s = sdscatprintf(s,"\\%c",*p);
|
||||
break;
|
||||
case '\n': s = sdscatlen(s,"\\n",1); break;
|
||||
case '\r': s = sdscatlen(s,"\\r",1); break;
|
||||
case '\t': s = sdscatlen(s,"\\t",1); break;
|
||||
case '\a': s = sdscatlen(s,"\\a",1); break;
|
||||
case '\b': s = sdscatlen(s,"\\b",1); break;
|
||||
default:
|
||||
if (isprint(*p))
|
||||
s = sdscatprintf(s,"%c",*p);
|
||||
else
|
||||
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return sdscatlen(s,"\"",1);
|
||||
}
|
||||
|
||||
/* Split a line into arguments, where every argument can be in the
|
||||
* following programming-language REPL-alike form:
|
||||
*
|
||||
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
|
||||
*
|
||||
* The number of arguments is stored into *argc, and an array
|
||||
* of sds is returned. The caller should sdsfree() all the returned
|
||||
* strings and finally free() the array itself.
|
||||
*
|
||||
* Note that sdscatrepr() is able to convert back a string into
|
||||
* a quoted string in the same format sdssplitargs() is able to parse.
|
||||
*/
|
||||
sds *sdssplitargs(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
/* skip blanks */
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done=0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
switch(*p) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'a': c = '\a'; break;
|
||||
default: c = *p; break;
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
/* closing quote must be followed by a space */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
/* unterminated quotes */
|
||||
goto err;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else {
|
||||
switch(*p) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
done=1;
|
||||
break;
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
while((*argc)--)
|
||||
sdsfree(vector[*argc]);
|
||||
free(vector);
|
||||
if (current) sdsfree(current);
|
||||
return NULL;
|
||||
}
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __SDS_H
|
||||
#define __SDS_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen);
|
||||
sds sdsnew(const char *init);
|
||||
sds sdsempty();
|
||||
size_t sdslen(const sds s);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
size_t sdsavail(sds s);
|
||||
sds sdscatlen(sds s, const void *t, size_t len);
|
||||
sds sdscat(sds s, const char *t);
|
||||
sds sdscpylen(sds s, char *t, size_t len);
|
||||
sds sdscpy(sds s, char *t);
|
||||
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||
#ifdef __GNUC__
|
||||
sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
int sdscmp(sds s1, sds s2);
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, char *p, size_t len);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
|
||||
#endif
|
||||
Vendored
+401
@@ -0,0 +1,401 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
/* The following lines make up our testing "framework" :) */
|
||||
static int tests = 0, fails = 0;
|
||||
#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
|
||||
#define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;}
|
||||
|
||||
static long long usec(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||
}
|
||||
|
||||
static int use_unix = 0;
|
||||
static redisContext *blocking_context = NULL;
|
||||
static void __connect(redisContext **target) {
|
||||
*target = blocking_context = (use_unix ?
|
||||
redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379));
|
||||
if (blocking_context->err) {
|
||||
printf("Connection error: %s\n", blocking_context->errstr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_format_commands() {
|
||||
char *cmd;
|
||||
int len;
|
||||
|
||||
test("Format command without interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET foo bar");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%s string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%b string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
const char *argv[3];
|
||||
argv[0] = "SET";
|
||||
argv[1] = "foo";
|
||||
argv[2] = "bar";
|
||||
size_t lens[3] = { 3, 3, 3 };
|
||||
int argc = 3;
|
||||
|
||||
test("Format command by passing argc/argv without lengths: ");
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command by passing argc/argv with lengths: ");
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static void test_blocking_connection() {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
|
||||
__connect(&c);
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
|
||||
|
||||
/* Two conditions may happen, depending on the type of connection.
|
||||
* When connected via TCP, the socket will not yet be aware of the closed
|
||||
* connection and the write(2) call will succeed, but the read(2) will
|
||||
* result in an EOF. When connected via Unix sockets, the socket will be
|
||||
* immediately aware that it was closed and fail on the write(2) call. */
|
||||
if (use_unix) {
|
||||
fprintf(stderr,"Error: %s\n", c->errstr);
|
||||
assert(c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr,"Broken pipe") == 0);
|
||||
} else {
|
||||
fprintf(stderr,"Error: %s\n", c->errstr);
|
||||
assert(c->err == REDIS_ERR_EOF &&
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
redisFree(c);
|
||||
|
||||
__connect(&c); /* reconnect */
|
||||
test("Is able to deliver commands: ");
|
||||
reply = redisCommand(c,"PING");
|
||||
test_cond(reply->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->str,"pong") == 0)
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Switch to DB 9 for testing, now that we know we can chat. */
|
||||
reply = redisCommand(c,"SELECT 9");
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Make sure the DB is emtpy */
|
||||
reply = redisCommand(c,"DBSIZE");
|
||||
if (reply->type != REDIS_REPLY_INTEGER ||
|
||||
reply->integer != 0) {
|
||||
printf("Sorry DB 9 is not empty, test can not continue\n");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("DB 9 is empty... test can continue\n");
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Is a able to send commands verbatim: ");
|
||||
reply = redisCommand(c,"SET foo bar");
|
||||
test_cond (reply->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->str,"ok") == 0)
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("%%s String interpolation works: ");
|
||||
reply = redisCommand(c,"SET %s %s","foo","hello world");
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c,"GET foo");
|
||||
test_cond(reply->type == REDIS_REPLY_STRING &&
|
||||
strcmp(reply->str,"hello world") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("%%b String interpolation works: ");
|
||||
reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c,"GET foo");
|
||||
test_cond(reply->type == REDIS_REPLY_STRING &&
|
||||
memcmp(reply->str,"hello\x00world",11) == 0)
|
||||
|
||||
test("Binary reply length is correct: ");
|
||||
test_cond(reply->len == 11)
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Can parse nil replies: ");
|
||||
reply = redisCommand(c,"GET nokey");
|
||||
test_cond(reply->type == REDIS_REPLY_NIL)
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* test 7 */
|
||||
test("Can parse integer replies: ");
|
||||
reply = redisCommand(c,"INCR mycounter");
|
||||
test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Can parse multi bulk replies: ");
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
|
||||
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
||||
test_cond(reply->type == REDIS_REPLY_ARRAY &&
|
||||
reply->elements == 2 &&
|
||||
!memcmp(reply->element[0]->str,"bar",3) &&
|
||||
!memcmp(reply->element[1]->str,"foo",3))
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* m/e with multi bulk reply *before* other reply.
|
||||
* specifically test ordering of reply items to parse. */
|
||||
test("Can handle nested multi bulk replies: ");
|
||||
freeReplyObject(redisCommand(c,"MULTI"));
|
||||
freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
|
||||
freeReplyObject(redisCommand(c,"PING"));
|
||||
reply = (redisCommand(c,"EXEC"));
|
||||
test_cond(reply->type == REDIS_REPLY_ARRAY &&
|
||||
reply->elements == 2 &&
|
||||
reply->element[0]->type == REDIS_REPLY_ARRAY &&
|
||||
reply->element[0]->elements == 2 &&
|
||||
!memcmp(reply->element[0]->element[0]->str,"bar",3) &&
|
||||
!memcmp(reply->element[0]->element[1]->str,"foo",3) &&
|
||||
reply->element[1]->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->element[1]->str,"pong") == 0);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
static void test_reply_reader() {
|
||||
void *reader;
|
||||
void *reply;
|
||||
char *err;
|
||||
int ret;
|
||||
|
||||
test("Error handling in reply parser: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
/* when the reply already contains multiple items, they must be free'd
|
||||
* on an error. valgrind will bark when this doesn't happen. */
|
||||
test("Memory cleanup in reply parser: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderFeed(reader,(char*)"*2\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
|
||||
redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Works with NULL functions for reply: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderSetReplyObjectFunctions(reader,NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"+OK\r\n",5);
|
||||
ret = redisReplyReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Works when a single newline (\\r\\n) covers two calls to feed: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderSetReplyObjectFunctions(reader,NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"+OK\r",4);
|
||||
ret = redisReplyReaderGetReply(reader,&reply);
|
||||
assert(ret == REDIS_OK && reply == NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"\n",1);
|
||||
ret = redisReplyReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
|
||||
redisReplyReaderFree(reader);
|
||||
}
|
||||
|
||||
static void test_throughput() {
|
||||
int i;
|
||||
long long t1, t2;
|
||||
redisContext *c = blocking_context;
|
||||
redisReply **replies;
|
||||
|
||||
test("Throughput:\n");
|
||||
for (i = 0; i < 500; i++)
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*1000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < 1000; i++) {
|
||||
replies[i] = redisCommand(c,"PING");
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(1000x PING: %.2fs)\n", (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*1000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < 1000; i++) {
|
||||
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
|
||||
assert(replies[i] != NULL && replies[i]->elements == 500);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(1000x LRANGE with 500 elements: %.2fs)\n", (t2-t1)/1000000.0);
|
||||
}
|
||||
|
||||
static void cleanup() {
|
||||
redisContext *c = blocking_context;
|
||||
redisReply *reply;
|
||||
|
||||
/* Make sure we're on DB 9 */
|
||||
reply = redisCommand(c,"SELECT 9");
|
||||
assert(reply != NULL); freeReplyObject(reply);
|
||||
reply = redisCommand(c,"FLUSHDB");
|
||||
assert(reply != NULL); freeReplyObject(reply);
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
// static long __test_callback_flags = 0;
|
||||
// static void __test_callback(redisContext *c, void *privdata) {
|
||||
// ((void)c);
|
||||
// /* Shift to detect execution order */
|
||||
// __test_callback_flags <<= 8;
|
||||
// __test_callback_flags |= (long)privdata;
|
||||
// }
|
||||
//
|
||||
// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
|
||||
// ((void)c);
|
||||
// /* Shift to detect execution order */
|
||||
// __test_callback_flags <<= 8;
|
||||
// __test_callback_flags |= (long)privdata;
|
||||
// if (reply) freeReplyObject(reply);
|
||||
// }
|
||||
//
|
||||
// static redisContext *__connect_nonblock() {
|
||||
// /* Reset callback flags */
|
||||
// __test_callback_flags = 0;
|
||||
// return redisConnectNonBlock("127.0.0.1", 6379, NULL);
|
||||
// }
|
||||
//
|
||||
// static void test_nonblocking_connection() {
|
||||
// redisContext *c;
|
||||
// int wdone = 0;
|
||||
//
|
||||
// test("Calls command callback when command is issued: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetCommandCallback(c,__test_callback,(void*)1);
|
||||
// redisCommand(c,"PING");
|
||||
// test_cond(__test_callback_flags == 1);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("Calls disconnect callback on redisDisconnect: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
|
||||
// redisDisconnect(c);
|
||||
// test_cond(__test_callback_flags == 2);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("Calls disconnect callback and free callback on redisFree: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
|
||||
// redisSetFreeCallback(c,__test_callback,(void*)4);
|
||||
// redisFree(c);
|
||||
// test_cond(__test_callback_flags == ((2 << 8) | 4));
|
||||
//
|
||||
// test("redisBufferWrite against empty write buffer: ");
|
||||
// c = __connect_nonblock();
|
||||
// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("redisBufferWrite against not yet connected fd: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisCommand(c,"PING");
|
||||
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
|
||||
// strncmp(c->error,"write:",6) == 0);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("redisBufferWrite against closed fd: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisCommand(c,"PING");
|
||||
// redisDisconnect(c);
|
||||
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
|
||||
// strncmp(c->error,"write:",6) == 0);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("Process callbacks in the right sequence: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
|
||||
//
|
||||
// /* Write output buffer */
|
||||
// wdone = 0;
|
||||
// while(!wdone) {
|
||||
// usleep(500);
|
||||
// redisBufferWrite(c,&wdone);
|
||||
// }
|
||||
//
|
||||
// /* Read until at least one callback is executed (the 3 replies will
|
||||
// * arrive in a single packet, causing all callbacks to be executed in
|
||||
// * a single pass). */
|
||||
// while(__test_callback_flags == 0) {
|
||||
// assert(redisBufferRead(c) == REDIS_OK);
|
||||
// redisProcessCallbacks(c);
|
||||
// }
|
||||
// test_cond(__test_callback_flags == 0x010203);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("redisDisconnect executes pending callbacks with NULL reply: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetDisconnectCallback(c,__test_callback,(void*)1);
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
|
||||
// redisDisconnect(c);
|
||||
// test_cond(__test_callback_flags == 0x0201);
|
||||
// redisFree(c);
|
||||
// }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1) {
|
||||
if (strcmp(argv[1],"-s") == 0)
|
||||
use_unix = 1;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
test_format_commands();
|
||||
test_blocking_connection();
|
||||
test_reply_reader();
|
||||
// test_nonblocking_connection();
|
||||
test_throughput();
|
||||
cleanup();
|
||||
|
||||
if (fails == 0) {
|
||||
printf("ALL TESTS PASSED\n");
|
||||
} else {
|
||||
printf("*** %d TESTS FAILED ***\n", fails);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Vendored
+8
-43
@@ -27,49 +27,14 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_H__
|
||||
#define __REDIS_H__
|
||||
#ifndef __UTIL_H
|
||||
#define __UTIL_H
|
||||
#include <stdlib.h>
|
||||
|
||||
enum
|
||||
{
|
||||
REG_GS = 0,
|
||||
# define REG_GS REG_GS
|
||||
REG_FS,
|
||||
# define REG_FS REG_FS
|
||||
REG_ES,
|
||||
# define REG_ES REG_ES
|
||||
REG_DS,
|
||||
# define REG_DS REG_DS
|
||||
REG_EDI,
|
||||
# define REG_EDI REG_EDI
|
||||
REG_ESI,
|
||||
# define REG_ESI REG_ESI
|
||||
REG_EBP,
|
||||
# define REG_EBP REG_EBP
|
||||
REG_ESP,
|
||||
# define REG_ESP REG_ESP
|
||||
REG_EBX,
|
||||
# define REG_EBX REG_EBX
|
||||
REG_EDX,
|
||||
# define REG_EDX REG_EDX
|
||||
REG_ECX,
|
||||
# define REG_ECX REG_ECX
|
||||
REG_EAX,
|
||||
# define REG_EAX REG_EAX
|
||||
REG_TRAPNO,
|
||||
# define REG_TRAPNO REG_TRAPNO
|
||||
REG_ERR,
|
||||
# define REG_ERR REG_ERR
|
||||
REG_EIP,
|
||||
# define REG_EIP REG_EIP
|
||||
REG_CS,
|
||||
# define REG_CS REG_CS
|
||||
REG_EFL,
|
||||
# define REG_EFL REG_EFL
|
||||
REG_UESP,
|
||||
# define REG_UESP REG_UESP
|
||||
REG_SS
|
||||
# define REG_SS REG_SS
|
||||
};
|
||||
/* Abort on out of memory */
|
||||
static void redisOOM(void) {
|
||||
fprintf(stderr,"Out of memory in hiredis");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
linenoise_example*
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
linenoise_example: linenoise.h linenoise.c
|
||||
|
||||
linenoise_example: linenoise.o example.o
|
||||
$(CC) $(ARCH) -Wall -W -Os -g -o linenoise_example linenoise.o example.o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(ARCH) -c -Wall -W -Os -g $<
|
||||
|
||||
clean:
|
||||
rm -f linenoise_example *.o
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
# Linenoise
|
||||
|
||||
A minimal, zero-config, BSD licensed, readline replacement.
|
||||
|
||||
News: linenoise is now part of [Android](http://android.git.kernel.org/?p=platform/system/core.git;a=tree;f=liblinenoise;h=56450eaed7f783760e5e6a5993ef75cde2e29dea;hb=HEAD Android)!
|
||||
|
||||
## Can a line editing library be 20k lines of code?
|
||||
|
||||
Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?
|
||||
|
||||
So what usually happens is either:
|
||||
|
||||
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Readl world example of this problem: Tclsh).
|
||||
* Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance).
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to linenoise if not.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
Apparently almost every terminal you can happen to use today has some kind of support for VT100 alike escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it.
|
||||
|
||||
Since it's so young I guess there are a few bugs, or the lib may not compile or work with some operating system, but it's a matter of a few weeks and eventually we'll get it right, and there will be no excuses for not shipping command line tools without built-in line editing support.
|
||||
|
||||
The library is currently less than 400 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
|
||||
|
||||
## Tested with...
|
||||
|
||||
* Linux text only console ($TERM = linux)
|
||||
* Linux KDE terminal application ($TERM = xterm)
|
||||
* Linux xterm ($TERM = xterm)
|
||||
* Mac OS X iTerm ($TERM = xterm)
|
||||
* Mac OS X default Terminal.app ($TERM = xterm)
|
||||
* OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
|
||||
* IBM AIX 6.1
|
||||
* FreeBSD xterm ($TERM = xterm)
|
||||
|
||||
Please test it everywhere you can and report back!
|
||||
|
||||
## Let's push this forward!
|
||||
|
||||
Please fork it and add something interesting and send me a pull request. What's especially interesting are fixes, new key bindings, completion.
|
||||
|
||||
Send feedbacks to antirez at gmail
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
|
||||
void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
if (buf[0] == 'h') {
|
||||
linenoiseAddCompletion(lc,"hello");
|
||||
linenoiseAddCompletion(lc,"hello there");
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *line;
|
||||
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
printf("echo: '%s'\n", line);
|
||||
linenoiseHistoryAdd(line);
|
||||
linenoiseHistorySave("history.txt"); /* Save every new entry */
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+150
-7
@@ -69,7 +69,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
@@ -80,14 +79,17 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
#define LINENOISE_MAX_LINE 4096
|
||||
static char *unsupported_term[] = {"dumb","cons25",NULL};
|
||||
static linenoiseCompletionCallback *completionCallback = NULL;
|
||||
|
||||
static struct termios orig_termios; /* in order to restore at exit */
|
||||
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
|
||||
static int atexit_registered = 0; /* register atexit just 1 time */
|
||||
static int history_max_len = 100;
|
||||
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
|
||||
static int history_len = 0;
|
||||
char **history = NULL;
|
||||
|
||||
@@ -195,6 +197,74 @@ static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_
|
||||
if (write(fd,seq,strlen(seq)) == -1) return;
|
||||
}
|
||||
|
||||
static void beep() {
|
||||
fprintf(stderr, "\x7");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static void freeCompletions(linenoiseCompletions *lc) {
|
||||
size_t i;
|
||||
for (i = 0; i < lc->len; i++)
|
||||
free(lc->cvec[i]);
|
||||
if (lc->cvec != NULL)
|
||||
free(lc->cvec);
|
||||
}
|
||||
|
||||
static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) {
|
||||
linenoiseCompletions lc = { 0, NULL };
|
||||
int nread, nwritten;
|
||||
char c = 0;
|
||||
|
||||
completionCallback(buf,&lc);
|
||||
if (lc.len == 0) {
|
||||
beep();
|
||||
} else {
|
||||
size_t stop = 0, i = 0;
|
||||
size_t clen;
|
||||
|
||||
while(!stop) {
|
||||
/* Show completion or original buffer */
|
||||
if (i < lc.len) {
|
||||
clen = strlen(lc.cvec[i]);
|
||||
refreshLine(fd,prompt,lc.cvec[i],clen,clen,cols);
|
||||
} else {
|
||||
refreshLine(fd,prompt,buf,*len,*pos,cols);
|
||||
}
|
||||
|
||||
nread = read(fd,&c,1);
|
||||
if (nread <= 0) {
|
||||
freeCompletions(&lc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 9: /* tab */
|
||||
i = (i+1) % (lc.len+1);
|
||||
if (i == lc.len) beep();
|
||||
break;
|
||||
case 27: /* escape */
|
||||
/* Re-show original buffer */
|
||||
if (i < lc.len) {
|
||||
refreshLine(fd,prompt,buf,*len,*pos,cols);
|
||||
}
|
||||
stop = 1;
|
||||
break;
|
||||
default:
|
||||
/* Update buffer and return */
|
||||
if (i < lc.len) {
|
||||
nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]);
|
||||
*len = *pos = nwritten;
|
||||
}
|
||||
stop = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeCompletions(&lc);
|
||||
return c; /* Return last read character */
|
||||
}
|
||||
|
||||
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
|
||||
size_t plen = strlen(prompt);
|
||||
size_t pos = 0;
|
||||
@@ -213,17 +283,28 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
|
||||
while(1) {
|
||||
char c;
|
||||
int nread;
|
||||
char seq[2];
|
||||
char seq[2], seq2[2];
|
||||
|
||||
nread = read(fd,&c,1);
|
||||
if (nread <= 0) return len;
|
||||
|
||||
/* Only autocomplete when the callback is set. It returns < 0 when
|
||||
* there was an error reading from fd. Otherwise it will return the
|
||||
* character that should be handled next. */
|
||||
if (c == 9 && completionCallback != NULL) {
|
||||
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
|
||||
/* Return on errors */
|
||||
if (c < 0) return len;
|
||||
/* Read next character when 0 */
|
||||
if (c == 0) continue;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 13: /* enter */
|
||||
history_len--;
|
||||
return len;
|
||||
case 4: /* ctrl-d */
|
||||
history_len--;
|
||||
return (len == 0) ? -1 : (int)len;
|
||||
free(history[history_len]);
|
||||
return (len == 0 && c == 4) ? -1 : (int)len;
|
||||
case 3: /* ctrl-c */
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
@@ -295,6 +376,18 @@ up_down_arrow:
|
||||
len = pos = strlen(buf);
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
} else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
|
||||
/* extended escape */
|
||||
if (read(fd,seq2,2) == -1) break;
|
||||
if (seq[1] == 51 && seq2[0] == 126) {
|
||||
/* delete */
|
||||
if (len > 0 && pos < len) {
|
||||
memmove(buf+pos,buf+pos+1,len-pos-1);
|
||||
len--;
|
||||
buf[len] = '\0';
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -391,12 +484,25 @@ char *linenoise(const char *prompt) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Register a callback function to be called for tab-completion. */
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
|
||||
completionCallback = fn;
|
||||
}
|
||||
|
||||
void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) {
|
||||
size_t len = strlen(str);
|
||||
char *copy = malloc(len+1);
|
||||
memcpy(copy,str,len+1);
|
||||
lc->cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
|
||||
lc->cvec[lc->len++] = copy;
|
||||
}
|
||||
|
||||
/* Using a circular buffer is smarter, but a bit more complex to handle. */
|
||||
int linenoiseHistoryAdd(const char *line) {
|
||||
char *linecopy;
|
||||
|
||||
if (history_max_len == 0) return 0;
|
||||
if (history == 0) {
|
||||
if (history == NULL) {
|
||||
history = malloc(sizeof(char*)*history_max_len);
|
||||
if (history == NULL) return 0;
|
||||
memset(history,0,(sizeof(char*)*history_max_len));
|
||||
@@ -404,6 +510,7 @@ int linenoiseHistoryAdd(const char *line) {
|
||||
linecopy = strdup(line);
|
||||
if (!linecopy) return 0;
|
||||
if (history_len == history_max_len) {
|
||||
free(history[0]);
|
||||
memmove(history,history+1,sizeof(char*)*(history_max_len-1));
|
||||
history_len--;
|
||||
}
|
||||
@@ -431,3 +538,39 @@ int linenoiseHistorySetMaxLen(int len) {
|
||||
history_len = history_max_len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Save the history in the specified file. On success 0 is returned
|
||||
* otherwise -1 is returned. */
|
||||
int linenoiseHistorySave(char *filename) {
|
||||
FILE *fp = fopen(filename,"w");
|
||||
int j;
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
for (j = 0; j < history_len; j++)
|
||||
fprintf(fp,"%s\n",history[j]);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Load the history from the specified file. If the file does not exist
|
||||
* zero is returned and no operation is performed.
|
||||
*
|
||||
* If the file exists and the operation succeeded 0 is returned, otherwise
|
||||
* on error -1 is returned. */
|
||||
int linenoiseHistoryLoad(char *filename) {
|
||||
FILE *fp = fopen(filename,"r");
|
||||
char buf[LINENOISE_MAX_LINE];
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
|
||||
char *p;
|
||||
|
||||
p = strchr(buf,'\r');
|
||||
if (!p) p = strchr(buf,'\n');
|
||||
if (p) *p = '\0';
|
||||
linenoiseHistoryAdd(buf);
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
+12
-1
@@ -34,8 +34,19 @@
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
} linenoiseCompletions;
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(char *line);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>BlpopCommand: Contents</b><br> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#Return value">Return value</a>
|
||||
<b>BlpopCommand: Contents</b><br> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">BlpopCommand</h1>
|
||||
@@ -35,8 +35,10 @@
|
||||
<h2><a name="Blocking behavior">Blocking behavior</a></h2><blockquote>If none of the specified keys exist or contain non empty lists, BLPOPblocks until some other client performs a <a href="RpushCommand.html">LPUSH</a> oran <a href="RpushCommand.html">RPUSH</a> operation against one of the lists.</blockquote>
|
||||
<blockquote>Once new data is present on one of the lists, the client finally returnswith the name of the key unblocking it and the popped value.</blockquote>
|
||||
<blockquote>When blocking, if a non-zero timeout is specified, the client will unblockreturning a nil special value if the specified amount of seconds passedwithout a push operation against at least one of the specified keys.</blockquote>
|
||||
<blockquote>A timeout of zero means instead to block forever.</blockquote>
|
||||
<blockquote>The timeout argument is interpreted as an integer value. A timeout of zero means instead to block forever.</blockquote>
|
||||
<h2><a name="Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a></h2><blockquote>Multiple clients can block for the same key. They are put intoa queue, so the first to be served will be the one that started to waitearlier, in a first-blpopping first-served fashion.</blockquote>
|
||||
<h2><a name="blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a></h2><blockquote>BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis transaction).</blockquote>
|
||||
<blockquote>The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil reply, exactly what happens when the timeout is reached. If you like science fiction, think at it like if inside MULTI/EXEC the time will flow at infinite speed :) </blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><blockquote>BLPOP returns a two-elements array via a multi bulk reply in order to returnboth the unblocking key and the popped value.</blockquote>
|
||||
<blockquote>When a non-zero timeout is specified, and the BLPOP operation timed out,the return value is a nil multi bulk reply. Most client values will returnfalse or nil accordingly to the programming language used.</blockquote>
|
||||
<a href="ReplyTypes.html">Multi bulk reply</a>
|
||||
|
||||
+13
-13
File diff suppressed because one or more lines are too long
+1
-2
@@ -27,12 +27,11 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="DEL _key1_ _key2_ ... _keyN_">DEL _key1_ _key2_ ... _keyN_</a></h1>
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The commnad returns the number ofkeys removed.</blockquote>
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The command returns the number ofkeys removed.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
an integer greater than 0 if one or more keys were removed
|
||||
0 if none of the specified key existed
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+21
-10
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a>
|
||||
<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#PERSIST _key_">PERSIST _key_</a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a><br> <a href="#FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ExpireCommand</h1>
|
||||
@@ -28,12 +28,15 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a></h1>
|
||||
<h1><a name="EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a></h1> 1.1)=
|
||||
<h1><a name="PERSIST _key_">PERSIST _key_</a></h1>
|
||||
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
|
||||
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingthe dataset and stopping the server does not stop the flow of time as Redisregisters on disk when the key will no longer be available as Unix time, andnot the remaining seconds.</blockquote>
|
||||
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingexpires and stopping the server does not stop the flow of time as Redisstores on disk the time when the key will no longer be available as Unixtime, and not the remaining seconds.</blockquote>
|
||||
<blockquote>EXPIREAT works exctly like EXPIRE but instead to get the number of secondsrepresenting the Time To Live of the key as a second argument (that is arelative way of specifing the TTL), it takes an absolute one in the form ofa UNIX timestamp (Number of seconds elapsed since 1 Gen 1970).</blockquote>
|
||||
<blockquote>EXPIREAT was introduced in order to implement [Persistence append only saving mode] so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
|
||||
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, the INCR commandor any other command that modify the value stored at key the timeout isremoved from the key and the key becomes non volatile.</blockquote>
|
||||
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
|
||||
<blockquote>EXPIREAT was introduced in order to implement <a href="AppendOnlyFileHowto.html">the Append Only File persistence mode</a>so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
|
||||
<blockquote>Since Redis 2.1.3 you can update the value of the timeout of a key alreadyhaving an expire set. It is also possible to undo the expire at allturning the key into a normal key using the PERSIST command.</blockquote>
|
||||
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, or when a keyis destroied via DEL, the timeout is removed from the key.</blockquote>
|
||||
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>IMPORTANT: Since Redis 2.1.3 or greater, there are no restrictions aboutthe operations you can perform against volatile keys, however older versionsof Redis, including the current stable version 2.0.0, has the followinglimitations:</blockquote>
|
||||
<blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
|
||||
<pre class="codeblock python" name="code">
|
||||
% ./redis-cli lpush mylist foobar /Users/antirez/hack/redis
|
||||
OK
|
||||
@@ -45,8 +48,13 @@ OK
|
||||
OK
|
||||
% ./redis-cli lrange mylist 0 -1 /Users/antirez/hack/redis
|
||||
1. newelement
|
||||
</pre><blockquote>What happened here is that lpush against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timeof the commands to. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
|
||||
<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
|
||||
</pre><blockquote>What happened here is that LPUSH against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timethe commands were issued. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
|
||||
<h2><a name="Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a></h2>Even when the volatile key is not modified as part of a write operation, if it is
|
||||
read in a composite write operation (such as SINTERSTORE) it will be cleared at the
|
||||
start of the operation. This is done to avoid concurrency issues in replication.
|
||||
Imagine a key that is about to expire and the composite operation is run against it.
|
||||
On a slave node, this key might already be expired, which leaves you with a
|
||||
desync in your dataset.<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
|
||||
<h2><a name="Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a></h2><blockquote>Redis does not constantly monitor keys that are going to be expired.Keys are expired simply when some client tries to access a key, andthe key is found to be timed out.</blockquote>
|
||||
<blockquote>Of course this is not enough as there are expired keys that will neverbe accessed again. This keys should be expired anyway, so once everysecond Redis test a few keys at random among keys with an expire set.All the keys that are already expired are deleted from the keyspace. </blockquote>
|
||||
<h3><a name="Version 1.0">Version 1.0</a></h3><blockquote>Each time a fixed number of keys where tested (100 by default). So ifyou had a client setting keys with a very short expire faster than 100for second the memory continued to grow. When you stopped to insertnew keys the memory started to be freed, 100 keys every second in thebest conditions. Under a peak Redis continues to use more and more RAMeven if most keys are expired in each sweep.</blockquote>
|
||||
@@ -56,8 +64,10 @@ OK
|
||||
<blockquote>This means that at any given moment the maximum amount of keys alreadyexpired that are using memory is at max equal to max setting operations per second divided by 4.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python python" name="code">
|
||||
1: the timeout was set.
|
||||
0: the timeout was not set since the key already has an associated timeout, or the key does not exist.
|
||||
</pre><h2><a name="FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a></h2>
|
||||
0: the timeout was not set since the key already has an associated timeout
|
||||
(this may happen only in Redis versions < 2.1.3, Redis >= 2.1.3 will
|
||||
happily update the timeout), or the key does not exist.
|
||||
</pre><h2><a name="FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a></h2>
|
||||
Ok let's start with the problem:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
redis> set a 100
|
||||
@@ -76,7 +86,8 @@ EXPIRE a 5
|
||||
INCR a
|
||||
</pre>
|
||||
Imagine a Redis version that does not implement the "Delete keys with an expire set on write operation" semantic.
|
||||
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)
|
||||
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)<h2><a name="FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a></h2>Since Redis 2.1.3 there are no longer restrictions in the use you can do of write commands against volatile keys, still the replication and AOF file are guaranteed to be fully consistent.<br/><br/>In order to obtain a correct behavior without sacrificing consistency now when a key expires, a DEL operation is synthesized in both the AOF file and against all the attached slaves. This way the expiration process is centralized in the master instance, and there is no longer a chance of consistency errors.<br/><br/>However while the slaves while connected to a master will not expire keys independently, they'll still take the full state of the expires existing in the dataset, so when a slave is elected to a master it will be able to expire the keys independently, fully acting as a master.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li></ul>
|
||||
== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="ExpireCommand.html">PERSIST</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li><li> <a href="MultiExecCommand.html">Redis Transactions</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
|
||||
== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HmgetCommand.html">HMGET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<blockquote>the slow commands that may ruin the DB performance if not usedwith care*.</blockquote>
|
||||
<blockquote>In other words this command is intended only for debugging and *special* operations like creating a script to change the DB schema. Don't use it in your normal code. Use Redis <a href="Sets.html">Sets</a> in order to group together a subset of objects.</blockquote>
|
||||
Glob style patterns examples:
|
||||
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically a string in the form of space separated list of keys. Note that most client libraries will return an Array of keys and not a single string with space separated keys (that is, split by " " is performed in the client library usually).</b></blockquote>
|
||||
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2>
|
||||
<a href="ReplyTypes.html">Multi bulk reply</a></b></blockquote>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LINDEX _key_ _index_">LINDEX _key_ _index_</a></h1>
|
||||
<i>Time complexity: O(n) (with n being the length of the list)</i><blockquote>Return the specified element of the list stored at the specifiedkey. 0 is the first element, 1 the second and so on. Negative indexesare supported, for example -1 is the last element, -2 the penultimateand so on.</blockquote>
|
||||
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range an empty string is returned.</blockquote>
|
||||
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range a 'nil' reply is returned.</blockquote>
|
||||
<blockquote>Note that even if the average time complexity is O(n) asking forthe first or the last element of the list is O(1).</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically the requested element.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+88
-17
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>MultiExecCommand: Contents</b><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Return value">Return value</a>
|
||||
<b>MultiExecCommand: Contents</b><br> <a href="#WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a><br> <a href="#UNWATCH">UNWATCH</a><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a><br> <a href="#WATCH explained">WATCH explained</a><br> <a href="#WATCH used to implement ZPOP">WATCH used to implement ZPOP</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">MultiExecCommand</h1>
|
||||
@@ -26,15 +26,21 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="MULTI">MULTI</a></h1>
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a></h1> 2.1.0)=
|
||||
<h1><a name="UNWATCH">UNWATCH</a></h1>
|
||||
<h1><a name="MULTI">MULTI</a></h1>
|
||||
<h1><a name="COMMAND_1 ...">COMMAND_1 ...</a></h1>
|
||||
<h1><a name="COMMAND_2 ...">COMMAND_2 ...</a></h1>
|
||||
<h1><a name="COMMAND_N ...">COMMAND_N ...</a></h1>
|
||||
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1><blockquote>MULTI, EXEC and DISCARD commands are the fundation of Redis Transactions.A Redis Transaction allows to execute a group of Redis commands in a singlestep, with two important guarantees:</blockquote>
|
||||
<ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
|
||||
<h2><a name="Usage">Usage</a></h2><blockquote>A Redis transaction is entered using the MULTI command. The command alwaysreplies with OK. At this point the user can issue multiple commands. Insteadto execute this commands Redis will "queue" them. All the commands areexecuted once EXEC is called.</blockquote>
|
||||
<blockquote>Calling DISCARD instead will flush the transaction queue and will exitthe transaction.</blockquote>
|
||||
<blockquote>The following is an example using the Ruby client:</blockquote><pre class="codeblock python" name="code">
|
||||
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the fundation of Redis Transactions.
|
||||
A Redis Transaction allows the execution of a group of Redis commands in a single
|
||||
step, with two important guarantees:<br/><br/><ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
|
||||
Since Redis 2.1.0, it's also possible to add a further guarantee to the above two, in the form of optimistic locking of a set of keys in a way very similar to a CAS (check and set) operation. This is documented later in this manual page.<h2><a name="Usage">Usage</a></h2>A Redis transaction is entered using the MULTI command. The command always
|
||||
replies with OK. At this point the user can issue multiple commands. Instead
|
||||
to execute this commands Redis will "queue" them. All the commands are
|
||||
executed once EXEC is called.<br/><br/>Calling DISCARD instead will flush the transaction queue and will exit
|
||||
the transaction.<br/><br/>The following is an example using the Ruby client:
|
||||
<pre class="codeblock python" name="code">
|
||||
?> r.multi
|
||||
=> "OK"
|
||||
>> r.incr "foo"
|
||||
@@ -46,9 +52,14 @@
|
||||
>> r.exec
|
||||
=> [1, 1, 2]
|
||||
</pre>
|
||||
<blockquote>As it is possible to see from the session above, MULTI returns an "array" ofreplies, where every element is the reply of a single command in thetransaction, in the same order the commands were queued.</blockquote>
|
||||
<blockquote>When a Redis connection is in the context of a MULTI request, all the commandswill reply with a simple string "QUEUED" if they are correct from thepoint of view of the syntax and arity (number of arguments) of the commaand.Some command is still allowed to fail during execution time.</blockquote>
|
||||
<blockquote>This is more clear if at protocol level: in the following example one commandwill fail when executed even if the syntax is right:</blockquote><pre class="codeblock python python" name="code">
|
||||
As it is possible to see from the session above, MULTI returns an "array" of
|
||||
replies, where every element is the reply of a single command in the
|
||||
transaction, in the same order the commands were queued.<br/><br/>When a Redis connection is in the context of a MULTI request, all the commands
|
||||
will reply with a simple string "QUEUED" if they are correct from the
|
||||
point of view of the syntax and arity (number of arguments) of the commaand.
|
||||
Some command is still allowed to fail during execution time.<br/><br/>This is more clear if at protocol level: in the following example one command
|
||||
will fail when executed even if the syntax is right:
|
||||
<pre class="codeblock python python" name="code">
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
@@ -64,16 +75,21 @@ EXEC
|
||||
+OK
|
||||
-ERR Operation against a key holding the wrong kind of value
|
||||
</pre>
|
||||
<blockquote>MULTI returned a two elements bulk reply in witch one of this is a +OKcode and one is a -ERR reply. It's up to the client lib to find a sensibleway to provide the error to the user.</blockquote>
|
||||
<blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
|
||||
<blockquote>Another example, again using the write protocol with telnet, shows howsyntax errors are reported ASAP instead:</blockquote><pre class="codeblock python python python" name="code">
|
||||
MULTI returned a two elements bulk reply in witch one of this is a +OK
|
||||
code and one is a -ERR reply. It's up to the client lib to find a sensible
|
||||
way to provide the error to the user.<br/><br/><blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
|
||||
Another example, again using the write protocol with telnet, shows how
|
||||
syntax errors are reported ASAP instead:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
MULTI
|
||||
+OK
|
||||
INCR a b c
|
||||
-ERR wrong number of arguments for 'incr' command
|
||||
</pre>
|
||||
<blockquote>This time due to the syntax error the "bad" INCR command is not queuedat all.</blockquote>
|
||||
<h2><a name="The DISCARD command">The DISCARD command</a></h2><blockquote>DISCARD can be used in order to abort a transaction. No command will beexecuted, and the state of the client is again the normal one, outsideof a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
|
||||
This time due to the syntax error the "bad" INCR command is not queued
|
||||
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be
|
||||
executed, and the state of the client is again the normal one, outside
|
||||
<blockquote>of a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
|
||||
?> r.set("foo",1)
|
||||
=> true
|
||||
>> r.multi
|
||||
@@ -84,9 +100,64 @@ INCR a b c
|
||||
=> "OK"
|
||||
>> r.get("foo")
|
||||
=> "1"
|
||||
</pre><h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
|
||||
</pre><h2><a name="Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a></h2>WATCH is used in order to provide a CAS (Check and Set) behavior to
|
||||
Redis Transactions.<br/><br/>WATCHed keys are monitored in order to detect changes against this keys.
|
||||
If at least a watched key will be modified before the EXEC call, the
|
||||
whole transaction will abort, and EXEC will return a nil object
|
||||
(A Null Multi Bulk reply) to notify that the transaction failed.<br/><br/>For example imagine we have the need to atomically increment the value
|
||||
of a key by 1 (I know we have INCR, let's suppose we don't have it).<br/><br/>The first try may be the following:
|
||||
<pre class="codeblock python python python python python" name="code">
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
SET mykey $val
|
||||
</pre>
|
||||
This will work reliably only if we have a single client performing the operation in a given time.
|
||||
If multiple clients will try to increment the key about at the same time
|
||||
there will be a race condition. For instance client A and B will read the
|
||||
old value, for instance, 10. The value will be incremented to 11 by both
|
||||
the clients, and finally SET as the value of the key. So the final value
|
||||
will be "11" instead of "12".<br/><br/>Thanks to WATCH we are able to model the problem very well:
|
||||
<pre class="codeblock python python python python python python" name="code">
|
||||
WATCH mykey
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
MULTI
|
||||
SET mykey $val
|
||||
EXEC
|
||||
</pre>
|
||||
Using the above code, if there are race conditions and another client
|
||||
modified the result of <i>val</i> in the time between our call to WATCH and
|
||||
our call to EXEC, the transaction will fail.<br/><br/>We'll have just to re-iterate the operation hoping this time we'll not get
|
||||
a new race. This form of locking is called <b>optimistic locking</b> and is
|
||||
a very powerful form of locking as in many problems there are multiple
|
||||
clients accessing a much bigger number of keys, so it's very unlikely that
|
||||
there are collisions: usually operations don't need to be performed
|
||||
multiple times.<h2><a name="WATCH explained">WATCH explained</a></h2>So what is WATCH really about? It is a command that will make the EXEC
|
||||
conditional: we are asking Redis to perform the transaction only if no
|
||||
other client modified any of the WATCHed keys. Otherwise the transaction is not
|
||||
entered at all. (Note that if you WATCH a volatile key and Redis expires the key after you WATCHed it, EXEC will still work. <a href="http://code.google.com/p/redis/issues/detail?id=270" target="_blank">More</a>.)<br/><br/>WATCH can be called multiple times. Simply all the WATCH calls will
|
||||
have the effects to watch for changes starting from the call, up to the
|
||||
moment EXEC is called.<br/><br/>When EXEC is called, either if it will fail or succeed, all keys are
|
||||
UNWATCHed. Also when a client connection is closed, everything gets
|
||||
UNWATCHed.<br/><br/>It is also possible to use the UNWATCH command (without arguments) in order
|
||||
to flush all the watched keys. Sometimes this is useful as we
|
||||
optimistically lock a few keys, since possibly we need to perform a transaction
|
||||
to alter those keys, but after reading the current content of the keys
|
||||
we don't want to proceed. When this happens we just call UNWATCH so that
|
||||
the connection can already be used freely for new transactions.<h2><a name="WATCH used to implement ZPOP">WATCH used to implement ZPOP</a></h2>A good example to illustrate how WATCH can be used to create new atomic
|
||||
operations otherwise not supported by Redis is to implement ZPOP, that is
|
||||
a command that pops the element with the lower score from a sorted set
|
||||
in an atomic way. This is the simplest implementation:
|
||||
<pre class="codeblock python python python python python python python" name="code">
|
||||
WATCH zset
|
||||
ele = ZRANGE zset 0 0
|
||||
MULTI
|
||||
ZREM zset ele
|
||||
EXEC
|
||||
</pre>
|
||||
If EXEC fails (returns a nil value) we just re-iterate the operation.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
|
||||
</pre>If a MULTI/EXEC transaction is aborted because of WATCH detected modified keys, a <a href="ReplyTypes.html">Null Multi Bulk reply</a> is returned.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+40
-9
@@ -26,11 +26,42 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Introduction =<br/><br/>Redis is a database. To be specific, Redis is a database implementing a dictionary, where every key is associated with a value. For example I can set the key "surname_1992" to the string "Smith".
|
||||
What makes Redis different from many other key-value stores, is that every single value has a type. The following types are supported:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Set</a> (since version 1.1)</li></ul>
|
||||
The type of a value determines what operations (called commands) are available for the value itself.
|
||||
For example you can append elements to a list stored at the key "mylist" using the LPUSH or RPUSH command in O(1). Later you'll be able to get a range of elements with LRANGE or trim the list with LTRIM. Sets are very flexible too, it is possible to add and remove elements from Sets (unsorted collections of strings), and then ask for server-side intersection, union, difference of Sets. Each command is performed through server-side atomic operations.
|
||||
Please refer to the <a href="CommandReference.html">Command Reference</a> to see the full list of operations associated to these data types.<br/><br/>In other words, you can look at Redis as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving her from the responsibility to implement concrete data structures and algorithms. Indeed both algorithms and data structures in Redis are properly choosed in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kind of persistence supported: the first one is called snapshotting. In this mode Redis, from time to time, writes a dump on disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
|
||||
= Introduction =<br/><br/>Redis is an extremely fast and powerful key-value store database and server implemented in ANSI C. Redis offers many different ways to do one straightforward thing: store a value ("antirez") to a key ("redis"). While the format of keys must always be simple strings, the power is with the values, which support the following data types:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Sets (zsets)</a></li><li> <a href="Hashes.html">Hashes</a></li></ul>
|
||||
Each value type has an associated list of commands which can operate on them, and the <a href="CommandReference.html">The Redis Command Reference</a> contains an up to date list of these commands, organized primarily by data type. The Redis source also includes a <a href="RedisCLI.html">Redis command line interface</a> which allows you to interact directly with the server, and is the means by which this introduction will provide examples. Once you walk through the <a href="QuickStart.html">Redis Quick Start Guide</a> to get your instance of Redis running, you can follow along. <br/><br/>One of the most powerful aspects of Redis is the wide range of commands which are optimized to work with specific data value types and executed as atomic server-side operations. The <a href="Lists.html">List</a> type is a great example - Redis implements O(1) operations such as <a href="RpushCommand.html">LPUSH</a> or <a href="RpushCommand.html">RPUSH</a>, which have accompanying <a href="LpopCommand.html">LPOP</a> and <a href="LpopCommand.html">RPOP</a> methods:<br/><br/><pre class="codeblock python" name="code">
|
||||
redis> lpush programming_languages C
|
||||
OK
|
||||
redis> lpush programming_languages Ruby
|
||||
OK
|
||||
redis> rpush programming_languages Python
|
||||
OK
|
||||
redis> rpop programming_languages
|
||||
Python
|
||||
redis> lpop programming_languages
|
||||
Ruby
|
||||
</pre>More complex operations are available for each data type as well. Continuing with lists, you can get a range of elements with <a href="LrangeCommand.html">LRANGE</a> (O(start+n)) or trim the list with <a href="LtrimCommand.html">LTRIM</a> (O(n)):<br/><br/><pre class="codeblock python python" name="code">
|
||||
redis> lpush cities NYC
|
||||
OK
|
||||
redis> lpush cities SF
|
||||
OK
|
||||
redis> lpush cities Tokyo
|
||||
OK
|
||||
redis> lpush cities London
|
||||
OK
|
||||
redis> lpush cities Paris
|
||||
OK
|
||||
redis> lrange cities 0 2
|
||||
1. Paris
|
||||
2. London
|
||||
3. Tokyo
|
||||
redis> ltrim cities 0 1
|
||||
OK
|
||||
redis> lpop cities
|
||||
Paris
|
||||
redis> lpop cities
|
||||
London
|
||||
redis> lpop cities
|
||||
(nil)
|
||||
</pre>You can also add and remove elements from a set, and perform intersections, unions, and differences. <br/><br/>Redis can also be looked at as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving them from the responsibility of implementing concrete data structures and algorithms -- indeed both algorithms and data structures in Redis are properly chosen in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kinds of persistence supported: the first one is called snapshotting. In this mode Redis periodically writes to disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
|
||||
the protocol specification, and so on. This kind of documentation is useful
|
||||
but... if you are new to Redis it is also BORING! The Redis protocol is designed
|
||||
so that is both pretty efficient to be parsed by computers, but simple enough
|
||||
@@ -40,7 +71,7 @@ feeling about it, and how it works.<br/><br/>To start just compile redis with 'm
|
||||
The server will start and log stuff on the standard output, if you want
|
||||
it to log more edit redis.conf, set the loglevel to debug, and restart it.<br/><br/>You can specify a configuration file as unique parameter:<br/><br/><blockquote>./redis-server /etc/redis.conf</blockquote>
|
||||
This is NOT required. The server will start even without a configuration file
|
||||
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python" name="code">
|
||||
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
$ telnet localhost 6379
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
@@ -59,17 +90,17 @@ the point of view of both the server and client but allows us to play with
|
||||
Redis with the telnet command easily.<br/><br/>The last line of the chat between server and client is "+OK". This means
|
||||
our key was added without problems. Actually SET can never fail but
|
||||
the "+OK" sent lets us know that the server received everything and
|
||||
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python" name="code">
|
||||
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
GET foo
|
||||
$3
|
||||
bar
|
||||
</pre>Ok that's very similar to 'set', just the other way around. We sent "get foo",
|
||||
the server replied with a first line that is just the $ character follwed by
|
||||
the number of bytes the value stored at key contained, followed by the actual
|
||||
bytes. Again "\r\n" are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python" name="code">
|
||||
bytes. Again "\r\n" are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
GET blabla
|
||||
$-1
|
||||
</pre>When the key does not exist instead of the length, just the "$-1" string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
</pre>When the key does not exist instead of the length, just the "$-1" string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
EXISTS nokey
|
||||
:0
|
||||
EXISTS foo
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNION / ZINTER</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
== Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZrangebyscoreCommand.html">ZCOUNT</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNIONSTORE</a></li><li> <a href="ZunionstoreCommand.html">ZINTERSTORE</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -30,8 +30,8 @@
|
||||
<i>Time complexity O(log(N)) with N being the number of elements in the sorted set</i><blockquote>If <i>member</i> already exists in the sorted set adds the <i>increment</i> to its scoreand updates the position of the element in the sorted set accordingly.If <i>member</i> does not already exist in the sorted set it is added with_increment_ as score (that is, like if the previous score was virtually zero).If <i>key</i> does not exist a new sorted set with the specified_member_ as sole member is crated. If the key exists but does not hold asorted set value an error is returned.</blockquote>
|
||||
<blockquote>The score value can be the string representation of a double precision floatingpoint number. It's possible to provide a negative value to perform a decrement.</blockquote>
|
||||
<blockquote>For an introduction to sorted sets check the <a href="IntroductionToRedisDataTypes.html">Introduction to Redis data types</a> page.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
The score of the member after the increment is performed.
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><pre class="codeblock python" name="code">
|
||||
The new score (a double precision floating point number) represented as string.
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ZrangebyscoreCommand: Contents</b><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a><br> <a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
<b>ZrangebyscoreCommand: Contents</b><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a><br> <a href="#ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a><br> <a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ZrangebyscoreCommand</h1>
|
||||
@@ -28,9 +28,11 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="SortedSetCommandsSidebar.html">SortedSetCommandsSidebar</a><h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a></h1> 1.1) =
|
||||
<h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a></h1> 1.3.4) =
|
||||
<h1><a name="ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a></h1>
|
||||
<i>Time complexity: O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of elements returned by the command, so if M is constant (for instance you always ask for the first ten elements with LIMIT) you can consider it O(log(N))</i><blockquote>Return the all the elements in the sorted set at key with a score between_min_ and <i>max</i> (including elements with score equal to min or max).</blockquote>
|
||||
<blockquote>The elements having the same score are returned sorted lexicographically asASCII strings (this follows from a property of Redis sorted sets and does notinvolve further computation).</blockquote>
|
||||
<blockquote>Using the optional LIMIT it's possible to get only a range of the matchingelements in an SQL-alike way. Note that if <i>offset</i> is large the commandsneeds to traverse the list for <i>offset</i> elements and this adds up to theO(M) figure.</blockquote><h2><a name="Exclusive intervals and infinity">Exclusive intervals and infinity</a></h2>
|
||||
<blockquote>Using the optional LIMIT it's possible to get only a range of the matchingelements in an SQL-alike way. Note that if <i>offset</i> is large the commandsneeds to traverse the list for <i>offset</i> elements and this adds up to theO(M) figure.</blockquote>
|
||||
<blockquote>The <b>ZCOUNT</b> command is similar to <b>ZRANGEBYSCORE</b> but instead of returningthe actual elements in the specified interval, it just returns the numberof matching elements.</blockquote><h2><a name="Exclusive intervals and infinity">Exclusive intervals and infinity</a></h2>
|
||||
<i>min</i> and <i>max</i> can be -inf and +inf, so that you are not required to know what's the greatest or smallest element in order to take, for instance, elements "up to a given value".<br/><br/>Also while the interval is for default closed (inclusive) it's possible to specify open intervals prefixing the score with a "(" character, so for instance:
|
||||
<pre class="codeblock python" name="code">
|
||||
ZRANGEBYSCORE zset (1.3 5
|
||||
@@ -40,7 +42,7 @@ Will return all the values with score <b>> 1.3 and <= 5</b>, while for ins
|
||||
ZRANGEBYSCORE zset (5 (10
|
||||
</pre>
|
||||
Will return all the values with score <b>> 5 and < 10</b> (5 and 10 excluded).
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of elements in the specified score range.
|
||||
<h2><a name="Return value">Return value</a></h2>ZRANGEBYSCORE returns a <a href="ReplyTypes.html">Multi bulk reply</a> specifically a list of elements in the specified score range.<br/><br/>ZCOUNT returns a <a href="ReplyTypes.html">Integer reply</a> specifically the number of elements matching the specified score range.
|
||||
<h2><a name="Examples">Examples</a></h2>
|
||||
<pre class="codeblock python python python" name="code">
|
||||
redis> zadd zset 1 foo
|
||||
@@ -56,6 +58,8 @@ redis> zrangebyscore zset -inf +inf
|
||||
2. "bar"
|
||||
3. "biz"
|
||||
4. "foz"
|
||||
redis> zcount zset 1 2
|
||||
(integer) 2
|
||||
redis> zrangebyscore zset 1 2
|
||||
1. "foo"
|
||||
2. "bar"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ZunionCommand: Contents</b><br> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
<b>ZunionCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ZunionCommand</h1>
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
|
||||
<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
|
||||
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
|
||||
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ZunionstoreCommand: Contents</b><br> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
<b>ZunionstoreCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ZunionstoreCommand</h1>
|
||||
@@ -27,8 +27,10 @@
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
|
||||
<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
|
||||
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
|
||||
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
|
||||
|
||||
+3
-3
@@ -26,12 +26,12 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tuturial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
|
||||
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python"><-></code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virutal Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
|
||||
= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
|
||||
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python"><-></code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
|
||||
<h1><a name="Hacking">Hacking</a></h1>
|
||||
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul>
|
||||
<ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
|
||||
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li></ul>
|
||||
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
-634
@@ -1,634 +0,0 @@
|
||||
/* Redis CLI (command line interface)
|
||||
*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "anet.h"
|
||||
#include "sds.h"
|
||||
#include "adlist.h"
|
||||
#include "zmalloc.h"
|
||||
#include "linenoise.h"
|
||||
|
||||
#define REDIS_CMD_INLINE 1
|
||||
#define REDIS_CMD_BULK 2
|
||||
#define REDIS_CMD_MULTIBULK 4
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
|
||||
static struct config {
|
||||
char *hostip;
|
||||
int hostport;
|
||||
long repeat;
|
||||
int dbnum;
|
||||
int interactive;
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int raw_output;
|
||||
char *auth;
|
||||
} config;
|
||||
|
||||
struct redisCommand {
|
||||
char *name;
|
||||
int arity;
|
||||
int flags;
|
||||
};
|
||||
|
||||
#define CMDFLAG_NONE 0
|
||||
#define CMDFLAG_RAWOUTPUT 1
|
||||
|
||||
static struct redisCommand cmdTable[] = {
|
||||
{"auth",2,CMDFLAG_NONE},
|
||||
{"get",2,CMDFLAG_NONE},
|
||||
{"set",3,CMDFLAG_NONE},
|
||||
{"setnx",3,CMDFLAG_NONE},
|
||||
{"setex",4,CMDFLAG_NONE},
|
||||
{"append",3,CMDFLAG_NONE},
|
||||
{"substr",4,CMDFLAG_NONE},
|
||||
{"del",-2,CMDFLAG_NONE},
|
||||
{"exists",2,CMDFLAG_NONE},
|
||||
{"incr",2,CMDFLAG_NONE},
|
||||
{"decr",2,CMDFLAG_NONE},
|
||||
{"rpush",3,CMDFLAG_NONE},
|
||||
{"lpush",3,CMDFLAG_NONE},
|
||||
{"rpop",2,CMDFLAG_NONE},
|
||||
{"lpop",2,CMDFLAG_NONE},
|
||||
{"brpop",-3,CMDFLAG_NONE},
|
||||
{"blpop",-3,CMDFLAG_NONE},
|
||||
{"llen",2,CMDFLAG_NONE},
|
||||
{"lindex",3,CMDFLAG_NONE},
|
||||
{"lset",4,CMDFLAG_NONE},
|
||||
{"lrange",4,CMDFLAG_NONE},
|
||||
{"ltrim",4,CMDFLAG_NONE},
|
||||
{"lrem",4,CMDFLAG_NONE},
|
||||
{"rpoplpush",3,CMDFLAG_NONE},
|
||||
{"sadd",3,CMDFLAG_NONE},
|
||||
{"srem",3,CMDFLAG_NONE},
|
||||
{"smove",4,CMDFLAG_NONE},
|
||||
{"sismember",3,CMDFLAG_NONE},
|
||||
{"scard",2,CMDFLAG_NONE},
|
||||
{"spop",2,CMDFLAG_NONE},
|
||||
{"srandmember",2,CMDFLAG_NONE},
|
||||
{"sinter",-2,CMDFLAG_NONE},
|
||||
{"sinterstore",-3,CMDFLAG_NONE},
|
||||
{"sunion",-2,CMDFLAG_NONE},
|
||||
{"sunionstore",-3,CMDFLAG_NONE},
|
||||
{"sdiff",-2,CMDFLAG_NONE},
|
||||
{"sdiffstore",-3,CMDFLAG_NONE},
|
||||
{"smembers",2,CMDFLAG_NONE},
|
||||
{"zadd",4,CMDFLAG_NONE},
|
||||
{"zincrby",4,CMDFLAG_NONE},
|
||||
{"zrem",3,CMDFLAG_NONE},
|
||||
{"zremrangebyscore",4,CMDFLAG_NONE},
|
||||
{"zunion",-4,CMDFLAG_NONE},
|
||||
{"zinter",-4,CMDFLAG_NONE},
|
||||
{"zrange",-4,CMDFLAG_NONE},
|
||||
{"zrank",3,CMDFLAG_NONE},
|
||||
{"zrevrank",3,CMDFLAG_NONE},
|
||||
{"zrangebyscore",-4,CMDFLAG_NONE},
|
||||
{"zcount",4,CMDFLAG_NONE},
|
||||
{"zrevrange",-4,CMDFLAG_NONE},
|
||||
{"zcard",2,CMDFLAG_NONE},
|
||||
{"zscore",3,CMDFLAG_NONE},
|
||||
{"incrby",3,CMDFLAG_NONE},
|
||||
{"decrby",3,CMDFLAG_NONE},
|
||||
{"getset",3,CMDFLAG_NONE},
|
||||
{"randomkey",1,CMDFLAG_NONE},
|
||||
{"select",2,CMDFLAG_NONE},
|
||||
{"move",3,CMDFLAG_NONE},
|
||||
{"rename",3,CMDFLAG_NONE},
|
||||
{"renamenx",3,CMDFLAG_NONE},
|
||||
{"keys",2,CMDFLAG_NONE},
|
||||
{"dbsize",1,CMDFLAG_NONE},
|
||||
{"ping",1,CMDFLAG_NONE},
|
||||
{"echo",2,CMDFLAG_NONE},
|
||||
{"save",1,CMDFLAG_NONE},
|
||||
{"bgsave",1,CMDFLAG_NONE},
|
||||
{"rewriteaof",1,CMDFLAG_NONE},
|
||||
{"bgrewriteaof",1,CMDFLAG_NONE},
|
||||
{"shutdown",1,CMDFLAG_NONE},
|
||||
{"lastsave",1,CMDFLAG_NONE},
|
||||
{"type",2,CMDFLAG_NONE},
|
||||
{"flushdb",1,CMDFLAG_NONE},
|
||||
{"flushall",1,CMDFLAG_NONE},
|
||||
{"sort",-2,CMDFLAG_NONE},
|
||||
{"info",1,CMDFLAG_RAWOUTPUT},
|
||||
{"mget",-2,CMDFLAG_NONE},
|
||||
{"expire",3,CMDFLAG_NONE},
|
||||
{"expireat",3,CMDFLAG_NONE},
|
||||
{"ttl",2,CMDFLAG_NONE},
|
||||
{"slaveof",3,CMDFLAG_NONE},
|
||||
{"debug",-2,CMDFLAG_NONE},
|
||||
{"mset",-3,CMDFLAG_NONE},
|
||||
{"msetnx",-3,CMDFLAG_NONE},
|
||||
{"monitor",1,CMDFLAG_NONE},
|
||||
{"multi",1,CMDFLAG_NONE},
|
||||
{"exec",1,CMDFLAG_NONE},
|
||||
{"discard",1,CMDFLAG_NONE},
|
||||
{"hset",4,CMDFLAG_NONE},
|
||||
{"hget",3,CMDFLAG_NONE},
|
||||
{"hmset",-4,CMDFLAG_NONE},
|
||||
{"hmget",-3,CMDFLAG_NONE},
|
||||
{"hincrby",4,CMDFLAG_NONE},
|
||||
{"hdel",3,CMDFLAG_NONE},
|
||||
{"hlen",2,CMDFLAG_NONE},
|
||||
{"hkeys",2,CMDFLAG_NONE},
|
||||
{"hvals",2,CMDFLAG_NONE},
|
||||
{"hgetall",2,CMDFLAG_NONE},
|
||||
{"hexists",3,CMDFLAG_NONE},
|
||||
{"config",-2,CMDFLAG_NONE},
|
||||
{"subscribe",-2,CMDFLAG_NONE},
|
||||
{"unsubscribe",-1,CMDFLAG_NONE},
|
||||
{"psubscribe",-2,CMDFLAG_NONE},
|
||||
{"punsubscribe",-1,CMDFLAG_NONE},
|
||||
{"publish",3,CMDFLAG_NONE},
|
||||
{NULL,0,CMDFLAG_NONE}
|
||||
};
|
||||
|
||||
static int cliReadReply(int fd);
|
||||
static void usage();
|
||||
|
||||
static struct redisCommand *lookupCommand(char *name) {
|
||||
int j = 0;
|
||||
while(cmdTable[j].name != NULL) {
|
||||
if (!strcasecmp(name,cmdTable[j].name)) return &cmdTable[j];
|
||||
j++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int cliConnect(void) {
|
||||
char err[ANET_ERR_LEN];
|
||||
static int fd = ANET_ERR;
|
||||
|
||||
if (fd == ANET_ERR) {
|
||||
fd = anetTcpConnect(err,config.hostip,config.hostport);
|
||||
if (fd == ANET_ERR) {
|
||||
fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
|
||||
return -1;
|
||||
}
|
||||
anetTcpNoDelay(NULL,fd);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static sds cliReadLine(int fd) {
|
||||
sds line = sdsempty();
|
||||
|
||||
while(1) {
|
||||
char c;
|
||||
ssize_t ret;
|
||||
|
||||
ret = read(fd,&c,1);
|
||||
if (ret == -1) {
|
||||
sdsfree(line);
|
||||
return NULL;
|
||||
} else if ((ret == 0) || (c == '\n')) {
|
||||
break;
|
||||
} else {
|
||||
line = sdscatlen(line,&c,1);
|
||||
}
|
||||
}
|
||||
return sdstrim(line,"\r\n");
|
||||
}
|
||||
|
||||
static int cliReadSingleLineReply(int fd, int quiet) {
|
||||
sds reply = cliReadLine(fd);
|
||||
|
||||
if (reply == NULL) return 1;
|
||||
if (!quiet)
|
||||
printf("%s\n", reply);
|
||||
sdsfree(reply);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void printStringRepr(char *s, int len) {
|
||||
printf("\"");
|
||||
while(len--) {
|
||||
switch(*s) {
|
||||
case '\\':
|
||||
case '"':
|
||||
printf("\\%c",*s);
|
||||
break;
|
||||
case '\n': printf("\\n"); break;
|
||||
case '\r': printf("\\r"); break;
|
||||
case '\t': printf("\\t"); break;
|
||||
case '\a': printf("\\a"); break;
|
||||
case '\b': printf("\\b"); break;
|
||||
default:
|
||||
if (isprint(*s))
|
||||
printf("%c",*s);
|
||||
else
|
||||
printf("\\x%02x",(unsigned char)*s);
|
||||
break;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
printf("\"\n");
|
||||
}
|
||||
|
||||
static int cliReadBulkReply(int fd) {
|
||||
sds replylen = cliReadLine(fd);
|
||||
char *reply, crlf[2];
|
||||
int bulklen;
|
||||
|
||||
if (replylen == NULL) return 1;
|
||||
bulklen = atoi(replylen);
|
||||
if (bulklen == -1) {
|
||||
sdsfree(replylen);
|
||||
printf("(nil)\n");
|
||||
return 0;
|
||||
}
|
||||
reply = zmalloc(bulklen);
|
||||
anetRead(fd,reply,bulklen);
|
||||
anetRead(fd,crlf,2);
|
||||
if (config.raw_output || !isatty(fileno(stdout))) {
|
||||
if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
|
||||
zfree(reply);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
/* If you are producing output for the standard output we want
|
||||
* a more interesting output with quoted characters and so forth */
|
||||
printStringRepr(reply,bulklen);
|
||||
}
|
||||
zfree(reply);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cliReadMultiBulkReply(int fd) {
|
||||
sds replylen = cliReadLine(fd);
|
||||
int elements, c = 1;
|
||||
|
||||
if (replylen == NULL) return 1;
|
||||
elements = atoi(replylen);
|
||||
if (elements == -1) {
|
||||
sdsfree(replylen);
|
||||
printf("(nil)\n");
|
||||
return 0;
|
||||
}
|
||||
if (elements == 0) {
|
||||
printf("(empty list or set)\n");
|
||||
}
|
||||
while(elements--) {
|
||||
printf("%d. ", c);
|
||||
if (cliReadReply(fd)) return 1;
|
||||
c++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cliReadReply(int fd) {
|
||||
char type;
|
||||
|
||||
if (anetRead(fd,&type,1) <= 0) {
|
||||
if (config.shutdown) return 0;
|
||||
exit(1);
|
||||
}
|
||||
switch(type) {
|
||||
case '-':
|
||||
printf("(error) ");
|
||||
cliReadSingleLineReply(fd,0);
|
||||
return 1;
|
||||
case '+':
|
||||
return cliReadSingleLineReply(fd,0);
|
||||
case ':':
|
||||
printf("(integer) ");
|
||||
return cliReadSingleLineReply(fd,0);
|
||||
case '$':
|
||||
return cliReadBulkReply(fd);
|
||||
case '*':
|
||||
return cliReadMultiBulkReply(fd);
|
||||
default:
|
||||
printf("protocol error, got '%c' as reply type byte\n", type);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int selectDb(int fd) {
|
||||
int retval;
|
||||
sds cmd;
|
||||
char type;
|
||||
|
||||
if (config.dbnum == 0)
|
||||
return 0;
|
||||
|
||||
cmd = sdsempty();
|
||||
cmd = sdscatprintf(cmd,"SELECT %d\r\n",config.dbnum);
|
||||
anetWrite(fd,cmd,sdslen(cmd));
|
||||
anetRead(fd,&type,1);
|
||||
if (type <= 0 || type != '+') return 1;
|
||||
retval = cliReadSingleLineReply(fd,1);
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
struct redisCommand *rc = lookupCommand(argv[0]);
|
||||
int fd, j, retval = 0;
|
||||
sds cmd;
|
||||
|
||||
if (!rc) {
|
||||
fprintf(stderr,"Unknown command '%s'\n",argv[0]);
|
||||
return 1;
|
||||
}
|
||||
config.raw_output = (rc->flags & CMDFLAG_RAWOUTPUT);
|
||||
|
||||
if ((rc->arity > 0 && argc != rc->arity) ||
|
||||
(rc->arity < 0 && argc < -rc->arity)) {
|
||||
fprintf(stderr,"Wrong number of arguments for '%s'\n",rc->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(rc->name,"shutdown")) config.shutdown = 1;
|
||||
if (!strcasecmp(rc->name,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(rc->name,"subscribe") ||
|
||||
!strcasecmp(rc->name,"psubscribe")) config.pubsub_mode = 1;
|
||||
if ((fd = cliConnect()) == -1) return 1;
|
||||
|
||||
/* Select db number */
|
||||
retval = selectDb(fd);
|
||||
if (retval) {
|
||||
fprintf(stderr,"Error setting DB num\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while(repeat--) {
|
||||
/* Build the command to send */
|
||||
cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
cmd = sdscatprintf(cmd,"$%lu\r\n",
|
||||
(unsigned long)sdslen(argv[j]));
|
||||
cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
}
|
||||
anetWrite(fd,cmd,sdslen(cmd));
|
||||
sdsfree(cmd);
|
||||
|
||||
while (config.monitor_mode) {
|
||||
cliReadSingleLineReply(fd,0);
|
||||
}
|
||||
|
||||
if (config.pubsub_mode) {
|
||||
printf("Reading messages... (press Ctrl-c to quit)\n");
|
||||
while (1) {
|
||||
cliReadReply(fd);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
retval = cliReadReply(fd);
|
||||
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parseOptions(int argc, char **argv) {
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
int lastarg = i==argc-1;
|
||||
|
||||
if (!strcmp(argv[i],"-h") && !lastarg) {
|
||||
char *ip = zmalloc(32);
|
||||
if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
|
||||
printf("Can't resolve %s\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
config.hostip = ip;
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-h") && lastarg) {
|
||||
usage();
|
||||
} else if (!strcmp(argv[i],"-p") && !lastarg) {
|
||||
config.hostport = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-r") && !lastarg) {
|
||||
config.repeat = strtoll(argv[i+1],NULL,10);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
config.auth = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-i")) {
|
||||
config.interactive = 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static sds readArgFromStdin(void) {
|
||||
char buf[1024];
|
||||
sds arg = sdsempty();
|
||||
|
||||
while(1) {
|
||||
int nread = read(fileno(stdin),buf,1024);
|
||||
|
||||
if (nread == 0) break;
|
||||
else if (nread == -1) {
|
||||
perror("Reading from standard input");
|
||||
exit(1);
|
||||
}
|
||||
arg = sdscatlen(arg,buf,nread);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n");
|
||||
fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-a authpw] [-p port] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
|
||||
fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
|
||||
fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
|
||||
fprintf(stderr, "example: redis-cli get my_passwd\n");
|
||||
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
|
||||
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Turn the plain C strings into Sds strings */
|
||||
static char **convertToSds(int count, char** args) {
|
||||
int j;
|
||||
char **sds = zmalloc(sizeof(char*)*count+1);
|
||||
|
||||
for(j = 0; j < count; j++)
|
||||
sds[j] = sdsnew(args[j]);
|
||||
|
||||
return sds;
|
||||
}
|
||||
|
||||
static char **splitArguments(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
/* skip blanks */
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done = 0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
switch(*p) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'a': c = '\a'; break;
|
||||
default: c = *p; break;
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
done = 1;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else {
|
||||
switch(*p) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
done=1;
|
||||
break;
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define LINE_BUFLEN 4096
|
||||
static void repl() {
|
||||
int argc, j;
|
||||
char *line, **argv;
|
||||
|
||||
while((line = linenoise("redis> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
argv = splitArguments(line,&argc);
|
||||
linenoiseHistoryAdd(line);
|
||||
if (argc > 0) {
|
||||
if (strcasecmp(argv[0],"quit") == 0 ||
|
||||
strcasecmp(argv[0],"exit") == 0)
|
||||
exit(0);
|
||||
else
|
||||
cliSendCommand(argc, argv, 1);
|
||||
}
|
||||
/* Free the argument vector */
|
||||
for (j = 0; j < argc; j++)
|
||||
sdsfree(argv[j]);
|
||||
zfree(argv);
|
||||
}
|
||||
/* linenoise() returns malloc-ed lines like readline() */
|
||||
free(line);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
char **argvcopy;
|
||||
struct redisCommand *rc;
|
||||
|
||||
config.hostip = "127.0.0.1";
|
||||
config.hostport = 6379;
|
||||
config.repeat = 1;
|
||||
config.dbnum = 0;
|
||||
config.shutdown = 0;
|
||||
config.interactive = 0;
|
||||
config.monitor_mode = 0;
|
||||
config.pubsub_mode = 0;
|
||||
config.raw_output = 0;
|
||||
config.auth = NULL;
|
||||
|
||||
firstarg = parseOptions(argc,argv);
|
||||
argc -= firstarg;
|
||||
argv += firstarg;
|
||||
|
||||
if (config.auth != NULL) {
|
||||
char *authargv[2];
|
||||
|
||||
authargv[0] = "AUTH";
|
||||
authargv[1] = config.auth;
|
||||
cliSendCommand(2, convertToSds(2, authargv), 1);
|
||||
}
|
||||
|
||||
if (argc == 0 || config.interactive == 1) repl();
|
||||
|
||||
argvcopy = convertToSds(argc, argv);
|
||||
|
||||
/* Read the last argument from stdandard input if needed */
|
||||
if ((rc = lookupCommand(argv[0])) != NULL) {
|
||||
if (rc->arity > 0 && argc == rc->arity-1) {
|
||||
sds lastarg = readArgFromStdin();
|
||||
argvcopy[argc] = lastarg;
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
|
||||
return cliSendCommand(argc, argvcopy, config.repeat);
|
||||
}
|
||||
+87
-1
@@ -20,7 +20,7 @@ daemonize no
|
||||
# default. You can specify a custom pid file location here.
|
||||
pidfile /var/run/redis.pid
|
||||
|
||||
# Accept connections on the specified port, default is 6379
|
||||
# Accept connections on the specified port, default is 6379.
|
||||
port 6379
|
||||
|
||||
# If you want you can bind a single interface, if the bind option is not
|
||||
@@ -28,6 +28,12 @@ port 6379
|
||||
#
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 300
|
||||
|
||||
@@ -104,6 +110,19 @@ dir ./
|
||||
#
|
||||
# masterauth <master-password>
|
||||
|
||||
# When a slave lost the connection with the master, or when the replication
|
||||
# is still in progress, the slave can act in two different ways:
|
||||
#
|
||||
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||
# still reply to client requests, possibly with out of data data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale data is set to 'no' the slave will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO and SLAVEOF.
|
||||
#
|
||||
slave-serve-stale-data yes
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
@@ -119,6 +138,22 @@ dir ./
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming.
|
||||
#
|
||||
# It is possilbe to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
# of hard to guess so that it will be still available for internal-use
|
||||
# tools but not available for general clients.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
|
||||
#
|
||||
# It is also possilbe to completely kill a command renaming it into
|
||||
# an empty string:
|
||||
#
|
||||
# rename-command CONFIG ""
|
||||
|
||||
################################### LIMITS ####################################
|
||||
|
||||
# Set the max number of connected clients at the same time. By default there
|
||||
@@ -148,6 +183,37 @@ dir ./
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached? You can select among five behavior:
|
||||
#
|
||||
# volatile-lru -> remove the key with an expire set using an LRU algorithm
|
||||
# allkeys-lru -> remove any key accordingly to the LRU algorithm
|
||||
# volatile-random -> remove a random key with an expire set
|
||||
# allkeys->random -> remove a random key, any key
|
||||
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> don't expire at all, just return an error on write operations
|
||||
#
|
||||
# Note: with all the kind of policies, Redis will return an error on write
|
||||
# operations, when there are not suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing this commands are: set setnx setex append
|
||||
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
|
||||
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
|
||||
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
|
||||
# getset mset msetnx exec sort
|
||||
#
|
||||
# The default is:
|
||||
#
|
||||
# maxmemory-policy volatile-lru
|
||||
|
||||
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can select as well the sample
|
||||
# size to check. For instance for default Redis will check three keys and
|
||||
# pick the one that was used less recently, you can change the sample size
|
||||
# using the following configuration directive.
|
||||
#
|
||||
# maxmemory-samples 3
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default Redis asynchronously dumps the dataset on disk. If you can live
|
||||
@@ -195,6 +261,26 @@ appendonly no
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
|
||||
# When the AOF fsync policy is set to always or everysec, and a background
|
||||
# saving process (a background save or AOF log background rewriting) is
|
||||
# performing a lot of I/O against the disk, in some Linux configurations
|
||||
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||
# this currently, as even performing fsync in a different thread will block
|
||||
# our synchronous write(2) call.
|
||||
#
|
||||
# In order to mitigate this problem it's possible to use the following option
|
||||
# that will prevent fsync() from being called in the main process while a
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving the durability of Redis is
|
||||
# the same as "appendfsync none", that in pratical terms means that it is
|
||||
# possible to lost up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
#
|
||||
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
################################ VIRTUAL MEMORY ###############################
|
||||
|
||||
# Virtual Memory allows Redis to work with datasets bigger than the actual
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
# Redis Makefile
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
|
||||
else
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
endif
|
||||
|
||||
ifeq ($(USE_TCMALLOC),yes)
|
||||
CCLINK+= -ltcmalloc
|
||||
CFLAGS+= -DUSE_TCMALLOC
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
|
||||
PREFIX= /usr/local
|
||||
INSTALL_BIN= $(PREFIX)/bin
|
||||
INSTALL= cp -p
|
||||
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o syncio.o
|
||||
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
|
||||
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o
|
||||
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
|
||||
CHECKAOFOBJ = redis-check-aof.o
|
||||
|
||||
PRGNAME = redis-server
|
||||
BENCHPRGNAME = redis-benchmark
|
||||
CLIPRGNAME = redis-cli
|
||||
CHECKDUMPPRGNAME = redis-check-dump
|
||||
CHECKAOFPRGNAME = redis-check-aof
|
||||
|
||||
all: redis-benchmark redis-cli redis-check-dump redis-check-aof redis-server
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
|
||||
ae_epoll.o: ae_epoll.c
|
||||
ae_kqueue.o: ae_kqueue.c
|
||||
ae_select.o: ae_select.c
|
||||
anet.o: anet.c fmacros.h anet.h
|
||||
aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
chprgname.o: chprgname.c
|
||||
config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
debug.o: debug.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h sha1.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
intset.o: intset.c intset.h zmalloc.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
multi.o: multi.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
networking.o: networking.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
object.o: object.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
pqsort.o: pqsort.c
|
||||
pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
|
||||
zmalloc.h
|
||||
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h
|
||||
redis-cli.o: redis-cli.c fmacros.h version.h sds.h adlist.h zmalloc.h
|
||||
redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
release.o: release.c release.h
|
||||
replication.o: replication.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
sha1.o: sha1.c sha1.h
|
||||
sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h
|
||||
syncio.o: syncio.c
|
||||
t_hash.o: t_hash.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_list.o: t_list.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_set.o: t_set.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_string.o: t_string.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_zset.o: t_zset.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
util.o: util.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
vm.o: vm.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
ziplist.o: ziplist.c zmalloc.h ziplist.h
|
||||
zipmap.o: zipmap.c zmalloc.h
|
||||
zmalloc.o: zmalloc.c config.h
|
||||
|
||||
redis-server: $(OBJ)
|
||||
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
|
||||
@echo ""
|
||||
@echo "Hint: To run 'make test' is a good idea ;)"
|
||||
@echo ""
|
||||
|
||||
redis-benchmark: $(BENCHOBJ)
|
||||
cd ../deps/hiredis && $(MAKE) static
|
||||
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a
|
||||
|
||||
redis-benchmark.o:
|
||||
$(CC) -c $(CFLAGS) -I../deps/hiredis $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-cli: $(CLIOBJ)
|
||||
cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
|
||||
cd ../deps/linenoise && $(MAKE) ARCH="$(ARCH)"
|
||||
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o
|
||||
|
||||
redis-cli.o:
|
||||
$(CC) -c $(CFLAGS) -I../deps/hiredis -I../deps/linenoise $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-check-dump: $(CHECKDUMPOBJ)
|
||||
$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ)
|
||||
|
||||
redis-check-aof: $(CHECKAOFOBJ)
|
||||
$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
cd ../deps/hiredis && $(MAKE) clean
|
||||
cd ../deps/linenoise && $(MAKE) clean
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
test:
|
||||
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
|
||||
|
||||
bench:
|
||||
./redis-benchmark
|
||||
|
||||
log:
|
||||
git log '--pretty=format:%ad %s (%cn)' --date=short > ../Changelog
|
||||
|
||||
32bit:
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
$(MAKE) ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
$(MAKE) PROF="-pg"
|
||||
|
||||
gcov:
|
||||
$(MAKE) PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
|
||||
32bitgprof:
|
||||
$(MAKE) PROF="-pg" ARCH="-arch i386"
|
||||
|
||||
install: all
|
||||
mkdir -p $(INSTALL_BIN)
|
||||
$(INSTALL) $(PRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(CHECKDUMPPRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(CHECKAOFPRGNAME) $(INSTALL_BIN)
|
||||
+32
-3
@@ -123,6 +123,35 @@ list *listAddNodeTail(list *list, void *value)
|
||||
return list;
|
||||
}
|
||||
|
||||
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
|
||||
listNode *node;
|
||||
|
||||
if ((node = zmalloc(sizeof(*node))) == NULL)
|
||||
return NULL;
|
||||
node->value = value;
|
||||
if (after) {
|
||||
node->prev = old_node;
|
||||
node->next = old_node->next;
|
||||
if (list->tail == old_node) {
|
||||
list->tail = node;
|
||||
}
|
||||
} else {
|
||||
node->next = old_node;
|
||||
node->prev = old_node->prev;
|
||||
if (list->head == old_node) {
|
||||
list->head = node;
|
||||
}
|
||||
}
|
||||
if (node->prev != NULL) {
|
||||
node->prev->next = node;
|
||||
}
|
||||
if (node->next != NULL) {
|
||||
node->next->prev = node;
|
||||
}
|
||||
list->len++;
|
||||
return list;
|
||||
}
|
||||
|
||||
/* Remove the specified node from the specified list.
|
||||
* It's up to the caller to free the private value of the node.
|
||||
*
|
||||
@@ -183,9 +212,9 @@ void listRewindTail(list *list, listIter *li) {
|
||||
* or NULL if there are no more elements, so the classical usage patter
|
||||
* is:
|
||||
*
|
||||
* iter = listGetItarotr(list,<direction>);
|
||||
* while ((node = listNextIterator(iter)) != NULL) {
|
||||
* DoSomethingWith(listNodeValue(node));
|
||||
* iter = listGetIterator(list,<direction>);
|
||||
* while ((node = listNext(iter)) != NULL) {
|
||||
* doSomethingWith(listNodeValue(node));
|
||||
* }
|
||||
*
|
||||
* */
|
||||
@@ -74,6 +74,7 @@ list *listCreate(void);
|
||||
void listRelease(list *list);
|
||||
list *listAddNodeHead(list *list, void *value);
|
||||
list *listAddNodeTail(list *list, void *value);
|
||||
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
|
||||
void listDelNode(list *list, listNode *node);
|
||||
listIter *listGetIterator(list *list, int direction);
|
||||
listNode *listNext(listIter *iter);
|
||||
+116
-39
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
@@ -123,20 +124,31 @@ int anetResolve(char *err, char *host, char *ipbuf)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int anetCreateSocket(char *err, int domain) {
|
||||
int s, on = 1;
|
||||
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
|
||||
anetSetError(err, "creating socket: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
* will be able to close/open sockets a zillion of times */
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
{
|
||||
int s, on = 1;
|
||||
int s;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
|
||||
anetSetError(err, "creating socket: %s\n", strerror(errno));
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
}
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
* will be able to close/open sockets a zillion of times */
|
||||
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
@@ -177,6 +189,42 @@ int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (flags & ANET_CONNECT_NONBLOCK) {
|
||||
if (anetNonBlock(err,s) != ANET_OK)
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (connect(s,(struct sockaddr*)&sa,sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS &&
|
||||
flags & ANET_CONNECT_NONBLOCK)
|
||||
return s;
|
||||
|
||||
anetSetError(err, "connect: %s\n", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixConnect(char *err, char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetUnixNonBlockConnect(char *err, char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
/* Like read(2) but make sure 'count' is read before to return
|
||||
* (unless error or EOF condition is encountered) */
|
||||
int anetRead(int fd, char *buf, int count)
|
||||
@@ -207,32 +255,8 @@ int anetWrite(int fd, char *buf, int count)
|
||||
return totlen;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
{
|
||||
int s, on = 1;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
|
||||
anetSetError(err, "socket: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bindaddr) {
|
||||
if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
|
||||
anetSetError(err, "Invalid bind address\n");
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
}
|
||||
if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
|
||||
if (bind(s,sa,len) == -1) {
|
||||
anetSetError(err, "bind: %s\n", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
@@ -242,18 +266,51 @@ int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bindaddr && inet_aton(bindaddr, &sa.sin_addr) == 0) {
|
||||
anetSetError(err, "Invalid bind address\n");
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetAccept(char *err, int serversock, char *ip, int *port)
|
||||
int anetUnixServer(char *err, char *path)
|
||||
{
|
||||
int fd;
|
||||
struct sockaddr_in sa;
|
||||
unsigned int saLen;
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
return s;
|
||||
}
|
||||
|
||||
static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
|
||||
int fd;
|
||||
while(1) {
|
||||
saLen = sizeof(sa);
|
||||
fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
|
||||
fd = accept(s,sa,len);
|
||||
if (fd == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
@@ -264,7 +321,27 @@ int anetAccept(char *err, int serversock, char *ip, int *port)
|
||||
}
|
||||
break;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetTcpAccept(char *err, int s, char *ip, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetUnixAccept(char *err, int s) {
|
||||
int fd;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
return fd;
|
||||
}
|
||||
+5
-1
@@ -37,10 +37,14 @@
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetAccept(char *err, int serversock, char *ip, int *port);
|
||||
int anetUnixServer(char *err, char *path);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
int anetNonBlock(char *err, int fd);
|
||||
int anetTcpNoDelay(char *err, int fd);
|
||||
@@ -0,0 +1,671 @@
|
||||
#include "redis.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
* at runtime using the CONFIG command. */
|
||||
void stopAppendOnly(void) {
|
||||
flushAppendOnlyFile();
|
||||
aof_fsync(server.appendfd);
|
||||
close(server.appendfd);
|
||||
|
||||
server.appendfd = -1;
|
||||
server.appendseldb = -1;
|
||||
server.appendonly = 0;
|
||||
/* rewrite operation in progress? kill it, wait child exit */
|
||||
if (server.bgsavechildpid != -1) {
|
||||
int statloc;
|
||||
|
||||
if (kill(server.bgsavechildpid,SIGKILL) != -1)
|
||||
wait3(&statloc,0,NULL);
|
||||
/* reset the buffer accumulating changes while the child saves */
|
||||
sdsfree(server.bgrewritebuf);
|
||||
server.bgrewritebuf = sdsempty();
|
||||
server.bgsavechildpid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly no" to "appendonly yes"
|
||||
* at runtime using the CONFIG command. */
|
||||
int startAppendOnly(void) {
|
||||
server.appendonly = 1;
|
||||
server.lastfsync = time(NULL);
|
||||
server.appendfd = open(server.appendfilename,O_WRONLY|O_APPEND|O_CREAT,0644);
|
||||
if (server.appendfd == -1) {
|
||||
redisLog(REDIS_WARNING,"Used tried to switch on AOF via CONFIG, but I can't open the AOF file: %s",strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (rewriteAppendOnlyFileBackground() == REDIS_ERR) {
|
||||
server.appendonly = 0;
|
||||
close(server.appendfd);
|
||||
redisLog(REDIS_WARNING,"Used tried to switch on AOF via CONFIG, I can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.",strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Write the append only file buffer on disk.
|
||||
*
|
||||
* Since we are required to write the AOF before replying to the client,
|
||||
* and the only way the client socket can get a write is entering when the
|
||||
* the event loop, we accumulate all the AOF writes in a memory
|
||||
* buffer and write it on disk using this function just before entering
|
||||
* the event loop again. */
|
||||
void flushAppendOnlyFile(void) {
|
||||
time_t now;
|
||||
ssize_t nwritten;
|
||||
|
||||
if (sdslen(server.aofbuf) == 0) return;
|
||||
|
||||
/* We want to perform a single write. This should be guaranteed atomic
|
||||
* at least if the filesystem we are writing is a real physical one.
|
||||
* While this will save us against the server being killed I don't think
|
||||
* there is much to do about the whole server stopping for power problems
|
||||
* or alike */
|
||||
nwritten = write(server.appendfd,server.aofbuf,sdslen(server.aofbuf));
|
||||
if (nwritten != (signed)sdslen(server.aofbuf)) {
|
||||
/* Ooops, we are in troubles. The best thing to do for now is
|
||||
* aborting instead of giving the illusion that everything is
|
||||
* working as expected. */
|
||||
if (nwritten == -1) {
|
||||
redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Exiting on short write while writing to the append-only file: %s",strerror(errno));
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
sdsfree(server.aofbuf);
|
||||
server.aofbuf = sdsempty();
|
||||
|
||||
/* Don't Fsync if no-appendfsync-on-rewrite is set to yes and we have
|
||||
* childs performing heavy I/O on disk. */
|
||||
if (server.no_appendfsync_on_rewrite &&
|
||||
(server.bgrewritechildpid != -1 || server.bgsavechildpid != -1))
|
||||
return;
|
||||
/* Fsync if needed */
|
||||
now = time(NULL);
|
||||
if (server.appendfsync == APPENDFSYNC_ALWAYS ||
|
||||
(server.appendfsync == APPENDFSYNC_EVERYSEC &&
|
||||
now-server.lastfsync > 1))
|
||||
{
|
||||
/* aof_fsync is defined as fdatasync() for Linux in order to avoid
|
||||
* flushing metadata. */
|
||||
aof_fsync(server.appendfd); /* Let's try to get this data on the disk */
|
||||
server.lastfsync = now;
|
||||
}
|
||||
}
|
||||
|
||||
sds catAppendOnlyGenericCommand(sds buf, int argc, robj **argv) {
|
||||
int j;
|
||||
buf = sdscatprintf(buf,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
robj *o = getDecodedObject(argv[j]);
|
||||
buf = sdscatprintf(buf,"$%lu\r\n",(unsigned long)sdslen(o->ptr));
|
||||
buf = sdscatlen(buf,o->ptr,sdslen(o->ptr));
|
||||
buf = sdscatlen(buf,"\r\n",2);
|
||||
decrRefCount(o);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
sds catAppendOnlyExpireAtCommand(sds buf, robj *key, robj *seconds) {
|
||||
int argc = 3;
|
||||
long when;
|
||||
robj *argv[3];
|
||||
|
||||
/* Make sure we can use strtol */
|
||||
seconds = getDecodedObject(seconds);
|
||||
when = time(NULL)+strtol(seconds->ptr,NULL,10);
|
||||
decrRefCount(seconds);
|
||||
|
||||
argv[0] = createStringObject("EXPIREAT",8);
|
||||
argv[1] = key;
|
||||
argv[2] = createObject(REDIS_STRING,
|
||||
sdscatprintf(sdsempty(),"%ld",when));
|
||||
buf = catAppendOnlyGenericCommand(buf, argc, argv);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[2]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
|
||||
sds buf = sdsempty();
|
||||
robj *tmpargv[3];
|
||||
|
||||
/* The DB this command was targetting is not the same as the last command
|
||||
* we appendend. To issue a SELECT command is needed. */
|
||||
if (dictid != server.appendseldb) {
|
||||
char seldb[64];
|
||||
|
||||
snprintf(seldb,sizeof(seldb),"%d",dictid);
|
||||
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
|
||||
(unsigned long)strlen(seldb),seldb);
|
||||
server.appendseldb = dictid;
|
||||
}
|
||||
|
||||
if (cmd->proc == expireCommand) {
|
||||
/* Translate EXPIRE into EXPIREAT */
|
||||
buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
|
||||
} else if (cmd->proc == setexCommand) {
|
||||
/* Translate SETEX to SET and EXPIREAT */
|
||||
tmpargv[0] = createStringObject("SET",3);
|
||||
tmpargv[1] = argv[1];
|
||||
tmpargv[2] = argv[3];
|
||||
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
|
||||
decrRefCount(tmpargv[0]);
|
||||
buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
|
||||
} else {
|
||||
buf = catAppendOnlyGenericCommand(buf,argc,argv);
|
||||
}
|
||||
|
||||
/* Append to the AOF buffer. This will be flushed on disk just before
|
||||
* of re-entering the event loop, so before the client will get a
|
||||
* positive reply about the operation performed. */
|
||||
server.aofbuf = sdscatlen(server.aofbuf,buf,sdslen(buf));
|
||||
|
||||
/* If a background append only file rewriting is in progress we want to
|
||||
* accumulate the differences between the child DB and the current one
|
||||
* in a buffer, so that when the child process will do its work we
|
||||
* can append the differences to the new append only file. */
|
||||
if (server.bgrewritechildpid != -1)
|
||||
server.bgrewritebuf = sdscatlen(server.bgrewritebuf,buf,sdslen(buf));
|
||||
|
||||
sdsfree(buf);
|
||||
}
|
||||
|
||||
/* In Redis commands are always executed in the context of a client, so in
|
||||
* order to load the append only file we need to create a fake client. */
|
||||
struct redisClient *createFakeClient(void) {
|
||||
struct redisClient *c = zmalloc(sizeof(*c));
|
||||
|
||||
selectDb(c,0);
|
||||
c->fd = -1;
|
||||
c->querybuf = sdsempty();
|
||||
c->argc = 0;
|
||||
c->argv = NULL;
|
||||
c->bufpos = 0;
|
||||
c->flags = 0;
|
||||
/* We set the fake client as a slave waiting for the synchronization
|
||||
* so that Redis will not try to send replies to this client. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
c->reply = listCreate();
|
||||
c->watched_keys = listCreate();
|
||||
listSetFreeMethod(c->reply,decrRefCount);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
void freeFakeClient(struct redisClient *c) {
|
||||
sdsfree(c->querybuf);
|
||||
listRelease(c->reply);
|
||||
listRelease(c->watched_keys);
|
||||
freeClientMultiState(c);
|
||||
zfree(c);
|
||||
}
|
||||
|
||||
/* Replay the append log file. On error REDIS_OK is returned. On non fatal
|
||||
* error (the append only file is zero-length) REDIS_ERR is returned. On
|
||||
* fatal error an error message is logged and the program exists. */
|
||||
int loadAppendOnlyFile(char *filename) {
|
||||
struct redisClient *fakeClient;
|
||||
FILE *fp = fopen(filename,"r");
|
||||
struct redis_stat sb;
|
||||
int appendonly = server.appendonly;
|
||||
long loops = 0;
|
||||
|
||||
if (redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (fp == NULL) {
|
||||
redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Temporarily disable AOF, to prevent EXEC from feeding a MULTI
|
||||
* to the same file we're about to read. */
|
||||
server.appendonly = 0;
|
||||
|
||||
fakeClient = createFakeClient();
|
||||
startLoading(fp);
|
||||
|
||||
while(1) {
|
||||
int argc, j;
|
||||
unsigned long len;
|
||||
robj **argv;
|
||||
char buf[128];
|
||||
sds argsds;
|
||||
struct redisCommand *cmd;
|
||||
int force_swapout;
|
||||
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
if (feof(fp))
|
||||
break;
|
||||
else
|
||||
goto readerr;
|
||||
}
|
||||
if (buf[0] != '*') goto fmterr;
|
||||
argc = atoi(buf+1);
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
argsds = sdsnewlen(NULL,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
|
||||
argv[j] = createObject(REDIS_STRING,argsds);
|
||||
if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
|
||||
}
|
||||
|
||||
/* Command lookup */
|
||||
cmd = lookupCommand(argv[0]->ptr);
|
||||
if (!cmd) {
|
||||
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
cmd->proc(fakeClient);
|
||||
|
||||
/* The fake client should not have a reply */
|
||||
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
|
||||
|
||||
/* Clean up, ready for the next command */
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
zfree(argv);
|
||||
|
||||
/* Handle swapping while loading big datasets when VM is on */
|
||||
force_swapout = 0;
|
||||
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
|
||||
force_swapout = 1;
|
||||
|
||||
if (server.vm_enabled && force_swapout) {
|
||||
while (zmalloc_used_memory() > server.vm_max_memory) {
|
||||
if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
if (fakeClient->flags & REDIS_MULTI) goto readerr;
|
||||
|
||||
fclose(fp);
|
||||
freeFakeClient(fakeClient);
|
||||
server.appendonly = appendonly;
|
||||
stopLoading();
|
||||
return REDIS_OK;
|
||||
|
||||
readerr:
|
||||
if (feof(fp)) {
|
||||
redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
|
||||
}
|
||||
exit(1);
|
||||
fmterr:
|
||||
redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Write a sequence of commands able to fully rebuild the dataset into
|
||||
* "filename". Used both by REWRITEAOF and BGREWRITEAOF. */
|
||||
int rewriteAppendOnlyFile(char *filename) {
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
FILE *fp;
|
||||
char tmpfile[256];
|
||||
int j;
|
||||
time_t now = time(NULL);
|
||||
|
||||
/* Note that we have to use a different temp name here compared to the
|
||||
* one used by rewriteAppendOnlyFileBackground() function. */
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
if (!fp) {
|
||||
redisLog(REDIS_WARNING, "Failed rewriting the append only file: %s", strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
redisDb *db = server.db+j;
|
||||
dict *d = db->dict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetIterator(d);
|
||||
if (!di) {
|
||||
fclose(fp);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* SELECT the new DB */
|
||||
if (fwrite(selectcmd,sizeof(selectcmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkLongLong(fp,j) == 0) goto werr;
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr = dictGetEntryKey(de);
|
||||
robj key, *o;
|
||||
time_t expiretime;
|
||||
int swapped;
|
||||
|
||||
keystr = dictGetEntryKey(de);
|
||||
o = dictGetEntryVal(de);
|
||||
initStaticStringObject(key,keystr);
|
||||
/* If the value for this key is swapped, load a preview in memory.
|
||||
* We use a "swapped" flag to remember if we need to free the
|
||||
* value object instead to just increment the ref count anyway
|
||||
* in order to avoid copy-on-write of pages if we are forked() */
|
||||
if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY ||
|
||||
o->storage == REDIS_VM_SWAPPING) {
|
||||
swapped = 0;
|
||||
} else {
|
||||
o = vmPreviewObject(o);
|
||||
swapped = 1;
|
||||
}
|
||||
expiretime = getExpire(db,&key);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == REDIS_STRING) {
|
||||
/* Emit a SET command */
|
||||
char cmd[]="*3\r\n$3\r\nSET\r\n";
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
/* Key and value */
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,o) == 0) goto werr;
|
||||
} else if (o->type == REDIS_LIST) {
|
||||
/* Emit the RPUSHes needed to rebuild the list */
|
||||
char cmd[]="*3\r\n$5\r\nRPUSH\r\n";
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = o->ptr;
|
||||
unsigned char *p = ziplistIndex(zl,0);
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
|
||||
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (vstr) {
|
||||
if (fwriteBulkString(fp,(char*)vstr,vlen) == 0)
|
||||
goto werr;
|
||||
} else {
|
||||
if (fwriteBulkLongLong(fp,vlong) == 0)
|
||||
goto werr;
|
||||
}
|
||||
p = ziplistNext(zl,p);
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
list *list = o->ptr;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(list,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
robj *eleobj = listNodeValue(ln);
|
||||
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
} else if (o->type == REDIS_SET) {
|
||||
char cmd[]="*3\r\n$4\r\nSADD\r\n";
|
||||
|
||||
/* Emit the SADDs needed to rebuild the set */
|
||||
if (o->encoding == REDIS_ENCODING_INTSET) {
|
||||
int ii = 0;
|
||||
int64_t llval;
|
||||
while(intsetGet(o->ptr,ii++,&llval)) {
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkLongLong(fp,llval) == 0) goto werr;
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_HT) {
|
||||
dictIterator *di = dictGetIterator(o->ptr);
|
||||
dictEntry *de;
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *eleobj = dictGetEntryKey(de);
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
redisPanic("Unknown set encoding");
|
||||
}
|
||||
} else if (o->type == REDIS_ZSET) {
|
||||
/* Emit the ZADDs needed to rebuild the sorted set */
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char cmd[]="*4\r\n$4\r\nZADD\r\n";
|
||||
robj *eleobj = dictGetEntryKey(de);
|
||||
double *score = dictGetEntryVal(de);
|
||||
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkDouble(fp,*score) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (o->type == REDIS_HASH) {
|
||||
char cmd[]="*4\r\n$4\r\nHSET\r\n";
|
||||
|
||||
/* Emit the HSETs needed to rebuild the hash */
|
||||
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
|
||||
unsigned char *p = zipmapRewind(o->ptr);
|
||||
unsigned char *field, *val;
|
||||
unsigned int flen, vlen;
|
||||
|
||||
while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) {
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkString(fp,(char*)field,flen) == 0)
|
||||
goto werr;
|
||||
if (fwriteBulkString(fp,(char*)val,vlen) == 0)
|
||||
goto werr;
|
||||
}
|
||||
} else {
|
||||
dictIterator *di = dictGetIterator(o->ptr);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *field = dictGetEntryKey(de);
|
||||
robj *val = dictGetEntryVal(de);
|
||||
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,field) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,val) == 0) goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown object type");
|
||||
}
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
char cmd[]="*3\r\n$8\r\nEXPIREAT\r\n";
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) continue;
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkLongLong(fp,expiretime) == 0) goto werr;
|
||||
}
|
||||
if (swapped) decrRefCount(o);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
aof_fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
if (rename(tmpfile,filename) == -1) {
|
||||
redisLog(REDIS_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
||||
unlink(tmpfile);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
redisLog(REDIS_NOTICE,"SYNC append only file rewrite performed");
|
||||
return REDIS_OK;
|
||||
|
||||
werr:
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
if (di) dictReleaseIterator(di);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* This is how rewriting of the append only file in background works:
|
||||
*
|
||||
* 1) The user calls BGREWRITEAOF
|
||||
* 2) Redis calls this function, that forks():
|
||||
* 2a) the child rewrite the append only file in a temp file.
|
||||
* 2b) the parent accumulates differences in server.bgrewritebuf.
|
||||
* 3) When the child finished '2a' exists.
|
||||
* 4) The parent will trap the exit code, if it's OK, will append the
|
||||
* data accumulated into server.bgrewritebuf into the temp file, and
|
||||
* finally will rename(2) the temp file in the actual file name.
|
||||
* The the new file is reopened as the new append only file. Profit!
|
||||
*/
|
||||
int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
|
||||
if (server.bgrewritechildpid != -1) return REDIS_ERR;
|
||||
if (server.vm_enabled) waitEmptyIOJobsQueue();
|
||||
if ((childpid = fork()) == 0) {
|
||||
/* Child */
|
||||
char tmpfile[256];
|
||||
|
||||
if (server.vm_enabled) vmReopenSwapFile();
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {
|
||||
_exit(0);
|
||||
} else {
|
||||
_exit(1);
|
||||
}
|
||||
} else {
|
||||
/* Parent */
|
||||
if (childpid == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Can't rewrite append only file in background: fork: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Background append only file rewriting started by pid %d",childpid);
|
||||
server.bgrewritechildpid = childpid;
|
||||
updateDictResizePolicy();
|
||||
/* We set appendseldb to -1 in order to force the next call to the
|
||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||
* accumulated by the parent into server.bgrewritebuf will start
|
||||
* with a SELECT statement and it will be safe to merge. */
|
||||
server.appendseldb = -1;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_OK; /* unreached */
|
||||
}
|
||||
|
||||
void bgrewriteaofCommand(redisClient *c) {
|
||||
if (server.bgrewritechildpid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
return;
|
||||
}
|
||||
if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
|
||||
addReplyStatus(c,"Background append only file rewriting started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
void aofRemoveTempFile(pid_t childpid) {
|
||||
char tmpfile[256];
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* A background append only file rewriting (BGREWRITEAOF) terminated its work.
|
||||
* Handle this. */
|
||||
void backgroundRewriteDoneHandler(int statloc) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = WIFSIGNALED(statloc);
|
||||
|
||||
if (!bysignal && exitcode == 0) {
|
||||
int fd;
|
||||
char tmpfile[256];
|
||||
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Background append only file rewriting terminated with success");
|
||||
/* Now it's time to flush the differences accumulated by the parent */
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) server.bgrewritechildpid);
|
||||
fd = open(tmpfile,O_WRONLY|O_APPEND);
|
||||
if (fd == -1) {
|
||||
redisLog(REDIS_WARNING, "Not able to open the temp append only file produced by the child: %s", strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
/* Flush our data... */
|
||||
if (write(fd,server.bgrewritebuf,sdslen(server.bgrewritebuf)) !=
|
||||
(signed) sdslen(server.bgrewritebuf)) {
|
||||
redisLog(REDIS_WARNING, "Error or short write trying to flush the parent diff of the append log file in the child temp file: %s", strerror(errno));
|
||||
close(fd);
|
||||
goto cleanup;
|
||||
}
|
||||
redisLog(REDIS_NOTICE,"Parent diff flushed into the new append log file with success (%lu bytes)",sdslen(server.bgrewritebuf));
|
||||
/* Now our work is to rename the temp file into the stable file. And
|
||||
* switch the file descriptor used by the server for append only. */
|
||||
if (rename(tmpfile,server.appendfilename) == -1) {
|
||||
redisLog(REDIS_WARNING,"Can't rename the temp append only file into the stable one: %s", strerror(errno));
|
||||
close(fd);
|
||||
goto cleanup;
|
||||
}
|
||||
/* Mission completed... almost */
|
||||
redisLog(REDIS_NOTICE,"Append only file successfully rewritten.");
|
||||
if (server.appendfd != -1) {
|
||||
/* If append only is actually enabled... */
|
||||
close(server.appendfd);
|
||||
server.appendfd = fd;
|
||||
if (server.appendfsync != APPENDFSYNC_NO) aof_fsync(fd);
|
||||
server.appendseldb = -1; /* Make sure it will issue SELECT */
|
||||
redisLog(REDIS_NOTICE,"The new append only file was selected for future appends.");
|
||||
} else {
|
||||
/* If append only is disabled we just generate a dump in this
|
||||
* format. Why not? */
|
||||
close(fd);
|
||||
}
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
redisLog(REDIS_WARNING, "Background append only file rewriting error");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Background append only file rewriting terminated by signal %d",
|
||||
WTERMSIG(statloc));
|
||||
}
|
||||
cleanup:
|
||||
sdsfree(server.bgrewritebuf);
|
||||
server.bgrewritebuf = sdsempty();
|
||||
aofRemoveTempFile(server.bgrewritechildpid);
|
||||
server.bgrewritechildpid = -1;
|
||||
}
|
||||
+537
@@ -0,0 +1,537 @@
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Config file parsing
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
int yesnotoi(char *s) {
|
||||
if (!strcasecmp(s,"yes")) return 1;
|
||||
else if (!strcasecmp(s,"no")) return 0;
|
||||
else return -1;
|
||||
}
|
||||
|
||||
void appendServerSaveParams(time_t seconds, int changes) {
|
||||
server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1));
|
||||
server.saveparams[server.saveparamslen].seconds = seconds;
|
||||
server.saveparams[server.saveparamslen].changes = changes;
|
||||
server.saveparamslen++;
|
||||
}
|
||||
|
||||
void resetServerSaveParams() {
|
||||
zfree(server.saveparams);
|
||||
server.saveparams = NULL;
|
||||
server.saveparamslen = 0;
|
||||
}
|
||||
|
||||
/* I agree, this is a very rudimental way to load a configuration...
|
||||
will improve later if the config gets more complex */
|
||||
void loadServerConfig(char *filename) {
|
||||
FILE *fp;
|
||||
char buf[REDIS_CONFIGLINE_MAX+1], *err = NULL;
|
||||
int linenum = 0;
|
||||
sds line = NULL;
|
||||
|
||||
if (filename[0] == '-' && filename[1] == '\0')
|
||||
fp = stdin;
|
||||
else {
|
||||
if ((fp = fopen(filename,"r")) == NULL) {
|
||||
redisLog(REDIS_WARNING, "Fatal error, can't open config file '%s'", filename);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL) {
|
||||
sds *argv;
|
||||
int argc, j;
|
||||
|
||||
linenum++;
|
||||
line = sdsnew(buf);
|
||||
line = sdstrim(line," \t\r\n");
|
||||
|
||||
/* Skip comments and blank lines*/
|
||||
if (line[0] == '#' || line[0] == '\0') {
|
||||
sdsfree(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Split into arguments */
|
||||
argv = sdssplitargs(line,&argc);
|
||||
sdstolower(argv[0]);
|
||||
|
||||
/* Execute config directives */
|
||||
if (!strcasecmp(argv[0],"timeout") && argc == 2) {
|
||||
server.maxidletime = atoi(argv[1]);
|
||||
if (server.maxidletime < 0) {
|
||||
err = "Invalid timeout value"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
|
||||
server.port = atoi(argv[1]);
|
||||
if (server.port < 1 || server.port > 65535) {
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"bind") && argc == 2) {
|
||||
server.bindaddr = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"unixsocket") && argc == 2) {
|
||||
server.unixsocket = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"save") && argc == 3) {
|
||||
int seconds = atoi(argv[1]);
|
||||
int changes = atoi(argv[2]);
|
||||
if (seconds < 1 || changes < 0) {
|
||||
err = "Invalid save parameters"; goto loaderr;
|
||||
}
|
||||
appendServerSaveParams(seconds,changes);
|
||||
} else if (!strcasecmp(argv[0],"dir") && argc == 2) {
|
||||
if (chdir(argv[1]) == -1) {
|
||||
redisLog(REDIS_WARNING,"Can't chdir to '%s': %s",
|
||||
argv[1], strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"loglevel") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"debug")) server.verbosity = REDIS_DEBUG;
|
||||
else if (!strcasecmp(argv[1],"verbose")) server.verbosity = REDIS_VERBOSE;
|
||||
else if (!strcasecmp(argv[1],"notice")) server.verbosity = REDIS_NOTICE;
|
||||
else if (!strcasecmp(argv[1],"warning")) server.verbosity = REDIS_WARNING;
|
||||
else {
|
||||
err = "Invalid log level. Must be one of debug, notice, warning";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
|
||||
FILE *logfp;
|
||||
|
||||
server.logfile = zstrdup(argv[1]);
|
||||
if (!strcasecmp(server.logfile,"stdout")) {
|
||||
zfree(server.logfile);
|
||||
server.logfile = NULL;
|
||||
}
|
||||
if (server.logfile) {
|
||||
/* Test if we are able to open the file. The server will not
|
||||
* be able to abort just for this problem later... */
|
||||
logfp = fopen(server.logfile,"a");
|
||||
if (logfp == NULL) {
|
||||
err = sdscatprintf(sdsempty(),
|
||||
"Can't open the log file: %s", strerror(errno));
|
||||
goto loaderr;
|
||||
}
|
||||
fclose(logfp);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"databases") && argc == 2) {
|
||||
server.dbnum = atoi(argv[1]);
|
||||
if (server.dbnum < 1) {
|
||||
err = "Invalid number of databases"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
|
||||
loadServerConfig(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
|
||||
server.maxclients = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
|
||||
server.maxmemory = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"volatile-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
} else if (!strcasecmp(argv[1],"volatile-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
|
||||
} else if (!strcasecmp(argv[1],"volatile-ttl")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
|
||||
} else if (!strcasecmp(argv[1],"allkeys-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
|
||||
} else if (!strcasecmp(argv[1],"allkeys-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
|
||||
} else if (!strcasecmp(argv[1],"noeviction")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
|
||||
} else {
|
||||
err = "Invalid maxmemory policy";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"maxmemory-samples") && argc == 2) {
|
||||
server.maxmemory_samples = atoi(argv[1]);
|
||||
if (server.maxmemory_samples <= 0) {
|
||||
err = "maxmemory-samples must be 1 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
|
||||
server.masterhost = sdsnew(argv[1]);
|
||||
server.masterport = atoi(argv[2]);
|
||||
server.replstate = REDIS_REPL_CONNECT;
|
||||
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
|
||||
server.masterauth = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
|
||||
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"glueoutputbuf") && argc == 2) {
|
||||
if ((server.glueoutputbuf = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
|
||||
if ((server.rdbcompression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
|
||||
if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
|
||||
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
|
||||
if ((server.appendonly = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
|
||||
zfree(server.appendfilename);
|
||||
server.appendfilename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
|
||||
&& argc == 2) {
|
||||
if ((server.no_appendfsync_on_rewrite= yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"no")) {
|
||||
server.appendfsync = APPENDFSYNC_NO;
|
||||
} else if (!strcasecmp(argv[1],"always")) {
|
||||
server.appendfsync = APPENDFSYNC_ALWAYS;
|
||||
} else if (!strcasecmp(argv[1],"everysec")) {
|
||||
server.appendfsync = APPENDFSYNC_EVERYSEC;
|
||||
} else {
|
||||
err = "argument must be 'no', 'always' or 'everysec'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
server.requirepass = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
|
||||
zfree(server.pidfile);
|
||||
server.pidfile = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
|
||||
zfree(server.dbfilename);
|
||||
server.dbfilename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"vm-enabled") && argc == 2) {
|
||||
if ((server.vm_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"vm-swap-file") && argc == 2) {
|
||||
zfree(server.vm_swap_file);
|
||||
server.vm_swap_file = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"vm-max-memory") && argc == 2) {
|
||||
server.vm_max_memory = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"vm-page-size") && argc == 2) {
|
||||
server.vm_page_size = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"vm-pages") && argc == 2) {
|
||||
server.vm_pages = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"vm-max-threads") && argc == 2) {
|
||||
server.vm_max_threads = strtoll(argv[1], NULL, 10);
|
||||
} else if (!strcasecmp(argv[0],"hash-max-zipmap-entries") && argc == 2) {
|
||||
server.hash_max_zipmap_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"hash-max-zipmap-value") && argc == 2) {
|
||||
server.hash_max_zipmap_value = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
||||
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
||||
server.list_max_ziplist_value = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
|
||||
server.set_max_intset_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
|
||||
struct redisCommand *cmd = lookupCommand(argv[1]);
|
||||
int retval;
|
||||
|
||||
if (!cmd) {
|
||||
err = "No such command in rename-command";
|
||||
goto loaderr;
|
||||
}
|
||||
|
||||
/* If the target command name is the emtpy string we just
|
||||
* remove it from the command table. */
|
||||
retval = dictDelete(server.commands, argv[1]);
|
||||
redisAssert(retval == DICT_OK);
|
||||
|
||||
/* Otherwise we re-add the command under a different name. */
|
||||
if (sdslen(argv[2]) != 0) {
|
||||
sds copy = sdsdup(argv[2]);
|
||||
|
||||
retval = dictAdd(server.commands, copy, cmd);
|
||||
if (retval != DICT_OK) {
|
||||
sdsfree(copy);
|
||||
err = "Target command name already exists"; goto loaderr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
for (j = 0; j < argc; j++)
|
||||
sdsfree(argv[j]);
|
||||
zfree(argv);
|
||||
sdsfree(line);
|
||||
}
|
||||
if (fp != stdin) fclose(fp);
|
||||
return;
|
||||
|
||||
loaderr:
|
||||
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
|
||||
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
|
||||
fprintf(stderr, ">>> '%s'\n", line);
|
||||
fprintf(stderr, "%s\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG command for remote configuration
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void configSetCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long long ll;
|
||||
redisAssert(c->argv[2]->encoding == REDIS_ENCODING_RAW);
|
||||
redisAssert(c->argv[3]->encoding == REDIS_ENCODING_RAW);
|
||||
o = c->argv[3];
|
||||
|
||||
if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
|
||||
zfree(server.dbfilename);
|
||||
server.dbfilename = zstrdup(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) {
|
||||
zfree(server.requirepass);
|
||||
server.requirepass = zstrdup(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = zstrdup(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.maxmemory = ll;
|
||||
if (server.maxmemory) freeMemoryIfNeeded();
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
|
||||
if (!strcasecmp(o->ptr,"volatile-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
} else if (!strcasecmp(o->ptr,"volatile-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
|
||||
} else if (!strcasecmp(o->ptr,"volatile-ttl")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
|
||||
} else if (!strcasecmp(o->ptr,"allkeys-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
|
||||
} else if (!strcasecmp(o->ptr,"allkeys-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
|
||||
} else if (!strcasecmp(o->ptr,"noeviction")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
|
||||
} else {
|
||||
goto badfmt;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll <= 0) goto badfmt;
|
||||
server.maxmemory_samples = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0 || ll > LONG_MAX) goto badfmt;
|
||||
server.maxidletime = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
|
||||
if (!strcasecmp(o->ptr,"no")) {
|
||||
server.appendfsync = APPENDFSYNC_NO;
|
||||
} else if (!strcasecmp(o->ptr,"everysec")) {
|
||||
server.appendfsync = APPENDFSYNC_EVERYSEC;
|
||||
} else if (!strcasecmp(o->ptr,"always")) {
|
||||
server.appendfsync = APPENDFSYNC_ALWAYS;
|
||||
} else {
|
||||
goto badfmt;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"no-appendfsync-on-rewrite")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.no_appendfsync_on_rewrite = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {
|
||||
int old = server.appendonly;
|
||||
int new = yesnotoi(o->ptr);
|
||||
|
||||
if (new == -1) goto badfmt;
|
||||
if (old != new) {
|
||||
if (new == 0) {
|
||||
stopAppendOnly();
|
||||
} else {
|
||||
if (startAppendOnly() == REDIS_ERR) {
|
||||
addReplyError(c,
|
||||
"Unable to turn on AOF. Check server logs.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
|
||||
/* Perform sanity check before setting the new config:
|
||||
* - Even number of args
|
||||
* - Seconds >= 1, changes >= 0 */
|
||||
if (vlen & 1) {
|
||||
sdsfreesplitres(v,vlen);
|
||||
goto badfmt;
|
||||
}
|
||||
for (j = 0; j < vlen; j++) {
|
||||
char *eptr;
|
||||
long val;
|
||||
|
||||
val = strtoll(v[j], &eptr, 10);
|
||||
if (eptr[0] != '\0' ||
|
||||
((j & 1) == 0 && val < 1) ||
|
||||
((j & 1) == 1 && val < 0)) {
|
||||
sdsfreesplitres(v,vlen);
|
||||
goto badfmt;
|
||||
}
|
||||
}
|
||||
/* Finally set the new config */
|
||||
resetServerSaveParams();
|
||||
for (j = 0; j < vlen; j += 2) {
|
||||
time_t seconds;
|
||||
int changes;
|
||||
|
||||
seconds = strtoll(v[j],NULL,10);
|
||||
changes = strtoll(v[j+1],NULL,10);
|
||||
appendServerSaveParams(seconds, changes);
|
||||
}
|
||||
sdsfreesplitres(v,vlen);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slave-serve-stale-data")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.repl_serve_stale_data = yn;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
return;
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
|
||||
badfmt: /* Bad format errors */
|
||||
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
|
||||
(char*)o->ptr,
|
||||
(char*)c->argv[2]->ptr);
|
||||
}
|
||||
|
||||
void configGetCommand(redisClient *c) {
|
||||
robj *o = c->argv[2];
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
char *pattern = o->ptr;
|
||||
char buf[128];
|
||||
int matches = 0;
|
||||
redisAssert(o->encoding == REDIS_ENCODING_RAW);
|
||||
|
||||
if (stringmatch(pattern,"dbfilename",0)) {
|
||||
addReplyBulkCString(c,"dbfilename");
|
||||
addReplyBulkCString(c,server.dbfilename);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"requirepass",0)) {
|
||||
addReplyBulkCString(c,"requirepass");
|
||||
addReplyBulkCString(c,server.requirepass);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"masterauth",0)) {
|
||||
addReplyBulkCString(c,"masterauth");
|
||||
addReplyBulkCString(c,server.masterauth);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"maxmemory",0)) {
|
||||
ll2string(buf,sizeof(buf),server.maxmemory);
|
||||
addReplyBulkCString(c,"maxmemory");
|
||||
addReplyBulkCString(c,buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"maxmemory-policy",0)) {
|
||||
char *s;
|
||||
|
||||
switch(server.maxmemory_policy) {
|
||||
case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break;
|
||||
case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break;
|
||||
case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break;
|
||||
case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break;
|
||||
case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break;
|
||||
case REDIS_MAXMEMORY_NO_EVICTION: s = "noeviction"; break;
|
||||
default: s = "unknown"; break; /* too harmless to panic */
|
||||
}
|
||||
addReplyBulkCString(c,"maxmemory-policy");
|
||||
addReplyBulkCString(c,s);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"maxmemory-samples",0)) {
|
||||
ll2string(buf,sizeof(buf),server.maxmemory_samples);
|
||||
addReplyBulkCString(c,"maxmemory-samples");
|
||||
addReplyBulkCString(c,buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"timeout",0)) {
|
||||
ll2string(buf,sizeof(buf),server.maxidletime);
|
||||
addReplyBulkCString(c,"timeout");
|
||||
addReplyBulkCString(c,buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"appendonly",0)) {
|
||||
addReplyBulkCString(c,"appendonly");
|
||||
addReplyBulkCString(c,server.appendonly ? "yes" : "no");
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"no-appendfsync-on-rewrite",0)) {
|
||||
addReplyBulkCString(c,"no-appendfsync-on-rewrite");
|
||||
addReplyBulkCString(c,server.no_appendfsync_on_rewrite ? "yes" : "no");
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"appendfsync",0)) {
|
||||
char *policy;
|
||||
|
||||
switch(server.appendfsync) {
|
||||
case APPENDFSYNC_NO: policy = "no"; break;
|
||||
case APPENDFSYNC_EVERYSEC: policy = "everysec"; break;
|
||||
case APPENDFSYNC_ALWAYS: policy = "always"; break;
|
||||
default: policy = "unknown"; break; /* too harmless to panic */
|
||||
}
|
||||
addReplyBulkCString(c,"appendfsync");
|
||||
addReplyBulkCString(c,policy);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"save",0)) {
|
||||
sds buf = sdsempty();
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
buf = sdscatprintf(buf,"%ld %d",
|
||||
server.saveparams[j].seconds,
|
||||
server.saveparams[j].changes);
|
||||
if (j != server.saveparamslen-1)
|
||||
buf = sdscatlen(buf," ",1);
|
||||
}
|
||||
addReplyBulkCString(c,"save");
|
||||
addReplyBulkCString(c,buf);
|
||||
sdsfree(buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"slave-serve-stale-data",0)) {
|
||||
addReplyBulkCString(c,"slave-serve-stale-data");
|
||||
addReplyBulkCString(c,server.repl_serve_stale_data ? "yes" : "no");
|
||||
matches++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,matches*2);
|
||||
}
|
||||
|
||||
void configCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc != 4) goto badarity;
|
||||
configSetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"get")) {
|
||||
if (c->argc != 3) goto badarity;
|
||||
configGetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
|
||||
if (c->argc != 2) goto badarity;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"CONFIG subcommand must be one of GET, SET, RESETSTAT");
|
||||
}
|
||||
return;
|
||||
|
||||
badarity:
|
||||
addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
|
||||
(char*) c->argv[1]->ptr);
|
||||
}
|
||||
+21
-2
@@ -5,8 +5,17 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
/* test for malloc_size() */
|
||||
#ifdef __APPLE__
|
||||
/* Use tcmalloc's malloc_size() when available.
|
||||
* When tcmalloc is used, native OSX malloc_size() may never be used because
|
||||
* this expects a different allocation scheme. Therefore, *exclusively* use
|
||||
* either tcmalloc or OSX's malloc_size()! */
|
||||
#if defined(USE_TCMALLOC)
|
||||
#include <google/tcmalloc.h>
|
||||
#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6
|
||||
#define HAVE_MALLOC_SIZE 1
|
||||
#define redis_malloc_size(p) tc_malloc_size(p)
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#include <malloc/malloc.h>
|
||||
#define HAVE_MALLOC_SIZE 1
|
||||
#define redis_malloc_size(p) malloc_size(p)
|
||||
@@ -21,6 +30,16 @@
|
||||
#define redis_stat stat
|
||||
#endif
|
||||
|
||||
/* test for proc filesystem */
|
||||
#ifdef __linux__
|
||||
#define HAVE_PROCFS 1
|
||||
#endif
|
||||
|
||||
/* test for task_info() */
|
||||
#if defined(__APPLE__)
|
||||
#define HAVE_TASKINFO 1
|
||||
#endif
|
||||
|
||||
/* test for backtrace() */
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
@@ -0,0 +1,543 @@
|
||||
#include "redis.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
robj *lookupKey(redisDb *db, robj *key) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
if (de) {
|
||||
robj *val = dictGetEntryVal(de);
|
||||
|
||||
/* Update the access time for the aging algorithm.
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1)
|
||||
val->lru = server.lruclock;
|
||||
|
||||
if (server.vm_enabled) {
|
||||
if (val->storage == REDIS_VM_MEMORY ||
|
||||
val->storage == REDIS_VM_SWAPPING)
|
||||
{
|
||||
/* If we were swapping the object out, cancel the operation */
|
||||
if (val->storage == REDIS_VM_SWAPPING)
|
||||
vmCancelThreadedIOJob(val);
|
||||
} else {
|
||||
int notify = (val->storage == REDIS_VM_LOADING);
|
||||
|
||||
/* Our value was swapped on disk. Bring it at home. */
|
||||
redisAssert(val->type == REDIS_VMPOINTER);
|
||||
val = vmLoadObject(val);
|
||||
dictGetEntryVal(de) = val;
|
||||
|
||||
/* Clients blocked by the VM subsystem may be waiting for
|
||||
* this key... */
|
||||
if (notify) handleClientsBlockedOnSwappedKey(db,key);
|
||||
}
|
||||
}
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
} else {
|
||||
server.stat_keyspace_misses++;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
robj *lookupKeyRead(redisDb *db, robj *key) {
|
||||
expireIfNeeded(db,key);
|
||||
return lookupKey(db,key);
|
||||
}
|
||||
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
expireIfNeeded(db,key);
|
||||
return lookupKey(db,key);
|
||||
}
|
||||
|
||||
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
|
||||
robj *o = lookupKeyRead(c->db, key);
|
||||
if (!o) addReply(c,reply);
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) {
|
||||
robj *o = lookupKeyWrite(c->db, key);
|
||||
if (!o) addReply(c,reply);
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Add the key to the DB. If the key already exists REDIS_ERR is returned,
|
||||
* otherwise REDIS_OK is returned, and the caller should increment the
|
||||
* refcount of 'val'. */
|
||||
int dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
/* Perform a lookup before adding the key, as we need to copy the
|
||||
* key value. */
|
||||
if (dictFind(db->dict, key->ptr) != NULL) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictAdd(db->dict, copy, val);
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the key does not exist, this is just like dbAdd(). Otherwise
|
||||
* the value associated to the key is replaced with the new one.
|
||||
*
|
||||
* On update (key already existed) 0 is returned. Otherwise 1. */
|
||||
int dbReplace(redisDb *db, robj *key, robj *val) {
|
||||
if (dictFind(db->dict,key->ptr) == NULL) {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictAdd(db->dict, copy, val);
|
||||
return 1;
|
||||
} else {
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int dbExists(redisDb *db, robj *key) {
|
||||
return dictFind(db->dict,key->ptr) != NULL;
|
||||
}
|
||||
|
||||
/* Return a random key, in form of a Redis object.
|
||||
* If there are no keys, NULL is returned.
|
||||
*
|
||||
* The function makes sure to return keys not already expired. */
|
||||
robj *dbRandomKey(redisDb *db) {
|
||||
struct dictEntry *de;
|
||||
|
||||
while(1) {
|
||||
sds key;
|
||||
robj *keyobj;
|
||||
|
||||
de = dictGetRandomKey(db->dict);
|
||||
if (de == NULL) return NULL;
|
||||
|
||||
key = dictGetEntryKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (dictFind(db->expires,key)) {
|
||||
if (expireIfNeeded(db,keyobj)) {
|
||||
decrRefCount(keyobj);
|
||||
continue; /* search for another key. This expired. */
|
||||
}
|
||||
}
|
||||
return keyobj;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete a key, value, and associated expiration entry if any, from the DB */
|
||||
int dbDelete(redisDb *db, robj *key) {
|
||||
/* If VM is enabled make sure to awake waiting clients for this key:
|
||||
* deleting the key will kill the I/O thread bringing the key from swap
|
||||
* to memory, so the client will never be notified and unblocked if we
|
||||
* don't do it now. */
|
||||
if (server.vm_enabled) handleClientsBlockedOnSwappedKey(db,key);
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||||
return dictDelete(db->dict,key->ptr) == DICT_OK;
|
||||
}
|
||||
|
||||
/* Empty the whole database */
|
||||
long long emptyDb() {
|
||||
int j;
|
||||
long long removed = 0;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
removed += dictSize(server.db[j].dict);
|
||||
dictEmpty(server.db[j].dict);
|
||||
dictEmpty(server.db[j].expires);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
int selectDb(redisClient *c, int id) {
|
||||
if (id < 0 || id >= server.dbnum)
|
||||
return REDIS_ERR;
|
||||
c->db = &server.db[id];
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Type agnostic commands operating on the key space
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void flushdbCommand(redisClient *c) {
|
||||
server.dirty += dictSize(c->db->dict);
|
||||
touchWatchedKeysOnFlush(c->db->id);
|
||||
dictEmpty(c->db->dict);
|
||||
dictEmpty(c->db->expires);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void flushallCommand(redisClient *c) {
|
||||
touchWatchedKeysOnFlush(-1);
|
||||
server.dirty += emptyDb();
|
||||
addReply(c,shared.ok);
|
||||
if (server.bgsavechildpid != -1) {
|
||||
kill(server.bgsavechildpid,SIGKILL);
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
rdbSave(server.dbfilename);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
void delCommand(redisClient *c) {
|
||||
int deleted = 0, j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
touchWatchedKey(c->db,c->argv[j]);
|
||||
server.dirty++;
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
void existsCommand(redisClient *c) {
|
||||
expireIfNeeded(c->db,c->argv[1]);
|
||||
if (dbExists(c->db,c->argv[1])) {
|
||||
addReply(c, shared.cone);
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
}
|
||||
}
|
||||
|
||||
void selectCommand(redisClient *c) {
|
||||
int id = atoi(c->argv[1]->ptr);
|
||||
|
||||
if (selectDb(c,id) == REDIS_ERR) {
|
||||
addReplyError(c,"invalid DB index");
|
||||
} else {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
}
|
||||
|
||||
void randomkeyCommand(redisClient *c) {
|
||||
robj *key;
|
||||
|
||||
if ((key = dbRandomKey(c->db)) == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyBulk(c,key);
|
||||
decrRefCount(key);
|
||||
}
|
||||
|
||||
void keysCommand(redisClient *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
sds pattern = c->argv[1]->ptr;
|
||||
int plen = sdslen(pattern), allkeys;
|
||||
unsigned long numkeys = 0;
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
|
||||
di = dictGetIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = dictGetEntryKey(de);
|
||||
robj *keyobj;
|
||||
|
||||
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (expireIfNeeded(c->db,keyobj) == 0) {
|
||||
addReplyBulk(c,keyobj);
|
||||
numkeys++;
|
||||
}
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c,replylen,numkeys);
|
||||
}
|
||||
|
||||
void dbsizeCommand(redisClient *c) {
|
||||
addReplyLongLong(c,dictSize(c->db->dict));
|
||||
}
|
||||
|
||||
void lastsaveCommand(redisClient *c) {
|
||||
addReplyLongLong(c,server.lastsave);
|
||||
}
|
||||
|
||||
void typeCommand(redisClient *c) {
|
||||
robj *o;
|
||||
char *type;
|
||||
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o == NULL) {
|
||||
type = "none";
|
||||
} else {
|
||||
switch(o->type) {
|
||||
case REDIS_STRING: type = "string"; break;
|
||||
case REDIS_LIST: type = "list"; break;
|
||||
case REDIS_SET: type = "set"; break;
|
||||
case REDIS_ZSET: type = "zset"; break;
|
||||
case REDIS_HASH: type = "hash"; break;
|
||||
default: type = "unknown"; break;
|
||||
}
|
||||
}
|
||||
addReplyStatus(c,type);
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
return;
|
||||
}
|
||||
if (rdbSave(server.dbfilename) == REDIS_OK) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
void bgsaveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
return;
|
||||
}
|
||||
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdownCommand(redisClient *c) {
|
||||
if (prepareForShutdown() == REDIS_OK)
|
||||
exit(0);
|
||||
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
|
||||
}
|
||||
|
||||
void renameGenericCommand(redisClient *c, int nx) {
|
||||
robj *o;
|
||||
|
||||
/* To use the same key as src and dst is probably an error */
|
||||
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.sameobjecterr);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)
|
||||
return;
|
||||
|
||||
incrRefCount(o);
|
||||
if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
|
||||
if (nx) {
|
||||
decrRefCount(o);
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
dbReplace(c->db,c->argv[2],o);
|
||||
}
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
addReply(c,nx ? shared.cone : shared.ok);
|
||||
}
|
||||
|
||||
void renameCommand(redisClient *c) {
|
||||
renameGenericCommand(c,0);
|
||||
}
|
||||
|
||||
void renamenxCommand(redisClient *c) {
|
||||
renameGenericCommand(c,1);
|
||||
}
|
||||
|
||||
void moveCommand(redisClient *c) {
|
||||
robj *o;
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
|
||||
/* Obtain source and target DB pointers */
|
||||
src = c->db;
|
||||
srcid = c->db->id;
|
||||
if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
|
||||
addReply(c,shared.outofrangeerr);
|
||||
return;
|
||||
}
|
||||
dst = c->db;
|
||||
selectDb(c,srcid); /* Back to the source DB */
|
||||
|
||||
/* If the user is moving using as target the same
|
||||
* DB as the source DB it is probably an error. */
|
||||
if (src == dst) {
|
||||
addReply(c,shared.sameobjecterr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if the element exists and get a reference */
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (!o) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Try to add the element to the target DB */
|
||||
if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
incrRefCount(o);
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
dbDelete(src,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
int removeExpire(redisDb *db, robj *key) {
|
||||
/* An expire may only be removed if there is a corresponding entry in the
|
||||
* main dict. Otherwise, the key will never be freed. */
|
||||
redisAssert(dictFind(db->dict,key->ptr) != NULL);
|
||||
return dictDelete(db->expires,key->ptr) == DICT_OK;
|
||||
}
|
||||
|
||||
void setExpire(redisDb *db, robj *key, time_t when) {
|
||||
dictEntry *de;
|
||||
|
||||
/* Reuse the sds from the main dict in the expire dict */
|
||||
de = dictFind(db->dict,key->ptr);
|
||||
redisAssert(de != NULL);
|
||||
dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
|
||||
}
|
||||
|
||||
/* Return the expire time of the specified key, or -1 if no expire
|
||||
* is associated with this key (i.e. the key is non volatile) */
|
||||
time_t getExpire(redisDb *db, robj *key) {
|
||||
dictEntry *de;
|
||||
|
||||
/* No expire? return ASAP */
|
||||
if (dictSize(db->expires) == 0 ||
|
||||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
|
||||
|
||||
/* The entry was found in the expire dict, this means it should also
|
||||
* be present in the main dict (safety check). */
|
||||
redisAssert(dictFind(db->dict,key->ptr) != NULL);
|
||||
return (time_t) dictGetEntryVal(de);
|
||||
}
|
||||
|
||||
/* Propagate expires into slaves and the AOF file.
|
||||
* When a key expires in the master, a DEL operation for this key is sent
|
||||
* to all the slaves and the AOF file if enabled.
|
||||
*
|
||||
* This way the key expiry is centralized in one place, and since both
|
||||
* AOF and the master->slave link guarantee operation ordering, everything
|
||||
* will be consistent even if we allow write operations against expiring
|
||||
* keys. */
|
||||
void propagateExpire(redisDb *db, robj *key) {
|
||||
robj *argv[2];
|
||||
|
||||
argv[0] = createStringObject("DEL",3);
|
||||
argv[1] = key;
|
||||
incrRefCount(key);
|
||||
|
||||
if (server.appendonly)
|
||||
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
|
||||
if (listLength(server.slaves))
|
||||
replicationFeedSlaves(server.slaves,db->id,argv,2);
|
||||
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
time_t when = getExpire(db,key);
|
||||
|
||||
/* If we are running in the context of a slave, return ASAP:
|
||||
* the slave key expiration is controlled by the master that will
|
||||
* send us synthesized DEL operations for expired keys.
|
||||
*
|
||||
* Still we try to return the right information to the caller,
|
||||
* that is, 0 if we think the key should be still valid, 1 if
|
||||
* we think the key is expired at this time. */
|
||||
if (server.masterhost != NULL) {
|
||||
return time(NULL) > when;
|
||||
}
|
||||
|
||||
if (when < 0) return 0;
|
||||
|
||||
/* Return when this key has not expired */
|
||||
if (time(NULL) <= when) return 0;
|
||||
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
propagateExpire(db,key);
|
||||
return dbDelete(db,key);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
|
||||
dictEntry *de;
|
||||
long seconds;
|
||||
|
||||
if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
|
||||
|
||||
seconds -= offset;
|
||||
|
||||
de = dictFind(c->db->dict,key->ptr);
|
||||
if (de == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
if (seconds <= 0) {
|
||||
if (dbDelete(c->db,key)) server.dirty++;
|
||||
addReply(c, shared.cone);
|
||||
touchWatchedKey(c->db,key);
|
||||
return;
|
||||
} else {
|
||||
time_t when = time(NULL)+seconds;
|
||||
setExpire(c->db,key,when);
|
||||
addReply(c,shared.cone);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void expireCommand(redisClient *c) {
|
||||
expireGenericCommand(c,c->argv[1],c->argv[2],0);
|
||||
}
|
||||
|
||||
void expireatCommand(redisClient *c) {
|
||||
expireGenericCommand(c,c->argv[1],c->argv[2],time(NULL));
|
||||
}
|
||||
|
||||
void ttlCommand(redisClient *c) {
|
||||
time_t expire, ttl = -1;
|
||||
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
if (expire != -1) {
|
||||
ttl = (expire-time(NULL));
|
||||
if (ttl < 0) ttl = -1;
|
||||
}
|
||||
addReplyLongLong(c,(long long)ttl);
|
||||
}
|
||||
|
||||
void persistCommand(redisClient *c) {
|
||||
dictEntry *de;
|
||||
|
||||
de = dictFind(c->db->dict,c->argv[1]->ptr);
|
||||
if (de == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
#include "redis.h"
|
||||
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
|
||||
/* Compute the sha1 of string at 's' with 'len' bytes long.
|
||||
* The SHA1 is then xored againt the string pointed by digest.
|
||||
* Since xor is commutative, this operation is used in order to
|
||||
* "add" digests relative to unordered elements.
|
||||
*
|
||||
* So digest(a,b,c,d) will be the same of digest(b,a,c,d) */
|
||||
void xorDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
SHA1_CTX ctx;
|
||||
unsigned char hash[20], *s = ptr;
|
||||
int j;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx,s,len);
|
||||
SHA1Final(hash,&ctx);
|
||||
|
||||
for (j = 0; j < 20; j++)
|
||||
digest[j] ^= hash[j];
|
||||
}
|
||||
|
||||
void xorObjectDigest(unsigned char *digest, robj *o) {
|
||||
o = getDecodedObject(o);
|
||||
xorDigest(digest,o->ptr,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
}
|
||||
|
||||
/* This function instead of just computing the SHA1 and xoring it
|
||||
* against diget, also perform the digest of "digest" itself and
|
||||
* replace the old value with the new one.
|
||||
*
|
||||
* So the final digest will be:
|
||||
*
|
||||
* digest = SHA1(digest xor SHA1(data))
|
||||
*
|
||||
* This function is used every time we want to preserve the order so
|
||||
* that digest(a,b,c,d) will be different than digest(b,c,d,a)
|
||||
*
|
||||
* Also note that mixdigest("foo") followed by mixdigest("bar")
|
||||
* will lead to a different digest compared to "fo", "obar".
|
||||
*/
|
||||
void mixDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
SHA1_CTX ctx;
|
||||
char *s = ptr;
|
||||
|
||||
xorDigest(digest,s,len);
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx,digest,20);
|
||||
SHA1Final(digest,&ctx);
|
||||
}
|
||||
|
||||
void mixObjectDigest(unsigned char *digest, robj *o) {
|
||||
o = getDecodedObject(o);
|
||||
mixDigest(digest,o->ptr,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
}
|
||||
|
||||
/* Compute the dataset digest. Since keys, sets elements, hashes elements
|
||||
* are not ordered, we use a trick: every aggregate digest is the xor
|
||||
* of the digests of their elements. This way the order will not change
|
||||
* the result. For list instead we use a feedback entering the output digest
|
||||
* as input in order to ensure that a different ordered list will result in
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
char buf[128];
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
uint32_t aux;
|
||||
|
||||
memset(final,0,20); /* Start with a clean result */
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
|
||||
if (dictSize(db->dict) == 0) continue;
|
||||
di = dictGetIterator(db->dict);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
aux = htonl(j);
|
||||
mixDigest(final,&aux,sizeof(aux));
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key;
|
||||
robj *keyobj, *o;
|
||||
time_t expiretime;
|
||||
|
||||
memset(digest,0,20); /* This key-val digest */
|
||||
key = dictGetEntryKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
mixDigest(digest,key,sdslen(key));
|
||||
|
||||
/* Make sure the key is loaded if VM is active */
|
||||
o = lookupKeyRead(db,keyobj);
|
||||
|
||||
aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
expiretime = getExpire(db,keyobj);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == REDIS_STRING) {
|
||||
mixObjectDigest(digest,o);
|
||||
} else if (o->type == REDIS_LIST) {
|
||||
listTypeIterator *li = listTypeInitIterator(o,0,REDIS_TAIL);
|
||||
listTypeEntry entry;
|
||||
while(listTypeNext(li,&entry)) {
|
||||
robj *eleobj = listTypeGet(&entry);
|
||||
mixObjectDigest(digest,eleobj);
|
||||
decrRefCount(eleobj);
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
} else if (o->type == REDIS_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
robj *ele;
|
||||
while((ele = setTypeNext(si)) != NULL) {
|
||||
xorObjectDigest(digest,ele);
|
||||
decrRefCount(ele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
} else if (o->type == REDIS_ZSET) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *eleobj = dictGetEntryKey(de);
|
||||
double *score = dictGetEntryVal(de);
|
||||
unsigned char eledigest[20];
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",*score);
|
||||
memset(eledigest,0,20);
|
||||
mixObjectDigest(eledigest,eleobj);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (o->type == REDIS_HASH) {
|
||||
hashTypeIterator *hi;
|
||||
robj *obj;
|
||||
|
||||
hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != REDIS_ERR) {
|
||||
unsigned char eledigest[20];
|
||||
|
||||
memset(eledigest,0,20);
|
||||
obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
|
||||
mixObjectDigest(eledigest,obj);
|
||||
decrRefCount(obj);
|
||||
obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
|
||||
mixObjectDigest(eledigest,obj);
|
||||
decrRefCount(obj);
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
} else {
|
||||
redisPanic("Unknown object type");
|
||||
}
|
||||
/* If the key has an expire, add it to the mix */
|
||||
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
|
||||
/* We can finally xor the key-val digest to the final digest */
|
||||
xorDigest(final,digest,20);
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
}
|
||||
|
||||
void debugCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
if (rdbSave(server.dbfilename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
emptyDb();
|
||||
if (rdbLoad(server.dbfilename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
|
||||
emptyDb();
|
||||
if (loadAppendOnlyFile(server.appendfilename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) {
|
||||
dictEntry *de = dictFind(c->db->dict,c->argv[2]->ptr);
|
||||
robj *val;
|
||||
|
||||
if (!de) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = dictGetEntryVal(de);
|
||||
if (!server.vm_enabled || (val->storage == REDIS_VM_MEMORY ||
|
||||
val->storage == REDIS_VM_SWAPPING)) {
|
||||
char *strenc;
|
||||
|
||||
strenc = strEncoding(val->encoding);
|
||||
addReplyStatusFormat(c,
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%lu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
} else {
|
||||
vmpointer *vp = (vmpointer*) val;
|
||||
addReplyStatusFormat(c,
|
||||
"Value swapped at: page %llu "
|
||||
"using %llu pages",
|
||||
(unsigned long long) vp->page,
|
||||
(unsigned long long) vp->usedpages);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) {
|
||||
lookupKeyRead(c->db,c->argv[2]);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"swapout") && c->argc == 3) {
|
||||
dictEntry *de = dictFind(c->db->dict,c->argv[2]->ptr);
|
||||
robj *val;
|
||||
vmpointer *vp;
|
||||
|
||||
if (!server.vm_enabled) {
|
||||
addReplyError(c,"Virtual Memory is disabled");
|
||||
return;
|
||||
}
|
||||
if (!de) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = dictGetEntryVal(de);
|
||||
/* Swap it */
|
||||
if (val->storage != REDIS_VM_MEMORY) {
|
||||
addReplyError(c,"This key is not in memory");
|
||||
} else if (val->refcount != 1) {
|
||||
addReplyError(c,"Object is shared");
|
||||
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
|
||||
dictGetEntryVal(de) = vp;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
|
||||
long keys, j;
|
||||
robj *key, *val;
|
||||
char buf[128];
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
|
||||
return;
|
||||
for (j = 0; j < keys; j++) {
|
||||
snprintf(buf,sizeof(buf),"key:%lu",j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
if (lookupKeyRead(c->db,key) != NULL) {
|
||||
decrRefCount(key);
|
||||
continue;
|
||||
}
|
||||
snprintf(buf,sizeof(buf),"value:%lu",j);
|
||||
val = createStringObject(buf,strlen(buf));
|
||||
dbAdd(c->db,key,val);
|
||||
decrRefCount(key);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
|
||||
unsigned char digest[20];
|
||||
sds d = sdsempty();
|
||||
int j;
|
||||
|
||||
computeDatasetDigest(digest);
|
||||
for (j = 0; j < 20; j++)
|
||||
d = sdscatprintf(d, "%02x",digest[j]);
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
|
||||
}
|
||||
}
|
||||
|
||||
void _redisAssert(char *estr, char *file, int line) {
|
||||
redisLog(REDIS_WARNING,"=== ASSERTION FAILED ===");
|
||||
redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
|
||||
#ifdef HAVE_BACKTRACE
|
||||
redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
|
||||
*((char*)-1) = 'x';
|
||||
#endif
|
||||
}
|
||||
|
||||
void _redisPanic(char *msg, char *file, int line) {
|
||||
redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue");
|
||||
redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line);
|
||||
#ifdef HAVE_BACKTRACE
|
||||
redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
|
||||
*((char*)-1) = 'x';
|
||||
#endif
|
||||
}
|
||||
+71
-87
@@ -42,6 +42,7 @@
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <sys/time.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "dict.h"
|
||||
#include "zmalloc.h"
|
||||
@@ -49,35 +50,13 @@
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
||||
* enable/disable resizing of the hash table as needed. This is very important
|
||||
* for Redis, as we use copy-on-write and don't want to move too much memory
|
||||
* around when there is a child performing saving operations. */
|
||||
* around when there is a child performing saving operations.
|
||||
*
|
||||
* Note that even when dict_can_resize is set to 0, not all resizes are
|
||||
* prevented: an hash table is still allowed to grow if the ratio between
|
||||
* the number of elements and the buckets > dict_force_resize_ratio. */
|
||||
static int dict_can_resize = 1;
|
||||
|
||||
/* ---------------------------- Utility funcitons --------------------------- */
|
||||
|
||||
static void _dictPanic(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "\nDICT LIBRARY PANIC: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n\n");
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* ------------------------- Heap Management Wrappers------------------------ */
|
||||
|
||||
static void *_dictAlloc(size_t size)
|
||||
{
|
||||
void *p = zmalloc(size);
|
||||
if (p == NULL)
|
||||
_dictPanic("Out of memory");
|
||||
return p;
|
||||
}
|
||||
|
||||
static void _dictFree(void *ptr) {
|
||||
zfree(ptr);
|
||||
}
|
||||
static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
/* -------------------------- private prototypes ---------------------------- */
|
||||
|
||||
@@ -116,6 +95,15 @@ unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* And a case insensitive version */
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) {
|
||||
unsigned int hash = 5381;
|
||||
|
||||
while (len--)
|
||||
hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* ----------------------------- API implementation ------------------------- */
|
||||
|
||||
/* Reset an hashtable already initialized with ht_init().
|
||||
@@ -132,7 +120,7 @@ static void _dictReset(dictht *ht)
|
||||
dict *dictCreate(dictType *type,
|
||||
void *privDataPtr)
|
||||
{
|
||||
dict *d = _dictAlloc(sizeof(*d));
|
||||
dict *d = zmalloc(sizeof(*d));
|
||||
|
||||
_dictInit(d,type,privDataPtr);
|
||||
return d;
|
||||
@@ -152,7 +140,7 @@ int _dictInit(dict *d, dictType *type,
|
||||
}
|
||||
|
||||
/* Resize the table to the minimal size that contains all the elements,
|
||||
* but with the invariant of a USER/BUCKETS ration near to <= 1 */
|
||||
* but with the invariant of a USER/BUCKETS ratio near to <= 1 */
|
||||
int dictResize(dict *d)
|
||||
{
|
||||
int minimal;
|
||||
@@ -175,14 +163,12 @@ int dictExpand(dict *d, unsigned long size)
|
||||
if (dictIsRehashing(d) || d->ht[0].used > size)
|
||||
return DICT_ERR;
|
||||
|
||||
/* Allocate the new hashtable and initialize all pointers to NULL */
|
||||
n.size = realsize;
|
||||
n.sizemask = realsize-1;
|
||||
n.table = _dictAlloc(realsize*sizeof(dictEntry*));
|
||||
n.table = zcalloc(realsize*sizeof(dictEntry*));
|
||||
n.used = 0;
|
||||
|
||||
/* Initialize all the pointers to NULL */
|
||||
memset(n.table, 0, realsize*sizeof(dictEntry*));
|
||||
|
||||
/* Is this the first initialization? If so it's not really a rehashing
|
||||
* we just set the first hash table so that it can accept keys. */
|
||||
if (d->ht[0].table == NULL) {
|
||||
@@ -208,7 +194,7 @@ int dictRehash(dict *d, int n) {
|
||||
|
||||
/* Check if we already rehashed the whole table... */
|
||||
if (d->ht[0].used == 0) {
|
||||
_dictFree(d->ht[0].table);
|
||||
zfree(d->ht[0].table);
|
||||
d->ht[0] = d->ht[1];
|
||||
_dictReset(&d->ht[1]);
|
||||
d->rehashidx = -1;
|
||||
@@ -285,7 +271,7 @@ int dictAdd(dict *d, void *key, void *val)
|
||||
|
||||
/* Allocates the memory and stores key */
|
||||
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
|
||||
entry = _dictAlloc(sizeof(*entry));
|
||||
entry = zmalloc(sizeof(*entry));
|
||||
entry->next = ht->table[index];
|
||||
ht->table[index] = entry;
|
||||
ht->used++;
|
||||
@@ -348,7 +334,7 @@ static int dictGenericDelete(dict *d, const void *key, int nofree)
|
||||
dictFreeEntryKey(d, he);
|
||||
dictFreeEntryVal(d, he);
|
||||
}
|
||||
_dictFree(he);
|
||||
zfree(he);
|
||||
d->ht[table].used--;
|
||||
return DICT_OK;
|
||||
}
|
||||
@@ -382,13 +368,13 @@ int _dictClear(dict *d, dictht *ht)
|
||||
nextHe = he->next;
|
||||
dictFreeEntryKey(d, he);
|
||||
dictFreeEntryVal(d, he);
|
||||
_dictFree(he);
|
||||
zfree(he);
|
||||
ht->used--;
|
||||
he = nextHe;
|
||||
}
|
||||
}
|
||||
/* Free the table and the allocated cache structure */
|
||||
_dictFree(ht->table);
|
||||
zfree(ht->table);
|
||||
/* Re-initialize the table */
|
||||
_dictReset(ht);
|
||||
return DICT_OK; /* never fails */
|
||||
@@ -399,7 +385,7 @@ void dictRelease(dict *d)
|
||||
{
|
||||
_dictClear(d,&d->ht[0]);
|
||||
_dictClear(d,&d->ht[1]);
|
||||
_dictFree(d);
|
||||
zfree(d);
|
||||
}
|
||||
|
||||
dictEntry *dictFind(dict *d, const void *key)
|
||||
@@ -432,7 +418,7 @@ void *dictFetchValue(dict *d, const void *key) {
|
||||
|
||||
dictIterator *dictGetIterator(dict *d)
|
||||
{
|
||||
dictIterator *iter = _dictAlloc(sizeof(*iter));
|
||||
dictIterator *iter = zmalloc(sizeof(*iter));
|
||||
|
||||
iter->d = d;
|
||||
iter->table = 0;
|
||||
@@ -475,7 +461,7 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
void dictReleaseIterator(dictIterator *iter)
|
||||
{
|
||||
if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--;
|
||||
_dictFree(iter);
|
||||
zfree(iter);
|
||||
}
|
||||
|
||||
/* Return a random entry from the hash table. Useful to
|
||||
@@ -522,14 +508,23 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
/* Expand the hash table if needed */
|
||||
static int _dictExpandIfNeeded(dict *d)
|
||||
{
|
||||
/* If the hash table is empty expand it to the intial size,
|
||||
* if the table is "full" dobule its size. */
|
||||
/* Incremental rehashing already in progress. Return. */
|
||||
if (dictIsRehashing(d)) return DICT_OK;
|
||||
if (d->ht[0].size == 0)
|
||||
return dictExpand(d, DICT_HT_INITIAL_SIZE);
|
||||
if (d->ht[0].used >= d->ht[0].size && dict_can_resize)
|
||||
|
||||
/* If the hash table is empty expand it to the intial size. */
|
||||
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
|
||||
|
||||
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
|
||||
* table (global setting) or we should avoid it but the ratio between
|
||||
* elements/buckets is over the "safe" threshold, we resize doubling
|
||||
* the number of buckets. */
|
||||
if (d->ht[0].used >= d->ht[0].size &&
|
||||
(dict_can_resize ||
|
||||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
|
||||
{
|
||||
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
|
||||
d->ht[0].size : d->ht[0].used)*2);
|
||||
}
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
@@ -644,6 +639,12 @@ void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
/* The following are just example hash table types implementations.
|
||||
* Not useful for Redis so they are commented out.
|
||||
*/
|
||||
|
||||
/* ----------------------- StringCopy Hash Table Type ------------------------*/
|
||||
|
||||
static unsigned int _dictStringCopyHTHashFunction(const void *key)
|
||||
@@ -651,10 +652,10 @@ static unsigned int _dictStringCopyHTHashFunction(const void *key)
|
||||
return dictGenHashFunction(key, strlen(key));
|
||||
}
|
||||
|
||||
static void *_dictStringCopyHTKeyDup(void *privdata, const void *key)
|
||||
static void *_dictStringDup(void *privdata, const void *key)
|
||||
{
|
||||
int len = strlen(key);
|
||||
char *copy = _dictAlloc(len+1);
|
||||
char *copy = zmalloc(len+1);
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
memcpy(copy, key, len);
|
||||
@@ -662,17 +663,6 @@ static void *_dictStringCopyHTKeyDup(void *privdata, const void *key)
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void *_dictStringKeyValCopyHTValDup(void *privdata, const void *val)
|
||||
{
|
||||
int len = strlen(val);
|
||||
char *copy = _dictAlloc(len+1);
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
memcpy(copy, val, len);
|
||||
copy[len] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
|
||||
const void *key2)
|
||||
{
|
||||
@@ -681,47 +671,41 @@ static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
|
||||
return strcmp(key1, key2) == 0;
|
||||
}
|
||||
|
||||
static void _dictStringCopyHTKeyDestructor(void *privdata, void *key)
|
||||
static void _dictStringDestructor(void *privdata, void *key)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
_dictFree((void*)key); /* ATTENTION: const cast */
|
||||
}
|
||||
|
||||
static void _dictStringKeyValCopyHTValDestructor(void *privdata, void *val)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
_dictFree((void*)val); /* ATTENTION: const cast */
|
||||
zfree(key);
|
||||
}
|
||||
|
||||
dictType dictTypeHeapStringCopyKey = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringCopyHTKeyDup, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringCopyHTKeyDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringDup, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* This is like StringCopy but does not auto-duplicate the key.
|
||||
* It's used for intepreter's shared strings. */
|
||||
dictType dictTypeHeapStrings = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringCopyHTKeyDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* This is like StringCopy but also automatically handle dynamic
|
||||
* allocated C strings as values. */
|
||||
dictType dictTypeHeapStringCopyKeyValue = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringCopyHTKeyDup, /* key dup */
|
||||
_dictStringKeyValCopyHTValDup, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringCopyHTKeyDestructor, /* key destructor */
|
||||
_dictStringKeyValCopyHTValDestructor, /* val destructor */
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringDup, /* key dup */
|
||||
_dictStringDup, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringDestructor, /* key destructor */
|
||||
_dictStringDestructor, /* val destructor */
|
||||
};
|
||||
#endif
|
||||
@@ -137,6 +137,7 @@ void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
void dictPrintStats(dict *d);
|
||||
unsigned int dictGenHashFunction(const unsigned char *buf, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d);
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef _REDIS_FMACRO_H
|
||||
#define _REDIS_FMACRO_H
|
||||
|
||||
#define _BSD_SOURCE
|
||||
|
||||
#ifdef __linux__
|
||||
#define _XOPEN_SOURCE 700
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
#define _LARGEFILE_SOURCE
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#endif
|
||||
+608
@@ -0,0 +1,608 @@
|
||||
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
|
||||
static char *commandGroups[] = {
|
||||
"generic",
|
||||
"string",
|
||||
"list",
|
||||
"set",
|
||||
"sorted_set",
|
||||
"hash",
|
||||
"pubsub",
|
||||
"transactions",
|
||||
"connection",
|
||||
"server"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
char *name;
|
||||
char *params;
|
||||
char *summary;
|
||||
int group;
|
||||
char *since;
|
||||
} commandHelp[] = {
|
||||
{ "APPEND",
|
||||
"key value",
|
||||
"Append a value to a key",
|
||||
1,
|
||||
"1.3.3" },
|
||||
{ "AUTH",
|
||||
"password",
|
||||
"Authenticate to the server",
|
||||
8,
|
||||
"0.08" },
|
||||
{ "BGREWRITEAOF",
|
||||
"-",
|
||||
"Asynchronously rewrite the append-only file",
|
||||
9,
|
||||
"1.07" },
|
||||
{ "BGSAVE",
|
||||
"-",
|
||||
"Asynchronously save the dataset to disk",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "BLPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the first element in a list, or block until one is available",
|
||||
2,
|
||||
"1.3.1" },
|
||||
{ "BRPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the last element in a list, or block until one is available",
|
||||
2,
|
||||
"1.3.1" },
|
||||
{ "CONFIG GET",
|
||||
"parameter",
|
||||
"Get the value of a configuration parameter",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "CONFIG SET",
|
||||
"parameter value",
|
||||
"Set a configuration parameter to the given value",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "DBSIZE",
|
||||
"-",
|
||||
"Return the number of keys in the selected database",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "DEBUG OBJECT",
|
||||
"key",
|
||||
"Get debugging information about a key",
|
||||
9,
|
||||
"0.101" },
|
||||
{ "DEBUG SEGFAULT",
|
||||
"-",
|
||||
"Make the server crash",
|
||||
9,
|
||||
"0.101" },
|
||||
{ "DECR",
|
||||
"key decrement",
|
||||
"Decrement the integer value of a key by one",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "DECRBY",
|
||||
"key decrement",
|
||||
"Decrement the integer value of a key by the given number",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "DEL",
|
||||
"key [key ...]",
|
||||
"Delete a key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "DISCARD",
|
||||
"-",
|
||||
"Discard all commands issued after MULTI",
|
||||
7,
|
||||
"1.3.3" },
|
||||
{ "ECHO",
|
||||
"message",
|
||||
"Echo the given string",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "EXEC",
|
||||
"-",
|
||||
"Execute all commands issued after MULTI",
|
||||
7,
|
||||
"1.1.95" },
|
||||
{ "EXISTS",
|
||||
"key",
|
||||
"Determine if a key exists",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "EXPIRE",
|
||||
"key seconds",
|
||||
"Set a key's time to live in seconds",
|
||||
0,
|
||||
"0.09" },
|
||||
{ "EXPIREAT",
|
||||
"key timestamp",
|
||||
"Set the expiration for a key as a UNIX timestamp",
|
||||
0,
|
||||
"1.1" },
|
||||
{ "FLUSHALL",
|
||||
"-",
|
||||
"Remove all keys from all databases",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "FLUSHDB",
|
||||
"-",
|
||||
"Remove all keys from the current database",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "GET",
|
||||
"key",
|
||||
"Get the value of a key",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "GETSET",
|
||||
"key value",
|
||||
"Set the string value of a key and return its old value",
|
||||
1,
|
||||
"0.091" },
|
||||
{ "HDEL",
|
||||
"key field",
|
||||
"Delete a hash field",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HEXISTS",
|
||||
"key field",
|
||||
"Determine if a hash field exists",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HGET",
|
||||
"key field",
|
||||
"Get the value of a hash field",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HGETALL",
|
||||
"key",
|
||||
"Get all the fields and values in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HINCRBY",
|
||||
"key field increment",
|
||||
"Increment the integer value of a hash field by the given number",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HKEYS",
|
||||
"key",
|
||||
"Get all the fields in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HLEN",
|
||||
"key",
|
||||
"Get the number of fields in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HMGET",
|
||||
"key field [field ...]",
|
||||
"Get the values of all the given hash fields",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HMSET",
|
||||
"key field value [field value ...]",
|
||||
"Set multiple hash fields to multiple values",
|
||||
5,
|
||||
"1.3.8" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"Set the string value of a hash field",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HSETNX",
|
||||
"key field value",
|
||||
"Set the value of a hash field, only if the field does not exist",
|
||||
5,
|
||||
"1.3.8" },
|
||||
{ "HVALS",
|
||||
"key",
|
||||
"Get all the values in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "INCR",
|
||||
"key",
|
||||
"Increment the integer value of a key by one",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "INCRBY",
|
||||
"key increment",
|
||||
"Increment the integer value of a key by the given number",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "INFO",
|
||||
"-",
|
||||
"Get information and statistics about the server",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "KEYS",
|
||||
"pattern",
|
||||
"Find all keys matching the given pattern",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "LASTSAVE",
|
||||
"-",
|
||||
"Get the UNIX time stamp of the last successful save to disk",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "LINDEX",
|
||||
"key index",
|
||||
"Get an element from a list by its index",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LINSERT",
|
||||
"key BEFORE|AFTER pivot value",
|
||||
"Insert an element before or after another element in a list",
|
||||
2,
|
||||
"2.1.1" },
|
||||
{ "LLEN",
|
||||
"key",
|
||||
"Get the length of a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LPOP",
|
||||
"key",
|
||||
"Remove and get the first element in a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LPUSH",
|
||||
"key value",
|
||||
"Prepend a value to a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LPUSHX",
|
||||
"key value",
|
||||
"Prepend a value to a list, only if the list exists",
|
||||
2,
|
||||
"2.1.1" },
|
||||
{ "LRANGE",
|
||||
"key start stop",
|
||||
"Get a range of elements from a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LREM",
|
||||
"key count value",
|
||||
"Remove elements from a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LSET",
|
||||
"key index value",
|
||||
"Set the value of an element in a list by its index",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LTRIM",
|
||||
"key start stop",
|
||||
"Trim a list to the specified range",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "MGET",
|
||||
"key [key ...]",
|
||||
"Get the values of all the given keys",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "MONITOR",
|
||||
"-",
|
||||
"Listen for all requests received by the server in real time",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "MOVE",
|
||||
"key db",
|
||||
"Move a key to another database",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "MSET",
|
||||
"key value [key value ...]",
|
||||
"Set multiple keys to multiple values",
|
||||
1,
|
||||
"1.001" },
|
||||
{ "MSETNX",
|
||||
"key value [key value ...]",
|
||||
"Set multiple keys to multiple values, only if none of the keys exist",
|
||||
1,
|
||||
"1.001" },
|
||||
{ "MULTI",
|
||||
"-",
|
||||
"Mark the start of a transaction block",
|
||||
7,
|
||||
"1.1.95" },
|
||||
{ "PERSIST",
|
||||
"key",
|
||||
"Remove the expiration from a key",
|
||||
0,
|
||||
"2.1.2" },
|
||||
{ "PING",
|
||||
"-",
|
||||
"Ping the server",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "PSUBSCRIBE",
|
||||
"pattern",
|
||||
"Listen for messages published to channels matching the given patterns",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "PUBLISH",
|
||||
"channel message",
|
||||
"Post a message to a channel",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "PUNSUBSCRIBE",
|
||||
"[pattern [pattern ...]]",
|
||||
"Stop listening for messages posted to channels matching the given patterns",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "QUIT",
|
||||
"-",
|
||||
"Close the connection",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "RANDOMKEY",
|
||||
"-",
|
||||
"Return a random key from the keyspace",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RENAME",
|
||||
"old new",
|
||||
"Rename a key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RENAMENX",
|
||||
"old new",
|
||||
"Rename a key, only if the new key does not exist",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RPOP",
|
||||
"key",
|
||||
"Remove and get the last element in a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "RPOPLPUSH",
|
||||
"source destination",
|
||||
"Remove the last element in a list, append it to another list and return it",
|
||||
2,
|
||||
"1.1" },
|
||||
{ "RPUSH",
|
||||
"key value",
|
||||
"Append a value to a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "RPUSHX",
|
||||
"key value",
|
||||
"Append a value to a list, only if the list exists",
|
||||
2,
|
||||
"2.1.1" },
|
||||
{ "SADD",
|
||||
"key member",
|
||||
"Add a member to a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SAVE",
|
||||
"-",
|
||||
"Synchronously save the dataset to disk",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "SCARD",
|
||||
"key",
|
||||
"Get the number of members in a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SDIFF",
|
||||
"key [key ...]",
|
||||
"Subtract multiple sets",
|
||||
3,
|
||||
"0.100" },
|
||||
{ "SDIFFSTORE",
|
||||
"destination key [key ...]",
|
||||
"Subtract multiple sets and store the resulting set in a key",
|
||||
3,
|
||||
"0.100" },
|
||||
{ "SELECT",
|
||||
"index",
|
||||
"Change the selected database for the current connection",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "SET",
|
||||
"key value",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "SETEX",
|
||||
"key timestamp value",
|
||||
"Set the value and expiration of a key",
|
||||
1,
|
||||
"1.3.10" },
|
||||
{ "SETNX",
|
||||
"key value",
|
||||
"Set the value of a key, only if the key does not exist",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "SHUTDOWN",
|
||||
"-",
|
||||
"Synchronously save the dataset to disk and then shut down the server",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "SINTER",
|
||||
"key [key ...]",
|
||||
"Intersect multiple sets",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SINTERSTORE",
|
||||
"destination key [key ...]",
|
||||
"Intersect multiple sets and store the resulting set in a key",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SISMEMBER",
|
||||
"key member",
|
||||
"Determine if a given value is a member of a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SLAVEOF",
|
||||
"host port",
|
||||
"Make the server a slave of another instance, or promote it as master",
|
||||
9,
|
||||
"0.100" },
|
||||
{ "SMEMBERS",
|
||||
"key",
|
||||
"Get all the members in a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SMOVE",
|
||||
"source destination member",
|
||||
"Move a member from one set to another",
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SORT",
|
||||
"key [BY pattern] [LIMIT start count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
|
||||
"Sort the elements in a list, set or sorted set",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "SPOP",
|
||||
"key",
|
||||
"Remove and return a random member from a set",
|
||||
3,
|
||||
"0.101" },
|
||||
{ "SRANDMEMBER",
|
||||
"key",
|
||||
"Get a random member from a set",
|
||||
3,
|
||||
"1.001" },
|
||||
{ "SREM",
|
||||
"key member",
|
||||
"Remove a member from a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
1,
|
||||
"2.1.2" },
|
||||
{ "SUBSCRIBE",
|
||||
"channel",
|
||||
"Listen for messages published to the given channels",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "SUBSTR",
|
||||
"key start stop",
|
||||
"Get a substring of the string stored at a key",
|
||||
1,
|
||||
"1.3.4" },
|
||||
{ "SUNION",
|
||||
"key [key ...]",
|
||||
"Add multiple sets",
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SUNIONSTORE",
|
||||
"destination key [key ...]",
|
||||
"Add multiple sets and store the resulting set in a key",
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SYNC",
|
||||
"-",
|
||||
"Internal command used for replication",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "TTL",
|
||||
"key",
|
||||
"Get the time to live for a key",
|
||||
0,
|
||||
"0.100" },
|
||||
{ "TYPE",
|
||||
"key",
|
||||
"Determine the type stored at key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "UNSUBSCRIBE",
|
||||
"[channel [channel ...]]",
|
||||
"Stop listening for messages posted to the given channels",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "UNWATCH",
|
||||
"-",
|
||||
"Forget about all watched keys",
|
||||
7,
|
||||
"2.1.0" },
|
||||
{ "WATCH",
|
||||
"key [key ...]",
|
||||
"Watch the given keys to determine execution of the MULTI/EXEC block",
|
||||
7,
|
||||
"2.1.0" },
|
||||
{ "ZADD",
|
||||
"key score member",
|
||||
"Add a member to a sorted set, or update its score if it already exists",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZCARD",
|
||||
"key",
|
||||
"Get the number of members in a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZCOUNT",
|
||||
"key min max",
|
||||
"Count the members in a sorted set with scores within the given values",
|
||||
4,
|
||||
"1.3.3" },
|
||||
{ "ZINCRBY",
|
||||
"key increment member",
|
||||
"Increment the score of a member in a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZINTERSTORE",
|
||||
"destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"1.3.10" },
|
||||
{ "ZRANGE",
|
||||
"key start stop",
|
||||
"Return a range of members in a sorted set, by index",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZRANGEBYSCORE",
|
||||
"key min max",
|
||||
"Return a range of members in a sorted set, by score",
|
||||
4,
|
||||
"1.050" },
|
||||
{ "ZRANK",
|
||||
"key member",
|
||||
"Determine the index of a member in a sorted set",
|
||||
4,
|
||||
"1.3.4" },
|
||||
{ "ZREM",
|
||||
"key member",
|
||||
"Remove a member from a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREMRANGEBYRANK",
|
||||
"key start stop",
|
||||
"Remove all members in a sorted set within the given indexes",
|
||||
4,
|
||||
"1.3.4" },
|
||||
{ "ZREMRANGEBYSCORE",
|
||||
"key min max",
|
||||
"Remove all members in a sorted set within the given scores",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREVRANGE",
|
||||
"key start stop",
|
||||
"Return a range of members in a sorted set, by index, with scores ordered from high to low",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREVRANK",
|
||||
"key member",
|
||||
"Determine the index of a member in a sorted set, with scores ordered from high to low",
|
||||
4,
|
||||
"1.3.4" },
|
||||
{ "ZSCORE",
|
||||
"key member",
|
||||
"Get the score associated with the given member in a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZUNIONSTORE",
|
||||
"destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"Add multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"1.3.10" }
|
||||
};
|
||||
|
||||
#endif
|
||||
+422
@@ -0,0 +1,422 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "intset.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
/* Note that these encodings are ordered, so:
|
||||
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
|
||||
#define INTSET_ENC_INT16 (sizeof(int16_t))
|
||||
#define INTSET_ENC_INT32 (sizeof(int32_t))
|
||||
#define INTSET_ENC_INT64 (sizeof(int64_t))
|
||||
|
||||
/* Return the required encoding for the provided value. */
|
||||
static uint8_t _intsetValueEncoding(int64_t v) {
|
||||
if (v < INT32_MIN || v > INT32_MAX)
|
||||
return INTSET_ENC_INT64;
|
||||
else if (v < INT16_MIN || v > INT16_MAX)
|
||||
return INTSET_ENC_INT32;
|
||||
return INTSET_ENC_INT16;
|
||||
}
|
||||
|
||||
/* Return the value at pos, given an encoding. */
|
||||
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
|
||||
if (enc == INTSET_ENC_INT64)
|
||||
return ((int64_t*)is->contents)[pos];
|
||||
else if (enc == INTSET_ENC_INT32)
|
||||
return ((int32_t*)is->contents)[pos];
|
||||
return ((int16_t*)is->contents)[pos];
|
||||
}
|
||||
|
||||
/* Return the value at pos, using the configured encoding. */
|
||||
static int64_t _intsetGet(intset *is, int pos) {
|
||||
return _intsetGetEncoded(is,pos,is->encoding);
|
||||
}
|
||||
|
||||
/* Set the value at pos, using the configured encoding. */
|
||||
static void _intsetSet(intset *is, int pos, int64_t value) {
|
||||
if (is->encoding == INTSET_ENC_INT64)
|
||||
((int64_t*)is->contents)[pos] = value;
|
||||
else if (is->encoding == INTSET_ENC_INT32)
|
||||
((int32_t*)is->contents)[pos] = value;
|
||||
else
|
||||
((int16_t*)is->contents)[pos] = value;
|
||||
}
|
||||
|
||||
/* Create an empty intset. */
|
||||
intset *intsetNew(void) {
|
||||
intset *is = zmalloc(sizeof(intset));
|
||||
is->encoding = INTSET_ENC_INT16;
|
||||
is->length = 0;
|
||||
return is;
|
||||
}
|
||||
|
||||
/* Resize the intset */
|
||||
static intset *intsetResize(intset *is, uint32_t len) {
|
||||
uint32_t size = len*is->encoding;
|
||||
is = zrealloc(is,sizeof(intset)+size);
|
||||
return is;
|
||||
}
|
||||
|
||||
/* Search for the position of "value". Return 1 when the value was found and
|
||||
* sets "pos" to the position of the value within the intset. Return 0 when
|
||||
* the value is not present in the intset and sets "pos" to the position
|
||||
* where "value" can be inserted. */
|
||||
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
|
||||
int min = 0, max = is->length-1, mid = -1;
|
||||
int64_t cur = -1;
|
||||
|
||||
/* The value can never be found when the set is empty */
|
||||
if (is->length == 0) {
|
||||
if (pos) *pos = 0;
|
||||
return 0;
|
||||
} else {
|
||||
/* Check for the case where we know we cannot find the value,
|
||||
* but do know the insert position. */
|
||||
if (value > _intsetGet(is,is->length-1)) {
|
||||
if (pos) *pos = is->length;
|
||||
return 0;
|
||||
} else if (value < _intsetGet(is,0)) {
|
||||
if (pos) *pos = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
while(max >= min) {
|
||||
mid = (min+max)/2;
|
||||
cur = _intsetGet(is,mid);
|
||||
if (value > cur) {
|
||||
min = mid+1;
|
||||
} else if (value < cur) {
|
||||
max = mid-1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (value == cur) {
|
||||
if (pos) *pos = mid;
|
||||
return 1;
|
||||
} else {
|
||||
if (pos) *pos = min;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Upgrades the intset to a larger encoding and inserts the given integer. */
|
||||
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
|
||||
uint8_t curenc = is->encoding;
|
||||
uint8_t newenc = _intsetValueEncoding(value);
|
||||
int length = is->length;
|
||||
int prepend = value < 0 ? 1 : 0;
|
||||
|
||||
/* First set new encoding and resize */
|
||||
is->encoding = newenc;
|
||||
is = intsetResize(is,is->length+1);
|
||||
|
||||
/* Upgrade back-to-front so we don't overwrite values.
|
||||
* Note that the "prepend" variable is used to make sure we have an empty
|
||||
* space at either the beginning or the end of the intset. */
|
||||
while(length--)
|
||||
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
|
||||
|
||||
/* Set the value at the beginning or the end. */
|
||||
if (prepend)
|
||||
_intsetSet(is,0,value);
|
||||
else
|
||||
_intsetSet(is,is->length,value);
|
||||
is->length++;
|
||||
return is;
|
||||
}
|
||||
|
||||
static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
|
||||
void *src, *dst;
|
||||
uint32_t bytes = is->length-from;
|
||||
if (is->encoding == INTSET_ENC_INT64) {
|
||||
src = (int64_t*)is->contents+from;
|
||||
dst = (int64_t*)is->contents+to;
|
||||
bytes *= sizeof(int64_t);
|
||||
} else if (is->encoding == INTSET_ENC_INT32) {
|
||||
src = (int32_t*)is->contents+from;
|
||||
dst = (int32_t*)is->contents+to;
|
||||
bytes *= sizeof(int32_t);
|
||||
} else {
|
||||
src = (int16_t*)is->contents+from;
|
||||
dst = (int16_t*)is->contents+to;
|
||||
bytes *= sizeof(int16_t);
|
||||
}
|
||||
memmove(dst,src,bytes);
|
||||
}
|
||||
|
||||
/* Insert an integer in the intset */
|
||||
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
|
||||
uint8_t valenc = _intsetValueEncoding(value);
|
||||
uint32_t pos;
|
||||
if (success) *success = 1;
|
||||
|
||||
/* Upgrade encoding if necessary. If we need to upgrade, we know that
|
||||
* this value should be either appended (if > 0) or prepended (if < 0),
|
||||
* because it lies outside the range of existing values. */
|
||||
if (valenc > is->encoding) {
|
||||
/* This always succeeds, so we don't need to curry *success. */
|
||||
return intsetUpgradeAndAdd(is,value);
|
||||
} else {
|
||||
/* Abort if the value is already present in the set.
|
||||
* This call will populate "pos" with the right position to insert
|
||||
* the value when it cannot be found. */
|
||||
if (intsetSearch(is,value,&pos)) {
|
||||
if (success) *success = 0;
|
||||
return is;
|
||||
}
|
||||
|
||||
is = intsetResize(is,is->length+1);
|
||||
if (pos < is->length) intsetMoveTail(is,pos,pos+1);
|
||||
}
|
||||
|
||||
_intsetSet(is,pos,value);
|
||||
is->length++;
|
||||
return is;
|
||||
}
|
||||
|
||||
/* Delete integer from intset */
|
||||
intset *intsetRemove(intset *is, int64_t value, uint8_t *success) {
|
||||
uint8_t valenc = _intsetValueEncoding(value);
|
||||
uint32_t pos;
|
||||
if (success) *success = 0;
|
||||
|
||||
if (valenc <= is->encoding && intsetSearch(is,value,&pos)) {
|
||||
/* We know we can delete */
|
||||
if (success) *success = 1;
|
||||
|
||||
/* Overwrite value with tail and update length */
|
||||
if (pos < (is->length-1)) intsetMoveTail(is,pos+1,pos);
|
||||
is = intsetResize(is,is->length-1);
|
||||
is->length--;
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
/* Determine whether a value belongs to this set */
|
||||
uint8_t intsetFind(intset *is, int64_t value) {
|
||||
uint8_t valenc = _intsetValueEncoding(value);
|
||||
return valenc <= is->encoding && intsetSearch(is,value,NULL);
|
||||
}
|
||||
|
||||
/* Return random member */
|
||||
int64_t intsetRandom(intset *is) {
|
||||
return _intsetGet(is,rand()%is->length);
|
||||
}
|
||||
|
||||
/* Sets the value to the value at the given position. When this position is
|
||||
* out of range the function returns 0, when in range it returns 1. */
|
||||
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
|
||||
if (pos < is->length) {
|
||||
*value = _intsetGet(is,pos);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return intset length */
|
||||
uint32_t intsetLen(intset *is) {
|
||||
return is->length;
|
||||
}
|
||||
|
||||
#ifdef INTSET_TEST_MAIN
|
||||
#include <sys/time.h>
|
||||
|
||||
void intsetRepr(intset *is) {
|
||||
int i;
|
||||
for (i = 0; i < is->length; i++) {
|
||||
printf("%lld\n", (uint64_t)_intsetGet(is,i));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void error(char *err) {
|
||||
printf("%s\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void ok(void) {
|
||||
printf("OK\n");
|
||||
}
|
||||
|
||||
long long usec(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||
}
|
||||
|
||||
#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
|
||||
void _assert(char *estr, char *file, int line) {
|
||||
printf("\n\n=== ASSERTION FAILED ===\n");
|
||||
printf("==> %s:%d '%s' is not true\n",file,line,estr);
|
||||
}
|
||||
|
||||
intset *createSet(int bits, int size) {
|
||||
uint64_t mask = (1<<bits)-1;
|
||||
uint64_t i, value;
|
||||
intset *is = intsetNew();
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (bits > 32) {
|
||||
value = (rand()*rand()) & mask;
|
||||
} else {
|
||||
value = rand() & mask;
|
||||
}
|
||||
is = intsetAdd(is,value,NULL);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
void checkConsistency(intset *is) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (is->length-1); i++) {
|
||||
if (is->encoding == INTSET_ENC_INT16) {
|
||||
int16_t *i16 = (int16_t*)is->contents;
|
||||
assert(i16[i] < i16[i+1]);
|
||||
} else if (is->encoding == INTSET_ENC_INT32) {
|
||||
int32_t *i32 = (int32_t*)is->contents;
|
||||
assert(i32[i] < i32[i+1]);
|
||||
} else {
|
||||
int64_t *i64 = (int64_t*)is->contents;
|
||||
assert(i64[i] < i64[i+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint8_t success;
|
||||
int i;
|
||||
intset *is;
|
||||
sranddev();
|
||||
|
||||
printf("Value encodings: "); {
|
||||
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
|
||||
assert(_intsetValueEncoding(+32767) == INTSET_ENC_INT16);
|
||||
assert(_intsetValueEncoding(-32769) == INTSET_ENC_INT32);
|
||||
assert(_intsetValueEncoding(+32768) == INTSET_ENC_INT32);
|
||||
assert(_intsetValueEncoding(-2147483648) == INTSET_ENC_INT32);
|
||||
assert(_intsetValueEncoding(+2147483647) == INTSET_ENC_INT32);
|
||||
assert(_intsetValueEncoding(-2147483649) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(+2147483648) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(-9223372036854775808ull) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(+9223372036854775807ull) == INTSET_ENC_INT64);
|
||||
ok();
|
||||
}
|
||||
|
||||
printf("Basic adding: "); {
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,5,&success); assert(success);
|
||||
is = intsetAdd(is,6,&success); assert(success);
|
||||
is = intsetAdd(is,4,&success); assert(success);
|
||||
is = intsetAdd(is,4,&success); assert(!success);
|
||||
ok();
|
||||
}
|
||||
|
||||
printf("Large number of random adds: "); {
|
||||
int inserts = 0;
|
||||
is = intsetNew();
|
||||
for (i = 0; i < 1024; i++) {
|
||||
is = intsetAdd(is,rand()%0x800,&success);
|
||||
if (success) inserts++;
|
||||
}
|
||||
assert(is->length == inserts);
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
}
|
||||
|
||||
printf("Upgrade from int16 to int32: "); {
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,32,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT16);
|
||||
is = intsetAdd(is,65535,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT32);
|
||||
assert(intsetFind(is,32));
|
||||
assert(intsetFind(is,65535));
|
||||
checkConsistency(is);
|
||||
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,32,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT16);
|
||||
is = intsetAdd(is,-65535,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT32);
|
||||
assert(intsetFind(is,32));
|
||||
assert(intsetFind(is,-65535));
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
}
|
||||
|
||||
printf("Upgrade from int16 to int64: "); {
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,32,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT16);
|
||||
is = intsetAdd(is,4294967295,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT64);
|
||||
assert(intsetFind(is,32));
|
||||
assert(intsetFind(is,4294967295));
|
||||
checkConsistency(is);
|
||||
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,32,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT16);
|
||||
is = intsetAdd(is,-4294967295,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT64);
|
||||
assert(intsetFind(is,32));
|
||||
assert(intsetFind(is,-4294967295));
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
}
|
||||
|
||||
printf("Upgrade from int32 to int64: "); {
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,65535,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT32);
|
||||
is = intsetAdd(is,4294967295,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT64);
|
||||
assert(intsetFind(is,65535));
|
||||
assert(intsetFind(is,4294967295));
|
||||
checkConsistency(is);
|
||||
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,65535,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT32);
|
||||
is = intsetAdd(is,-4294967295,NULL);
|
||||
assert(is->encoding == INTSET_ENC_INT64);
|
||||
assert(intsetFind(is,65535));
|
||||
assert(intsetFind(is,-4294967295));
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
}
|
||||
|
||||
printf("Stress lookups: "); {
|
||||
long num = 100000, size = 10000;
|
||||
int i, bits = 20;
|
||||
long long start;
|
||||
is = createSet(bits,size);
|
||||
checkConsistency(is);
|
||||
|
||||
start = usec();
|
||||
for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
|
||||
printf("%ld lookups, %ld element set, %lldusec\n",num,size,usec()-start);
|
||||
}
|
||||
|
||||
printf("Stress add+delete: "); {
|
||||
int i, v1, v2;
|
||||
is = intsetNew();
|
||||
for (i = 0; i < 0xffff; i++) {
|
||||
v1 = rand() % 0xfff;
|
||||
is = intsetAdd(is,v1,NULL);
|
||||
assert(intsetFind(is,v1));
|
||||
|
||||
v2 = rand() % 0xfff;
|
||||
is = intsetRemove(is,v2,NULL);
|
||||
assert(!intsetFind(is,v2));
|
||||
}
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef __INTSET_H
|
||||
#define __INTSET_H
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct intset {
|
||||
uint32_t encoding;
|
||||
uint32_t length;
|
||||
int8_t contents[];
|
||||
} intset;
|
||||
|
||||
intset *intsetNew(void);
|
||||
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
|
||||
intset *intsetRemove(intset *is, int64_t value, uint8_t *success);
|
||||
uint8_t intsetFind(intset *is, int64_t value);
|
||||
int64_t intsetRandom(intset *is);
|
||||
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
|
||||
uint32_t intsetLen(intset *is);
|
||||
|
||||
#endif // __INTSET_H
|
||||
@@ -6,4 +6,4 @@ test -f release.h || touch release.h
|
||||
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already uptodate
|
||||
echo "#define REDIS_GIT_SHA1 \"$GIT_SHA1\"" > release.h
|
||||
echo "#define REDIS_GIT_DIRTY \"$GIT_DIRTY\"" >> release.h
|
||||
touch redis.c # Force recompile of redis.c
|
||||
touch release.c # Force recompile of release.c
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
#include "redis.h"
|
||||
|
||||
/* ================================ MULTI/EXEC ============================== */
|
||||
|
||||
/* Client state initialization for MULTI/EXEC */
|
||||
void initClientMultiState(redisClient *c) {
|
||||
c->mstate.commands = NULL;
|
||||
c->mstate.count = 0;
|
||||
}
|
||||
|
||||
/* Release all the resources associated with MULTI/EXEC state */
|
||||
void freeClientMultiState(redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < c->mstate.count; j++) {
|
||||
int i;
|
||||
multiCmd *mc = c->mstate.commands+j;
|
||||
|
||||
for (i = 0; i < mc->argc; i++)
|
||||
decrRefCount(mc->argv[i]);
|
||||
zfree(mc->argv);
|
||||
}
|
||||
zfree(c->mstate.commands);
|
||||
}
|
||||
|
||||
/* Add a new command into the MULTI commands queue */
|
||||
void queueMultiCommand(redisClient *c, struct redisCommand *cmd) {
|
||||
multiCmd *mc;
|
||||
int j;
|
||||
|
||||
c->mstate.commands = zrealloc(c->mstate.commands,
|
||||
sizeof(multiCmd)*(c->mstate.count+1));
|
||||
mc = c->mstate.commands+c->mstate.count;
|
||||
mc->cmd = cmd;
|
||||
mc->argc = c->argc;
|
||||
mc->argv = zmalloc(sizeof(robj*)*c->argc);
|
||||
memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
|
||||
for (j = 0; j < c->argc; j++)
|
||||
incrRefCount(mc->argv[j]);
|
||||
c->mstate.count++;
|
||||
}
|
||||
|
||||
void multiCommand(redisClient *c) {
|
||||
if (c->flags & REDIS_MULTI) {
|
||||
addReplyError(c,"MULTI calls can not be nested");
|
||||
return;
|
||||
}
|
||||
c->flags |= REDIS_MULTI;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void discardCommand(redisClient *c) {
|
||||
if (!(c->flags & REDIS_MULTI)) {
|
||||
addReplyError(c,"DISCARD without MULTI");
|
||||
return;
|
||||
}
|
||||
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= (~REDIS_MULTI);
|
||||
unwatchAllKeys(c);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
||||
* implememntation for more information. */
|
||||
void execCommandReplicateMulti(redisClient *c) {
|
||||
robj *multistring = createStringObject("MULTI",5);
|
||||
|
||||
if (server.appendonly)
|
||||
feedAppendOnlyFile(server.multiCommand,c->db->id,&multistring,1);
|
||||
if (listLength(server.slaves))
|
||||
replicationFeedSlaves(server.slaves,c->db->id,&multistring,1);
|
||||
decrRefCount(multistring);
|
||||
}
|
||||
|
||||
void execCommand(redisClient *c) {
|
||||
int j;
|
||||
robj **orig_argv;
|
||||
int orig_argc;
|
||||
|
||||
if (!(c->flags & REDIS_MULTI)) {
|
||||
addReplyError(c,"EXEC without MULTI");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if we need to abort the EXEC if some WATCHed key was touched.
|
||||
* A failed EXEC will return a multi bulk nil object. */
|
||||
if (c->flags & REDIS_DIRTY_CAS) {
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS);
|
||||
unwatchAllKeys(c);
|
||||
addReply(c,shared.nullmultibulk);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Replicate a MULTI request now that we are sure the block is executed.
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
execCommandReplicateMulti(c);
|
||||
|
||||
/* Exec all the queued commands */
|
||||
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
|
||||
orig_argv = c->argv;
|
||||
orig_argc = c->argc;
|
||||
addReplyMultiBulkLen(c,c->mstate.count);
|
||||
for (j = 0; j < c->mstate.count; j++) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
call(c,c->mstate.commands[j].cmd);
|
||||
}
|
||||
c->argv = orig_argv;
|
||||
c->argc = orig_argc;
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS);
|
||||
/* Make sure the EXEC command is always replicated / AOF, since we
|
||||
* always send the MULTI command (we can't know beforehand if the
|
||||
* next operations will contain at least a modification to the DB). */
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
/* ===================== WATCH (CAS alike for MULTI/EXEC) ===================
|
||||
*
|
||||
* The implementation uses a per-DB hash table mapping keys to list of clients
|
||||
* WATCHing those keys, so that given a key that is going to be modified
|
||||
* we can mark all the associated clients as dirty.
|
||||
*
|
||||
* Also every client contains a list of WATCHed keys so that's possible to
|
||||
* un-watch such keys when the client is freed or when UNWATCH is called. */
|
||||
|
||||
/* In the client->watched_keys list we need to use watchedKey structures
|
||||
* as in order to identify a key in Redis we need both the key name and the
|
||||
* DB */
|
||||
typedef struct watchedKey {
|
||||
robj *key;
|
||||
redisDb *db;
|
||||
} watchedKey;
|
||||
|
||||
/* Watch for the specified key */
|
||||
void watchForKey(redisClient *c, robj *key) {
|
||||
list *clients = NULL;
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
watchedKey *wk;
|
||||
|
||||
/* Check if we are already watching for this key */
|
||||
listRewind(c->watched_keys,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
wk = listNodeValue(ln);
|
||||
if (wk->db == c->db && equalStringObjects(key,wk->key))
|
||||
return; /* Key already watched */
|
||||
}
|
||||
/* This key is not already watched in this DB. Let's add it */
|
||||
clients = dictFetchValue(c->db->watched_keys,key);
|
||||
if (!clients) {
|
||||
clients = listCreate();
|
||||
dictAdd(c->db->watched_keys,key,clients);
|
||||
incrRefCount(key);
|
||||
}
|
||||
listAddNodeTail(clients,c);
|
||||
/* Add the new key to the lits of keys watched by this client */
|
||||
wk = zmalloc(sizeof(*wk));
|
||||
wk->key = key;
|
||||
wk->db = c->db;
|
||||
incrRefCount(key);
|
||||
listAddNodeTail(c->watched_keys,wk);
|
||||
}
|
||||
|
||||
/* Unwatch all the keys watched by this client. To clean the EXEC dirty
|
||||
* flag is up to the caller. */
|
||||
void unwatchAllKeys(redisClient *c) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
if (listLength(c->watched_keys) == 0) return;
|
||||
listRewind(c->watched_keys,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
list *clients;
|
||||
watchedKey *wk;
|
||||
|
||||
/* Lookup the watched key -> clients list and remove the client
|
||||
* from the list */
|
||||
wk = listNodeValue(ln);
|
||||
clients = dictFetchValue(wk->db->watched_keys, wk->key);
|
||||
redisAssert(clients != NULL);
|
||||
listDelNode(clients,listSearchKey(clients,c));
|
||||
/* Kill the entry at all if this was the only client */
|
||||
if (listLength(clients) == 0)
|
||||
dictDelete(wk->db->watched_keys, wk->key);
|
||||
/* Remove this watched key from the client->watched list */
|
||||
listDelNode(c->watched_keys,ln);
|
||||
decrRefCount(wk->key);
|
||||
zfree(wk);
|
||||
}
|
||||
}
|
||||
|
||||
/* "Touch" a key, so that if this key is being WATCHed by some client the
|
||||
* next EXEC will fail. */
|
||||
void touchWatchedKey(redisDb *db, robj *key) {
|
||||
list *clients;
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
if (dictSize(db->watched_keys) == 0) return;
|
||||
clients = dictFetchValue(db->watched_keys, key);
|
||||
if (!clients) return;
|
||||
|
||||
/* Mark all the clients watching this key as REDIS_DIRTY_CAS */
|
||||
/* Check if we are already watching for this key */
|
||||
listRewind(clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *c = listNodeValue(ln);
|
||||
|
||||
c->flags |= REDIS_DIRTY_CAS;
|
||||
}
|
||||
}
|
||||
|
||||
/* On FLUSHDB or FLUSHALL all the watched keys that are present before the
|
||||
* flush but will be deleted as effect of the flushing operation should
|
||||
* be touched. "dbid" is the DB that's getting the flush. -1 if it is
|
||||
* a FLUSHALL operation (all the DBs flushed). */
|
||||
void touchWatchedKeysOnFlush(int dbid) {
|
||||
listIter li1, li2;
|
||||
listNode *ln;
|
||||
|
||||
/* For every client, check all the waited keys */
|
||||
listRewind(server.clients,&li1);
|
||||
while((ln = listNext(&li1))) {
|
||||
redisClient *c = listNodeValue(ln);
|
||||
listRewind(c->watched_keys,&li2);
|
||||
while((ln = listNext(&li2))) {
|
||||
watchedKey *wk = listNodeValue(ln);
|
||||
|
||||
/* For every watched key matching the specified DB, if the
|
||||
* key exists, mark the client as dirty, as the key will be
|
||||
* removed. */
|
||||
if (dbid == -1 || wk->db->id == dbid) {
|
||||
if (dictFind(wk->db->dict, wk->key->ptr) != NULL)
|
||||
c->flags |= REDIS_DIRTY_CAS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void watchCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
if (c->flags & REDIS_MULTI) {
|
||||
addReplyError(c,"WATCH inside MULTI is not allowed");
|
||||
return;
|
||||
}
|
||||
for (j = 1; j < c->argc; j++)
|
||||
watchForKey(c,c->argv[j]);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void unwatchCommand(redisClient *c) {
|
||||
unwatchAllKeys(c);
|
||||
c->flags &= (~REDIS_DIRTY_CAS);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -0,0 +1,886 @@
|
||||
#include "redis.h"
|
||||
#include <sys/uio.h>
|
||||
|
||||
void *dupClientReplyValue(void *o) {
|
||||
incrRefCount((robj*)o);
|
||||
return o;
|
||||
}
|
||||
|
||||
int listMatchObjects(void *a, void *b) {
|
||||
return equalStringObjects(a,b);
|
||||
}
|
||||
|
||||
redisClient *createClient(int fd) {
|
||||
redisClient *c = zmalloc(sizeof(redisClient));
|
||||
c->bufpos = 0;
|
||||
|
||||
anetNonBlock(NULL,fd);
|
||||
anetTcpNoDelay(NULL,fd);
|
||||
if (!c) return NULL;
|
||||
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
|
||||
readQueryFromClient, c) == AE_ERR)
|
||||
{
|
||||
close(fd);
|
||||
zfree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
selectDb(c,0);
|
||||
c->fd = fd;
|
||||
c->querybuf = sdsempty();
|
||||
c->reqtype = 0;
|
||||
c->argc = 0;
|
||||
c->argv = NULL;
|
||||
c->multibulklen = 0;
|
||||
c->bulklen = -1;
|
||||
c->sentlen = 0;
|
||||
c->flags = 0;
|
||||
c->lastinteraction = time(NULL);
|
||||
c->authenticated = 0;
|
||||
c->replstate = REDIS_REPL_NONE;
|
||||
c->reply = listCreate();
|
||||
listSetFreeMethod(c->reply,decrRefCount);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
c->blocking_keys = NULL;
|
||||
c->blocking_keys_num = 0;
|
||||
c->io_keys = listCreate();
|
||||
c->watched_keys = listCreate();
|
||||
listSetFreeMethod(c->io_keys,decrRefCount);
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
listAddNodeTail(server.clients,c);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Set the event loop to listen for write events on the client's socket.
|
||||
* Typically gets called every time a reply is built. */
|
||||
int _installWriteEvent(redisClient *c) {
|
||||
/* When CLOSE_AFTER_REPLY is set, no more replies may be added! */
|
||||
redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY));
|
||||
|
||||
if (c->fd <= 0) return REDIS_ERR;
|
||||
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
|
||||
(c->replstate == REDIS_REPL_NONE ||
|
||||
c->replstate == REDIS_REPL_ONLINE) &&
|
||||
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
||||
sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Create a duplicate of the last object in the reply list when
|
||||
* it is not exclusively owned by the reply list. */
|
||||
robj *dupLastObjectIfNeeded(list *reply) {
|
||||
robj *new, *cur;
|
||||
listNode *ln;
|
||||
redisAssert(listLength(reply) > 0);
|
||||
ln = listLast(reply);
|
||||
cur = listNodeValue(ln);
|
||||
if (cur->refcount > 1) {
|
||||
new = dupStringObject(cur);
|
||||
decrRefCount(cur);
|
||||
listNodeValue(ln) = new;
|
||||
}
|
||||
return listNodeValue(ln);
|
||||
}
|
||||
|
||||
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
|
||||
size_t available = sizeof(c->buf)-c->bufpos;
|
||||
|
||||
/* If there already are entries in the reply list, we cannot
|
||||
* add anything more to the static buffer. */
|
||||
if (listLength(c->reply) > 0) return REDIS_ERR;
|
||||
|
||||
/* Check that the buffer has enough space available for this string. */
|
||||
if (len > available) return REDIS_ERR;
|
||||
|
||||
memcpy(c->buf+c->bufpos,s,len);
|
||||
c->bufpos+=len;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void _addReplyObjectToList(redisClient *c, robj *o) {
|
||||
robj *tail;
|
||||
if (listLength(c->reply) == 0) {
|
||||
incrRefCount(o);
|
||||
listAddNodeTail(c->reply,o);
|
||||
} else {
|
||||
tail = listNodeValue(listLast(c->reply));
|
||||
|
||||
/* Append to this object when possible. */
|
||||
if (tail->ptr != NULL &&
|
||||
sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
tail = dupLastObjectIfNeeded(c->reply);
|
||||
tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
|
||||
} else {
|
||||
incrRefCount(o);
|
||||
listAddNodeTail(c->reply,o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This method takes responsibility over the sds. When it is no longer
|
||||
* needed it will be free'd, otherwise it ends up in a robj. */
|
||||
void _addReplySdsToList(redisClient *c, sds s) {
|
||||
robj *tail;
|
||||
if (listLength(c->reply) == 0) {
|
||||
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
|
||||
} else {
|
||||
tail = listNodeValue(listLast(c->reply));
|
||||
|
||||
/* Append to this object when possible. */
|
||||
if (tail->ptr != NULL &&
|
||||
sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
tail = dupLastObjectIfNeeded(c->reply);
|
||||
tail->ptr = sdscatlen(tail->ptr,s,sdslen(s));
|
||||
sdsfree(s);
|
||||
} else {
|
||||
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _addReplyStringToList(redisClient *c, char *s, size_t len) {
|
||||
robj *tail;
|
||||
if (listLength(c->reply) == 0) {
|
||||
listAddNodeTail(c->reply,createStringObject(s,len));
|
||||
} else {
|
||||
tail = listNodeValue(listLast(c->reply));
|
||||
|
||||
/* Append to this object when possible. */
|
||||
if (tail->ptr != NULL &&
|
||||
sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
tail = dupLastObjectIfNeeded(c->reply);
|
||||
tail->ptr = sdscatlen(tail->ptr,s,len);
|
||||
} else {
|
||||
listAddNodeTail(c->reply,createStringObject(s,len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addReply(redisClient *c, robj *obj) {
|
||||
if (_installWriteEvent(c) != REDIS_OK) return;
|
||||
redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
|
||||
|
||||
/* This is an important place where we can avoid copy-on-write
|
||||
* when there is a saving child running, avoiding touching the
|
||||
* refcount field of the object if it's not needed.
|
||||
*
|
||||
* If the encoding is RAW and there is room in the static buffer
|
||||
* we'll be able to send the object to the client without
|
||||
* messing with its page. */
|
||||
if (obj->encoding == REDIS_ENCODING_RAW) {
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
} else {
|
||||
obj = getDecodedObject(obj);
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
decrRefCount(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void addReplySds(redisClient *c, sds s) {
|
||||
if (_installWriteEvent(c) != REDIS_OK) {
|
||||
/* The caller expects the sds to be free'd. */
|
||||
sdsfree(s);
|
||||
return;
|
||||
}
|
||||
if (_addReplyToBuffer(c,s,sdslen(s)) == REDIS_OK) {
|
||||
sdsfree(s);
|
||||
} else {
|
||||
/* This method free's the sds when it is no longer needed. */
|
||||
_addReplySdsToList(c,s);
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyString(redisClient *c, char *s, size_t len) {
|
||||
if (_installWriteEvent(c) != REDIS_OK) return;
|
||||
if (_addReplyToBuffer(c,s,len) != REDIS_OK)
|
||||
_addReplyStringToList(c,s,len);
|
||||
}
|
||||
|
||||
void _addReplyError(redisClient *c, char *s, size_t len) {
|
||||
addReplyString(c,"-ERR ",5);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
}
|
||||
|
||||
void addReplyError(redisClient *c, char *err) {
|
||||
_addReplyError(c,err,strlen(err));
|
||||
}
|
||||
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap,fmt);
|
||||
sds s = sdscatvprintf(sdsempty(),fmt,ap);
|
||||
va_end(ap);
|
||||
_addReplyError(c,s,sdslen(s));
|
||||
sdsfree(s);
|
||||
}
|
||||
|
||||
void _addReplyStatus(redisClient *c, char *s, size_t len) {
|
||||
addReplyString(c,"+",1);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
}
|
||||
|
||||
void addReplyStatus(redisClient *c, char *status) {
|
||||
_addReplyStatus(c,status,strlen(status));
|
||||
}
|
||||
|
||||
void addReplyStatusFormat(redisClient *c, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap,fmt);
|
||||
sds s = sdscatvprintf(sdsempty(),fmt,ap);
|
||||
va_end(ap);
|
||||
_addReplyStatus(c,s,sdslen(s));
|
||||
sdsfree(s);
|
||||
}
|
||||
|
||||
/* Adds an empty object to the reply list that will contain the multi bulk
|
||||
* length, which is not known when this function is called. */
|
||||
void *addDeferredMultiBulkLength(redisClient *c) {
|
||||
/* Note that we install the write event here even if the object is not
|
||||
* ready to be sent, since we are sure that before returning to the
|
||||
* event loop setDeferredMultiBulkLength() will be called. */
|
||||
if (_installWriteEvent(c) != REDIS_OK) return NULL;
|
||||
listAddNodeTail(c->reply,createObject(REDIS_STRING,NULL));
|
||||
return listLast(c->reply);
|
||||
}
|
||||
|
||||
/* Populate the length object and try glueing it to the next chunk. */
|
||||
void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
|
||||
listNode *ln = (listNode*)node;
|
||||
robj *len, *next;
|
||||
|
||||
/* Abort when *node is NULL (see addDeferredMultiBulkLength). */
|
||||
if (node == NULL) return;
|
||||
|
||||
len = listNodeValue(ln);
|
||||
len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length);
|
||||
if (ln->next != NULL) {
|
||||
next = listNodeValue(ln->next);
|
||||
|
||||
/* Only glue when the next node is non-NULL (an sds in this case) */
|
||||
if (next->ptr != NULL) {
|
||||
len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr));
|
||||
listDelNode(c->reply,ln->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyDouble(redisClient *c, double d) {
|
||||
char dbuf[128], sbuf[128];
|
||||
int dlen, slen;
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
|
||||
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
|
||||
addReplyString(c,sbuf,slen);
|
||||
}
|
||||
|
||||
void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
|
||||
char buf[128];
|
||||
int len;
|
||||
buf[0] = prefix;
|
||||
len = ll2string(buf+1,sizeof(buf)-1,ll);
|
||||
buf[len+1] = '\r';
|
||||
buf[len+2] = '\n';
|
||||
addReplyString(c,buf,len+3);
|
||||
}
|
||||
|
||||
void addReplyLongLong(redisClient *c, long long ll) {
|
||||
_addReplyLongLong(c,ll,':');
|
||||
}
|
||||
|
||||
void addReplyMultiBulkLen(redisClient *c, long length) {
|
||||
_addReplyLongLong(c,length,'*');
|
||||
}
|
||||
|
||||
void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
size_t len;
|
||||
|
||||
if (obj->encoding == REDIS_ENCODING_RAW) {
|
||||
len = sdslen(obj->ptr);
|
||||
} else {
|
||||
long n = (long)obj->ptr;
|
||||
|
||||
/* Compute how many bytes will take this integer as a radix 10 string */
|
||||
len = 1;
|
||||
if (n < 0) {
|
||||
len++;
|
||||
n = -n;
|
||||
}
|
||||
while((n = n/10) != 0) {
|
||||
len++;
|
||||
}
|
||||
}
|
||||
_addReplyLongLong(c,len,'$');
|
||||
}
|
||||
|
||||
void addReplyBulk(redisClient *c, robj *obj) {
|
||||
addReplyBulkLen(c,obj);
|
||||
addReply(c,obj);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
/* In the CONFIG command we need to add vanilla C string as bulk replies */
|
||||
void addReplyBulkCString(redisClient *c, char *s) {
|
||||
if (s == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
robj *o = createStringObject(s,strlen(s));
|
||||
addReplyBulk(c,o);
|
||||
decrRefCount(o);
|
||||
}
|
||||
}
|
||||
|
||||
static void acceptCommonHandler(int fd) {
|
||||
redisClient *c;
|
||||
if ((c = createClient(fd)) == NULL) {
|
||||
redisLog(REDIS_WARNING,"Error allocating resoures for the client");
|
||||
close(fd); /* May be already closed, just ingore errors */
|
||||
return;
|
||||
}
|
||||
/* If maxclient directive is set and this is one client more... close the
|
||||
* connection. Note that we create the client instead to check before
|
||||
* for this condition, since now the socket is already set in nonblocking
|
||||
* mode and we can send an error for free using the Kernel I/O */
|
||||
if (server.maxclients && listLength(server.clients) > server.maxclients) {
|
||||
char *err = "-ERR max number of clients reached\r\n";
|
||||
|
||||
/* That's a best effort error message, don't check write errors */
|
||||
if (write(c->fd,err,strlen(err)) == -1) {
|
||||
/* Nothing to do, Just to avoid the warning... */
|
||||
}
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
server.stat_numconnections++;
|
||||
}
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd;
|
||||
char cip[128];
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd);
|
||||
}
|
||||
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cfd;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd);
|
||||
}
|
||||
|
||||
|
||||
static void freeClientArgv(redisClient *c) {
|
||||
int j;
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
c->argc = 0;
|
||||
}
|
||||
|
||||
void freeClient(redisClient *c) {
|
||||
listNode *ln;
|
||||
|
||||
/* Note that if the client we are freeing is blocked into a blocking
|
||||
* call, we have to set querybuf to NULL *before* to call
|
||||
* unblockClientWaitingData() to avoid processInputBuffer() will get
|
||||
* called. Also it is important to remove the file events after
|
||||
* this, because this call adds the READABLE event. */
|
||||
sdsfree(c->querybuf);
|
||||
c->querybuf = NULL;
|
||||
if (c->flags & REDIS_BLOCKED)
|
||||
unblockClientWaitingData(c);
|
||||
|
||||
/* UNWATCH all the keys */
|
||||
unwatchAllKeys(c);
|
||||
listRelease(c->watched_keys);
|
||||
/* Unsubscribe from all the pubsub channels */
|
||||
pubsubUnsubscribeAllChannels(c,0);
|
||||
pubsubUnsubscribeAllPatterns(c,0);
|
||||
dictRelease(c->pubsub_channels);
|
||||
listRelease(c->pubsub_patterns);
|
||||
/* Obvious cleanup */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
listRelease(c->reply);
|
||||
freeClientArgv(c);
|
||||
close(c->fd);
|
||||
/* Remove from the list of clients */
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
/* Remove from the list of clients waiting for swapped keys, or ready
|
||||
* to be restarted, but not yet woken up again. */
|
||||
if (c->flags & REDIS_IO_WAIT) {
|
||||
redisAssert(server.vm_enabled);
|
||||
if (listLength(c->io_keys) == 0) {
|
||||
ln = listSearchKey(server.io_ready_clients,c);
|
||||
|
||||
/* When this client is waiting to be woken up (REDIS_IO_WAIT),
|
||||
* it should be present in the list io_ready_clients */
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.io_ready_clients,ln);
|
||||
} else {
|
||||
while (listLength(c->io_keys)) {
|
||||
ln = listFirst(c->io_keys);
|
||||
dontWaitForSwappedKey(c,ln->value);
|
||||
}
|
||||
}
|
||||
server.vm_blocked_clients--;
|
||||
}
|
||||
listRelease(c->io_keys);
|
||||
/* Master/slave cleanup.
|
||||
* Case 1: we lost the connection with a slave. */
|
||||
if (c->flags & REDIS_SLAVE) {
|
||||
if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
|
||||
close(c->repldbfd);
|
||||
list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves;
|
||||
ln = listSearchKey(l,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(l,ln);
|
||||
}
|
||||
|
||||
/* Case 2: we lost the connection with the master. */
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
server.master = NULL;
|
||||
/* FIXME */
|
||||
server.replstate = REDIS_REPL_CONNECT;
|
||||
/* Since we lost the connection with the master, we should also
|
||||
* close the connection with all our slaves if we have any, so
|
||||
* when we'll resync with the master the other slaves will sync again
|
||||
* with us as well. Note that also when the slave is not connected
|
||||
* to the master it will keep refusing connections by other slaves. */
|
||||
while (listLength(server.slaves)) {
|
||||
ln = listFirst(server.slaves);
|
||||
freeClient((redisClient*)ln->value);
|
||||
}
|
||||
}
|
||||
/* Release memory */
|
||||
zfree(c->argv);
|
||||
freeClientMultiState(c);
|
||||
zfree(c);
|
||||
}
|
||||
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
redisClient *c = privdata;
|
||||
int nwritten = 0, totwritten = 0, objlen;
|
||||
robj *o;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
/* Use writev() if we have enough buffers to send */
|
||||
if (!server.glueoutputbuf &&
|
||||
listLength(c->reply) > REDIS_WRITEV_THRESHOLD &&
|
||||
!(c->flags & REDIS_MASTER))
|
||||
{
|
||||
sendReplyToClientWritev(el, fd, privdata, mask);
|
||||
return;
|
||||
}
|
||||
|
||||
while(c->bufpos > 0 || listLength(c->reply)) {
|
||||
if (c->bufpos > 0) {
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
/* Don't reply to a master */
|
||||
nwritten = c->bufpos - c->sentlen;
|
||||
} else {
|
||||
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
}
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
/* If the buffer was sent, set bufpos to zero to continue with
|
||||
* the remainder of the reply. */
|
||||
if (c->sentlen == c->bufpos) {
|
||||
c->bufpos = 0;
|
||||
c->sentlen = 0;
|
||||
}
|
||||
} else {
|
||||
o = listNodeValue(listFirst(c->reply));
|
||||
objlen = sdslen(o->ptr);
|
||||
|
||||
if (objlen == 0) {
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
/* Don't reply to a master */
|
||||
nwritten = objlen - c->sentlen;
|
||||
} else {
|
||||
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
}
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
/* If we fully sent the object on head go to the next one */
|
||||
if (c->sentlen == objlen) {
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
c->sentlen = 0;
|
||||
}
|
||||
}
|
||||
/* Note that we avoid to send more thank REDIS_MAX_WRITE_PER_EVENT
|
||||
* bytes, in a single threaded server it's a good idea to serve
|
||||
* other clients as well, even if a very large request comes from
|
||||
* super fast link that is always able to accept data (in real world
|
||||
* scenario think about 'KEYS *' against the loopback interfae) */
|
||||
if (totwritten > REDIS_MAX_WRITE_PER_EVENT) break;
|
||||
}
|
||||
if (nwritten == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
nwritten = 0;
|
||||
} else {
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Error writing to client: %s", strerror(errno));
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (totwritten > 0) c->lastinteraction = time(NULL);
|
||||
if (listLength(c->reply) == 0) {
|
||||
c->sentlen = 0;
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
|
||||
/* Close connection after entire reply has been sent. */
|
||||
if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c);
|
||||
}
|
||||
}
|
||||
|
||||
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
{
|
||||
redisClient *c = privdata;
|
||||
int nwritten = 0, totwritten = 0, objlen, willwrite;
|
||||
robj *o;
|
||||
struct iovec iov[REDIS_WRITEV_IOVEC_COUNT];
|
||||
int offset, ion = 0;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
listNode *node;
|
||||
while (listLength(c->reply)) {
|
||||
offset = c->sentlen;
|
||||
ion = 0;
|
||||
willwrite = 0;
|
||||
|
||||
/* fill-in the iov[] array */
|
||||
for(node = listFirst(c->reply); node; node = listNextNode(node)) {
|
||||
o = listNodeValue(node);
|
||||
objlen = sdslen(o->ptr);
|
||||
|
||||
if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT)
|
||||
break;
|
||||
|
||||
if(ion == REDIS_WRITEV_IOVEC_COUNT)
|
||||
break; /* no more iovecs */
|
||||
|
||||
iov[ion].iov_base = ((char*)o->ptr) + offset;
|
||||
iov[ion].iov_len = objlen - offset;
|
||||
willwrite += objlen - offset;
|
||||
offset = 0; /* just for the first item */
|
||||
ion++;
|
||||
}
|
||||
|
||||
if(willwrite == 0)
|
||||
break;
|
||||
|
||||
/* write all collected blocks at once */
|
||||
if((nwritten = writev(fd, iov, ion)) < 0) {
|
||||
if (errno != EAGAIN) {
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Error writing to client: %s", strerror(errno));
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
totwritten += nwritten;
|
||||
offset = c->sentlen;
|
||||
|
||||
/* remove written robjs from c->reply */
|
||||
while (nwritten && listLength(c->reply)) {
|
||||
o = listNodeValue(listFirst(c->reply));
|
||||
objlen = sdslen(o->ptr);
|
||||
|
||||
if(nwritten >= objlen - offset) {
|
||||
listDelNode(c->reply, listFirst(c->reply));
|
||||
nwritten -= objlen - offset;
|
||||
c->sentlen = 0;
|
||||
} else {
|
||||
/* partial write */
|
||||
c->sentlen += nwritten;
|
||||
break;
|
||||
}
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (totwritten > 0)
|
||||
c->lastinteraction = time(NULL);
|
||||
|
||||
if (listLength(c->reply) == 0) {
|
||||
c->sentlen = 0;
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
}
|
||||
}
|
||||
|
||||
/* resetClient prepare the client to process the next command */
|
||||
void resetClient(redisClient *c) {
|
||||
freeClientArgv(c);
|
||||
c->reqtype = 0;
|
||||
c->multibulklen = 0;
|
||||
c->bulklen = -1;
|
||||
}
|
||||
|
||||
void closeTimedoutClients(void) {
|
||||
redisClient *c;
|
||||
listNode *ln;
|
||||
time_t now = time(NULL);
|
||||
listIter li;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = listNodeValue(ln);
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
|
||||
!(c->flags & REDIS_MASTER) && /* no timeout for masters */
|
||||
!(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */
|
||||
dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */
|
||||
listLength(c->pubsub_patterns) == 0 &&
|
||||
(now - c->lastinteraction > server.maxidletime))
|
||||
{
|
||||
redisLog(REDIS_VERBOSE,"Closing idle client");
|
||||
freeClient(c);
|
||||
} else if (c->flags & REDIS_BLOCKED) {
|
||||
if (c->blockingto != 0 && c->blockingto < now) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
unblockClientWaitingData(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int processInlineBuffer(redisClient *c) {
|
||||
char *newline = strstr(c->querybuf,"\r\n");
|
||||
int argc, j;
|
||||
sds *argv;
|
||||
size_t querylen;
|
||||
|
||||
/* Nothing to do without a \r\n */
|
||||
if (newline == NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf);
|
||||
argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
|
||||
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (c->argv) zfree(c->argv);
|
||||
c->argv = zmalloc(sizeof(robj*)*argc);
|
||||
|
||||
/* Create redis objects for all arguments. */
|
||||
for (c->argc = 0, j = 0; j < argc; j++) {
|
||||
if (sdslen(argv[j])) {
|
||||
c->argv[c->argc] = createObject(REDIS_STRING,argv[j]);
|
||||
c->argc++;
|
||||
} else {
|
||||
sdsfree(argv[j]);
|
||||
}
|
||||
}
|
||||
zfree(argv);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Helper function. Trims query buffer to make the function that processes
|
||||
* multi bulk requests idempotent. */
|
||||
static void setProtocolError(redisClient *c, int pos) {
|
||||
c->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
}
|
||||
|
||||
int processMultibulkBuffer(redisClient *c) {
|
||||
char *newline = NULL;
|
||||
char *eptr;
|
||||
int pos = 0, tolerr;
|
||||
long bulklen;
|
||||
|
||||
if (c->multibulklen == 0) {
|
||||
/* The client should have been reset */
|
||||
redisAssert(c->argc == 0);
|
||||
|
||||
/* Multi bulk length cannot be read without a \r\n */
|
||||
newline = strstr(c->querybuf,"\r\n");
|
||||
if (newline == NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* We know for sure there is a whole line since newline != NULL,
|
||||
* so go ahead and find out the multi bulk length. */
|
||||
redisAssert(c->querybuf[0] == '*');
|
||||
c->multibulklen = strtol(c->querybuf+1,&eptr,10);
|
||||
pos = (newline-c->querybuf)+2;
|
||||
if (c->multibulklen <= 0) {
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
return REDIS_OK;
|
||||
} else if (c->multibulklen > 1024*1024) {
|
||||
addReplyError(c,"Protocol error: invalid multibulk length");
|
||||
setProtocolError(c,pos);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (c->argv) zfree(c->argv);
|
||||
c->argv = zmalloc(sizeof(robj*)*c->multibulklen);
|
||||
|
||||
/* Search new newline */
|
||||
newline = strstr(c->querybuf+pos,"\r\n");
|
||||
}
|
||||
|
||||
redisAssert(c->multibulklen > 0);
|
||||
while(c->multibulklen) {
|
||||
/* Read bulk length if unknown */
|
||||
if (c->bulklen == -1) {
|
||||
newline = strstr(c->querybuf+pos,"\r\n");
|
||||
if (newline != NULL) {
|
||||
if (c->querybuf[pos] != '$') {
|
||||
addReplyErrorFormat(c,
|
||||
"Protocol error: expected '$', got '%c'",
|
||||
c->querybuf[pos]);
|
||||
setProtocolError(c,pos);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
bulklen = strtol(c->querybuf+pos+1,&eptr,10);
|
||||
tolerr = (eptr[0] != '\r');
|
||||
if (tolerr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
|
||||
bulklen < 0 || bulklen > 1024*1024*1024)
|
||||
{
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError(c,pos);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
pos += eptr-(c->querybuf+pos)+2;
|
||||
c->bulklen = bulklen;
|
||||
} else {
|
||||
/* No newline in current buffer, so wait for more data */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read bulk argument */
|
||||
if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {
|
||||
/* Not enough data (+2 == trailing \r\n) */
|
||||
break;
|
||||
} else {
|
||||
c->argv[c->argc++] = createStringObject(c->querybuf+pos,c->bulklen);
|
||||
pos += c->bulklen+2;
|
||||
c->bulklen = -1;
|
||||
c->multibulklen--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
void processInputBuffer(redisClient *c) {
|
||||
/* Keep processing while there is something in the input buffer */
|
||||
while(sdslen(c->querybuf)) {
|
||||
/* Immediately abort if the client is in the middle of something. */
|
||||
if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return;
|
||||
|
||||
/* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is
|
||||
* written to the client. Make sure to not let the reply grow after
|
||||
* this flag has been set (i.e. don't process more commands). */
|
||||
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
|
||||
|
||||
/* Determine request type when unknown. */
|
||||
if (!c->reqtype) {
|
||||
if (c->querybuf[0] == '*') {
|
||||
c->reqtype = REDIS_REQ_MULTIBULK;
|
||||
} else {
|
||||
c->reqtype = REDIS_REQ_INLINE;
|
||||
}
|
||||
}
|
||||
|
||||
if (c->reqtype == REDIS_REQ_INLINE) {
|
||||
if (processInlineBuffer(c) != REDIS_OK) break;
|
||||
} else if (c->reqtype == REDIS_REQ_MULTIBULK) {
|
||||
if (processMultibulkBuffer(c) != REDIS_OK) break;
|
||||
} else {
|
||||
redisPanic("Unknown request type");
|
||||
}
|
||||
|
||||
/* Multibulk processing could see a <= 0 length. */
|
||||
if (c->argc == 0) {
|
||||
resetClient(c);
|
||||
} else {
|
||||
/* Only reset the client when the command was executed. */
|
||||
if (processCommand(c) == REDIS_OK)
|
||||
resetClient(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
redisClient *c = (redisClient*) privdata;
|
||||
char buf[REDIS_IOBUF_LEN];
|
||||
int nread;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
nread = read(fd, buf, REDIS_IOBUF_LEN);
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
nread = 0;
|
||||
} else {
|
||||
redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
redisLog(REDIS_VERBOSE, "Client closed connection");
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
if (nread) {
|
||||
c->querybuf = sdscatlen(c->querybuf,buf,nread);
|
||||
c->lastinteraction = time(NULL);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
processInputBuffer(c);
|
||||
}
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
#include "redis.h"
|
||||
#include <pthread.h>
|
||||
#include <math.h>
|
||||
|
||||
robj *createObject(int type, void *ptr) {
|
||||
robj *o = zmalloc(sizeof(*o));
|
||||
o->type = type;
|
||||
o->encoding = REDIS_ENCODING_RAW;
|
||||
o->ptr = ptr;
|
||||
o->refcount = 1;
|
||||
|
||||
/* Set the LRU to the current lruclock (minutes resolution).
|
||||
* We do this regardless of the fact VM is active as LRU is also
|
||||
* used for the maxmemory directive when Redis is used as cache.
|
||||
*
|
||||
* Note that this code may run in the context of an I/O thread
|
||||
* and accessing server.lruclock in theory is an error
|
||||
* (no locks). But in practice this is safe, and even if we read
|
||||
* garbage Redis will not fail. */
|
||||
o->lru = server.lruclock;
|
||||
/* The following is only needed if VM is active, but since the conditional
|
||||
* is probably more costly than initializing the field it's better to
|
||||
* have every field properly initialized anyway. */
|
||||
o->storage = REDIS_VM_MEMORY;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createStringObject(char *ptr, size_t len) {
|
||||
return createObject(REDIS_STRING,sdsnewlen(ptr,len));
|
||||
}
|
||||
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
robj *o;
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
o = shared.integers[value];
|
||||
} else {
|
||||
if (value >= LONG_MIN && value <= LONG_MAX) {
|
||||
o = createObject(REDIS_STRING, NULL);
|
||||
o->encoding = REDIS_ENCODING_INT;
|
||||
o->ptr = (void*)((long)value);
|
||||
} else {
|
||||
o = createObject(REDIS_STRING,sdsfromlonglong(value));
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *dupStringObject(robj *o) {
|
||||
redisAssert(o->encoding == REDIS_ENCODING_RAW);
|
||||
return createStringObject(o->ptr,sdslen(o->ptr));
|
||||
}
|
||||
|
||||
robj *createListObject(void) {
|
||||
list *l = listCreate();
|
||||
robj *o = createObject(REDIS_LIST,l);
|
||||
listSetFreeMethod(l,decrRefCount);
|
||||
o->encoding = REDIS_ENCODING_LINKEDLIST;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createZiplistObject(void) {
|
||||
unsigned char *zl = ziplistNew();
|
||||
robj *o = createObject(REDIS_LIST,zl);
|
||||
o->encoding = REDIS_ENCODING_ZIPLIST;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createSetObject(void) {
|
||||
dict *d = dictCreate(&setDictType,NULL);
|
||||
robj *o = createObject(REDIS_SET,d);
|
||||
o->encoding = REDIS_ENCODING_HT;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createIntsetObject(void) {
|
||||
intset *is = intsetNew();
|
||||
robj *o = createObject(REDIS_SET,is);
|
||||
o->encoding = REDIS_ENCODING_INTSET;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createHashObject(void) {
|
||||
/* All the Hashes start as zipmaps. Will be automatically converted
|
||||
* into hash tables if there are enough elements or big elements
|
||||
* inside. */
|
||||
unsigned char *zm = zipmapNew();
|
||||
robj *o = createObject(REDIS_HASH,zm);
|
||||
o->encoding = REDIS_ENCODING_ZIPMAP;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createZsetObject(void) {
|
||||
zset *zs = zmalloc(sizeof(*zs));
|
||||
|
||||
zs->dict = dictCreate(&zsetDictType,NULL);
|
||||
zs->zsl = zslCreate();
|
||||
return createObject(REDIS_ZSET,zs);
|
||||
}
|
||||
|
||||
void freeStringObject(robj *o) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
sdsfree(o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void freeListObject(robj *o) {
|
||||
switch (o->encoding) {
|
||||
case REDIS_ENCODING_LINKEDLIST:
|
||||
listRelease((list*) o->ptr);
|
||||
break;
|
||||
case REDIS_ENCODING_ZIPLIST:
|
||||
zfree(o->ptr);
|
||||
break;
|
||||
default:
|
||||
redisPanic("Unknown list encoding type");
|
||||
}
|
||||
}
|
||||
|
||||
void freeSetObject(robj *o) {
|
||||
switch (o->encoding) {
|
||||
case REDIS_ENCODING_HT:
|
||||
dictRelease((dict*) o->ptr);
|
||||
break;
|
||||
case REDIS_ENCODING_INTSET:
|
||||
zfree(o->ptr);
|
||||
break;
|
||||
default:
|
||||
redisPanic("Unknown set encoding type");
|
||||
}
|
||||
}
|
||||
|
||||
void freeZsetObject(robj *o) {
|
||||
zset *zs = o->ptr;
|
||||
|
||||
dictRelease(zs->dict);
|
||||
zslFree(zs->zsl);
|
||||
zfree(zs);
|
||||
}
|
||||
|
||||
void freeHashObject(robj *o) {
|
||||
switch (o->encoding) {
|
||||
case REDIS_ENCODING_HT:
|
||||
dictRelease((dict*) o->ptr);
|
||||
break;
|
||||
case REDIS_ENCODING_ZIPMAP:
|
||||
zfree(o->ptr);
|
||||
break;
|
||||
default:
|
||||
redisPanic("Unknown hash encoding type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void incrRefCount(robj *o) {
|
||||
o->refcount++;
|
||||
}
|
||||
|
||||
void decrRefCount(void *obj) {
|
||||
robj *o = obj;
|
||||
|
||||
/* Object is a swapped out value, or in the process of being loaded. */
|
||||
if (server.vm_enabled &&
|
||||
(o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING))
|
||||
{
|
||||
vmpointer *vp = obj;
|
||||
if (o->storage == REDIS_VM_LOADING) vmCancelThreadedIOJob(o);
|
||||
vmMarkPagesFree(vp->page,vp->usedpages);
|
||||
server.vm_stats_swapped_objects--;
|
||||
zfree(vp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
|
||||
/* Object is in memory, or in the process of being swapped out.
|
||||
*
|
||||
* If the object is being swapped out, abort the operation on
|
||||
* decrRefCount even if the refcount does not drop to 0: the object
|
||||
* is referenced at least two times, as value of the key AND as
|
||||
* job->val in the iojob. So if we don't invalidate the iojob, when it is
|
||||
* done but the relevant key was removed in the meantime, the
|
||||
* complete jobs handler will not find the key about the job and the
|
||||
* assert will fail. */
|
||||
if (server.vm_enabled && o->storage == REDIS_VM_SWAPPING)
|
||||
vmCancelThreadedIOJob(o);
|
||||
if (--(o->refcount) == 0) {
|
||||
switch(o->type) {
|
||||
case REDIS_STRING: freeStringObject(o); break;
|
||||
case REDIS_LIST: freeListObject(o); break;
|
||||
case REDIS_SET: freeSetObject(o); break;
|
||||
case REDIS_ZSET: freeZsetObject(o); break;
|
||||
case REDIS_HASH: freeHashObject(o); break;
|
||||
default: redisPanic("Unknown object type"); break;
|
||||
}
|
||||
o->ptr = NULL; /* defensive programming. We'll see NULL in traces. */
|
||||
zfree(o);
|
||||
}
|
||||
}
|
||||
|
||||
int checkType(redisClient *c, robj *o, int type) {
|
||||
if (o->type != type) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
sds s = o->ptr;
|
||||
|
||||
if (o->encoding != REDIS_ENCODING_RAW)
|
||||
return o; /* Already encoded */
|
||||
|
||||
/* It's not safe to encode shared objects: shared objects can be shared
|
||||
* everywhere in the "object space" of Redis. Encoded objects can only
|
||||
* appear as "values" (and not, for instance, as keys) */
|
||||
if (o->refcount > 1) return o;
|
||||
|
||||
/* Currently we try to encode only strings */
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
|
||||
/* Check if we can represent this string as a long integer */
|
||||
if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o;
|
||||
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
* Can I use a shared object? Only if the object is inside a given
|
||||
* range and if this is the main thread, since when VM is enabled we
|
||||
* have the constraint that I/O thread should only handle non-shared
|
||||
* objects, in order to avoid race conditions (we don't have per-object
|
||||
* locking).
|
||||
*
|
||||
* Note that we also avoid using shared integers when maxmemory is used
|
||||
* because very object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
decrRefCount(o);
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
o->encoding = REDIS_ENCODING_INT;
|
||||
sdsfree(o->ptr);
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get a decoded version of an encoded object (returned as a new object).
|
||||
* If the object is already raw-encoded just increment the ref count. */
|
||||
robj *getDecodedObject(robj *o) {
|
||||
robj *dec;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
incrRefCount(o);
|
||||
return o;
|
||||
}
|
||||
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
|
||||
char buf[32];
|
||||
|
||||
ll2string(buf,32,(long)o->ptr);
|
||||
dec = createStringObject(buf,strlen(buf));
|
||||
return dec;
|
||||
} else {
|
||||
redisPanic("Unknown encoding type");
|
||||
}
|
||||
}
|
||||
|
||||
/* Compare two string objects via strcmp() or alike.
|
||||
* Note that the objects may be integer-encoded. In such a case we
|
||||
* use ll2string() to get a string representation of the numbers on the stack
|
||||
* and compare the strings, it's much faster than calling getDecodedObject().
|
||||
*
|
||||
* Important note: if objects are not integer encoded, but binary-safe strings,
|
||||
* sdscmp() from sds.c will apply memcmp() so this function ca be considered
|
||||
* binary safe. */
|
||||
int compareStringObjects(robj *a, robj *b) {
|
||||
redisAssert(a->type == REDIS_STRING && b->type == REDIS_STRING);
|
||||
char bufa[128], bufb[128], *astr, *bstr;
|
||||
int bothsds = 1;
|
||||
|
||||
if (a == b) return 0;
|
||||
if (a->encoding != REDIS_ENCODING_RAW) {
|
||||
ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
||||
astr = bufa;
|
||||
bothsds = 0;
|
||||
} else {
|
||||
astr = a->ptr;
|
||||
}
|
||||
if (b->encoding != REDIS_ENCODING_RAW) {
|
||||
ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
||||
bstr = bufb;
|
||||
bothsds = 0;
|
||||
} else {
|
||||
bstr = b->ptr;
|
||||
}
|
||||
return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr);
|
||||
}
|
||||
|
||||
/* Equal string objects return 1 if the two objects are the same from the
|
||||
* point of view of a string comparison, otherwise 0 is returned. Note that
|
||||
* this function is faster then checking for (compareStringObject(a,b) == 0)
|
||||
* because it can perform some more optimization. */
|
||||
int equalStringObjects(robj *a, robj *b) {
|
||||
if (a->encoding != REDIS_ENCODING_RAW && b->encoding != REDIS_ENCODING_RAW){
|
||||
return a->ptr == b->ptr;
|
||||
} else {
|
||||
return compareStringObjects(a,b) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t stringObjectLen(robj *o) {
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
return sdslen(o->ptr);
|
||||
} else {
|
||||
char buf[32];
|
||||
|
||||
return ll2string(buf,32,(long)o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
int getDoubleFromObject(robj *o, double *target) {
|
||||
double value;
|
||||
char *eptr;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
}
|
||||
|
||||
*target = value;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) {
|
||||
double value;
|
||||
if (getDoubleFromObject(o, &value) != REDIS_OK) {
|
||||
if (msg != NULL) {
|
||||
addReplyError(c,(char*)msg);
|
||||
} else {
|
||||
addReplyError(c,"value is not a double");
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
*target = value;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int getLongLongFromObject(robj *o, long long *target) {
|
||||
long long value;
|
||||
char *eptr;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
value = strtoll(o->ptr, &eptr, 10);
|
||||
if (eptr[0] != '\0') return REDIS_ERR;
|
||||
if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX))
|
||||
return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
}
|
||||
|
||||
if (target) *target = value;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) {
|
||||
long long value;
|
||||
if (getLongLongFromObject(o, &value) != REDIS_OK) {
|
||||
if (msg != NULL) {
|
||||
addReplyError(c,(char*)msg);
|
||||
} else {
|
||||
addReplyError(c,"value is not an integer or out of range");
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
*target = value;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) {
|
||||
long long value;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
|
||||
if (value < LONG_MIN || value > LONG_MAX) {
|
||||
if (msg != NULL) {
|
||||
addReplyError(c,(char*)msg);
|
||||
} else {
|
||||
addReplyError(c,"value is out of range");
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
*target = value;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
char *strEncoding(int encoding) {
|
||||
switch(encoding) {
|
||||
case REDIS_ENCODING_RAW: return "raw";
|
||||
case REDIS_ENCODING_INT: return "int";
|
||||
case REDIS_ENCODING_HT: return "hashtable";
|
||||
case REDIS_ENCODING_ZIPMAP: return "zipmap";
|
||||
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
|
||||
case REDIS_ENCODING_ZIPLIST: return "ziplist";
|
||||
case REDIS_ENCODING_INTSET: return "intset";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* Given an object returns the min number of seconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
unsigned long estimateObjectIdleTime(robj *o) {
|
||||
if (server.lruclock >= o->lru) {
|
||||
return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
|
||||
} else {
|
||||
return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
|
||||
REDIS_LRU_CLOCK_RESOLUTION;
|
||||
}
|
||||
}
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub low level API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void freePubsubPattern(void *p) {
|
||||
pubsubPattern *pat = p;
|
||||
|
||||
decrRefCount(pat->pattern);
|
||||
zfree(pat);
|
||||
}
|
||||
|
||||
int listMatchPubsubPattern(void *a, void *b) {
|
||||
pubsubPattern *pa = a, *pb = b;
|
||||
|
||||
return (pa->client == pb->client) &&
|
||||
(equalStringObjects(pa->pattern,pb->pattern));
|
||||
}
|
||||
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
struct dictEntry *de;
|
||||
list *clients = NULL;
|
||||
int retval = 0;
|
||||
|
||||
/* Add the channel to the client -> channels hash table */
|
||||
if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
|
||||
retval = 1;
|
||||
incrRefCount(channel);
|
||||
/* Add the client to the channel -> list of clients hash table */
|
||||
de = dictFind(server.pubsub_channels,channel);
|
||||
if (de == NULL) {
|
||||
clients = listCreate();
|
||||
dictAdd(server.pubsub_channels,channel,clients);
|
||||
incrRefCount(channel);
|
||||
} else {
|
||||
clients = dictGetEntryVal(de);
|
||||
}
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReply(c,shared.mbulk3);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
|
||||
struct dictEntry *de;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
int retval = 0;
|
||||
|
||||
/* Remove the channel from the client -> channels hash table */
|
||||
incrRefCount(channel); /* channel may be just a pointer to the same object
|
||||
we have in the hash tables. Protect it... */
|
||||
if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
|
||||
retval = 1;
|
||||
/* Remove the client from the channel -> clients list hash table */
|
||||
de = dictFind(server.pubsub_channels,channel);
|
||||
redisAssert(de != NULL);
|
||||
clients = dictGetEntryVal(de);
|
||||
ln = listSearchKey(clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(clients,ln);
|
||||
if (listLength(clients) == 0) {
|
||||
/* Free the list and associated hash entry at all if this was
|
||||
* the latest client, so that it will be possible to abuse
|
||||
* Redis PUBSUB creating millions of channels. */
|
||||
dictDelete(server.pubsub_channels,channel);
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulk3);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
|
||||
}
|
||||
decrRefCount(channel); /* it is finally safe to release it */
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the clinet was already subscribed to that pattern. */
|
||||
int pubsubSubscribePattern(redisClient *c, robj *pattern) {
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
retval = 1;
|
||||
pubsubPattern *pat;
|
||||
listAddNodeTail(c->pubsub_patterns,pattern);
|
||||
incrRefCount(pattern);
|
||||
pat = zmalloc(sizeof(*pat));
|
||||
pat->pattern = getDecodedObject(pattern);
|
||||
pat->client = c;
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReply(c,shared.mbulk3);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
|
||||
listNode *ln;
|
||||
pubsubPattern pat;
|
||||
int retval = 0;
|
||||
|
||||
incrRefCount(pattern); /* Protect the object. May be the same we remove */
|
||||
if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
|
||||
retval = 1;
|
||||
listDelNode(c->pubsub_patterns,ln);
|
||||
pat.client = c;
|
||||
pat.pattern = pattern;
|
||||
ln = listSearchKey(server.pubsub_patterns,&pat);
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulk3);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
decrRefCount(pattern);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed from. */
|
||||
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
|
||||
dictIterator *di = dictGetIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
int count = 0;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *channel = dictGetEntryKey(de);
|
||||
|
||||
count += pubsubUnsubscribeChannel(c,channel,notify);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Unsubscribe from all the patterns. Return the number of patterns the
|
||||
* client was subscribed from. */
|
||||
int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int count = 0;
|
||||
|
||||
listRewind(c->pubsub_patterns,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
robj *pattern = ln->value;
|
||||
|
||||
count += pubsubUnsubscribePattern(c,pattern,notify);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Publish a message */
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
int receivers = 0;
|
||||
struct dictEntry *de;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
/* Send to clients listening for that channel */
|
||||
de = dictFind(server.pubsub_channels,channel);
|
||||
if (de) {
|
||||
list *list = dictGetEntryVal(de);
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(list,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
redisClient *c = ln->value;
|
||||
|
||||
addReply(c,shared.mbulk3);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
/* Send to clients listening to matching channels */
|
||||
if (listLength(server.pubsub_patterns)) {
|
||||
listRewind(server.pubsub_patterns,&li);
|
||||
channel = getDecodedObject(channel);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
pubsubPattern *pat = ln->value;
|
||||
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0)) {
|
||||
addReply(pat->client,shared.mbulk4);
|
||||
addReply(pat->client,shared.pmessagebulk);
|
||||
addReplyBulk(pat->client,pat->pattern);
|
||||
addReplyBulk(pat->client,channel);
|
||||
addReplyBulk(pat->client,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
decrRefCount(channel);
|
||||
}
|
||||
return receivers;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub commands implementation
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void subscribeCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubSubscribeChannel(c,c->argv[j]);
|
||||
}
|
||||
|
||||
void unsubscribeCommand(redisClient *c) {
|
||||
if (c->argc == 1) {
|
||||
pubsubUnsubscribeAllChannels(c,1);
|
||||
return;
|
||||
} else {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubUnsubscribeChannel(c,c->argv[j],1);
|
||||
}
|
||||
}
|
||||
|
||||
void psubscribeCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubSubscribePattern(c,c->argv[j]);
|
||||
}
|
||||
|
||||
void punsubscribeCommand(redisClient *c) {
|
||||
if (c->argc == 1) {
|
||||
pubsubUnsubscribeAllPatterns(c,1);
|
||||
return;
|
||||
} else {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubUnsubscribePattern(c,c->argv[j],1);
|
||||
}
|
||||
}
|
||||
|
||||
void publishCommand(redisClient *c) {
|
||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||
addReplyLongLong(c,receivers);
|
||||
}
|
||||
@@ -40,22 +40,15 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "ae.h"
|
||||
#include "anet.h"
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
#include "adlist.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
#define REPLY_INT 0
|
||||
#define REPLY_RETCODE 1
|
||||
#define REPLY_BULK 2
|
||||
#define REPLY_MBULK 3
|
||||
|
||||
#define CLIENT_CONNECTING 0
|
||||
#define CLIENT_SENDQUERY 1
|
||||
#define CLIENT_READREPLY 2
|
||||
|
||||
#define MAX_LATENCY 5000
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
|
||||
static struct config {
|
||||
@@ -71,10 +64,12 @@ static struct config {
|
||||
aeEventLoop *el;
|
||||
char *hostip;
|
||||
int hostport;
|
||||
char *hostsocket;
|
||||
int keepalive;
|
||||
long long start;
|
||||
long long totlatency;
|
||||
int *latency;
|
||||
long long *latency;
|
||||
char *title;
|
||||
list *clients;
|
||||
int quiet;
|
||||
int loop;
|
||||
@@ -82,16 +77,13 @@ static struct config {
|
||||
} config;
|
||||
|
||||
typedef struct _client {
|
||||
redisContext *context;
|
||||
int state;
|
||||
int fd;
|
||||
sds obuf;
|
||||
sds ibuf;
|
||||
int mbulk; /* Number of elements in an mbulk reply */
|
||||
int readlen; /* readlen == -1 means read a single line */
|
||||
int totreceived;
|
||||
unsigned int written; /* bytes of 'obuf' already written */
|
||||
unsigned int written; /* bytes of 'obuf' already written */
|
||||
int replytype;
|
||||
long long start; /* start time in milliseconds */
|
||||
long long start; /* start time of a request */
|
||||
long long latency; /* request latency */
|
||||
} *client;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -99,6 +91,16 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
static void createMissingClients(client c);
|
||||
|
||||
/* Implementation */
|
||||
static long long ustime(void) {
|
||||
struct timeval tv;
|
||||
long long ust;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
ust = ((long)tv.tv_sec)*1000000;
|
||||
ust += tv.tv_usec;
|
||||
return ust;
|
||||
}
|
||||
|
||||
static long long mstime(void) {
|
||||
struct timeval tv;
|
||||
long long mst;
|
||||
@@ -111,12 +113,10 @@ static long long mstime(void) {
|
||||
|
||||
static void freeClient(client c) {
|
||||
listNode *ln;
|
||||
|
||||
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
|
||||
aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
|
||||
sdsfree(c->ibuf);
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
|
||||
redisFree(c->context);
|
||||
sdsfree(c->obuf);
|
||||
close(c->fd);
|
||||
zfree(c);
|
||||
config.liveclients--;
|
||||
ln = listSearchKey(config.clients,c);
|
||||
@@ -135,19 +135,13 @@ static void freeAllClients(void) {
|
||||
}
|
||||
|
||||
static void resetClient(client c) {
|
||||
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
|
||||
aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
|
||||
aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c);
|
||||
sdsfree(c->ibuf);
|
||||
c->ibuf = sdsempty();
|
||||
c->readlen = (c->replytype == REPLY_BULK ||
|
||||
c->replytype == REPLY_MBULK) ? -1 : 0;
|
||||
c->mbulk = -1;
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
c->written = 0;
|
||||
c->totreceived = 0;
|
||||
c->state = CLIENT_SENDQUERY;
|
||||
c->start = mstime();
|
||||
createMissingClients(c);
|
||||
c->start = ustime();
|
||||
c->latency = -1;
|
||||
}
|
||||
|
||||
static void randomizeClientKey(client c) {
|
||||
@@ -163,33 +157,7 @@ static void randomizeClientKey(client c) {
|
||||
memcpy(p,buf,strlen(buf));
|
||||
}
|
||||
|
||||
static void prepareClientForReply(client c, int type) {
|
||||
if (type == REPLY_BULK) {
|
||||
c->replytype = REPLY_BULK;
|
||||
c->readlen = -1;
|
||||
} else if (type == REPLY_MBULK) {
|
||||
c->replytype = REPLY_MBULK;
|
||||
c->readlen = -1;
|
||||
c->mbulk = -1;
|
||||
} else {
|
||||
c->replytype = type;
|
||||
c->readlen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void clientDone(client c) {
|
||||
static int last_tot_received = 1;
|
||||
|
||||
long long latency;
|
||||
config.donerequests ++;
|
||||
latency = mstime() - c->start;
|
||||
if (latency > MAX_LATENCY) latency = MAX_LATENCY;
|
||||
config.latency[latency]++;
|
||||
|
||||
if (config.debug && last_tot_received != c->totreceived) {
|
||||
printf("Tot bytes received: %d\n", c->totreceived);
|
||||
last_tot_received = c->totreceived;
|
||||
}
|
||||
if (config.donerequests == config.requests) {
|
||||
freeClient(c);
|
||||
aeStop(config.el);
|
||||
@@ -206,108 +174,35 @@ static void clientDone(client c) {
|
||||
}
|
||||
}
|
||||
|
||||
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
{
|
||||
char buf[1024];
|
||||
int nread;
|
||||
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
client c = privdata;
|
||||
void *reply = NULL;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(fd);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
nread = read(c->fd, buf, 1024);
|
||||
if (nread == -1) {
|
||||
fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
if (nread == 0) {
|
||||
fprintf(stderr, "EOF from client\n");
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
c->totreceived += nread;
|
||||
c->ibuf = sdscatlen(c->ibuf,buf,nread);
|
||||
/* Calculate latency only for the first read event. This means that the
|
||||
* server already sent the reply and we need to parse it. Parsing overhead
|
||||
* is not part of the latency, so calculate it only once, here. */
|
||||
if (c->latency < 0) c->latency = ustime()-(c->start);
|
||||
|
||||
processdata:
|
||||
/* Are we waiting for the first line of the command of for sdf
|
||||
* count in bulk or multi bulk operations? */
|
||||
if (c->replytype == REPLY_INT ||
|
||||
c->replytype == REPLY_RETCODE ||
|
||||
(c->replytype == REPLY_BULK && c->readlen == -1) ||
|
||||
(c->replytype == REPLY_MBULK && c->readlen == -1) ||
|
||||
(c->replytype == REPLY_MBULK && c->mbulk == -1)) {
|
||||
char *p;
|
||||
|
||||
/* Check if the first line is complete. This is only true if
|
||||
* there is a newline inside the buffer. */
|
||||
if ((p = strchr(c->ibuf,'\n')) != NULL) {
|
||||
if (c->replytype == REPLY_BULK ||
|
||||
(c->replytype == REPLY_MBULK && c->mbulk != -1))
|
||||
{
|
||||
/* Read the count of a bulk reply (being it a single bulk or
|
||||
* a multi bulk reply). "$<count>" for the protocol spec. */
|
||||
*p = '\0';
|
||||
*(p-1) = '\0';
|
||||
c->readlen = atoi(c->ibuf+1)+2;
|
||||
// printf("BULK ATOI: %s\n", c->ibuf+1);
|
||||
/* Handle null bulk reply "$-1" */
|
||||
if (c->readlen-2 == -1) {
|
||||
clientDone(c);
|
||||
return;
|
||||
}
|
||||
/* Leave all the rest in the input buffer */
|
||||
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
|
||||
/* fall through to reach the point where the code will try
|
||||
* to check if the bulk reply is complete. */
|
||||
} else if (c->replytype == REPLY_MBULK && c->mbulk == -1) {
|
||||
/* Read the count of a multi bulk reply. That is, how many
|
||||
* bulk replies we have to read next. "*<count>" protocol. */
|
||||
*p = '\0';
|
||||
*(p-1) = '\0';
|
||||
c->mbulk = atoi(c->ibuf+1);
|
||||
/* Handle null bulk reply "*-1" */
|
||||
if (c->mbulk == -1) {
|
||||
clientDone(c);
|
||||
return;
|
||||
}
|
||||
// printf("%p) %d elements list\n", c, c->mbulk);
|
||||
/* Leave all the rest in the input buffer */
|
||||
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
|
||||
goto processdata;
|
||||
} else {
|
||||
c->ibuf = sdstrim(c->ibuf,"\r\n");
|
||||
clientDone(c);
|
||||
return;
|
||||
}
|
||||
if (redisBufferRead(c->context) != REDIS_OK) {
|
||||
fprintf(stderr,"Error: %s\n",c->context->errstr);
|
||||
exit(1);
|
||||
} else {
|
||||
if (redisGetReply(c->context,&reply) != REDIS_OK) {
|
||||
fprintf(stderr,"Error: %s\n",c->context->errstr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
/* bulk read, did we read everything? */
|
||||
if (((c->replytype == REPLY_MBULK && c->mbulk != -1) ||
|
||||
(c->replytype == REPLY_BULK)) && c->readlen != -1 &&
|
||||
(unsigned)c->readlen <= sdslen(c->ibuf))
|
||||
{
|
||||
// printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n",
|
||||
// c->mbulk,c->readlen,sdslen(c->ibuf));
|
||||
if (c->replytype == REPLY_BULK) {
|
||||
if (reply != NULL) {
|
||||
if (config.donerequests < config.requests)
|
||||
config.latency[config.donerequests++] = c->latency;
|
||||
clientDone(c);
|
||||
} else if (c->replytype == REPLY_MBULK) {
|
||||
// printf("%p) %d (%d)) ",c, c->mbulk, c->readlen);
|
||||
// fwrite(c->ibuf,c->readlen,1,stdout);
|
||||
// printf("\n");
|
||||
if (--c->mbulk == 0) {
|
||||
clientDone(c);
|
||||
} else {
|
||||
c->ibuf = sdsrange(c->ibuf,c->readlen,-1);
|
||||
c->readlen = -1;
|
||||
goto processdata;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
{
|
||||
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
client c = privdata;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(fd);
|
||||
@@ -315,12 +210,12 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
|
||||
if (c->state == CLIENT_CONNECTING) {
|
||||
c->state = CLIENT_SENDQUERY;
|
||||
c->start = mstime();
|
||||
c->start = ustime();
|
||||
c->latency = -1;
|
||||
}
|
||||
if (sdslen(c->obuf) > c->written) {
|
||||
void *ptr = c->obuf+c->written;
|
||||
int len = sdslen(c->obuf) - c->written;
|
||||
int nwritten = write(c->fd, ptr, len);
|
||||
int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
|
||||
if (nwritten == -1) {
|
||||
if (errno != EPIPE)
|
||||
fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
|
||||
@@ -329,84 +224,89 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
}
|
||||
c->written += nwritten;
|
||||
if (sdslen(c->obuf) == c->written) {
|
||||
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
|
||||
aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c);
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
|
||||
c->state = CLIENT_READREPLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static client createClient(void) {
|
||||
static client createClient(int replytype) {
|
||||
client c = zmalloc(sizeof(struct _client));
|
||||
char err[ANET_ERR_LEN];
|
||||
|
||||
c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport);
|
||||
if (c->fd == ANET_ERR) {
|
||||
zfree(c);
|
||||
fprintf(stderr,"Connect: %s\n",err);
|
||||
return NULL;
|
||||
if (config.hostsocket == NULL) {
|
||||
c->context = redisConnectNonBlock(config.hostip,config.hostport);
|
||||
} else {
|
||||
c->context = redisConnectUnixNonBlock(config.hostsocket);
|
||||
}
|
||||
anetTcpNoDelay(NULL,c->fd);
|
||||
c->obuf = sdsempty();
|
||||
c->ibuf = sdsempty();
|
||||
c->mbulk = -1;
|
||||
c->readlen = 0;
|
||||
c->written = 0;
|
||||
c->totreceived = 0;
|
||||
if (c->context->err) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
if (config.hostsocket == NULL)
|
||||
fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr);
|
||||
else
|
||||
fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
|
||||
exit(1);
|
||||
}
|
||||
c->replytype = replytype;
|
||||
c->state = CLIENT_CONNECTING;
|
||||
aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c);
|
||||
config.liveclients++;
|
||||
c->obuf = sdsempty();
|
||||
c->written = 0;
|
||||
redisSetReplyObjectFunctions(c->context,NULL);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
listAddNodeTail(config.clients,c);
|
||||
config.liveclients++;
|
||||
return c;
|
||||
}
|
||||
|
||||
static void createMissingClients(client c) {
|
||||
while(config.liveclients < config.numclients) {
|
||||
client new = createClient();
|
||||
if (!new) continue;
|
||||
client new = createClient(c->replytype);
|
||||
sdsfree(new->obuf);
|
||||
new->obuf = sdsdup(c->obuf);
|
||||
if (config.randomkeys) randomizeClientKey(c);
|
||||
prepareClientForReply(new,c->replytype);
|
||||
}
|
||||
}
|
||||
|
||||
static void showLatencyReport(char *title) {
|
||||
int j, seen = 0;
|
||||
static int compareLatency(const void *a, const void *b) {
|
||||
return (*(long long*)a)-(*(long long*)b);
|
||||
}
|
||||
|
||||
static void showLatencyReport(void) {
|
||||
int i, curlat = 0;
|
||||
float perc, reqpersec;
|
||||
|
||||
reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
|
||||
if (!config.quiet) {
|
||||
printf("====== %s ======\n", title);
|
||||
printf("====== %s ======\n", config.title);
|
||||
printf(" %d requests completed in %.2f seconds\n", config.donerequests,
|
||||
(float)config.totlatency/1000);
|
||||
printf(" %d parallel clients\n", config.numclients);
|
||||
printf(" %d bytes payload\n", config.datasize);
|
||||
printf(" keep alive: %d\n", config.keepalive);
|
||||
printf("\n");
|
||||
for (j = 0; j <= MAX_LATENCY; j++) {
|
||||
if (config.latency[j]) {
|
||||
seen += config.latency[j];
|
||||
perc = ((float)seen*100)/config.donerequests;
|
||||
printf("%.2f%% <= %d milliseconds\n", perc, j);
|
||||
|
||||
qsort(config.latency,config.requests,sizeof(long long),compareLatency);
|
||||
for (i = 0; i < config.requests; i++) {
|
||||
if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {
|
||||
curlat = config.latency[i]/1000;
|
||||
perc = ((float)(i+1)*100)/config.requests;
|
||||
printf("%.2f%% <= %d milliseconds\n", perc, curlat);
|
||||
}
|
||||
}
|
||||
printf("%.2f requests per second\n\n", reqpersec);
|
||||
} else {
|
||||
printf("%s: %.2f requests per second\n", title, reqpersec);
|
||||
printf("%s: %.2f requests per second\n", config.title, reqpersec);
|
||||
}
|
||||
}
|
||||
|
||||
static void prepareForBenchmark(void)
|
||||
{
|
||||
memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
|
||||
static void prepareForBenchmark(char *title) {
|
||||
config.title = title;
|
||||
config.start = mstime();
|
||||
config.donerequests = 0;
|
||||
}
|
||||
|
||||
static void endBenchmark(char *title) {
|
||||
static void endBenchmark(void) {
|
||||
config.totlatency = mstime()-config.start;
|
||||
showLatencyReport(title);
|
||||
showLatencyReport();
|
||||
freeAllClients();
|
||||
}
|
||||
|
||||
@@ -426,16 +326,14 @@ void parseOptions(int argc, char **argv) {
|
||||
config.keepalive = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-h") && !lastarg) {
|
||||
char *ip = zmalloc(32);
|
||||
if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
|
||||
printf("Can't resolve %s\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
config.hostip = ip;
|
||||
config.hostip = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-p") && !lastarg) {
|
||||
config.hostport = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-s") && !lastarg) {
|
||||
config.hostsocket = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-d") && !lastarg) {
|
||||
config.datasize = atoi(argv[i+1]);
|
||||
i++;
|
||||
@@ -459,7 +357,8 @@ void parseOptions(int argc, char **argv) {
|
||||
printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
|
||||
printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
|
||||
printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
|
||||
printf(" -p <hostname> Server port (default 6379)\n");
|
||||
printf(" -p <port> Server port (default 6379)\n");
|
||||
printf(" -s <socket> Server socket (overrides host and port)\n");
|
||||
printf(" -c <clients> Number of parallel connections (default 50)\n");
|
||||
printf(" -n <requests> Total number of requests (default 10000)\n");
|
||||
printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
|
||||
@@ -480,6 +379,18 @@ void parseOptions(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
REDIS_NOTUSED(eventLoop);
|
||||
REDIS_NOTUSED(id);
|
||||
REDIS_NOTUSED(clientData);
|
||||
|
||||
float dt = (float)(mstime()-config.start)/1000.0;
|
||||
float rps = (float)config.donerequests/dt;
|
||||
printf("%s: %.2f\r", config.title, rps);
|
||||
fflush(stdout);
|
||||
return 250; /* every 250ms */
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
client c;
|
||||
|
||||
@@ -491,6 +402,7 @@ int main(int argc, char **argv) {
|
||||
config.requests = 10000;
|
||||
config.liveclients = 0;
|
||||
config.el = aeCreateEventLoop();
|
||||
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
|
||||
config.keepalive = 1;
|
||||
config.donerequests = 0;
|
||||
config.datasize = 3;
|
||||
@@ -501,12 +413,12 @@ int main(int argc, char **argv) {
|
||||
config.idlemode = 0;
|
||||
config.latency = NULL;
|
||||
config.clients = listCreate();
|
||||
config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
|
||||
|
||||
config.hostip = "127.0.0.1";
|
||||
config.hostport = 6379;
|
||||
config.hostsocket = NULL;
|
||||
|
||||
parseOptions(argc,argv);
|
||||
config.latency = zmalloc(sizeof(long long)*config.requests);
|
||||
|
||||
if (config.keepalive == 0) {
|
||||
printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n");
|
||||
@@ -514,149 +426,136 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (config.idlemode) {
|
||||
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("IDLE");
|
||||
c = createClient(0); /* will never receive a reply */
|
||||
c->obuf = sdsempty();
|
||||
prepareClientForReply(c,REPLY_RETCODE); /* will never receive it */
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
/* and will wait for every */
|
||||
}
|
||||
|
||||
do {
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("PING");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"PING\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("PING");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("PING (multi bulk)");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("PING (multi bulk)");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
|
||||
prepareForBenchmark("MSET (10 keys, multi bulk)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11);
|
||||
{
|
||||
int i;
|
||||
char *data = zmalloc(config.datasize+2);
|
||||
memset(data,'x',config.datasize);
|
||||
for (i = 0; i < 10; i++) {
|
||||
c->obuf = sdscatprintf(c->obuf,"$%d\r\n%s\r\n",config.datasize,data);
|
||||
}
|
||||
zfree(data);
|
||||
}
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark("SET");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"*3\r\n$3\r\nSET\r\n$20\r\nfoo_rand000000000000\r\n");
|
||||
{
|
||||
char *data = zmalloc(config.datasize+2);
|
||||
memset(data,'x',config.datasize);
|
||||
data[config.datasize] = '\r';
|
||||
data[config.datasize+1] = '\n';
|
||||
c->obuf = sdscatprintf(c->obuf,"$%d\r\n",config.datasize);
|
||||
c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
|
||||
}
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("SET");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("GET");
|
||||
c = createClient(REDIS_REPLY_STRING);
|
||||
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
|
||||
prepareClientForReply(c,REPLY_BULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("GET");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("INCR");
|
||||
c = createClient(REDIS_REPLY_INTEGER);
|
||||
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
|
||||
prepareClientForReply(c,REPLY_INT);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("INCR");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
|
||||
prepareClientForReply(c,REPLY_INT);
|
||||
prepareForBenchmark("LPUSH");
|
||||
c = createClient(REDIS_REPLY_INTEGER);
|
||||
c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LPUSH");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("LPOP");
|
||||
c = createClient(REDIS_REPLY_STRING);
|
||||
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
|
||||
prepareClientForReply(c,REPLY_BULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LPOP");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
prepareForBenchmark("SADD");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("SADD");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("SPOP");
|
||||
c = createClient(REDIS_REPLY_STRING);
|
||||
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
|
||||
prepareClientForReply(c,REPLY_BULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("SPOP");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LPUSH (again, in order to bench LRANGE)");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("LRANGE (first 100 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
|
||||
prepareClientForReply(c,REPLY_MBULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LRANGE (first 100 elements)");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("LRANGE (first 300 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
|
||||
prepareClientForReply(c,REPLY_MBULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LRANGE (first 300 elements)");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("LRANGE (first 450 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
|
||||
prepareClientForReply(c,REPLY_MBULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LRANGE (first 450 elements)");
|
||||
endBenchmark();
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
prepareForBenchmark("LRANGE (first 600 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
|
||||
prepareClientForReply(c,REPLY_MBULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("LRANGE (first 600 elements)");
|
||||
endBenchmark();
|
||||
|
||||
printf("\n");
|
||||
} while(config.loop);
|
||||
@@ -65,8 +65,8 @@
|
||||
/* data type to hold offset in file and size */
|
||||
typedef struct {
|
||||
void *data;
|
||||
unsigned long size;
|
||||
unsigned long offset;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
} pos;
|
||||
|
||||
static unsigned char level = 0;
|
||||
@@ -77,8 +77,8 @@ static pos positions[16];
|
||||
/* Hold a stack of errors */
|
||||
typedef struct {
|
||||
char error[16][1024];
|
||||
unsigned long offset[16];
|
||||
unsigned int level;
|
||||
size_t offset[16];
|
||||
size_t level;
|
||||
} errors_t;
|
||||
static errors_t errors;
|
||||
|
||||
@@ -112,7 +112,7 @@ int readBytes(void *target, long num) {
|
||||
if (p.offset + num > p.size) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(target, (void*)((unsigned long)p.data + p.offset), num);
|
||||
memcpy(target, (void*)((size_t)p.data + p.offset), num);
|
||||
if (!peek) positions[level].offset += num;
|
||||
}
|
||||
return 1;
|
||||
@@ -494,15 +494,17 @@ void printCentered(int indent, int width, char* body) {
|
||||
printf("%s %s %s\n", head, body, tail);
|
||||
}
|
||||
|
||||
void printValid(int ops, int bytes) {
|
||||
void printValid(uint64_t ops, uint64_t bytes) {
|
||||
char body[80];
|
||||
sprintf(body, "Processed %d valid opcodes (in %d bytes)", ops, bytes);
|
||||
sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
|
||||
(unsigned long long) ops, (unsigned long long) bytes);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
void printSkipped(int bytes, int offset) {
|
||||
void printSkipped(uint64_t bytes, uint64_t offset) {
|
||||
char body[80];
|
||||
sprintf(body, "Skipped %d bytes (resuming at 0x%08x)", bytes, offset);
|
||||
sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
|
||||
(unsigned long long) bytes, (unsigned long long) offset);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
@@ -536,12 +538,13 @@ void printErrorStack(entry *e) {
|
||||
|
||||
/* display error stack */
|
||||
for (i = 0; i < errors.level; i++) {
|
||||
printf("0x%08lx - %s\n", errors.offset[i], errors.error[i]);
|
||||
printf("0x%08lx - %s\n",
|
||||
(unsigned long) errors.offset[i], errors.error[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void process() {
|
||||
int i, num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
entry entry;
|
||||
processHeader();
|
||||
|
||||
@@ -558,7 +561,9 @@ void process() {
|
||||
num_valid_bytes = 0;
|
||||
|
||||
/* search for next valid entry */
|
||||
unsigned long offset = positions[0].offset + 1;
|
||||
uint64_t offset = positions[0].offset + 1;
|
||||
int i = 0;
|
||||
|
||||
while (!entry.success && offset < positions[0].size) {
|
||||
positions[1].offset = offset;
|
||||
|
||||
@@ -606,9 +611,10 @@ void process() {
|
||||
}
|
||||
|
||||
/* print summary on errors */
|
||||
if (num_errors > 0) {
|
||||
if (num_errors) {
|
||||
printf("\n");
|
||||
printf("Total unprocessable opcodes: %d\n", num_errors);
|
||||
printf("Total unprocessable opcodes: %llu\n",
|
||||
(unsigned long long) num_errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +626,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
int fd;
|
||||
unsigned long size;
|
||||
off_t size;
|
||||
struct stat stat;
|
||||
void *data;
|
||||
|
||||
@@ -634,6 +640,10 @@ int main(int argc, char **argv) {
|
||||
size = stat.st_size;
|
||||
}
|
||||
|
||||
if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) {
|
||||
ERROR("Cannot check dump files >2GB on a 32-bit platform\n");
|
||||
}
|
||||
|
||||
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
ERROR("Cannot mmap: %s\n", argv[1]);
|
||||
+642
@@ -0,0 +1,642 @@
|
||||
/* Redis CLI (command line interface)
|
||||
*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
#include "zmalloc.h"
|
||||
#include "linenoise.h"
|
||||
#include "help.h"
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
char *hostip;
|
||||
int hostport;
|
||||
char *hostsocket;
|
||||
long repeat;
|
||||
int dbnum;
|
||||
int interactive;
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int raw_output; /* output mode per command */
|
||||
int tty; /* flag for default output format */
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char mb_sep;
|
||||
char *auth;
|
||||
char *historyfile;
|
||||
} config;
|
||||
|
||||
static void usage();
|
||||
char *redisGitSHA1(void);
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Utility functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static long long mstime(void) {
|
||||
struct timeval tv;
|
||||
long long mst;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
mst = ((long)tv.tv_sec)*1000;
|
||||
mst += tv.tv_usec/1000;
|
||||
return mst;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Help functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define CLI_HELP_COMMAND 1
|
||||
#define CLI_HELP_GROUP 2
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
int argc;
|
||||
sds *argv;
|
||||
sds full;
|
||||
|
||||
/* Only used for help on commands */
|
||||
struct commandHelp *org;
|
||||
} helpEntry;
|
||||
|
||||
static helpEntry *helpEntries;
|
||||
static int helpEntriesLen;
|
||||
|
||||
static void cliInitHelp() {
|
||||
int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
|
||||
int groupslen = sizeof(commandGroups)/sizeof(char*);
|
||||
int i, len, pos = 0;
|
||||
helpEntry tmp;
|
||||
|
||||
helpEntriesLen = len = commandslen+groupslen;
|
||||
helpEntries = malloc(sizeof(helpEntry)*len);
|
||||
|
||||
for (i = 0; i < groupslen; i++) {
|
||||
tmp.argc = 1;
|
||||
tmp.argv = malloc(sizeof(sds));
|
||||
tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
|
||||
tmp.full = tmp.argv[0];
|
||||
tmp.type = CLI_HELP_GROUP;
|
||||
tmp.org = NULL;
|
||||
helpEntries[pos++] = tmp;
|
||||
}
|
||||
|
||||
for (i = 0; i < commandslen; i++) {
|
||||
tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
|
||||
tmp.full = sdsnew(commandHelp[i].name);
|
||||
tmp.type = CLI_HELP_COMMAND;
|
||||
tmp.org = &commandHelp[i];
|
||||
helpEntries[pos++] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Output command help to stdout. */
|
||||
static void cliOutputCommandHelp(struct commandHelp *help, int group) {
|
||||
printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
|
||||
printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
|
||||
printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since);
|
||||
if (group) {
|
||||
printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print generic help. */
|
||||
static void cliOutputGenericHelp() {
|
||||
printf(
|
||||
"redis-cli %s\r\n"
|
||||
"Type: \"help @<group>\" to get a list of commands in <group>\r\n"
|
||||
" \"help <command>\" for help on <command>\r\n"
|
||||
" \"help <tab>\" to get a list of possible help topics\r\n"
|
||||
" \"quit\" to exit\r\n",
|
||||
REDIS_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/* Output all command help, filtering by group or command name. */
|
||||
static void cliOutputHelp(int argc, char **argv) {
|
||||
int i, j, len;
|
||||
int group = -1;
|
||||
helpEntry *entry;
|
||||
struct commandHelp *help;
|
||||
|
||||
if (argc == 0) {
|
||||
cliOutputGenericHelp();
|
||||
return;
|
||||
} else if (argc > 0 && argv[0][0] == '@') {
|
||||
len = sizeof(commandGroups)/sizeof(char*);
|
||||
for (i = 0; i < len; i++) {
|
||||
if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
|
||||
group = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(argc > 0);
|
||||
for (i = 0; i < helpEntriesLen; i++) {
|
||||
entry = &helpEntries[i];
|
||||
if (entry->type != CLI_HELP_COMMAND) continue;
|
||||
|
||||
help = entry->org;
|
||||
if (group == -1) {
|
||||
/* Compare all arguments */
|
||||
if (argc == entry->argc) {
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
|
||||
}
|
||||
if (j == argc) {
|
||||
cliOutputCommandHelp(help,1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (group == help->group) {
|
||||
cliOutputCommandHelp(help,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
static void completionCallback(const char *buf, linenoiseCompletions *lc) {
|
||||
size_t startpos = 0;
|
||||
int mask;
|
||||
int i;
|
||||
size_t matchlen;
|
||||
sds tmp;
|
||||
|
||||
if (strncasecmp(buf,"help ",5) == 0) {
|
||||
startpos = 5;
|
||||
while (isspace(buf[startpos])) startpos++;
|
||||
mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
|
||||
} else {
|
||||
mask = CLI_HELP_COMMAND;
|
||||
}
|
||||
|
||||
for (i = 0; i < helpEntriesLen; i++) {
|
||||
if (!(helpEntries[i].type & mask)) continue;
|
||||
|
||||
matchlen = strlen(buf+startpos);
|
||||
if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
|
||||
tmp = sdsnewlen(buf,startpos);
|
||||
tmp = sdscat(tmp,helpEntries[i].full);
|
||||
linenoiseAddCompletion(lc,tmp);
|
||||
sdsfree(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Networking / parsing
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Send AUTH command to the server */
|
||||
static int cliAuth() {
|
||||
redisReply *reply;
|
||||
if (config.auth == NULL) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"AUTH %s",config.auth);
|
||||
if (reply != NULL) {
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Send SELECT dbnum to the server */
|
||||
static int cliSelect() {
|
||||
redisReply *reply;
|
||||
char dbnum[16];
|
||||
if (config.dbnum == 0) return REDIS_OK;
|
||||
|
||||
snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum);
|
||||
reply = redisCommand(context,"SELECT %s",dbnum);
|
||||
if (reply != NULL) {
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Connect to the client. If force is not zero the connection is performed
|
||||
* even if there is already a connected socket. */
|
||||
static int cliConnect(int force) {
|
||||
if (context == NULL || force) {
|
||||
if (context != NULL)
|
||||
redisFree(context);
|
||||
|
||||
if (config.hostsocket == NULL) {
|
||||
context = redisConnect(config.hostip,config.hostport);
|
||||
} else {
|
||||
context = redisConnectUnix(config.hostsocket);
|
||||
}
|
||||
|
||||
if (context->err) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
if (config.hostsocket == NULL)
|
||||
fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
|
||||
else
|
||||
fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
|
||||
redisFree(context);
|
||||
context = NULL;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Do AUTH and select the right DB. */
|
||||
if (cliAuth() != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
if (cliSelect() != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static void cliPrintContextErrorAndExit() {
|
||||
if (context == NULL) return;
|
||||
fprintf(stderr,"Error: %s\n",context->errstr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static sds cliFormatReply(redisReply *r, char *prefix) {
|
||||
sds out = sdsempty();
|
||||
switch (r->type) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
if (config.tty) out = sdscat(out,"(error) ");
|
||||
out = sdscatprintf(out,"%s\n", r->str);
|
||||
break;
|
||||
case REDIS_REPLY_STATUS:
|
||||
out = sdscat(out,r->str);
|
||||
out = sdscat(out,"\n");
|
||||
break;
|
||||
case REDIS_REPLY_INTEGER:
|
||||
if (config.tty) out = sdscat(out,"(integer) ");
|
||||
out = sdscatprintf(out,"%lld\n",r->integer);
|
||||
break;
|
||||
case REDIS_REPLY_STRING:
|
||||
if (config.raw_output || !config.tty) {
|
||||
out = sdscatlen(out,r->str,r->len);
|
||||
} else {
|
||||
/* If you are producing output for the standard output we want
|
||||
* a more interesting output with quoted characters and so forth */
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
out = sdscat(out,"\n");
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_NIL:
|
||||
out = sdscat(out,"(nil)\n");
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
if (r->elements == 0) {
|
||||
out = sdscat(out,"(empty list or set)\n");
|
||||
} else {
|
||||
unsigned int i, idxlen = 0;
|
||||
char _prefixlen[16];
|
||||
char _prefixfmt[16];
|
||||
sds _prefix;
|
||||
sds tmp;
|
||||
|
||||
/* Calculate chars needed to represent the largest index */
|
||||
i = r->elements;
|
||||
do {
|
||||
idxlen++;
|
||||
i /= 10;
|
||||
} while(i);
|
||||
|
||||
/* Prefix for nested multi bulks should grow with idxlen+2 spaces */
|
||||
memset(_prefixlen,' ',idxlen+2);
|
||||
_prefixlen[idxlen+2] = '\0';
|
||||
_prefix = sdscat(sdsnew(prefix),_prefixlen);
|
||||
|
||||
/* Setup prefix format for every entry */
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
|
||||
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
/* Don't use the prefix for the first element, as the parent
|
||||
* caller already prepended the index number. */
|
||||
out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
|
||||
|
||||
/* Format the multi bulk entry */
|
||||
tmp = cliFormatReply(r->element[i],_prefix);
|
||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||
sdsfree(tmp);
|
||||
}
|
||||
sdsfree(_prefix);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
||||
exit(1);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static int cliReadReply() {
|
||||
redisReply *reply;
|
||||
sds out;
|
||||
|
||||
if (redisGetReply(context,(void**)&reply) != REDIS_OK) {
|
||||
if (config.shutdown)
|
||||
return REDIS_OK;
|
||||
if (config.interactive) {
|
||||
/* Filter cases where we should reconnect */
|
||||
if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
|
||||
return REDIS_ERR;
|
||||
if (context->err == REDIS_ERR_EOF)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
cliPrintContextErrorAndExit();
|
||||
return REDIS_ERR; /* avoid compiler warning */
|
||||
}
|
||||
|
||||
out = cliFormatReply(reply,"");
|
||||
freeReplyObject(reply);
|
||||
fwrite(out,sdslen(out),1,stdout);
|
||||
sdsfree(out);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
char *command = argv[0];
|
||||
size_t *argvlen;
|
||||
int j;
|
||||
|
||||
if (context == NULL) {
|
||||
printf("Not connected, please use: connect <host> <port>\n");
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
config.raw_output = !strcasecmp(command,"info");
|
||||
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
|
||||
cliOutputHelp(--argc, ++argv);
|
||||
return REDIS_OK;
|
||||
}
|
||||
if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
|
||||
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(command,"subscribe") ||
|
||||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
|
||||
|
||||
/* Setup argument length */
|
||||
argvlen = malloc(argc*sizeof(size_t));
|
||||
for (j = 0; j < argc; j++)
|
||||
argvlen[j] = sdslen(argv[j]);
|
||||
|
||||
while(repeat--) {
|
||||
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
|
||||
while (config.monitor_mode) {
|
||||
if (cliReadReply() != REDIS_OK) exit(1);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (config.pubsub_mode) {
|
||||
printf("Reading messages... (press Ctrl-C to quit)\n");
|
||||
while (1) {
|
||||
if (cliReadReply() != REDIS_OK) exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (cliReadReply() != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* User interface
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static int parseOptions(int argc, char **argv) {
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
int lastarg = i==argc-1;
|
||||
|
||||
if (!strcmp(argv[i],"-h") && !lastarg) {
|
||||
sdsfree(config.hostip);
|
||||
config.hostip = sdsnew(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-h") && lastarg) {
|
||||
usage();
|
||||
} else if (!strcmp(argv[i],"-x")) {
|
||||
config.stdinarg = 1;
|
||||
} else if (!strcmp(argv[i],"-p") && !lastarg) {
|
||||
config.hostport = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-s") && !lastarg) {
|
||||
config.hostsocket = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-r") && !lastarg) {
|
||||
config.repeat = strtoll(argv[i+1],NULL,10);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
config.auth = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-i")) {
|
||||
fprintf(stderr,
|
||||
"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
|
||||
"by default when redis-cli is executed without a command to execute.\n"
|
||||
);
|
||||
} else if (!strcmp(argv[i],"-c")) {
|
||||
fprintf(stderr,
|
||||
"Reading last argument from standard input using -c is deprecated.\n"
|
||||
"When standard input is connected to a pipe or regular file, it is\n"
|
||||
"automatically used as last argument.\n"
|
||||
);
|
||||
} else if (!strcmp(argv[i],"-v")) {
|
||||
printf("redis-cli shipped with Redis version %s (%s)\n", REDIS_VERSION, redisGitSHA1());
|
||||
exit(0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static sds readArgFromStdin(void) {
|
||||
char buf[1024];
|
||||
sds arg = sdsempty();
|
||||
|
||||
while(1) {
|
||||
int nread = read(fileno(stdin),buf,1024);
|
||||
|
||||
if (nread == 0) break;
|
||||
else if (nread == -1) {
|
||||
perror("Reading from standard input");
|
||||
exit(1);
|
||||
}
|
||||
arg = sdscatlen(arg,buf,nread);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
|
||||
fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
|
||||
fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
|
||||
fprintf(stderr, "example: redis-cli get my_passwd\n");
|
||||
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
|
||||
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Turn the plain C strings into Sds strings */
|
||||
static char **convertToSds(int count, char** args) {
|
||||
int j;
|
||||
char **sds = zmalloc(sizeof(char*)*count);
|
||||
|
||||
for(j = 0; j < count; j++)
|
||||
sds[j] = sdsnew(args[j]);
|
||||
|
||||
return sds;
|
||||
}
|
||||
|
||||
#define LINE_BUFLEN 4096
|
||||
static void repl() {
|
||||
int argc, j;
|
||||
char *line;
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
|
||||
while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
argv = sdssplitargs(line,&argc);
|
||||
linenoiseHistoryAdd(line);
|
||||
if (config.historyfile) linenoiseHistorySave(config.historyfile);
|
||||
if (argv == NULL) {
|
||||
printf("Invalid argument(s)\n");
|
||||
continue;
|
||||
} else if (argc > 0) {
|
||||
if (strcasecmp(argv[0],"quit") == 0 ||
|
||||
strcasecmp(argv[0],"exit") == 0)
|
||||
{
|
||||
exit(0);
|
||||
} else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
|
||||
sdsfree(config.hostip);
|
||||
config.hostip = sdsnew(argv[1]);
|
||||
config.hostport = atoi(argv[2]);
|
||||
cliConnect(1);
|
||||
} else {
|
||||
long long start_time = mstime(), elapsed;
|
||||
|
||||
if (cliSendCommand(argc,argv,1) != REDIS_OK) {
|
||||
cliConnect(1);
|
||||
|
||||
/* If we still cannot send the command,
|
||||
* print error and abort. */
|
||||
if (cliSendCommand(argc,argv,1) != REDIS_OK)
|
||||
cliPrintContextErrorAndExit();
|
||||
}
|
||||
elapsed = mstime()-start_time;
|
||||
if (elapsed >= 500) {
|
||||
printf("(%.2fs)\n",(double)elapsed/1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Free the argument vector */
|
||||
for (j = 0; j < argc; j++)
|
||||
sdsfree(argv[j]);
|
||||
zfree(argv);
|
||||
}
|
||||
/* linenoise() returns malloc-ed lines like readline() */
|
||||
free(line);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int noninteractive(int argc, char **argv) {
|
||||
int retval = 0;
|
||||
if (config.stdinarg) {
|
||||
argv = zrealloc(argv, (argc+1)*sizeof(char*));
|
||||
argv[argc] = readArgFromStdin();
|
||||
retval = cliSendCommand(argc+1, argv, config.repeat);
|
||||
} else {
|
||||
/* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
|
||||
retval = cliSendCommand(argc, argv, config.repeat);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
config.hostip = sdsnew("127.0.0.1");
|
||||
config.hostport = 6379;
|
||||
config.hostsocket = NULL;
|
||||
config.repeat = 1;
|
||||
config.dbnum = 0;
|
||||
config.interactive = 0;
|
||||
config.shutdown = 0;
|
||||
config.monitor_mode = 0;
|
||||
config.pubsub_mode = 0;
|
||||
config.raw_output = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.historyfile = NULL;
|
||||
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
|
||||
config.mb_sep = '\n';
|
||||
cliInitHelp();
|
||||
|
||||
if (getenv("HOME") != NULL) {
|
||||
config.historyfile = malloc(256);
|
||||
snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
|
||||
linenoiseHistoryLoad(config.historyfile);
|
||||
}
|
||||
|
||||
firstarg = parseOptions(argc,argv);
|
||||
argc -= firstarg;
|
||||
argv += firstarg;
|
||||
|
||||
/* Try to connect */
|
||||
if (cliConnect(0) != REDIS_OK) exit(1);
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0) repl();
|
||||
/* Otherwise, we have some arguments to execute */
|
||||
return noninteractive(argc,convertToSds(argc,argv));
|
||||
}
|
||||
+1653
File diff suppressed because it is too large
Load Diff
+1000
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
/* Every time the Redis Git SHA1 or Dirty status changes only this file
|
||||
* small file is recompiled, as we access this information in all the other
|
||||
* files using this functions. */
|
||||
|
||||
#include "release.h"
|
||||
|
||||
char *redisGitSHA1(void) {
|
||||
return REDIS_GIT_SHA1;
|
||||
}
|
||||
|
||||
char *redisGitDirty(void) {
|
||||
return REDIS_GIT_DIRTY;
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
#include "redis.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int outc = 0, j;
|
||||
robj **outv;
|
||||
/* We need 1+(ARGS*3) objects since commands are using the new protocol
|
||||
* and we one 1 object for the first "*<count>\r\n" multibulk count, then
|
||||
* for every additional object we have "$<count>\r\n" + object + "\r\n". */
|
||||
robj *static_outv[REDIS_STATIC_ARGS*3+1];
|
||||
robj *lenobj;
|
||||
|
||||
if (argc <= REDIS_STATIC_ARGS) {
|
||||
outv = static_outv;
|
||||
} else {
|
||||
outv = zmalloc(sizeof(robj*)*(argc*3+1));
|
||||
}
|
||||
|
||||
lenobj = createObject(REDIS_STRING,
|
||||
sdscatprintf(sdsempty(), "*%d\r\n", argc));
|
||||
lenobj->refcount = 0;
|
||||
outv[outc++] = lenobj;
|
||||
for (j = 0; j < argc; j++) {
|
||||
lenobj = createObject(REDIS_STRING,
|
||||
sdscatprintf(sdsempty(),"$%lu\r\n",
|
||||
(unsigned long) stringObjectLen(argv[j])));
|
||||
lenobj->refcount = 0;
|
||||
outv[outc++] = lenobj;
|
||||
outv[outc++] = argv[j];
|
||||
outv[outc++] = shared.crlf;
|
||||
}
|
||||
|
||||
/* Increment all the refcounts at start and decrement at end in order to
|
||||
* be sure to free objects if there is no slave in a replication state
|
||||
* able to be feed with commands */
|
||||
for (j = 0; j < outc; j++) incrRefCount(outv[j]);
|
||||
listRewind(slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
/* Don't feed slaves that are still waiting for BGSAVE to start */
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue;
|
||||
|
||||
/* Feed all the other slaves, MONITORs and so on */
|
||||
if (slave->slaveseldb != dictid) {
|
||||
robj *selectcmd;
|
||||
|
||||
switch(dictid) {
|
||||
case 0: selectcmd = shared.select0; break;
|
||||
case 1: selectcmd = shared.select1; break;
|
||||
case 2: selectcmd = shared.select2; break;
|
||||
case 3: selectcmd = shared.select3; break;
|
||||
case 4: selectcmd = shared.select4; break;
|
||||
case 5: selectcmd = shared.select5; break;
|
||||
case 6: selectcmd = shared.select6; break;
|
||||
case 7: selectcmd = shared.select7; break;
|
||||
case 8: selectcmd = shared.select8; break;
|
||||
case 9: selectcmd = shared.select9; break;
|
||||
default:
|
||||
selectcmd = createObject(REDIS_STRING,
|
||||
sdscatprintf(sdsempty(),"select %d\r\n",dictid));
|
||||
selectcmd->refcount = 0;
|
||||
break;
|
||||
}
|
||||
addReply(slave,selectcmd);
|
||||
slave->slaveseldb = dictid;
|
||||
}
|
||||
for (j = 0; j < outc; j++) addReply(slave,outv[j]);
|
||||
}
|
||||
for (j = 0; j < outc; j++) decrRefCount(outv[j]);
|
||||
if (outv != static_outv) zfree(outv);
|
||||
}
|
||||
|
||||
void replicationFeedMonitors(list *monitors, int dictid, robj **argv, int argc) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int j;
|
||||
sds cmdrepr = sdsnew("+");
|
||||
robj *cmdobj;
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
cmdrepr = sdscatprintf(cmdrepr,"%ld.%ld ",(long)tv.tv_sec,(long)tv.tv_usec);
|
||||
if (dictid != 0) cmdrepr = sdscatprintf(cmdrepr,"(db %d) ", dictid);
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (argv[j]->encoding == REDIS_ENCODING_INT) {
|
||||
cmdrepr = sdscatprintf(cmdrepr, "\"%ld\"", (long)argv[j]->ptr);
|
||||
} else {
|
||||
cmdrepr = sdscatrepr(cmdrepr,(char*)argv[j]->ptr,
|
||||
sdslen(argv[j]->ptr));
|
||||
}
|
||||
if (j != argc-1)
|
||||
cmdrepr = sdscatlen(cmdrepr," ",1);
|
||||
}
|
||||
cmdrepr = sdscatlen(cmdrepr,"\r\n",2);
|
||||
cmdobj = createObject(REDIS_STRING,cmdrepr);
|
||||
|
||||
listRewind(monitors,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *monitor = ln->value;
|
||||
addReply(monitor,cmdobj);
|
||||
}
|
||||
decrRefCount(cmdobj);
|
||||
}
|
||||
|
||||
void syncCommand(redisClient *c) {
|
||||
/* ignore SYNC if aleady slave or in monitor mode */
|
||||
if (c->flags & REDIS_SLAVE) return;
|
||||
|
||||
/* Refuse SYNC requests if we are a slave but the link with our master
|
||||
* is not ok... */
|
||||
if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {
|
||||
addReplyError(c,"Can't SYNC while not connected with my master");
|
||||
return;
|
||||
}
|
||||
|
||||
/* SYNC can't be issued when the server has pending data to send to
|
||||
* the client about already issued commands. We need a fresh reply
|
||||
* buffer registering the differences between the BGSAVE and the current
|
||||
* dataset, so that we can copy to other slaves if needed. */
|
||||
if (listLength(c->reply) != 0) {
|
||||
addReplyError(c,"SYNC is invalid with pending input");
|
||||
return;
|
||||
}
|
||||
|
||||
redisLog(REDIS_NOTICE,"Slave ask for synchronization");
|
||||
/* Here we need to check if there is a background saving operation
|
||||
* in progress, or if it is required to start one */
|
||||
if (server.bgsavechildpid != -1) {
|
||||
/* Ok a background save is in progress. Let's check if it is a good
|
||||
* one for replication, i.e. if there is another slave that is
|
||||
* registering differences since the server forked to save */
|
||||
redisClient *slave;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) break;
|
||||
}
|
||||
if (ln) {
|
||||
/* Perfect, the server is already registering differences for
|
||||
* another slave. Set the right state, and copy the buffer. */
|
||||
listRelease(c->reply);
|
||||
c->reply = listDup(slave->reply);
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
redisLog(REDIS_NOTICE,"Waiting for end of BGSAVE for SYNC");
|
||||
} else {
|
||||
/* No way, we need to wait for the next BGSAVE in order to
|
||||
* register differences */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");
|
||||
}
|
||||
} else {
|
||||
/* Ok we don't have a BGSAVE in progress, let's start one */
|
||||
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
|
||||
if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
|
||||
addReplyError(c,"Unable to perform background save");
|
||||
return;
|
||||
}
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
}
|
||||
c->repldbfd = -1;
|
||||
c->flags |= REDIS_SLAVE;
|
||||
c->slaveseldb = 0;
|
||||
listAddNodeTail(server.slaves,c);
|
||||
return;
|
||||
}
|
||||
|
||||
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
redisClient *slave = privdata;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
char buf[REDIS_IOBUF_LEN];
|
||||
ssize_t nwritten, buflen;
|
||||
|
||||
if (slave->repldboff == 0) {
|
||||
/* Write the bulk write count before to transfer the DB. In theory here
|
||||
* we don't know how much room there is in the output buffer of the
|
||||
* socket, but in pratice SO_SNDLOWAT (the minimum count for output
|
||||
* operations) will never be smaller than the few bytes we need. */
|
||||
sds bulkcount;
|
||||
|
||||
bulkcount = sdscatprintf(sdsempty(),"$%lld\r\n",(unsigned long long)
|
||||
slave->repldbsize);
|
||||
if (write(fd,bulkcount,sdslen(bulkcount)) != (signed)sdslen(bulkcount))
|
||||
{
|
||||
sdsfree(bulkcount);
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
sdsfree(bulkcount);
|
||||
}
|
||||
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
|
||||
buflen = read(slave->repldbfd,buf,REDIS_IOBUF_LEN);
|
||||
if (buflen <= 0) {
|
||||
redisLog(REDIS_WARNING,"Read error sending DB to slave: %s",
|
||||
(buflen == 0) ? "premature EOF" : strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
if ((nwritten = write(fd,buf,buflen)) == -1) {
|
||||
redisLog(REDIS_VERBOSE,"Write error sending DB to slave: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
slave->repldboff += nwritten;
|
||||
if (slave->repldboff == slave->repldbsize) {
|
||||
close(slave->repldbfd);
|
||||
slave->repldbfd = -1;
|
||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
||||
slave->replstate = REDIS_REPL_ONLINE;
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
||||
sendReplyToClient, slave) == AE_ERR) {
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
addReplySds(slave,sdsempty());
|
||||
redisLog(REDIS_NOTICE,"Synchronization with slave succeeded");
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called at the end of every backgrond saving.
|
||||
* The argument bgsaveerr is REDIS_OK if the background saving succeeded
|
||||
* otherwise REDIS_ERR is passed to the function.
|
||||
*
|
||||
* The goal of this function is to handle slaves waiting for a successful
|
||||
* background saving in order to perform non-blocking synchronization. */
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr) {
|
||||
listNode *ln;
|
||||
int startbgsave = 0;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
startbgsave = 1;
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
} else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
|
||||
struct redis_stat buf;
|
||||
|
||||
if (bgsaveerr != REDIS_OK) {
|
||||
freeClient(slave);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");
|
||||
continue;
|
||||
}
|
||||
if ((slave->repldbfd = open(server.dbfilename,O_RDONLY)) == -1 ||
|
||||
redis_fstat(slave->repldbfd,&buf) == -1) {
|
||||
freeClient(slave);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
slave->repldboff = 0;
|
||||
slave->repldbsize = buf.st_size;
|
||||
slave->replstate = REDIS_REPL_SEND_BULK;
|
||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
|
||||
freeClient(slave);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startbgsave) {
|
||||
if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE failed");
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
|
||||
freeClient(slave);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------- SLAVE -------------------------------- */
|
||||
|
||||
/* Abort the async download of the bulk dataset while SYNC-ing with master */
|
||||
void replicationAbortSyncTransfer(void) {
|
||||
redisAssert(server.replstate == REDIS_REPL_TRANSFER);
|
||||
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
close(server.repl_transfer_s);
|
||||
close(server.repl_transfer_fd);
|
||||
unlink(server.repl_transfer_tmpfile);
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
server.replstate = REDIS_REPL_CONNECT;
|
||||
}
|
||||
|
||||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char buf[4096];
|
||||
ssize_t nread, readlen;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(privdata);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
/* If repl_transfer_left == -1 we still have to read the bulk length
|
||||
* from the master reply. */
|
||||
if (server.repl_transfer_left == -1) {
|
||||
if (syncReadLine(fd,buf,1024,3600) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"I/O error reading bulk count from MASTER: %s",
|
||||
strerror(errno));
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
if (buf[0] == '-') {
|
||||
redisLog(REDIS_WARNING,
|
||||
"MASTER aborted replication with an error: %s",
|
||||
buf+1);
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
} else if (buf[0] != '$') {
|
||||
redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?");
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
server.repl_transfer_left = strtol(buf+1,NULL,10);
|
||||
redisLog(REDIS_NOTICE,
|
||||
"MASTER <-> SLAVE sync: receiving %ld bytes from master",
|
||||
server.repl_transfer_left);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read bulk data */
|
||||
readlen = (server.repl_transfer_left < (signed)sizeof(buf)) ?
|
||||
server.repl_transfer_left : (signed)sizeof(buf);
|
||||
nread = read(fd,buf,readlen);
|
||||
if (nread <= 0) {
|
||||
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
|
||||
(nread == -1) ? strerror(errno) : "connection lost");
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
server.repl_transfer_lastio = time(NULL);
|
||||
if (write(server.repl_transfer_fd,buf,nread) != nread) {
|
||||
redisLog(REDIS_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchrnonization: %s", strerror(errno));
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
server.repl_transfer_left -= nread;
|
||||
/* Check if the transfer is now complete */
|
||||
if (server.repl_transfer_left == 0) {
|
||||
if (rename(server.repl_transfer_tmpfile,server.dbfilename) == -1) {
|
||||
redisLog(REDIS_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
|
||||
emptyDb();
|
||||
/* Before loading the DB into memory we need to delete the readable
|
||||
* handler, otherwise it will get called recursively since
|
||||
* rdbLoad() will call the event loop to process events from time to
|
||||
* time for non blocking loading. */
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
if (rdbLoad(server.dbfilename) != REDIS_OK) {
|
||||
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
close(server.repl_transfer_fd);
|
||||
server.master = createClient(server.repl_transfer_s);
|
||||
server.master->flags |= REDIS_MASTER;
|
||||
server.master->authenticated = 1;
|
||||
server.replstate = REDIS_REPL_CONNECTED;
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
}
|
||||
}
|
||||
|
||||
int syncWithMaster(void) {
|
||||
char buf[1024], tmpfile[256], authcmd[1024];
|
||||
int fd = anetTcpConnect(NULL,server.masterhost,server.masterport);
|
||||
int dfd, maxtries = 5;
|
||||
|
||||
if (fd == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* AUTH with the master if required. */
|
||||
if(server.masterauth) {
|
||||
snprintf(authcmd, 1024, "AUTH %s\r\n", server.masterauth);
|
||||
if (syncWrite(fd, authcmd, strlen(server.masterauth)+7, 5) == -1) {
|
||||
close(fd);
|
||||
redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* Read the AUTH result. */
|
||||
if (syncReadLine(fd,buf,1024,3600) == -1) {
|
||||
close(fd);
|
||||
redisLog(REDIS_WARNING,"I/O error reading auth result from MASTER: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (buf[0] != '+') {
|
||||
close(fd);
|
||||
redisLog(REDIS_WARNING,"Cannot AUTH to MASTER, is the masterauth password correct?");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Issue the SYNC command */
|
||||
if (syncWrite(fd,"SYNC \r\n",7,5) == -1) {
|
||||
close(fd);
|
||||
redisLog(REDIS_WARNING,"I/O error writing to MASTER: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Prepare a suitable temp file for bulk transfer */
|
||||
while(maxtries--) {
|
||||
snprintf(tmpfile,256,
|
||||
"temp-%d.%ld.rdb",(int)time(NULL),(long int)getpid());
|
||||
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
|
||||
if (dfd != -1) break;
|
||||
sleep(1);
|
||||
}
|
||||
if (dfd == -1) {
|
||||
close(fd);
|
||||
redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Setup the non blocking download of the bulk file. */
|
||||
if (aeCreateFileEvent(server.el, fd, AE_READABLE, readSyncBulkPayload, NULL)
|
||||
== AE_ERR)
|
||||
{
|
||||
close(fd);
|
||||
redisLog(REDIS_WARNING,"Can't create readable event for SYNC");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
server.replstate = REDIS_REPL_TRANSFER;
|
||||
server.repl_transfer_left = -1;
|
||||
server.repl_transfer_s = fd;
|
||||
server.repl_transfer_fd = dfd;
|
||||
server.repl_transfer_lastio = time(NULL);
|
||||
server.repl_transfer_tmpfile = zstrdup(tmpfile);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void slaveofCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"no") &&
|
||||
!strcasecmp(c->argv[2]->ptr,"one")) {
|
||||
if (server.masterhost) {
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = NULL;
|
||||
if (server.master) freeClient(server.master);
|
||||
if (server.replstate == REDIS_REPL_TRANSFER)
|
||||
replicationAbortSyncTransfer();
|
||||
server.replstate = REDIS_REPL_NONE;
|
||||
redisLog(REDIS_NOTICE,"MASTER MODE enabled (user request)");
|
||||
}
|
||||
} else {
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = sdsdup(c->argv[1]->ptr);
|
||||
server.masterport = atoi(c->argv[2]->ptr);
|
||||
if (server.master) freeClient(server.master);
|
||||
if (server.replstate == REDIS_REPL_TRANSFER)
|
||||
replicationAbortSyncTransfer();
|
||||
server.replstate = REDIS_REPL_CONNECT;
|
||||
redisLog(REDIS_NOTICE,"SLAVE OF %s:%d enabled (user request)",
|
||||
server.masterhost, server.masterport);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* --------------------------- REPLICATION CRON ---------------------------- */
|
||||
|
||||
#define REDIS_REPL_TRANSFER_TIMEOUT 60
|
||||
|
||||
void replicationCron(void) {
|
||||
/* Bulk transfer I/O timeout? */
|
||||
if (server.masterhost && server.replstate == REDIS_REPL_TRANSFER &&
|
||||
(time(NULL)-server.repl_transfer_lastio) > REDIS_REPL_TRANSFER_TIMEOUT)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER...");
|
||||
replicationAbortSyncTransfer();
|
||||
}
|
||||
|
||||
/* Check if we should connect to a MASTER */
|
||||
if (server.replstate == REDIS_REPL_CONNECT) {
|
||||
redisLog(REDIS_NOTICE,"Connecting to MASTER...");
|
||||
if (syncWithMaster() == REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started: SYNC sent");
|
||||
if (server.appendonly) rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define SDS_ABORT_ON_OOM
|
||||
|
||||
#include "sds.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "zmalloc.h"
|
||||
|
||||
static void sdsOomAbort(void) {
|
||||
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (sh == NULL) return NULL;
|
||||
#endif
|
||||
sh->len = initlen;
|
||||
sh->free = 0;
|
||||
if (initlen) {
|
||||
if (init) memcpy(sh->buf, init, initlen);
|
||||
else memset(sh->buf,0,initlen);
|
||||
}
|
||||
sh->buf[initlen] = '\0';
|
||||
return (char*)sh->buf;
|
||||
}
|
||||
|
||||
sds sdsempty(void) {
|
||||
return sdsnewlen("",0);
|
||||
}
|
||||
|
||||
sds sdsnew(const char *init) {
|
||||
size_t initlen = (init == NULL) ? 0 : strlen(init);
|
||||
return sdsnewlen(init, initlen);
|
||||
}
|
||||
|
||||
size_t sdslen(const sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
return sh->len;
|
||||
}
|
||||
|
||||
sds sdsdup(const sds s) {
|
||||
return sdsnewlen(s, sdslen(s));
|
||||
}
|
||||
|
||||
void sdsfree(sds s) {
|
||||
if (s == NULL) return;
|
||||
zfree(s-sizeof(struct sdshdr));
|
||||
}
|
||||
|
||||
size_t sdsavail(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
return sh->free;
|
||||
}
|
||||
|
||||
void sdsupdatelen(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
int reallen = strlen(s);
|
||||
sh->free += (sh->len-reallen);
|
||||
sh->len = reallen;
|
||||
}
|
||||
|
||||
static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
struct sdshdr *sh, *newsh;
|
||||
size_t free = sdsavail(s);
|
||||
size_t len, newlen;
|
||||
|
||||
if (free >= addlen) return s;
|
||||
len = sdslen(s);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
newlen = (len+addlen)*2;
|
||||
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (newsh == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (newsh == NULL) return NULL;
|
||||
#endif
|
||||
|
||||
newsh->free = newlen - len;
|
||||
return newsh->buf;
|
||||
}
|
||||
|
||||
sds sdscatlen(sds s, void *t, size_t len) {
|
||||
struct sdshdr *sh;
|
||||
size_t curlen = sdslen(s);
|
||||
|
||||
s = sdsMakeRoomFor(s,len);
|
||||
if (s == NULL) return NULL;
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
memcpy(s+curlen, t, len);
|
||||
sh->len = curlen+len;
|
||||
sh->free = sh->free-len;
|
||||
s[curlen+len] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdscat(sds s, char *t) {
|
||||
return sdscatlen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
sds sdscpylen(sds s, char *t, size_t len) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t totlen = sh->free+sh->len;
|
||||
|
||||
if (totlen < len) {
|
||||
s = sdsMakeRoomFor(s,len-sh->len);
|
||||
if (s == NULL) return NULL;
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
totlen = sh->free+sh->len;
|
||||
}
|
||||
memcpy(s, t, len);
|
||||
s[len] = '\0';
|
||||
sh->len = len;
|
||||
sh->free = totlen-len;
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdscpy(sds s, char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
|
||||
while(1) {
|
||||
buf = zmalloc(buflen);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (buf == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (buf == NULL) return NULL;
|
||||
#endif
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
zfree(buf);
|
||||
buflen *= 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
t = sdscat(s, buf);
|
||||
zfree(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char *t;
|
||||
va_start(ap, fmt);
|
||||
t = sdscatvprintf(s,fmt,ap);
|
||||
va_end(ap);
|
||||
return t;
|
||||
}
|
||||
|
||||
sds sdstrim(sds s, const char *cset) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
char *start, *end, *sp, *ep;
|
||||
size_t len;
|
||||
|
||||
sp = start = s;
|
||||
ep = end = s+sdslen(s)-1;
|
||||
while(sp <= end && strchr(cset, *sp)) sp++;
|
||||
while(ep > start && strchr(cset, *ep)) ep--;
|
||||
len = (sp > ep) ? 0 : ((ep-sp)+1);
|
||||
if (sh->buf != sp) memmove(sh->buf, sp, len);
|
||||
sh->buf[len] = '\0';
|
||||
sh->free = sh->free+(sh->len-len);
|
||||
sh->len = len;
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return s;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = len+end;
|
||||
if (end < 0) end = 0;
|
||||
}
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
if (newlen != 0) {
|
||||
if (start >= (signed)len) {
|
||||
newlen = 0;
|
||||
} else if (end >= (signed)len) {
|
||||
end = len-1;
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
}
|
||||
} else {
|
||||
start = 0;
|
||||
}
|
||||
if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
|
||||
sh->buf[newlen] = 0;
|
||||
sh->free = sh->free+(sh->len-newlen);
|
||||
sh->len = newlen;
|
||||
return s;
|
||||
}
|
||||
|
||||
void sdstolower(sds s) {
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
|
||||
}
|
||||
|
||||
void sdstoupper(sds s) {
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
|
||||
}
|
||||
|
||||
int sdscmp(sds s1, sds s2) {
|
||||
size_t l1, l2, minlen;
|
||||
int cmp;
|
||||
|
||||
l1 = sdslen(s1);
|
||||
l2 = sdslen(s2);
|
||||
minlen = (l1 < l2) ? l1 : l2;
|
||||
cmp = memcmp(s1,s2,minlen);
|
||||
if (cmp == 0) return l1-l2;
|
||||
return cmp;
|
||||
}
|
||||
|
||||
/* Split 's' with separator in 'sep'. An array
|
||||
* of sds strings is returned. *count will be set
|
||||
* by reference to the number of tokens returned.
|
||||
*
|
||||
* On out of memory, zero length string, zero length
|
||||
* separator, NULL is returned.
|
||||
*
|
||||
* Note that 'sep' is able to split a string using
|
||||
* a multi-character separator. For example
|
||||
* sdssplit("foo_-_bar","_-_"); will return two
|
||||
* elements "foo" and "bar".
|
||||
*
|
||||
* This version of the function is binary-safe but
|
||||
* requires length arguments. sdssplit() is just the
|
||||
* same function but for zero-terminated strings.
|
||||
*/
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5, start = 0, j;
|
||||
|
||||
sds *tokens = zmalloc(sizeof(sds)*slots);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (tokens == NULL) sdsOomAbort();
|
||||
#endif
|
||||
if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
|
||||
if (len == 0) {
|
||||
*count = 0;
|
||||
return tokens;
|
||||
}
|
||||
for (j = 0; j < (len-(seplen-1)); j++) {
|
||||
/* make sure there is room for the next element and the final one */
|
||||
if (slots < elements+2) {
|
||||
sds *newtokens;
|
||||
|
||||
slots *= 2;
|
||||
newtokens = zrealloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
tokens = newtokens;
|
||||
}
|
||||
/* search the separator */
|
||||
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
|
||||
tokens[elements] = sdsnewlen(s+start,j-start);
|
||||
if (tokens[elements] == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
elements++;
|
||||
start = j+seplen;
|
||||
j = j+seplen-1; /* skip the separator */
|
||||
}
|
||||
}
|
||||
/* Add the final element. We are sure there is room in the tokens array. */
|
||||
tokens[elements] = sdsnewlen(s+start,len-start);
|
||||
if (tokens[elements] == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
elements++;
|
||||
*count = elements;
|
||||
return tokens;
|
||||
|
||||
#ifndef SDS_ABORT_ON_OOM
|
||||
cleanup:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
|
||||
zfree(tokens);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdsfreesplitres(sds *tokens, int count) {
|
||||
if (!tokens) return;
|
||||
while(count--)
|
||||
sdsfree(tokens[count]);
|
||||
zfree(tokens);
|
||||
}
|
||||
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
|
||||
v = (value < 0) ? -value : value;
|
||||
p = buf+31; /* point to the last character */
|
||||
do {
|
||||
*p-- = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p-- = '-';
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
sds sdscatrepr(sds s, char *p, size_t len) {
|
||||
s = sdscatlen(s,"\"",1);
|
||||
while(len--) {
|
||||
switch(*p) {
|
||||
case '\\':
|
||||
case '"':
|
||||
s = sdscatprintf(s,"\\%c",*p);
|
||||
break;
|
||||
case '\n': s = sdscatlen(s,"\\n",1); break;
|
||||
case '\r': s = sdscatlen(s,"\\r",1); break;
|
||||
case '\t': s = sdscatlen(s,"\\t",1); break;
|
||||
case '\a': s = sdscatlen(s,"\\a",1); break;
|
||||
case '\b': s = sdscatlen(s,"\\b",1); break;
|
||||
default:
|
||||
if (isprint(*p))
|
||||
s = sdscatprintf(s,"%c",*p);
|
||||
else
|
||||
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return sdscatlen(s,"\"",1);
|
||||
}
|
||||
|
||||
/* Split a line into arguments, where every argument can be in the
|
||||
* following programming-language REPL-alike form:
|
||||
*
|
||||
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
|
||||
*
|
||||
* The number of arguments is stored into *argc, and an array
|
||||
* of sds is returned. The caller should sdsfree() all the returned
|
||||
* strings and finally zfree() the array itself.
|
||||
*
|
||||
* Note that sdscatrepr() is able to convert back a string into
|
||||
* a quoted string in the same format sdssplitargs() is able to parse.
|
||||
*/
|
||||
sds *sdssplitargs(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
/* skip blanks */
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done=0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
switch(*p) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'a': c = '\a'; break;
|
||||
default: c = *p; break;
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
/* closing quote must be followed by a space */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
/* unterminated quotes */
|
||||
goto err;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else {
|
||||
switch(*p) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
done=1;
|
||||
break;
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
while((*argc)--)
|
||||
sdsfree(vector[*argc]);
|
||||
zfree(vector);
|
||||
if (current) sdsfree(current);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
|
||||
int main(void) {
|
||||
{
|
||||
sds x = sdsnew("foo"), y;
|
||||
|
||||
test_cond("Create a string and obtain the length",
|
||||
sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnewlen("foo",2);
|
||||
test_cond("Create a string with specified length",
|
||||
sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
|
||||
|
||||
x = sdscat(x,"bar");
|
||||
test_cond("Strings concatenation",
|
||||
sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
|
||||
|
||||
x = sdscpy(x,"a");
|
||||
test_cond("sdscpy() against an originally longer string",
|
||||
sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
|
||||
|
||||
x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
|
||||
test_cond("sdscpy() against an originally shorter string",
|
||||
sdslen(x) == 33 &&
|
||||
memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdscatprintf(sdsempty(),"%d",123);
|
||||
test_cond("sdscatprintf() seems working in the base case",
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdstrim(sdsnew("xxciaoyyy"),"xy");
|
||||
test_cond("sdstrim() correctly trims characters",
|
||||
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
|
||||
|
||||
y = sdsrange(sdsdup(x),1,1);
|
||||
test_cond("sdsrange(...,1,1)",
|
||||
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),1,-1);
|
||||
test_cond("sdsrange(...,1,-1)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),-2,-1);
|
||||
test_cond("sdsrange(...,-2,-1)",
|
||||
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),2,1);
|
||||
test_cond("sdsrange(...,2,1)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),1,100);
|
||||
test_cond("sdsrange(...,1,100)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),100,100);
|
||||
test_cond("sdsrange(...,100,100)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnew("foo");
|
||||
y = sdsnew("foa");
|
||||
test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnew("bar");
|
||||
y = sdsnew("bar");
|
||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnew("aar");
|
||||
y = sdsnew("bar");
|
||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
|
||||
}
|
||||
test_report()
|
||||
}
|
||||
#endif
|
||||
+7
-3
@@ -32,12 +32,13 @@
|
||||
#define __SDS_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
long len;
|
||||
long free;
|
||||
int len;
|
||||
int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
@@ -53,6 +54,7 @@ sds sdscat(sds s, char *t);
|
||||
sds sdscpylen(sds s, char *t, size_t len);
|
||||
sds sdscpy(sds s, char *t);
|
||||
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||
#ifdef __GNUC__
|
||||
sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
@@ -61,7 +63,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
sds sdsrange(sds s, long start, long end);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
int sdscmp(sds s1, sds s2);
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
|
||||
@@ -69,5 +71,7 @@ void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, char *p, size_t len);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user