Compare commits
92
Commits
2.2.2
...
2.3-alpha0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bcffcbe5b | ||
|
|
b8cfcea424 | ||
|
|
52dc87bb18 | ||
|
|
7a1fd61e3d | ||
|
|
3a73be7524 | ||
|
|
5b831607e7 | ||
|
|
2d9fdb9d84 | ||
|
|
69cb546223 | ||
|
|
322ea972fe | ||
|
|
a5062bbab0 | ||
|
|
0a0f83ab2c | ||
|
|
69bfffb4a7 | ||
|
|
5e1d2d30f7 | ||
|
|
67a1810b32 | ||
|
|
7cca366bdf | ||
|
|
b39619d8e7 | ||
|
|
f1df1739e3 | ||
|
|
cc2750670f | ||
|
|
43574a72c3 | ||
|
|
f03fe802b7 | ||
|
|
5b8ce85378 | ||
|
|
36c17a53b6 | ||
|
|
cb9b35c8ca | ||
|
|
44898603aa | ||
|
|
249ad25f4f | ||
|
|
51335102ac | ||
|
|
f771dc23a0 | ||
|
|
9b1d738f96 | ||
|
|
a2b33f2f93 | ||
|
|
a510cb0c03 | ||
|
|
6eaad66373 | ||
|
|
1190c6cbf7 | ||
|
|
418d5eaf50 | ||
|
|
779fa2af7a | ||
|
|
fad97fbeea | ||
|
|
5ab7bbfc27 | ||
|
|
bafa88c8b7 | ||
|
|
9b24d8adbe | ||
|
|
5d46e370b7 | ||
|
|
d158dc28f6 | ||
|
|
0b305fcfd9 | ||
|
|
c15a3887e0 | ||
|
|
120b9ba8f8 | ||
|
|
d2033feb5c | ||
|
|
4942145d72 | ||
|
|
e37efb0d8b | ||
|
|
133cf28ee8 | ||
|
|
9a3730289f | ||
|
|
3be00d7ed6 | ||
|
|
aa81e4d5f4 | ||
|
|
d934e1e85b | ||
|
|
c4b64a1395 | ||
|
|
a440ecf0d3 | ||
|
|
8e6bb67108 | ||
|
|
ad01a25553 | ||
|
|
4e941ecacb | ||
|
|
3122229228 | ||
|
|
8d51fb6a80 | ||
|
|
98a9abb66d | ||
|
|
82ef6ebf73 | ||
|
|
eea15afe5f | ||
|
|
1fce320114 | ||
|
|
0b470ebdaa | ||
|
|
4ab988238f | ||
|
|
1609a1c42d | ||
|
|
67b0b41c87 | ||
|
|
ddbc81af33 | ||
|
|
f63f0928c3 | ||
|
|
cea8c5cd75 | ||
|
|
d021221086 | ||
|
|
5ef640986b | ||
|
|
5f6e1183e7 | ||
|
|
f34a6cd85e | ||
|
|
f2da3a620c | ||
|
|
f081eaf1c0 | ||
|
|
16d778780e | ||
|
|
697af434fb | ||
|
|
33388d4304 | ||
|
|
52970711cf | ||
|
|
04a2ade90d | ||
|
|
8ce39260a4 | ||
|
|
89191613f1 | ||
|
|
65b472acce | ||
|
|
ba55932674 | ||
|
|
a1e97d692e | ||
|
|
d3e5e28804 | ||
|
|
d841dedb5f | ||
|
|
4c22fd3409 | ||
|
|
180596766b | ||
|
|
f21779ff36 | ||
|
|
9d7165e885 | ||
|
|
6418b4c790 |
+2
-1
@@ -7,7 +7,6 @@ redis-benchmark
|
||||
redis-check-dump
|
||||
redis-check-aof
|
||||
doc-tools
|
||||
mkrelease.sh
|
||||
release
|
||||
myredis.conf
|
||||
misc/*
|
||||
@@ -19,3 +18,5 @@ release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
src/redis-server.dSYM
|
||||
redis.ds
|
||||
src/redis.conf
|
||||
|
||||
@@ -1,66 +1,58 @@
|
||||
Redis TODO and Roadmap
|
||||
----------------------
|
||||
Redis TODO
|
||||
----------
|
||||
|
||||
VERSION 2.2 TODO (Optimizations and latency)
|
||||
============================================
|
||||
WARNING: are you a possible Redis contributor?
|
||||
Before implementing what is listed what is listed in this file
|
||||
please drop a message in the Redis google group or chat with
|
||||
antirez or pietern on irc.freenode.org #redis to check if the work
|
||||
is already in progress and if the feature is still interesting for
|
||||
us, and *how* exactly this can be implemented to have good changes
|
||||
of a merge. Otherwise it is probably wasted work! Thank you
|
||||
|
||||
* 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
|
||||
DISKSTORE TODO
|
||||
==============
|
||||
|
||||
VM TODO
|
||||
=======
|
||||
* Check that 00/00 and ff/ff exist at startup, otherwise exit with error.
|
||||
* Implement sync flush option, where data is written synchronously on disk when a command is executed.
|
||||
* Implement MULTI/EXEC as transaction abstract API to diskstore.c, with transaction_start, transaction_end, and a journal to recover.
|
||||
* Stop BGSAVE thread on shutdown and any other condition where the child is killed during normal bgsave.
|
||||
* Fix RANDOMKEY to really do something interesting
|
||||
* Fix DBSIZE to really do something interesting
|
||||
* Add a DEBUG command to check if an entry is or not in memory currently
|
||||
|
||||
* 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.
|
||||
REPLICATION
|
||||
===========
|
||||
|
||||
STRING COMMANDS
|
||||
===============
|
||||
* PING between master and slave from time to time, so we can subject the
|
||||
master-slave link to timeout, and detect when the connection is gone even
|
||||
if the socket is still up.
|
||||
|
||||
* 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)
|
||||
|
||||
SMALL ONES:
|
||||
|
||||
* If sizeof(double) == sizeof(void*) we could store the double value of sorted sets directly in place of the pointer instead of allocating it in the heap.
|
||||
* Delete on writes against expire policy should only happen after argument parsing for commands doing their own arg parsing stuff.
|
||||
* Give errors when incrementing a key that does not look like an integer, when providing as a sorted set score something can't be parsed as a double, and so forth.
|
||||
* MSADD (n keys) (n values). See this thread in the Redis google group: http://groups.google.com/group/redis-db/browse_thread/thread/e766d84eb375cd41
|
||||
* Don't save empty lists / sets / zsets on disk with snapshotting.
|
||||
* Remove keys when a list / set / zset reaches length of 0.
|
||||
* An option to exec a command slave-side if the master connection is lost: even cooler: if the script returns "0" the slave elects itself as master, otherwise continue trying to reconnect.
|
||||
* PING the master from time to time to check if it's gone.
|
||||
|
||||
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 ;)
|
||||
OPTIMIZATIONS
|
||||
=============
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
* Read-only mode.
|
||||
* Kill the delete-on-write behavior of expires, replicating DELs
|
||||
* Multiple BY in SORT.
|
||||
* Read-only mode for slaves.
|
||||
|
||||
REPORTING
|
||||
=========
|
||||
|
||||
* Better INFO output with sections.
|
||||
|
||||
RANDOM
|
||||
======
|
||||
|
||||
* Clients should be closed as far as the output buffer list is bigger than a given number of elements (configurable in redis.conf)
|
||||
* Should the redis default configuration, and the default redis.conf, just bind 127.0.0.1?
|
||||
|
||||
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.
|
||||
* What happens in the following scenario:
|
||||
1) We are reading an AOF file.
|
||||
2) SETEX FOO 5 BAR
|
||||
3) APPEND FOO ZAP
|
||||
What happens if between 1 and 2 for some reason (system under huge load
|
||||
or alike) too many time passes? We should prevent expires while the
|
||||
AOF is loading.
|
||||
|
||||
REDIS CLI TODO
|
||||
==============
|
||||
|
||||
* Computer parsable output generation
|
||||
* Memoize return values so that they can be used later as arguments, like $1
|
||||
|
||||
+24
-75
@@ -291,91 +291,40 @@ appendfsync everysec
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
################################ VIRTUAL MEMORY ###############################
|
||||
#################################### DISK STORE ###############################
|
||||
|
||||
# Virtual Memory allows Redis to work with datasets bigger than the actual
|
||||
# amount of RAM needed to hold the whole dataset in memory.
|
||||
# In order to do so very used keys are taken in memory while the other keys
|
||||
# are swapped into a swap file, similarly to what operating systems do
|
||||
# with memory pages.
|
||||
# When disk store is active Redis works as an on-disk database, where memory
|
||||
# is only used as a object cache.
|
||||
#
|
||||
# To enable VM just set 'vm-enabled' to yes, and set the following three
|
||||
# VM parameters accordingly to your needs.
|
||||
# This mode is good for datasets that are bigger than memory, and in general
|
||||
# when you want to trade speed for:
|
||||
#
|
||||
# - less memory used
|
||||
# - immediate server restart
|
||||
# - per key durability, without need for backgrond savig
|
||||
#
|
||||
# On the other hand, with disk store enabled MULTI/EXEC are no longer
|
||||
# transactional from the point of view of the persistence on disk, that is,
|
||||
# Redis transactions will still guarantee that commands are either processed
|
||||
# all or nothing, but there is no guarantee that all the keys are flushed
|
||||
# on disk in an atomic way.
|
||||
#
|
||||
# Of course with disk store enabled Redis is not as fast as it is when
|
||||
# working with just the memory back end.
|
||||
|
||||
vm-enabled no
|
||||
# vm-enabled yes
|
||||
|
||||
# This is the path of the Redis swap file. As you can guess, swap files
|
||||
# can't be shared by different Redis instances, so make sure to use a swap
|
||||
# file for every redis process you are running. Redis will complain if the
|
||||
# swap file is already in use.
|
||||
#
|
||||
# The best kind of storage for the Redis swap file (that's accessed at random)
|
||||
# is a Solid State Disk (SSD).
|
||||
#
|
||||
# *** WARNING *** if you are using a shared hosting the default of putting
|
||||
# the swap file under /tmp is not secure. Create a dir with access granted
|
||||
# only to Redis user and configure Redis to create the swap file there.
|
||||
vm-swap-file /tmp/redis.swap
|
||||
|
||||
# vm-max-memory configures the VM to use at max the specified amount of
|
||||
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
|
||||
# is, if there is still enough contiguous space in the swap file.
|
||||
#
|
||||
# With vm-max-memory 0 the system will swap everything it can. Not a good
|
||||
# default, just specify the max amount of RAM you can in bytes, but it's
|
||||
# better to leave some margin. For instance specify an amount of RAM
|
||||
# that's more or less between 60 and 80% of your free RAM.
|
||||
vm-max-memory 0
|
||||
|
||||
# Redis swap files is split into pages. An object can be saved using multiple
|
||||
# contiguous pages, but pages can't be shared between different objects.
|
||||
# So if your page is too big, small objects swapped out on disk will waste
|
||||
# a lot of space. If you page is too small, there is less space in the swap
|
||||
# file (assuming you configured the same number of total swap file pages).
|
||||
#
|
||||
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
|
||||
# If you use a lot of big objects, use a bigger page size.
|
||||
# If unsure, use the default :)
|
||||
vm-page-size 32
|
||||
|
||||
# Number of total memory pages in the swap file.
|
||||
# Given that the page table (a bitmap of free/used pages) is taken in memory,
|
||||
# every 8 pages on disk will consume 1 byte of RAM.
|
||||
#
|
||||
# The total swap size is vm-page-size * vm-pages
|
||||
#
|
||||
# With the default of 32-bytes memory pages and 134217728 pages Redis will
|
||||
# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
|
||||
#
|
||||
# It's better to use the smallest acceptable value for your application,
|
||||
# but the default is large in order to work in most conditions.
|
||||
vm-pages 134217728
|
||||
|
||||
# Max number of VM I/O threads running at the same time.
|
||||
# This threads are used to read/write data from/to swap file, since they
|
||||
# also encode and decode objects from disk to memory or the reverse, a bigger
|
||||
# number of threads can help with big objects even if they can't help with
|
||||
# I/O itself as the physical device may not be able to couple with many
|
||||
# reads/writes operations at the same time.
|
||||
#
|
||||
# The special value of 0 turn off threaded I/O and enables the blocking
|
||||
# Virtual Memory implementation.
|
||||
vm-max-threads 4
|
||||
diskstore-enabled no
|
||||
diskstore-path redis.ds
|
||||
cache-max-memory 0
|
||||
cache-flush-delay 0
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Glue small output buffers together in order to send small replies in a
|
||||
# single TCP packet. Uses a bit more CPU but most of the times it is a win
|
||||
# in terms of number of queries per second. Use 'yes' if unsure.
|
||||
glueoutputbuf yes
|
||||
|
||||
# Hashes are encoded in a special way (much more memory efficient) when they
|
||||
# have at max a given numer of elements, and the biggest element does not
|
||||
# exceed a given threshold. You can configure this limits with the following
|
||||
# configuration directives.
|
||||
hash-max-zipmap-entries 64
|
||||
hash-max-zipmap-value 512
|
||||
hash-max-zipmap-entries 512
|
||||
hash-max-zipmap-value 64
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
|
||||
+14
-10
@@ -8,9 +8,11 @@ OPTIMIZATION?=-O2
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
|
||||
DEBUG?= -g -ggdb
|
||||
else
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
endif
|
||||
|
||||
ifeq ($(USE_TCMALLOC),yes)
|
||||
@@ -18,13 +20,12 @@ ifeq ($(USE_TCMALLOC),yes)
|
||||
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
|
||||
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 dscache.o pubsub.o multi.o debug.o sort.o intset.o syncio.o diskstore.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
|
||||
@@ -50,7 +51,6 @@ 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 \
|
||||
@@ -58,6 +58,10 @@ db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.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
|
||||
diskstore.o: diskstore.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
|
||||
dscache.o: dscache.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
|
||||
intset.o: intset.c intset.h zmalloc.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
@@ -72,11 +76,12 @@ 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-benchmark.o: redis-benchmark.c fmacros.h ae.h \
|
||||
../deps/hiredis/hiredis.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-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
|
||||
sds.h zmalloc.h ../deps/linenoise/linenoise.h help.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
|
||||
@@ -86,7 +91,8 @@ 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
|
||||
syncio.o: syncio.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_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 \
|
||||
@@ -99,8 +105,6 @@ 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
|
||||
@@ -138,7 +142,7 @@ clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
$(CC) -MM *.c -I ../deps/hiredis -I ../deps/linenoise
|
||||
|
||||
test: redis-server
|
||||
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
|
||||
|
||||
+15
-15
@@ -64,11 +64,11 @@ int anetNonBlock(char *err, int fd)
|
||||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(fd, F_GETFL)) == -1) {
|
||||
anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
|
||||
anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
|
||||
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
@@ -79,7 +79,7 @@ int anetTcpNoDelay(char *err, int fd)
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
|
||||
anetSetError(err, "setsockopt TCP_NODELAY: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
@@ -89,7 +89,7 @@ int anetSetSendBuffer(char *err, int fd, int buffsize)
|
||||
{
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
|
||||
{
|
||||
anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
|
||||
anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
@@ -99,7 +99,7 @@ int anetTcpKeepAlive(char *err, int fd)
|
||||
{
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
|
||||
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
@@ -115,7 +115,7 @@ int anetResolve(char *err, char *host, char *ipbuf)
|
||||
|
||||
he = gethostbyname(host);
|
||||
if (he == NULL) {
|
||||
anetSetError(err, "can't resolve: %s\n", host);
|
||||
anetSetError(err, "can't resolve: %s", host);
|
||||
return ANET_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
@@ -127,14 +127,14 @@ int anetResolve(char *err, char *host, char *ipbuf)
|
||||
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));
|
||||
anetSetError(err, "creating socket: %s", 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));
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
@@ -157,7 +157,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
|
||||
he = gethostbyname(addr);
|
||||
if (he == NULL) {
|
||||
anetSetError(err, "can't resolve: %s\n", addr);
|
||||
anetSetError(err, "can't resolve: %s", addr);
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
@@ -172,7 +172,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
flags & ANET_CONNECT_NONBLOCK)
|
||||
return s;
|
||||
|
||||
anetSetError(err, "connect: %s\n", strerror(errno));
|
||||
anetSetError(err, "connect: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
flags & ANET_CONNECT_NONBLOCK)
|
||||
return s;
|
||||
|
||||
anetSetError(err, "connect: %s\n", strerror(errno));
|
||||
anetSetError(err, "connect: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
@@ -257,12 +257,12 @@ int anetWrite(int fd, char *buf, int count)
|
||||
|
||||
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));
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (listen(s, 511) == -1) { /* the magic 511 constant is from nginx */
|
||||
anetSetError(err, "listen: %s\n", strerror(errno));
|
||||
anetSetError(err, "listen: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
@@ -282,7 +282,7 @@ int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
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");
|
||||
anetSetError(err, "invalid bind address");
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
@@ -315,7 +315,7 @@ static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *l
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else {
|
||||
anetSetError(err, "accept: %s\n", strerror(errno));
|
||||
anetSetError(err, "accept: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
#define ANET_ERR -1
|
||||
#define ANET_ERR_LEN 256
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
|
||||
@@ -242,7 +242,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
char buf[128];
|
||||
sds argsds;
|
||||
struct redisCommand *cmd;
|
||||
int force_swapout;
|
||||
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
@@ -286,17 +285,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* 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.
|
||||
@@ -359,22 +347,11 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
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 */
|
||||
@@ -509,7 +486,6 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkLongLong(fp,expiretime) == 0) goto werr;
|
||||
}
|
||||
if (swapped) decrRefCount(o);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
@@ -553,12 +529,14 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
|
||||
if (server.bgrewritechildpid != -1) return REDIS_ERR;
|
||||
if (server.vm_enabled) waitEmptyIOJobsQueue();
|
||||
if (server.ds_enabled != 0) {
|
||||
redisLog(REDIS_WARNING,"BGREWRITEAOF called with diskstore enabled: AOF is not supported when diskstore is enabled. Operation not performed.");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
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());
|
||||
@@ -610,10 +588,7 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
|
||||
/* A background append only file rewriting (BGREWRITEAOF) terminated its work.
|
||||
* Handle this. */
|
||||
void backgroundRewriteDoneHandler(int statloc) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = WIFSIGNALED(statloc);
|
||||
|
||||
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
if (!bysignal && exitcode == 0) {
|
||||
int fd;
|
||||
char tmpfile[256];
|
||||
@@ -661,7 +636,7 @@ void backgroundRewriteDoneHandler(int statloc) {
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Background append only file rewriting terminated by signal %d",
|
||||
WTERMSIG(statloc));
|
||||
bysignal);
|
||||
}
|
||||
cleanup:
|
||||
sdsfree(server.bgrewritebuf);
|
||||
|
||||
+12
-17
@@ -194,10 +194,8 @@ void loadServerConfig(char *filename) {
|
||||
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],"glueoutputbuf")) {
|
||||
redisLog(REDIS_WARNING, "Deprecated configuration directive: \"%s\"", argv[0]);
|
||||
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
|
||||
if ((server.rdbcompression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -241,21 +239,18 @@ void loadServerConfig(char *filename) {
|
||||
} 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) {
|
||||
} else if (!strcasecmp(argv[0],"diskstore-enabled") && argc == 2) {
|
||||
if ((server.ds_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],"diskstore-path") && argc == 2) {
|
||||
sdsfree(server.ds_path);
|
||||
server.ds_path = sdsnew(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"cache-max-memory") && argc == 2) {
|
||||
server.cache_max_memory = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"cache-flush-delay") && argc == 2) {
|
||||
server.cache_flush_delay = atoi(argv[1]);
|
||||
if (server.cache_flush_delay < 0) server.cache_flush_delay = 0;
|
||||
} 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) {
|
||||
|
||||
@@ -6,6 +6,35 @@
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Important notes on lookup and disk store.
|
||||
*
|
||||
* When disk store is enabled on lookup we can have different cases.
|
||||
*
|
||||
* a) The key is in memory:
|
||||
* - If the key is not in IO_SAVEINPROG state we can access it.
|
||||
* As if it's just IO_SAVE this means we have the key in the IO queue
|
||||
* but can't be accessed by the IO thread (it requires to be
|
||||
* translated into an IO Job by the cache cron function.)
|
||||
* - If the key is in IO_SAVEINPROG we can't touch the key and have
|
||||
* to blocking wait completion of operations.
|
||||
* b) The key is not in memory:
|
||||
* - If it's marked as non existing on disk as well (negative cache)
|
||||
* we don't need to perform the disk access.
|
||||
* - if the key MAY EXIST, but is not in memory, and it is marked as IO_SAVE
|
||||
* then the key can only be a deleted one. As IO_SAVE keys are never
|
||||
* evicted (dirty state), so the only possibility is that key was deleted.
|
||||
* - if the key MAY EXIST we need to blocking load it.
|
||||
* We check that the key is not in IO_SAVEINPROG state before accessing
|
||||
* the disk object. If it is in this state, we wait.
|
||||
*/
|
||||
|
||||
void lookupWaitBusyKey(redisDb *db, robj *key) {
|
||||
/* FIXME: wait just for this key, not everything */
|
||||
waitEmptyIOJobsQueue();
|
||||
processAllPendingIOJobs();
|
||||
redisAssert((cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG) == 0);
|
||||
}
|
||||
|
||||
robj *lookupKey(redisDb *db, robj *key) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
if (de) {
|
||||
@@ -17,29 +46,52 @@ robj *lookupKey(redisDb *db, robj *key) {
|
||||
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);
|
||||
}
|
||||
if (server.ds_enabled &&
|
||||
cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG)
|
||||
{
|
||||
/* Need to wait for the key to get unbusy */
|
||||
redisLog(REDIS_DEBUG,"Lookup found a key in SAVEINPROG state. Waiting. (Key was in the cache)");
|
||||
lookupWaitBusyKey(db,key);
|
||||
}
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
} else {
|
||||
time_t expire;
|
||||
robj *val;
|
||||
|
||||
/* Key not found in the in memory hash table, but if disk store is
|
||||
* enabled we may have this key on disk. If so load it in memory
|
||||
* in a blocking way. */
|
||||
if (server.ds_enabled && cacheKeyMayExist(db,key)) {
|
||||
long flags = cacheScheduleIOGetFlags(db,key);
|
||||
|
||||
/* They key is not in cache, but it has a SAVE op in queue?
|
||||
* The only possibility is that the key was deleted, since
|
||||
* dirty keys are not evicted. */
|
||||
if (flags & REDIS_IO_SAVE) {
|
||||
server.stat_keyspace_misses++;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* At this point we need to blocking load the key in memory.
|
||||
* The first thing we do is waiting here if the key is busy. */
|
||||
if (flags & REDIS_IO_SAVEINPROG) {
|
||||
redisLog(REDIS_DEBUG,"Lookup found a key in SAVEINPROG state. Waiting (while force loading).");
|
||||
lookupWaitBusyKey(db,key);
|
||||
}
|
||||
|
||||
redisLog(REDIS_DEBUG,"Force loading key %s via lookup", key->ptr);
|
||||
val = dsGet(db,key,&expire);
|
||||
if (val) {
|
||||
int retval = dbAdd(db,key,val);
|
||||
redisAssert(retval == REDIS_OK);
|
||||
if (expire != -1) setExpire(db,key,expire);
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
} else {
|
||||
cacheSetKeyDoesNotExist(db,key);
|
||||
}
|
||||
}
|
||||
server.stat_keyspace_misses++;
|
||||
return NULL;
|
||||
}
|
||||
@@ -78,6 +130,7 @@ int dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
} else {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictAdd(db->dict, copy, val);
|
||||
if (server.ds_enabled) cacheSetKeyMayExist(db,key);
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
@@ -87,14 +140,19 @@ int dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
*
|
||||
* 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) {
|
||||
robj *oldval;
|
||||
int retval;
|
||||
|
||||
if ((oldval = dictFetchValue(db->dict,key->ptr)) == NULL) {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictAdd(db->dict, copy, val);
|
||||
return 1;
|
||||
retval = 1;
|
||||
} else {
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
return 0;
|
||||
retval = 0;
|
||||
}
|
||||
if (server.ds_enabled) cacheSetKeyMayExist(db,key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int dbExists(redisDb *db, robj *key) {
|
||||
@@ -129,18 +187,22 @@ robj *dbRandomKey(redisDb *db) {
|
||||
|
||||
/* 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);
|
||||
/* If diskstore is enabled make sure to awake waiting clients for this key
|
||||
* as it is not really useful to wait for a key already deleted to be
|
||||
* loaded from disk. */
|
||||
if (server.ds_enabled) {
|
||||
handleClientsBlockedOnSwappedKey(db,key);
|
||||
cacheSetKeyDoesNotExist(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 */
|
||||
/* Empty the whole database.
|
||||
* If diskstore is enabled this function will just flush the in-memory cache. */
|
||||
long long emptyDb() {
|
||||
int j;
|
||||
long long removed = 0;
|
||||
@@ -149,6 +211,7 @@ long long emptyDb() {
|
||||
removed += dictSize(server.db[j].dict);
|
||||
dictEmpty(server.db[j].dict);
|
||||
dictEmpty(server.db[j].expires);
|
||||
if (server.ds_enabled) dictEmpty(server.db[j].io_negcache);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
@@ -160,27 +223,50 @@ int selectDb(redisClient *c, int id) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hooks for key space changes.
|
||||
*
|
||||
* Every time a key in the database is modified the function
|
||||
* signalModifiedKey() is called.
|
||||
*
|
||||
* Every time a DB is flushed the function signalFlushDb() is called.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void signalModifiedKey(redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
if (server.ds_enabled)
|
||||
cacheScheduleIO(db,key,REDIS_IO_SAVE);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
touchWatchedKeysOnFlush(dbid);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Type agnostic commands operating on the key space
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void flushdbCommand(redisClient *c) {
|
||||
server.dirty += dictSize(c->db->dict);
|
||||
touchWatchedKeysOnFlush(c->db->id);
|
||||
signalFlushedDb(c->db->id);
|
||||
dictEmpty(c->db->dict);
|
||||
dictEmpty(c->db->expires);
|
||||
if (server.ds_enabled) dsFlushDb(c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void flushallCommand(redisClient *c) {
|
||||
touchWatchedKeysOnFlush(-1);
|
||||
signalFlushedDb(-1);
|
||||
server.dirty += emptyDb();
|
||||
addReply(c,shared.ok);
|
||||
if (server.bgsavechildpid != -1) {
|
||||
kill(server.bgsavechildpid,SIGKILL);
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
rdbSave(server.dbfilename);
|
||||
if (server.ds_enabled)
|
||||
dsFlushDb(-1);
|
||||
else
|
||||
rdbSave(server.dbfilename);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -188,10 +274,22 @@ void delCommand(redisClient *c) {
|
||||
int deleted = 0, j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (server.ds_enabled) {
|
||||
lookupKeyRead(c->db,c->argv[j]);
|
||||
/* FIXME: this can be optimized a lot, no real need to load
|
||||
* a possibly huge value. */
|
||||
}
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
touchWatchedKey(c->db,c->argv[j]);
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
server.dirty++;
|
||||
deleted++;
|
||||
} else if (server.ds_enabled) {
|
||||
if (cacheKeyMayExist(c->db,c->argv[j]) &&
|
||||
dsExists(c->db,c->argv[j]))
|
||||
{
|
||||
cacheScheduleIO(c->db,c->argv[j],REDIS_IO_SAVE);
|
||||
deleted = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -283,30 +381,6 @@ void typeCommand(redisClient *c) {
|
||||
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);
|
||||
@@ -335,8 +409,8 @@ void renameGenericCommand(redisClient *c, int nx) {
|
||||
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]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
addReply(c,nx ? shared.cone : shared.ok);
|
||||
}
|
||||
@@ -453,6 +527,8 @@ void propagateExpire(redisDb *db, robj *key) {
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
time_t when = getExpire(db,key);
|
||||
|
||||
if (when < 0) return 0; /* No expire for this 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.
|
||||
@@ -464,8 +540,6 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
return time(NULL) > when;
|
||||
}
|
||||
|
||||
if (when < 0) return 0;
|
||||
|
||||
/* Return when this key has not expired */
|
||||
if (time(NULL) <= when) return 0;
|
||||
|
||||
@@ -495,13 +569,13 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
|
||||
if (seconds <= 0) {
|
||||
if (dbDelete(c->db,key)) server.dirty++;
|
||||
addReply(c, shared.cone);
|
||||
touchWatchedKey(c->db,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
return;
|
||||
} else {
|
||||
time_t when = time(NULL)+seconds;
|
||||
setExpire(c->db,key,when);
|
||||
addReply(c,shared.cone);
|
||||
touchWatchedKey(c->db,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty++;
|
||||
return;
|
||||
}
|
||||
|
||||
+33
-49
@@ -177,7 +177,26 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
void debugCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"flushcache")) {
|
||||
if (!server.ds_enabled) {
|
||||
addReplyError(c, "DEBUG FLUSHCACHE called with diskstore off.");
|
||||
return;
|
||||
} else if (server.bgsavethread != (pthread_t) -1) {
|
||||
addReplyError(c, "Can't flush cache while BGSAVE is in progress.");
|
||||
return;
|
||||
} else {
|
||||
/* To flush the whole cache we need to wait for everything to
|
||||
* be flushed on disk... */
|
||||
cacheForcePointInTime();
|
||||
emptyDb();
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
if (server.ds_enabled) {
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
}
|
||||
if (rdbSave(server.dbfilename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
@@ -198,62 +217,25 @@ void debugCommand(redisClient *c) {
|
||||
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);
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
char *strenc;
|
||||
|
||||
if (!de) {
|
||||
if (server.ds_enabled) lookupKeyRead(c->db,c->argv[2]);
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
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 if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
|
||||
long keys, j;
|
||||
robj *key, *val;
|
||||
@@ -300,10 +282,12 @@ void _redisAssert(char *estr, char *file, int line) {
|
||||
}
|
||||
|
||||
void _redisPanic(char *msg, char *file, int line) {
|
||||
redisLog(REDIS_WARNING,"------------------------------------------------");
|
||||
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)");
|
||||
redisLog(REDIS_WARNING,"------------------------------------------------");
|
||||
*((char*)-1) = 'x';
|
||||
#endif
|
||||
}
|
||||
|
||||
+509
@@ -0,0 +1,509 @@
|
||||
/* diskstore.c implements a very simple disk backed key-value store used
|
||||
* by Redis for the "disk" backend. This implementation uses the filesystem
|
||||
* to store key/value pairs. Every file represents a given key.
|
||||
*
|
||||
* The key path is calculated using the SHA1 of the key name. For instance
|
||||
* the key "foo" is stored as a file name called:
|
||||
*
|
||||
* /0b/ee/0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
|
||||
*
|
||||
* The couples of characters from the hex output of SHA1 are also used
|
||||
* to locate two two levels of directories to store the file (as most
|
||||
* filesystems are not able to handle too many files in a single dir).
|
||||
*
|
||||
* In the end there are 65536 final directories (256 directories inside
|
||||
* every 256 top level directories), so that with 1 billion of files every
|
||||
* directory will contain in the average 15258 entires, that is ok with
|
||||
* most filesystems implementation.
|
||||
*
|
||||
* Note that since Redis supports multiple databases, the actual key name
|
||||
* is:
|
||||
*
|
||||
* /0b/ee/<dbid>_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
|
||||
*
|
||||
* so for instance if the key is inside DB 0:
|
||||
*
|
||||
* /0b/ee/0_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
|
||||
*
|
||||
* The actaul implementation of this disk store is highly dependant to the
|
||||
* filesystem implementation itself. This implementation may be replaced by
|
||||
* a B+TREE implementation in future implementations.
|
||||
*
|
||||
* Data ok every key is serialized using the same format used for .rdb
|
||||
* serialization. Everything is serialized on every entry: key name,
|
||||
* ttl information in case of keys with an associated expire time, and the
|
||||
* serialized value itself.
|
||||
*
|
||||
* Because the format is the same of the .rdb files it is trivial to create
|
||||
* an .rdb file starting from this format just by mean of scanning the
|
||||
* directories and concatenating entries, with the sole addition of an
|
||||
* .rdb header at the start and the end-of-db opcode at the end.
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010-2011, 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 "redis.h"
|
||||
#include "sha1.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
int create256dir(char *prefix) {
|
||||
char buf[1024];
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 256; j++) {
|
||||
snprintf(buf,sizeof(buf),"%s%02x",prefix,j);
|
||||
if (mkdir(buf,0755) == -1) {
|
||||
redisLog(REDIS_WARNING,"Error creating dir %s for diskstore: %s",
|
||||
buf,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int dsOpen(void) {
|
||||
struct stat sb;
|
||||
int retval, j;
|
||||
char *path = server.ds_path;
|
||||
char buf[1024];
|
||||
|
||||
if ((retval = stat(path,&sb) == -1) && errno != ENOENT) {
|
||||
redisLog(REDIS_WARNING, "Error opening disk store at %s: %s",
|
||||
path, strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Directory already in place. Assume everything is ok. */
|
||||
if (retval == 0 && S_ISDIR(sb.st_mode)) {
|
||||
redisLog(REDIS_NOTICE,"Disk store %s exists", path);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* File exists but it's not a directory */
|
||||
if (retval == 0 && !S_ISDIR(sb.st_mode)) {
|
||||
redisLog(REDIS_WARNING,"Disk store at %s is not a directory", path);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* New disk store, create the directory structure now, as creating
|
||||
* them in a lazy way is not a good idea, after very few insertions
|
||||
* we'll need most of the 65536 directories anyway. */
|
||||
redisLog(REDIS_NOTICE,"Disk store %s does not exist: creating", path);
|
||||
if (mkdir(path,0755) == -1) {
|
||||
redisLog(REDIS_WARNING,"Disk store init failed creating dir %s: %s",
|
||||
path, strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* Create the top level 256 directories */
|
||||
snprintf(buf,sizeof(buf),"%s/",path);
|
||||
if (create256dir(buf) == REDIS_ERR) return REDIS_ERR;
|
||||
|
||||
/* For every 256 top level dir, create 256 nested dirs */
|
||||
for (j = 0; j < 256; j++) {
|
||||
snprintf(buf,sizeof(buf),"%s/%02x/",path,j);
|
||||
if (create256dir(buf) == REDIS_ERR) return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int dsClose(void) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Convert key into full path for this object. Dirty but hopefully
|
||||
* is fast enough. Returns the length of the returned path. */
|
||||
int dsKeyToPath(redisDb *db, char *buf, robj *key) {
|
||||
SHA1_CTX ctx;
|
||||
unsigned char hash[20];
|
||||
char hex[40], digits[] = "0123456789abcdef";
|
||||
int j, l;
|
||||
char *origbuf = buf;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx,key->ptr,sdslen(key->ptr));
|
||||
SHA1Final(hash,&ctx);
|
||||
|
||||
/* Convert the hash into hex format */
|
||||
for (j = 0; j < 20; j++) {
|
||||
hex[j*2] = digits[(hash[j]&0xF0)>>4];
|
||||
hex[(j*2)+1] = digits[hash[j]&0x0F];
|
||||
}
|
||||
|
||||
/* Create the object path. Start with server.ds_path that's the root dir */
|
||||
l = sdslen(server.ds_path);
|
||||
memcpy(buf,server.ds_path,l);
|
||||
buf += l;
|
||||
*buf++ = '/';
|
||||
|
||||
/* Then add xx/yy/ that is the two level directories */
|
||||
buf[0] = hex[0];
|
||||
buf[1] = hex[1];
|
||||
buf[2] = '/';
|
||||
buf[3] = hex[2];
|
||||
buf[4] = hex[3];
|
||||
buf[5] = '/';
|
||||
buf += 6;
|
||||
|
||||
/* Add the database number followed by _ and finall the SHA1 hex */
|
||||
l = ll2string(buf,64,db->id);
|
||||
buf += l;
|
||||
buf[0] = '_';
|
||||
memcpy(buf+1,hex,40);
|
||||
buf[41] = '\0';
|
||||
return (buf-origbuf)+41;
|
||||
}
|
||||
|
||||
int dsSet(redisDb *db, robj *key, robj *val) {
|
||||
char buf[1024], buf2[1024];
|
||||
FILE *fp;
|
||||
int retval, len;
|
||||
|
||||
len = dsKeyToPath(db,buf,key);
|
||||
memcpy(buf2,buf,len);
|
||||
snprintf(buf2+len,sizeof(buf2)-len,"-%ld-%ld",(long)time(NULL),(long)val);
|
||||
while ((fp = fopen(buf2,"w")) == NULL) {
|
||||
if (errno == ENOSPC) {
|
||||
redisLog(REDIS_WARNING,"Diskstore: No space left on device. Please make room and wait 30 seconds for Redis to continue.");
|
||||
sleep(30);
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"diskstore error opening %s: %s",
|
||||
buf2, strerror(errno));
|
||||
redisPanic("Unrecoverable diskstore error. Exiting.");
|
||||
}
|
||||
}
|
||||
if ((retval = rdbSaveKeyValuePair(fp,db,key,val,time(NULL))) == -1)
|
||||
return REDIS_ERR;
|
||||
fclose(fp);
|
||||
if (retval == 0) {
|
||||
/* Expired key. Unlink failing not critical */
|
||||
unlink(buf);
|
||||
unlink(buf2);
|
||||
} else {
|
||||
/* Use rename for atomic updadte of value */
|
||||
if (rename(buf2,buf) == -1) {
|
||||
redisLog(REDIS_WARNING,"rename(2) returned an error: %s",
|
||||
strerror(errno));
|
||||
redisPanic("Unrecoverable diskstore error. Exiting.");
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
robj *dsGet(redisDb *db, robj *key, time_t *expire) {
|
||||
char buf[1024];
|
||||
int type;
|
||||
time_t expiretime = -1; /* -1 means: no expire */
|
||||
robj *dskey; /* Key as loaded from disk. */
|
||||
robj *val;
|
||||
FILE *fp;
|
||||
|
||||
dsKeyToPath(db,buf,key);
|
||||
fp = fopen(buf,"r");
|
||||
if (fp == NULL && errno == ENOENT) return NULL; /* No such key */
|
||||
if (fp == NULL) {
|
||||
redisLog(REDIS_WARNING,"Disk store failed opening %s: %s",
|
||||
buf, strerror(errno));
|
||||
goto readerr;
|
||||
}
|
||||
|
||||
if ((type = rdbLoadType(fp)) == -1) goto readerr;
|
||||
if (type == REDIS_EXPIRETIME) {
|
||||
if ((expiretime = rdbLoadTime(fp)) == -1) goto readerr;
|
||||
/* We read the time so we need to read the object type again */
|
||||
if ((type = rdbLoadType(fp)) == -1) goto readerr;
|
||||
}
|
||||
/* Read key */
|
||||
if ((dskey = rdbLoadStringObject(fp)) == NULL) goto readerr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,fp)) == NULL) goto readerr;
|
||||
fclose(fp);
|
||||
|
||||
/* The key we asked, and the key returned, must be the same */
|
||||
redisAssert(equalStringObjects(key,dskey));
|
||||
|
||||
/* Check if the key already expired */
|
||||
decrRefCount(dskey);
|
||||
if (expiretime != -1 && expiretime < time(NULL)) {
|
||||
decrRefCount(val);
|
||||
unlink(buf); /* This failing is non critical here */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Everything ok... */
|
||||
*expire = expiretime;
|
||||
return val;
|
||||
|
||||
readerr:
|
||||
redisLog(REDIS_WARNING,"Read error reading reading %s. Corrupted key?",
|
||||
buf);
|
||||
redisPanic("Unrecoverable error reading from disk store");
|
||||
return NULL; /* unreached */
|
||||
}
|
||||
|
||||
int dsDel(redisDb *db, robj *key) {
|
||||
char buf[1024];
|
||||
|
||||
dsKeyToPath(db,buf,key);
|
||||
if (unlink(buf) == -1) {
|
||||
if (errno == ENOENT) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Disk store can't remove %s: %s",
|
||||
buf, strerror(errno));
|
||||
redisPanic("Unrecoverable Disk store errore. Existing.");
|
||||
return REDIS_ERR; /* unreached */
|
||||
}
|
||||
} else {
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
int dsExists(redisDb *db, robj *key) {
|
||||
char buf[1024];
|
||||
|
||||
dsKeyToPath(db,buf,key);
|
||||
return access(buf,R_OK) == 0;
|
||||
}
|
||||
|
||||
int dsGetDbidFromFilename(char *path) {
|
||||
char id[64];
|
||||
char *p = strchr(path,'_');
|
||||
int len = (p - path);
|
||||
|
||||
redisAssert(p != NULL && len < 64);
|
||||
memcpy(id,path,len);
|
||||
id[len] = '\0';
|
||||
return atoi(id);
|
||||
}
|
||||
|
||||
void dsFlushOneDir(char *path, int dbid) {
|
||||
DIR *dir;
|
||||
struct dirent *dp, de;
|
||||
|
||||
dir = opendir(path);
|
||||
if (dir == NULL) {
|
||||
redisLog(REDIS_WARNING,"Disk store can't open dir %s: %s",
|
||||
path, strerror(errno));
|
||||
redisPanic("Unrecoverable Disk store errore. Existing.");
|
||||
}
|
||||
while(1) {
|
||||
char buf[1024];
|
||||
|
||||
readdir_r(dir,&de,&dp);
|
||||
if (dp == NULL) break;
|
||||
if (dp->d_name[0] == '.') continue;
|
||||
|
||||
/* Check if we need to remove this entry accordingly to the
|
||||
* DB number. */
|
||||
if (dbid != -1 && dsGetDbidFromFilename(dp->d_name)) continue;
|
||||
|
||||
/* Finally unlink the file */
|
||||
snprintf(buf,1024,"%s/%s",path,dp->d_name);
|
||||
if (unlink(buf) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Can't unlink %s: %s", buf, strerror(errno));
|
||||
redisPanic("Unrecoverable Disk store errore. Existing.");
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
void dsFlushDb(int dbid) {
|
||||
char buf[1024];
|
||||
int j, i;
|
||||
|
||||
redisLog(REDIS_NOTICE,"Flushing diskstore DB (%d)",dbid);
|
||||
for (j = 0; j < 256; j++) {
|
||||
for (i = 0; i < 256; i++) {
|
||||
snprintf(buf,1024,"%s/%02x/%02x",server.ds_path,j,i);
|
||||
dsFlushOneDir(buf,dbid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dsRdbSaveSetState(int state) {
|
||||
pthread_mutex_lock(&server.bgsavethread_mutex);
|
||||
server.bgsavethread_state = state;
|
||||
pthread_mutex_unlock(&server.bgsavethread_mutex);
|
||||
}
|
||||
|
||||
void *dsRdbSave_thread(void *arg) {
|
||||
char tmpfile[256], *filename = (char*)arg;
|
||||
struct dirent *dp, de;
|
||||
int j, i, last_dbid = -1;
|
||||
FILE *fp;
|
||||
|
||||
/* Change state to ACTIVE, to signal there is a saving thead working. */
|
||||
redisLog(REDIS_NOTICE,"Diskstore BGSAVE thread started");
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_ACTIVE);
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
if (!fp) {
|
||||
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
|
||||
strerror(errno));
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_ERR);
|
||||
return NULL;
|
||||
}
|
||||
if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
|
||||
|
||||
sleep(5);
|
||||
|
||||
/* Scan all diskstore dirs looking for keys */
|
||||
for (j = 0; j < 256; j++) {
|
||||
for (i = 0; i < 256; i++) {
|
||||
DIR *dir;
|
||||
char buf[1024];
|
||||
|
||||
/* For every directory, collect all the keys */
|
||||
snprintf(buf,sizeof(buf),"%s/%02x/%02x",server.ds_path,j,i);
|
||||
if ((dir = opendir(buf)) == NULL) {
|
||||
redisLog(REDIS_WARNING,"Disk store can't open dir %s: %s",
|
||||
buf, strerror(errno));
|
||||
goto werr;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
char buf[1024];
|
||||
int dbid;
|
||||
FILE *entryfp;
|
||||
|
||||
readdir_r(dir,&de,&dp);
|
||||
if (dp == NULL) break;
|
||||
if (dp->d_name[0] == '.') continue;
|
||||
/* If there is a '-' char in the file name, it's a temp file */
|
||||
if (strchr(dp->d_name,'-') != NULL) continue;
|
||||
|
||||
/* Emit the SELECT DB opcode if needed. */
|
||||
dbid = dsGetDbidFromFilename(dp->d_name);
|
||||
if (dbid != last_dbid) {
|
||||
last_dbid = dbid;
|
||||
if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr;
|
||||
if (rdbSaveLen(fp,dbid) == -1) goto werr;
|
||||
}
|
||||
|
||||
/* Let's copy this file into the target .rdb */
|
||||
snprintf(buf,sizeof(buf),"%s/%02x/%02x/%s",
|
||||
server.ds_path,j,i,dp->d_name);
|
||||
if ((entryfp = fopen(buf,"r")) == NULL) {
|
||||
redisLog(REDIS_WARNING,"Can't open %s: %s",
|
||||
buf,strerror(errno));
|
||||
closedir(dir);
|
||||
goto werr;
|
||||
}
|
||||
while(1) {
|
||||
int nread = fread(buf,1,sizeof(buf),entryfp);
|
||||
|
||||
if (nread == 0) {
|
||||
if (ferror(entryfp)) {
|
||||
redisLog(REDIS_WARNING,"Error reading from file entry while performing BGSAVE for diskstore: %s", strerror(errno));
|
||||
closedir(dir);
|
||||
goto werr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fwrite(buf,1,nread,fp) != (unsigned)nread) {
|
||||
closedir(dir);
|
||||
goto werr;
|
||||
}
|
||||
}
|
||||
fclose(entryfp);
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/* Output the end of file opcode */
|
||||
if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr;
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
zfree(filename);
|
||||
|
||||
/* 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 DB file on the final destination: %s (diskstore)", strerror(errno));
|
||||
unlink(tmpfile);
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_ERR);
|
||||
return NULL;
|
||||
}
|
||||
redisLog(REDIS_NOTICE,"DB saved on disk by diskstore thread");
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_OK);
|
||||
return NULL;
|
||||
|
||||
werr:
|
||||
zfree(filename);
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_ERR);
|
||||
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int dsRdbSaveBackground(char *filename) {
|
||||
pthread_t thread;
|
||||
|
||||
if (pthread_create(&thread,NULL,dsRdbSave_thread,zstrdup(filename)) != 0) {
|
||||
redisLog(REDIS_WARNING,"Can't create diskstore BGSAVE thread: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
server.bgsavethread = thread;
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
int dsRdbSave(char *filename) {
|
||||
/* A blocking save is actually a non blocking save... just we wait
|
||||
* for it to terminate in a non-busy loop. */
|
||||
|
||||
redisLog(REDIS_NOTICE,"Starting a blocking SAVE (BGSAVE + blocking wait)");
|
||||
server.dirty_before_bgsave = server.dirty;
|
||||
if (dsRdbSaveBackground(filename) == REDIS_ERR) return REDIS_ERR;
|
||||
while(1) {
|
||||
usleep(1000);
|
||||
int state;
|
||||
|
||||
pthread_mutex_lock(&server.bgsavethread_mutex);
|
||||
state = server.bgsavethread_state;
|
||||
pthread_mutex_unlock(&server.bgsavethread_mutex);
|
||||
|
||||
if (state == REDIS_BGSAVE_THREAD_DONE_OK ||
|
||||
state == REDIS_BGSAVE_THREAD_DONE_ERR) break;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
+1026
File diff suppressed because it is too large
Load Diff
+42
-12
@@ -1,4 +1,4 @@
|
||||
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
|
||||
/* Automatically generated by generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
@@ -53,11 +53,21 @@ struct commandHelp {
|
||||
"Remove and get the last element in a list, or block until one is available",
|
||||
2,
|
||||
"1.3.1" },
|
||||
{ "BRPOPLPUSH",
|
||||
"source destination timeout",
|
||||
"Pop a value from a list, push it to another list and return it; or block until one is available",
|
||||
2,
|
||||
"2.1.7" },
|
||||
{ "CONFIG GET",
|
||||
"parameter",
|
||||
"Get the value of a configuration parameter",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "CONFIG RESETSTAT",
|
||||
"-",
|
||||
"Reset the stats returned by INFO",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "CONFIG SET",
|
||||
"parameter value",
|
||||
"Set a configuration parameter to the given value",
|
||||
@@ -79,7 +89,7 @@ struct commandHelp {
|
||||
9,
|
||||
"0.101" },
|
||||
{ "DECR",
|
||||
"key decrement",
|
||||
"key",
|
||||
"Decrement the integer value of a key by one",
|
||||
1,
|
||||
"0.07" },
|
||||
@@ -138,6 +148,11 @@ struct commandHelp {
|
||||
"Get the value of a key",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "GETBIT",
|
||||
"key offset",
|
||||
"Returns the bit value at offset in the string value stored at key",
|
||||
1,
|
||||
"2.1.8" },
|
||||
{ "GETSET",
|
||||
"key value",
|
||||
"Set the string value of a key and return its old value",
|
||||
@@ -344,12 +359,12 @@ struct commandHelp {
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RENAME",
|
||||
"old new",
|
||||
"key newkey",
|
||||
"Rename a key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RENAMENX",
|
||||
"old new",
|
||||
"key newkey",
|
||||
"Rename a key, only if the new key does not exist",
|
||||
0,
|
||||
"0.07" },
|
||||
@@ -408,8 +423,13 @@ struct commandHelp {
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "SETBIT",
|
||||
"key offset value",
|
||||
"Sets or clears the bit at offset in the string value stored at key",
|
||||
1,
|
||||
"2.1.8" },
|
||||
{ "SETEX",
|
||||
"key timestamp value",
|
||||
"key seconds value",
|
||||
"Set the value and expiration of a key",
|
||||
1,
|
||||
"1.3.10" },
|
||||
@@ -418,6 +438,11 @@ struct commandHelp {
|
||||
"Set the value of a key, only if the key does not exist",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "SETRANGE",
|
||||
"key offset value",
|
||||
"Overwrite part of a string at key starting at the specified offset",
|
||||
1,
|
||||
"2.1.8" },
|
||||
{ "SHUTDOWN",
|
||||
"-",
|
||||
"Synchronously save the dataset to disk and then shut down the server",
|
||||
@@ -454,7 +479,7 @@ struct commandHelp {
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SORT",
|
||||
"key [BY pattern] [LIMIT start count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
|
||||
"key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
|
||||
"Sort the elements in a list, set or sorted set",
|
||||
0,
|
||||
"0.07" },
|
||||
@@ -484,7 +509,7 @@ struct commandHelp {
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "SUBSTR",
|
||||
"key start stop",
|
||||
"key start end",
|
||||
"Get a substring of the string stored at a key",
|
||||
1,
|
||||
"1.3.4" },
|
||||
@@ -549,17 +574,17 @@ struct commandHelp {
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZINTERSTORE",
|
||||
"destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"destination numkeys 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",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZRANGEBYSCORE",
|
||||
"key min max",
|
||||
"key min max [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score",
|
||||
4,
|
||||
"1.050" },
|
||||
@@ -584,10 +609,15 @@ struct commandHelp {
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREVRANGE",
|
||||
"key start stop",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index, with scores ordered from high to low",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREVRANGEBYSCORE",
|
||||
"key max min [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score, with scores ordered from high to low",
|
||||
4,
|
||||
"2.1.6" },
|
||||
{ "ZREVRANK",
|
||||
"key member",
|
||||
"Determine the index of a member in a sorted set, with scores ordered from high to low",
|
||||
@@ -599,7 +629,7 @@ struct commandHelp {
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZUNIONSTORE",
|
||||
"destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"destination numkeys 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" }
|
||||
|
||||
+28
-90
@@ -167,7 +167,6 @@ void _addReplyStringToList(redisClient *c, char *s, size_t 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
|
||||
@@ -457,10 +456,17 @@ void freeClient(redisClient *c) {
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
/* When client was just unblocked because of a blocking operation,
|
||||
* remove it from the list with unblocked clients. */
|
||||
if (c->flags & REDIS_UNBLOCKED) {
|
||||
ln = listSearchKey(server.unblocked_clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.unblocked_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);
|
||||
redisAssert(server.ds_enabled);
|
||||
if (listLength(c->io_keys) == 0) {
|
||||
ln = listSearchKey(server.io_ready_clients,c);
|
||||
|
||||
@@ -474,7 +480,7 @@ void freeClient(redisClient *c) {
|
||||
dontWaitForSwappedKey(c,ln->value);
|
||||
}
|
||||
}
|
||||
server.vm_blocked_clients--;
|
||||
server.cache_blocked_clients--;
|
||||
}
|
||||
listRelease(c->io_keys);
|
||||
/* Master/slave cleanup.
|
||||
@@ -516,15 +522,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
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) {
|
||||
@@ -595,84 +592,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -908,3 +827,22 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
processInputBuffer(c);
|
||||
}
|
||||
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer) {
|
||||
redisClient *c;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
unsigned long lol = 0, bib = 0;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = listNodeValue(ln);
|
||||
|
||||
if (listLength(c->reply) > lol) lol = listLength(c->reply);
|
||||
if (sdslen(c->querybuf) > bib) bib = sdslen(c->querybuf);
|
||||
}
|
||||
*longest_output_list = lol;
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
|
||||
+8
-31
@@ -21,7 +21,6 @@ robj *createObject(int type, void *ptr) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -32,6 +31,7 @@ robj *createStringObject(char *ptr, size_t len) {
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
robj *o;
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
!server.ds_enabled &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
o = shared.integers[value];
|
||||
@@ -160,30 +160,7 @@ void incrRefCount(robj *o) {
|
||||
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;
|
||||
@@ -228,16 +205,16 @@ robj *tryObjectEncoding(robj *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).
|
||||
* range and if the back end in use is in-memory. For disk store every
|
||||
* object in memory used as value should be independent.
|
||||
*
|
||||
* 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
|
||||
* because every 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)) {
|
||||
if (!server.ds_enabled &&
|
||||
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];
|
||||
|
||||
@@ -395,10 +395,29 @@ off_t rdbSavedObjectLen(robj *o) {
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Return the number of pages required to save this object in the swap file */
|
||||
off_t rdbSavedObjectPages(robj *o) {
|
||||
off_t bytes = rdbSavedObjectLen(o);
|
||||
return (bytes+(server.vm_page_size-1))/server.vm_page_size;
|
||||
/* Save a key-value pair, with expire time, type, key, value.
|
||||
* On error -1 is returned.
|
||||
* On success if the key was actaully saved 1 is returned, otherwise 0
|
||||
* is returned (the key was already expired). */
|
||||
int rdbSaveKeyValuePair(FILE *fp, redisDb *db, robj *key, robj *val,
|
||||
time_t now)
|
||||
{
|
||||
time_t expiretime;
|
||||
|
||||
expiretime = getExpire(db,key);
|
||||
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) return 0;
|
||||
if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) return -1;
|
||||
if (rdbSaveTime(fp,expiretime) == -1) return -1;
|
||||
}
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveType(fp,val->type) == -1) return -1;
|
||||
if (rdbSaveStringObject(fp,key) == -1) return -1;
|
||||
if (rdbSaveObject(fp,val) == -1) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
|
||||
@@ -410,16 +429,16 @@ int rdbSave(char *filename) {
|
||||
int j;
|
||||
time_t now = time(NULL);
|
||||
|
||||
/* Wait for I/O therads to terminate, just in case this is a
|
||||
* foreground-saving, to avoid seeking the swap file descriptor at the
|
||||
* same time. */
|
||||
if (server.vm_enabled)
|
||||
waitEmptyIOJobsQueue();
|
||||
if (server.ds_enabled) {
|
||||
cacheForcePointInTime();
|
||||
return dsRdbSave(filename);
|
||||
}
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
if (!fp) {
|
||||
redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno));
|
||||
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
|
||||
@@ -441,38 +460,9 @@ int rdbSave(char *filename) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr = dictGetEntryKey(de);
|
||||
robj key, *o = dictGetEntryVal(de);
|
||||
time_t expiretime;
|
||||
|
||||
initStaticStringObject(key,keystr);
|
||||
expiretime = getExpire(db,&key);
|
||||
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) continue;
|
||||
if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr;
|
||||
if (rdbSaveTime(fp,expiretime) == -1) goto werr;
|
||||
}
|
||||
/* Save the key and associated value. This requires special
|
||||
* handling if the value is swapped out. */
|
||||
if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY ||
|
||||
o->storage == REDIS_VM_SWAPPING) {
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveType(fp,o->type) == -1) goto werr;
|
||||
if (rdbSaveStringObject(fp,&key) == -1) goto werr;
|
||||
if (rdbSaveObject(fp,o) == -1) goto werr;
|
||||
} else {
|
||||
/* REDIS_VM_SWAPPED or REDIS_VM_LOADING */
|
||||
robj *po;
|
||||
/* Get a preview of the object in memory */
|
||||
po = vmPreviewObject(o);
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveType(fp,po->type) == -1) goto werr;
|
||||
if (rdbSaveStringObject(fp,&key) == -1) goto werr;
|
||||
if (rdbSaveObject(fp,po) == -1) goto werr;
|
||||
/* Remove the loaded object from memory */
|
||||
decrRefCount(po);
|
||||
}
|
||||
if (rdbSaveKeyValuePair(fp,db,&key,o,now) == -1) goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
@@ -507,19 +497,24 @@ werr:
|
||||
int rdbSaveBackground(char *filename) {
|
||||
pid_t childpid;
|
||||
|
||||
if (server.bgsavechildpid != -1) return REDIS_ERR;
|
||||
if (server.vm_enabled) waitEmptyIOJobsQueue();
|
||||
if (server.bgsavechildpid != -1 ||
|
||||
server.bgsavethread != (pthread_t) -1) return REDIS_ERR;
|
||||
|
||||
server.dirty_before_bgsave = server.dirty;
|
||||
|
||||
if (server.ds_enabled) {
|
||||
cacheForcePointInTime();
|
||||
return dsRdbSaveBackground(filename);
|
||||
}
|
||||
|
||||
if ((childpid = fork()) == 0) {
|
||||
int retval;
|
||||
|
||||
/* Child */
|
||||
if (server.vm_enabled) vmReopenSwapFile();
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
if (rdbSave(filename) == REDIS_OK) {
|
||||
_exit(0);
|
||||
} else {
|
||||
_exit(1);
|
||||
}
|
||||
retval = rdbSave(filename);
|
||||
_exit((retval == REDIS_OK) ? 0 : 1);
|
||||
} else {
|
||||
/* Parent */
|
||||
if (childpid == -1) {
|
||||
@@ -874,7 +869,6 @@ int rdbLoad(char *filename) {
|
||||
FILE *fp;
|
||||
uint32_t dbid;
|
||||
int type, retval, rdbver;
|
||||
int swap_all_values = 0;
|
||||
redisDb *db = server.db+0;
|
||||
char buf[1024];
|
||||
time_t expiretime, now = time(NULL);
|
||||
@@ -899,8 +893,6 @@ int rdbLoad(char *filename) {
|
||||
startLoading(fp);
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
int force_swapout;
|
||||
|
||||
expiretime = -1;
|
||||
|
||||
/* Serve the clients from time to time */
|
||||
@@ -947,44 +939,7 @@ int rdbLoad(char *filename) {
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(db,key,expiretime);
|
||||
|
||||
/* Handle swapping while loading big datasets when VM is on */
|
||||
|
||||
/* If we detecter we are hopeless about fitting something in memory
|
||||
* we just swap every new key on disk. Directly...
|
||||
* Note that's important to check for this condition before resorting
|
||||
* to random sampling, otherwise we may try to swap already
|
||||
* swapped keys. */
|
||||
if (swap_all_values) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
/* de may be NULL since the key already expired */
|
||||
if (de) {
|
||||
vmpointer *vp;
|
||||
val = dictGetEntryVal(de);
|
||||
|
||||
if (val->refcount == 1 &&
|
||||
(vp = vmSwapObjectBlocking(val)) != NULL)
|
||||
dictGetEntryVal(de) = vp;
|
||||
}
|
||||
decrRefCount(key);
|
||||
continue;
|
||||
}
|
||||
decrRefCount(key);
|
||||
|
||||
/* Flush data on disk once 32 MB of additional RAM are used... */
|
||||
force_swapout = 0;
|
||||
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
|
||||
force_swapout = 1;
|
||||
|
||||
/* If we have still some hope of having some value fitting memory
|
||||
* then we try random sampling. */
|
||||
if (!swap_all_values && server.vm_enabled && force_swapout) {
|
||||
while (zmalloc_used_memory() > server.vm_max_memory) {
|
||||
if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
|
||||
}
|
||||
if (zmalloc_used_memory() > server.vm_max_memory)
|
||||
swap_all_values = 1; /* We are already using too much mem */
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
stopLoading();
|
||||
@@ -997,10 +952,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
}
|
||||
|
||||
/* A background saving child (BGSAVE) terminated its work. Handle this. */
|
||||
void backgroundSaveDoneHandler(int statloc) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = WIFSIGNALED(statloc);
|
||||
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
if (!bysignal && exitcode == 0) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Background saving terminated with success");
|
||||
@@ -1010,11 +962,37 @@ void backgroundSaveDoneHandler(int statloc) {
|
||||
redisLog(REDIS_WARNING, "Background saving error");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Background saving terminated by signal %d", WTERMSIG(statloc));
|
||||
"Background saving terminated by signal %d", bysignal);
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
server.bgsavechildpid = -1;
|
||||
server.bgsavethread = (pthread_t) -1;
|
||||
server.bgsavethread_state = REDIS_BGSAVE_THREAD_UNACTIVE;
|
||||
/* Possibly there are slaves waiting for a BGSAVE in order to be served
|
||||
* (the first stage of SYNC is a bulk transfer of dump.rdb) */
|
||||
updateSlavesWaitingBgsave(exitcode == 0 ? REDIS_OK : REDIS_ERR);
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1 || server.bgsavethread != (pthread_t)-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 || server.bgsavethread != (pthread_t)-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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -414,10 +414,11 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
}
|
||||
|
||||
static int cliReadReply(int output_raw_strings) {
|
||||
void *_reply;
|
||||
redisReply *reply;
|
||||
sds out;
|
||||
|
||||
if (redisGetReply(context,(void**)&reply) != REDIS_OK) {
|
||||
if (redisGetReply(context,&_reply) != REDIS_OK) {
|
||||
if (config.shutdown)
|
||||
return REDIS_OK;
|
||||
if (config.interactive) {
|
||||
@@ -431,6 +432,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
return REDIS_ERR; /* avoid compiler warning */
|
||||
}
|
||||
|
||||
reply = (redisReply*)_reply;
|
||||
if (output_raw_strings) {
|
||||
out = cliFormatReplyRaw(reply);
|
||||
} else {
|
||||
|
||||
+85
-106
@@ -345,7 +345,7 @@ unsigned int dictEncObjHash(const void *key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Sets type */
|
||||
/* Sets type and diskstore negative caching hash table */
|
||||
dictType setDictType = {
|
||||
dictEncObjHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
@@ -583,20 +583,40 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)
|
||||
closeTimedoutClients();
|
||||
|
||||
/* Check if a background saving or AOF rewrite in progress terminated */
|
||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||
if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) {
|
||||
int statloc;
|
||||
pid_t pid;
|
||||
|
||||
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = 0;
|
||||
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
|
||||
if (pid == server.bgsavechildpid) {
|
||||
backgroundSaveDoneHandler(statloc);
|
||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||
} else {
|
||||
backgroundRewriteDoneHandler(statloc);
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
}
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
} else {
|
||||
} else if (server.bgsavethread != (pthread_t) -1) {
|
||||
if (server.bgsavethread != (pthread_t) -1) {
|
||||
int state;
|
||||
|
||||
pthread_mutex_lock(&server.bgsavethread_mutex);
|
||||
state = server.bgsavethread_state;
|
||||
pthread_mutex_unlock(&server.bgsavethread_mutex);
|
||||
|
||||
if (state == REDIS_BGSAVE_THREAD_DONE_OK ||
|
||||
state == REDIS_BGSAVE_THREAD_DONE_ERR)
|
||||
{
|
||||
backgroundSaveDoneHandler(
|
||||
(state == REDIS_BGSAVE_THREAD_DONE_OK) ? 0 : 1, 0);
|
||||
}
|
||||
}
|
||||
} else if (!server.ds_enabled) {
|
||||
/* If there is not a background saving in progress check if
|
||||
* we have to save now */
|
||||
time_t now = time(NULL);
|
||||
@@ -618,28 +638,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* in order to guarantee a strict consistency. */
|
||||
if (server.masterhost == NULL) activeExpireCycle();
|
||||
|
||||
/* Swap a few keys on disk if we are over the memory limit and VM
|
||||
* is enbled. Try to free objects from the free list first. */
|
||||
if (vmCanSwapOut()) {
|
||||
while (server.vm_enabled && zmalloc_used_memory() >
|
||||
server.vm_max_memory)
|
||||
{
|
||||
int retval = (server.vm_max_threads == 0) ?
|
||||
vmSwapOneObjectBlocking() :
|
||||
vmSwapOneObjectThreaded();
|
||||
if (retval == REDIS_ERR && !(loops % 300) &&
|
||||
zmalloc_used_memory() >
|
||||
(server.vm_max_memory+server.vm_max_memory/10))
|
||||
{
|
||||
redisLog(REDIS_WARNING,"WARNING: vm-max-memory limit exceeded by more than 10%% but unable to swap more objects out!");
|
||||
}
|
||||
/* Note that when using threade I/O we free just one object,
|
||||
* because anyway when the I/O thread in charge to swap this
|
||||
* object out will finish, the handler of completed jobs
|
||||
* will try to swap more objects if we are still out of memory. */
|
||||
if (retval == REDIS_ERR || server.vm_max_threads > 0) break;
|
||||
}
|
||||
}
|
||||
/* Remove a few cached objects from memory if we are over the
|
||||
* configured memory limit */
|
||||
if (server.ds_enabled) cacheCron();
|
||||
|
||||
/* Replication cron function -- used to reconnect to master and
|
||||
* to detect transfer failures. */
|
||||
@@ -656,8 +657,8 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
listNode *ln;
|
||||
redisClient *c;
|
||||
|
||||
/* Awake clients that got all the swapped keys they requested */
|
||||
if (server.vm_enabled && listLength(server.io_ready_clients)) {
|
||||
/* Awake clients that got all the on disk keys they requested */
|
||||
if (server.ds_enabled && listLength(server.io_ready_clients)) {
|
||||
listIter li;
|
||||
|
||||
listRewind(server.io_ready_clients,&li);
|
||||
@@ -668,7 +669,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
/* Resume the client. */
|
||||
listDelNode(server.io_ready_clients,ln);
|
||||
c->flags &= (~REDIS_IO_WAIT);
|
||||
server.vm_blocked_clients--;
|
||||
server.cache_blocked_clients--;
|
||||
aeCreateFileEvent(server.el, c->fd, AE_READABLE,
|
||||
readQueryFromClient, c);
|
||||
cmd = lookupCommand(c->argv[0]->ptr);
|
||||
@@ -687,6 +688,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
redisAssert(ln != NULL);
|
||||
c = ln->value;
|
||||
listDelNode(server.unblocked_clients,ln);
|
||||
c->flags &= ~REDIS_UNBLOCKED;
|
||||
|
||||
/* Process remaining data in the input buffer. */
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0)
|
||||
@@ -768,7 +770,6 @@ void initServerConfig() {
|
||||
server.syslog_enabled = 0;
|
||||
server.syslog_ident = zstrdup("redis");
|
||||
server.syslog_facility = LOG_LOCAL0;
|
||||
server.glueoutputbuf = 1;
|
||||
server.daemonize = 0;
|
||||
server.appendonly = 0;
|
||||
server.appendfsync = APPENDFSYNC_EVERYSEC;
|
||||
@@ -787,19 +788,17 @@ void initServerConfig() {
|
||||
server.maxmemory = 0;
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
server.maxmemory_samples = 3;
|
||||
server.vm_enabled = 0;
|
||||
server.vm_swap_file = zstrdup("/tmp/redis-%p.vm");
|
||||
server.vm_page_size = 256; /* 256 bytes per page */
|
||||
server.vm_pages = 1024*1024*100; /* 104 millions of pages */
|
||||
server.vm_max_memory = 1024LL*1024*1024*1; /* 1 GB of RAM */
|
||||
server.vm_max_threads = 4;
|
||||
server.vm_blocked_clients = 0;
|
||||
server.ds_enabled = 0;
|
||||
server.ds_path = sdsnew("/tmp/redis.ds");
|
||||
server.cache_max_memory = 64LL*1024*1024; /* 64 MB of RAM */
|
||||
server.cache_blocked_clients = 0;
|
||||
server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES;
|
||||
server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
|
||||
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
|
||||
server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
|
||||
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
|
||||
server.shutdown_asap = 0;
|
||||
server.cache_flush_delay = 0;
|
||||
|
||||
updateLRUClock();
|
||||
resetServerSaveParams();
|
||||
@@ -848,6 +847,8 @@ void initServer() {
|
||||
server.slaves = listCreate();
|
||||
server.monitors = listCreate();
|
||||
server.unblocked_clients = listCreate();
|
||||
server.cache_io_queue = listCreate();
|
||||
|
||||
createSharedObjects();
|
||||
server.el = aeCreateEventLoop();
|
||||
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
|
||||
@@ -873,8 +874,11 @@ void initServer() {
|
||||
server.db[j].expires = dictCreate(&keyptrDictType,NULL);
|
||||
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
||||
if (server.vm_enabled)
|
||||
if (server.ds_enabled) {
|
||||
server.db[j].io_keys = dictCreate(&keylistDictType,NULL);
|
||||
server.db[j].io_negcache = dictCreate(&setDictType,NULL);
|
||||
server.db[j].io_queued = dictCreate(&setDictType,NULL);
|
||||
}
|
||||
server.db[j].id = j;
|
||||
}
|
||||
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
|
||||
@@ -884,6 +888,8 @@ void initServer() {
|
||||
server.cronloops = 0;
|
||||
server.bgsavechildpid = -1;
|
||||
server.bgrewritechildpid = -1;
|
||||
server.bgsavethread_state = REDIS_BGSAVE_THREAD_UNACTIVE;
|
||||
server.bgsavethread = (pthread_t) -1;
|
||||
server.bgrewritebuf = sdsempty();
|
||||
server.aofbuf = sdsempty();
|
||||
server.lastsave = time(NULL);
|
||||
@@ -891,6 +897,7 @@ void initServer() {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_starttime = time(NULL);
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
@@ -910,7 +917,7 @@ void initServer() {
|
||||
}
|
||||
}
|
||||
|
||||
if (server.vm_enabled) vmInit();
|
||||
if (server.ds_enabled) dsInit();
|
||||
}
|
||||
|
||||
/* Populates the Redis Command Table starting from the hard coded list
|
||||
@@ -1049,8 +1056,8 @@ int processCommand(redisClient *c) {
|
||||
queueMultiCommand(c,cmd);
|
||||
addReply(c,shared.queued);
|
||||
} else {
|
||||
if (server.vm_enabled && server.vm_max_threads > 0 &&
|
||||
blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR;
|
||||
if (server.ds_enabled && blockClientOnSwappedKeys(c,cmd))
|
||||
return REDIS_ERR;
|
||||
call(c,cmd);
|
||||
}
|
||||
return REDIS_OK;
|
||||
@@ -1068,10 +1075,11 @@ int prepareForShutdown() {
|
||||
kill(server.bgsavechildpid,SIGKILL);
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
if (server.appendonly) {
|
||||
if (server.ds_enabled) {
|
||||
/* FIXME: flush all objects on disk */
|
||||
} else if (server.appendonly) {
|
||||
/* Append only file: fsync() the AOF and exit */
|
||||
aof_fsync(server.appendfd);
|
||||
if (server.vm_enabled) unlink(server.vm_swap_file);
|
||||
} else if (server.saveparamslen > 0) {
|
||||
/* Snapshotting. Perform a SYNC SAVE and exit */
|
||||
if (rdbSave(server.dbfilename) != REDIS_OK) {
|
||||
@@ -1141,9 +1149,11 @@ sds genRedisInfoString(void) {
|
||||
int j;
|
||||
char hmem[64];
|
||||
struct rusage self_ru, c_ru;
|
||||
unsigned long lol, bib;
|
||||
|
||||
getrusage(RUSAGE_SELF, &self_ru);
|
||||
getrusage(RUSAGE_CHILDREN, &c_ru);
|
||||
getClientsMaxBuffers(&lol,&bib);
|
||||
|
||||
bytesToHuman(hmem,zmalloc_used_memory());
|
||||
info = sdscatprintf(sdsempty(),
|
||||
@@ -1162,6 +1172,8 @@ sds genRedisInfoString(void) {
|
||||
"used_cpu_user_childrens:%.2f\r\n"
|
||||
"connected_clients:%d\r\n"
|
||||
"connected_slaves:%d\r\n"
|
||||
"client_longest_output_list:%lu\r\n"
|
||||
"client_biggest_input_buf:%lu\r\n"
|
||||
"blocked_clients:%d\r\n"
|
||||
"used_memory:%zu\r\n"
|
||||
"used_memory_human:%s\r\n"
|
||||
@@ -1177,13 +1189,14 @@ sds genRedisInfoString(void) {
|
||||
"total_connections_received:%lld\r\n"
|
||||
"total_commands_processed:%lld\r\n"
|
||||
"expired_keys:%lld\r\n"
|
||||
"evicted_keys:%lld\r\n"
|
||||
"keyspace_hits:%lld\r\n"
|
||||
"keyspace_misses:%lld\r\n"
|
||||
"hash_max_zipmap_entries:%zu\r\n"
|
||||
"hash_max_zipmap_value:%zu\r\n"
|
||||
"pubsub_channels:%ld\r\n"
|
||||
"pubsub_patterns:%u\r\n"
|
||||
"vm_enabled:%d\r\n"
|
||||
"ds_enabled:%d\r\n"
|
||||
"role:%s\r\n"
|
||||
,REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
@@ -1200,6 +1213,7 @@ sds genRedisInfoString(void) {
|
||||
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
listLength(server.slaves),
|
||||
lol, bib,
|
||||
server.bpop_blocked_clients,
|
||||
zmalloc_used_memory(),
|
||||
hmem,
|
||||
@@ -1213,19 +1227,20 @@ sds genRedisInfoString(void) {
|
||||
server.loading,
|
||||
server.appendonly,
|
||||
server.dirty,
|
||||
server.bgsavechildpid != -1,
|
||||
server.bgsavechildpid != -1 || server.bgsavethread != (pthread_t) -1,
|
||||
server.lastsave,
|
||||
server.bgrewritechildpid != -1,
|
||||
server.stat_numconnections,
|
||||
server.stat_numcommands,
|
||||
server.stat_expiredkeys,
|
||||
server.stat_evictedkeys,
|
||||
server.stat_keyspace_hits,
|
||||
server.stat_keyspace_misses,
|
||||
server.hash_max_zipmap_entries,
|
||||
server.hash_max_zipmap_value,
|
||||
dictSize(server.pubsub_channels),
|
||||
listLength(server.pubsub_patterns),
|
||||
server.vm_enabled != 0,
|
||||
server.ds_enabled != 0,
|
||||
server.masterhost == NULL ? "master" : "slave"
|
||||
);
|
||||
if (server.masterhost) {
|
||||
@@ -1252,33 +1267,13 @@ sds genRedisInfoString(void) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (server.vm_enabled) {
|
||||
if (server.ds_enabled) {
|
||||
lockThreadedIO();
|
||||
info = sdscatprintf(info,
|
||||
"vm_conf_max_memory:%llu\r\n"
|
||||
"vm_conf_page_size:%llu\r\n"
|
||||
"vm_conf_pages:%llu\r\n"
|
||||
"vm_stats_used_pages:%llu\r\n"
|
||||
"vm_stats_swapped_objects:%llu\r\n"
|
||||
"vm_stats_swappin_count:%llu\r\n"
|
||||
"vm_stats_swappout_count:%llu\r\n"
|
||||
"vm_stats_io_newjobs_len:%lu\r\n"
|
||||
"vm_stats_io_processing_len:%lu\r\n"
|
||||
"vm_stats_io_processed_len:%lu\r\n"
|
||||
"vm_stats_io_active_threads:%lu\r\n"
|
||||
"vm_stats_blocked_clients:%lu\r\n"
|
||||
,(unsigned long long) server.vm_max_memory,
|
||||
(unsigned long long) server.vm_page_size,
|
||||
(unsigned long long) server.vm_pages,
|
||||
(unsigned long long) server.vm_stats_used_pages,
|
||||
(unsigned long long) server.vm_stats_swapped_objects,
|
||||
(unsigned long long) server.vm_stats_swapins,
|
||||
(unsigned long long) server.vm_stats_swapouts,
|
||||
(unsigned long) listLength(server.io_newjobs),
|
||||
(unsigned long) listLength(server.io_processing),
|
||||
(unsigned long) listLength(server.io_processed),
|
||||
(unsigned long) server.io_active_threads,
|
||||
(unsigned long) server.vm_blocked_clients
|
||||
"cache_max_memory:%llu\r\n"
|
||||
"cache_blocked_clients:%lu\r\n"
|
||||
,(unsigned long long) server.cache_max_memory,
|
||||
(unsigned long) server.cache_blocked_clients
|
||||
);
|
||||
unlockThreadedIO();
|
||||
}
|
||||
@@ -1311,6 +1306,19 @@ sds genRedisInfoString(void) {
|
||||
eta
|
||||
);
|
||||
}
|
||||
|
||||
info = sdscat(info,"allocation_stats:");
|
||||
for (j = 0; j <= ZMALLOC_MAX_ALLOC_STAT; j++) {
|
||||
size_t count = zmalloc_allocations_for_size(j);
|
||||
if (count) {
|
||||
if (info[sdslen(info)-1] != ':') info = sdscatlen(info,",",1);
|
||||
info = sdscatprintf(info,"%s%d=%zu",
|
||||
(j == ZMALLOC_MAX_ALLOC_STAT) ? ">=" : "",
|
||||
j,count);
|
||||
}
|
||||
}
|
||||
info = sdscat(info,"\r\n");
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
long long keys, vkeys;
|
||||
|
||||
@@ -1436,44 +1444,13 @@ void freeMemoryIfNeeded(void) {
|
||||
if (bestkey) {
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
dbDelete(db,keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
server.stat_evictedkeys++;
|
||||
decrRefCount(keyobj);
|
||||
freed++;
|
||||
}
|
||||
}
|
||||
if (!freed) return; /* nothing to free... */
|
||||
}
|
||||
|
||||
while(0) {
|
||||
int j, k, freed = 0;
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
int minttl = -1;
|
||||
sds minkey = NULL;
|
||||
robj *keyobj = NULL;
|
||||
struct dictEntry *de;
|
||||
|
||||
if (dictSize(server.db[j].expires)) {
|
||||
freed = 1;
|
||||
/* From a sample of three keys drop the one nearest to
|
||||
* the natural expire */
|
||||
for (k = 0; k < 3; k++) {
|
||||
time_t t;
|
||||
|
||||
de = dictGetRandomKey(server.db[j].expires);
|
||||
t = (time_t) dictGetEntryVal(de);
|
||||
if (minttl == -1 || t < minttl) {
|
||||
minkey = dictGetEntryKey(de);
|
||||
minttl = t;
|
||||
}
|
||||
}
|
||||
keyobj = createStringObject(minkey,sdslen(minkey));
|
||||
dbDelete(server.db+j,keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
}
|
||||
if (!freed) return; /* nothing to free... */
|
||||
}
|
||||
}
|
||||
|
||||
/* =================================== Main! ================================ */
|
||||
@@ -1504,7 +1481,7 @@ void createPidFile(void) {
|
||||
/* Try to write the pid file in a best-effort way. */
|
||||
FILE *fp = fopen(server.pidfile,"w");
|
||||
if (fp) {
|
||||
fprintf(fp,"%d\n",getpid());
|
||||
fprintf(fp,"%d\n",(int)getpid());
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
@@ -1561,7 +1538,9 @@ int main(int argc, char **argv) {
|
||||
linuxOvercommitMemoryWarning();
|
||||
#endif
|
||||
start = time(NULL);
|
||||
if (server.appendonly) {
|
||||
if (server.ds_enabled) {
|
||||
redisLog(REDIS_NOTICE,"DB not loaded (running with disk back end)");
|
||||
} else if (server.appendonly) {
|
||||
if (loadAppendOnlyFile(server.appendfilename) == REDIS_OK)
|
||||
redisLog(REDIS_NOTICE,"DB loaded from append only file: %ld seconds",time(NULL)-start);
|
||||
} else {
|
||||
|
||||
+104
-84
@@ -50,11 +50,6 @@
|
||||
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
|
||||
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
|
||||
|
||||
/* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */
|
||||
#define REDIS_WRITEV_THRESHOLD 3
|
||||
/* Max number of iovecs used for each writev call */
|
||||
#define REDIS_WRITEV_IOVEC_COUNT 256
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
|
||||
@@ -119,23 +114,18 @@
|
||||
#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */
|
||||
#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */
|
||||
|
||||
/* Virtual memory object->where field. */
|
||||
#define REDIS_VM_MEMORY 0 /* The object is on memory */
|
||||
#define REDIS_VM_SWAPPED 1 /* The object is on disk */
|
||||
#define REDIS_VM_SWAPPING 2 /* Redis is swapping this object on disk */
|
||||
#define REDIS_VM_LOADING 3 /* Redis is loading this object from disk */
|
||||
/* Scheduled IO opeations flags. */
|
||||
#define REDIS_IO_LOAD 1
|
||||
#define REDIS_IO_SAVE 2
|
||||
#define REDIS_IO_LOADINPROG 4
|
||||
#define REDIS_IO_SAVEINPROG 8
|
||||
|
||||
/* Generic IO flags */
|
||||
#define REDIS_IO_ONLYLOADS 1
|
||||
#define REDIS_IO_ASAP 2
|
||||
|
||||
/* Virtual memory static configuration stuff.
|
||||
* Check vmFindContiguousPages() to know more about this magic numbers. */
|
||||
#define REDIS_VM_MAX_NEAR_PAGES 65536
|
||||
#define REDIS_VM_MAX_RANDOM_JUMP 4096
|
||||
#define REDIS_VM_MAX_THREADS 32
|
||||
#define REDIS_THREAD_STACK_SIZE (1024*1024*4)
|
||||
/* The following is the *percentage* of completed I/O jobs to process when the
|
||||
* handelr is called. While Virtual Memory I/O operations are performed by
|
||||
* threads, this operations must be processed by the main thread when completed
|
||||
* in order to take effect. */
|
||||
#define REDIS_MAX_COMPLETED_JOBS_PROCESSED 1
|
||||
#define REDIS_THREAD_STACK_SIZE (1024*1024*4)
|
||||
|
||||
/* Client flags */
|
||||
#define REDIS_SLAVE 1 /* This client is a slave server */
|
||||
@@ -146,6 +136,8 @@
|
||||
#define REDIS_IO_WAIT 32 /* The client is waiting for Virtual Memory I/O */
|
||||
#define REDIS_DIRTY_CAS 64 /* Watched keys modified. EXEC will fail. */
|
||||
#define REDIS_CLOSE_AFTER_REPLY 128 /* Close after writing entire reply. */
|
||||
#define REDIS_UNBLOCKED 256 /* This client was unblocked and is stored in
|
||||
server.unblocked_clients */
|
||||
|
||||
/* Client request types */
|
||||
#define REDIS_REQ_INLINE 1
|
||||
@@ -194,8 +186,8 @@
|
||||
#define APPENDFSYNC_EVERYSEC 2
|
||||
|
||||
/* Zip structure related defaults */
|
||||
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64
|
||||
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
|
||||
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 512
|
||||
#define REDIS_HASH_MAX_ZIPMAP_VALUE 64
|
||||
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
|
||||
#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
|
||||
#define REDIS_SET_MAX_INTSET_ENTRIES 512
|
||||
@@ -213,6 +205,12 @@
|
||||
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
|
||||
#define REDIS_MAXMEMORY_NO_EVICTION 5
|
||||
|
||||
/* Diskstore background saving thread states */
|
||||
#define REDIS_BGSAVE_THREAD_UNACTIVE 0
|
||||
#define REDIS_BGSAVE_THREAD_ACTIVE 1
|
||||
#define REDIS_BGSAVE_THREAD_DONE_OK 2
|
||||
#define REDIS_BGSAVE_THREAD_DONE_ERR 3
|
||||
|
||||
/* We can print the stacktrace, so our assert is defined this way: */
|
||||
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
|
||||
#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1)
|
||||
@@ -230,7 +228,7 @@ void _redisPanic(char *msg, char *file, int line);
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned storage:2; /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */
|
||||
unsigned notused:2; /* Not used */
|
||||
unsigned encoding:4;
|
||||
unsigned lru:22; /* lru time (relative to server.lruclock) */
|
||||
int refcount;
|
||||
@@ -271,14 +269,15 @@ typedef struct vmPointer {
|
||||
_var.type = REDIS_STRING; \
|
||||
_var.encoding = REDIS_ENCODING_RAW; \
|
||||
_var.ptr = _ptr; \
|
||||
_var.storage = REDIS_VM_MEMORY; \
|
||||
} while(0);
|
||||
|
||||
typedef struct redisDb {
|
||||
dict *dict; /* The keyspace for this DB */
|
||||
dict *expires; /* Timeout of keys with a timeout set */
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
|
||||
dict *io_keys; /* Keys with clients waiting for VM I/O */
|
||||
dict *io_keys; /* Keys with clients waiting for DS I/O */
|
||||
dict *io_negcache; /* Negative caching for disk store */
|
||||
dict *io_queued; /* Queued IO operations hash table */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
int id;
|
||||
} redisDb;
|
||||
@@ -359,17 +358,20 @@ struct sharedObjectsStruct {
|
||||
|
||||
/* Global server state structure */
|
||||
struct redisServer {
|
||||
/* General */
|
||||
pthread_t mainthread;
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table hahs table */
|
||||
aeEventLoop *el;
|
||||
/* Networking */
|
||||
int port;
|
||||
char *bindaddr;
|
||||
char *unixsocket;
|
||||
int ipfd;
|
||||
int sofd;
|
||||
redisDb *db;
|
||||
long long dirty; /* changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
|
||||
list *clients;
|
||||
dict *commands; /* Command table hahs table */
|
||||
list *slaves, *monitors;
|
||||
char neterr[ANET_ERR_LEN];
|
||||
/* RDB / AOF loading information */
|
||||
int loading;
|
||||
off_t loading_total_bytes;
|
||||
@@ -377,9 +379,6 @@ struct redisServer {
|
||||
time_t loading_start_time;
|
||||
/* Fast pointers to often looked up command */
|
||||
struct redisCommand *delCommand, *multiCommand;
|
||||
list *slaves, *monitors;
|
||||
char neterr[ANET_ERR_LEN];
|
||||
aeEventLoop *el;
|
||||
int cronloops; /* number of times the cron function run */
|
||||
time_t lastsave; /* Unix time of last save succeeede */
|
||||
/* Fields used only for stats */
|
||||
@@ -387,11 +386,11 @@ struct redisServer {
|
||||
long long stat_numcommands; /* number of processed commands */
|
||||
long long stat_numconnections; /* number of connections received */
|
||||
long long stat_expiredkeys; /* number of expired keys */
|
||||
long long stat_evictedkeys; /* number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* number of failed lookups of keys */
|
||||
/* Configuration */
|
||||
int verbosity;
|
||||
int glueoutputbuf;
|
||||
int maxidletime;
|
||||
int dbnum;
|
||||
int daemonize;
|
||||
@@ -399,25 +398,32 @@ struct redisServer {
|
||||
int appendfsync;
|
||||
int no_appendfsync_on_rewrite;
|
||||
int shutdown_asap;
|
||||
int activerehashing;
|
||||
char *requirepass;
|
||||
/* Persistence */
|
||||
long long dirty; /* changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
|
||||
time_t lastfsync;
|
||||
int appendfd;
|
||||
int appendseldb;
|
||||
char *pidfile;
|
||||
pid_t bgsavechildpid;
|
||||
pid_t bgrewritechildpid;
|
||||
int bgsavethread_state;
|
||||
pthread_mutex_t bgsavethread_mutex;
|
||||
pthread_t bgsavethread;
|
||||
sds bgrewritebuf; /* buffer taken by parent during oppend only rewrite */
|
||||
sds aofbuf; /* AOF buffer, written before entering the event loop */
|
||||
struct saveparam *saveparams;
|
||||
int saveparamslen;
|
||||
char *dbfilename;
|
||||
int rdbcompression;
|
||||
char *appendfilename;
|
||||
/* Logging */
|
||||
char *logfile;
|
||||
int syslog_enabled;
|
||||
char *syslog_ident;
|
||||
int syslog_facility;
|
||||
char *dbfilename;
|
||||
char *appendfilename;
|
||||
char *requirepass;
|
||||
int rdbcompression;
|
||||
int activerehashing;
|
||||
/* Replication related */
|
||||
int isslave;
|
||||
/* Slave specific fields */
|
||||
@@ -439,31 +445,25 @@ struct redisServer {
|
||||
int maxmemory_samples;
|
||||
/* Blocked clients */
|
||||
unsigned int bpop_blocked_clients;
|
||||
unsigned int vm_blocked_clients;
|
||||
list *unblocked_clients;
|
||||
unsigned int cache_blocked_clients;
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop */
|
||||
list *cache_io_queue; /* IO operations queue */
|
||||
int cache_flush_delay; /* seconds to wait before flushing keys */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
int sort_alpha;
|
||||
int sort_bypattern;
|
||||
/* Virtual memory configuration */
|
||||
int vm_enabled;
|
||||
char *vm_swap_file;
|
||||
off_t vm_page_size;
|
||||
off_t vm_pages;
|
||||
unsigned long long vm_max_memory;
|
||||
int ds_enabled; /* backend disk in redis.conf */
|
||||
char *ds_path; /* location of the disk store on disk */
|
||||
unsigned long long cache_max_memory;
|
||||
/* Zip structure config */
|
||||
size_t hash_max_zipmap_entries;
|
||||
size_t hash_max_zipmap_value;
|
||||
size_t list_max_ziplist_entries;
|
||||
size_t list_max_ziplist_value;
|
||||
size_t set_max_intset_entries;
|
||||
/* Virtual memory state */
|
||||
FILE *vm_fp;
|
||||
int vm_fd;
|
||||
off_t vm_next_page; /* Next probably empty page */
|
||||
off_t vm_near_pages; /* Number of pages allocated sequentially */
|
||||
unsigned char *vm_bitmap; /* Bitmap of free/used pages */
|
||||
time_t unixtime; /* Unix time sampled every second. */
|
||||
/* Virtual memory I/O threads stuff */
|
||||
/* An I/O thread process an element taken from the io_jobs queue and
|
||||
@@ -474,7 +474,7 @@ struct redisServer {
|
||||
list *io_processed; /* List of VM I/O jobs already processed */
|
||||
list *io_ready_clients; /* Clients ready to be unblocked. All keys loaded */
|
||||
pthread_mutex_t io_mutex; /* lock to access io_jobs/io_done/io_thread_job */
|
||||
pthread_mutex_t io_swapfile_mutex; /* So we can lseek + write */
|
||||
pthread_cond_t io_condvar; /* I/O threads conditional variable */
|
||||
pthread_attr_t io_threads_attr; /* attributes for threads creation */
|
||||
int io_active_threads; /* Number of running I/O threads */
|
||||
int vm_max_threads; /* Max number of I/O threads running at the same time */
|
||||
@@ -559,25 +559,27 @@ typedef struct zset {
|
||||
zskiplist *zsl;
|
||||
} zset;
|
||||
|
||||
/* VM threaded I/O request message */
|
||||
#define REDIS_IOJOB_LOAD 0 /* Load from disk to memory */
|
||||
#define REDIS_IOJOB_PREPARE_SWAP 1 /* Compute needed pages */
|
||||
#define REDIS_IOJOB_DO_SWAP 2 /* Swap from memory to disk */
|
||||
/* DIsk store threaded I/O request message */
|
||||
#define REDIS_IOJOB_LOAD 0
|
||||
#define REDIS_IOJOB_SAVE 1
|
||||
|
||||
typedef struct iojob {
|
||||
int type; /* Request type, REDIS_IOJOB_* */
|
||||
redisDb *db;/* Redis database */
|
||||
robj *key; /* This I/O request is about swapping this key */
|
||||
robj *id; /* Unique identifier of this job:
|
||||
this is the object to swap for REDIS_IOREQ_*_SWAP, or the
|
||||
vmpointer objct for REDIS_IOREQ_LOAD. */
|
||||
robj *val; /* the value to swap for REDIS_IOREQ_*_SWAP, otherwise this
|
||||
* field is populated by the I/O thread for REDIS_IOREQ_LOAD. */
|
||||
off_t page; /* Swap page where to read/write the object */
|
||||
off_t pages; /* Swap pages needed to save object. PREPARE_SWAP return val */
|
||||
int canceled; /* True if this command was canceled by blocking side of VM */
|
||||
pthread_t thread; /* ID of the thread processing this entry */
|
||||
robj *key; /* This I/O request is about this key */
|
||||
robj *val; /* the value to swap for REDIS_IOJOB_SAVE, otherwise this
|
||||
* field is populated by the I/O thread for REDIS_IOJOB_LOAD. */
|
||||
time_t expire; /* Expire time for this key on REDIS_IOJOB_LOAD */
|
||||
} iojob;
|
||||
|
||||
/* IO operations scheduled -- check dscache.c for more info */
|
||||
typedef struct ioop {
|
||||
int type;
|
||||
redisDb *db;
|
||||
robj *key;
|
||||
time_t ctime; /* This is the creation time of the entry. */
|
||||
} ioop;
|
||||
|
||||
/* Structure to hold list iteration abstraction. */
|
||||
typedef struct {
|
||||
robj *subject;
|
||||
@@ -640,7 +642,6 @@ void closeTimedoutClients(void);
|
||||
void freeClient(redisClient *c);
|
||||
void resetClient(redisClient *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
void *addDeferredMultiBulkLength(redisClient *c);
|
||||
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
|
||||
@@ -662,6 +663,8 @@ void addReplyDouble(redisClient *c, double d);
|
||||
void addReplyLongLong(redisClient *c, long long ll);
|
||||
void addReplyMultiBulkLen(redisClient *c, long length);
|
||||
void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
@@ -759,7 +762,13 @@ int rdbSaveObject(FILE *fp, robj *o);
|
||||
off_t rdbSavedObjectLen(robj *o);
|
||||
off_t rdbSavedObjectPages(robj *o);
|
||||
robj *rdbLoadObject(int type, FILE *fp);
|
||||
void backgroundSaveDoneHandler(int statloc);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(FILE *fp, redisDb *db, robj *key, robj *val, time_t now);
|
||||
int rdbLoadType(FILE *fp);
|
||||
time_t rdbLoadTime(FILE *fp);
|
||||
robj *rdbLoadStringObject(FILE *fp);
|
||||
int rdbSaveType(FILE *fp, unsigned char type);
|
||||
int rdbSaveLen(FILE *fp, uint32_t len);
|
||||
|
||||
/* AOF persistence */
|
||||
void flushAppendOnlyFile(void);
|
||||
@@ -769,7 +778,7 @@ int rewriteAppendOnlyFileBackground(void);
|
||||
int loadAppendOnlyFile(char *filename);
|
||||
void stopAppendOnly(void);
|
||||
int startAppendOnly(void);
|
||||
void backgroundRewriteDoneHandler(int statloc);
|
||||
void backgroundRewriteDoneHandler(int exitcode, int bysignal);
|
||||
|
||||
/* Sorted sets data type */
|
||||
zskiplist *zslCreate(void);
|
||||
@@ -791,32 +800,41 @@ int htNeedsResize(dict *dict);
|
||||
void oom(const char *msg);
|
||||
void populateCommandTable(void);
|
||||
|
||||
/* Virtual Memory */
|
||||
void vmInit(void);
|
||||
void vmMarkPagesFree(off_t page, off_t count);
|
||||
robj *vmLoadObject(robj *o);
|
||||
robj *vmPreviewObject(robj *o);
|
||||
int vmSwapOneObjectBlocking(void);
|
||||
int vmSwapOneObjectThreaded(void);
|
||||
int vmCanSwapOut(void);
|
||||
/* Disk store */
|
||||
int dsOpen(void);
|
||||
int dsClose(void);
|
||||
int dsSet(redisDb *db, robj *key, robj *val);
|
||||
robj *dsGet(redisDb *db, robj *key, time_t *expire);
|
||||
int dsDel(redisDb *db, robj *key);
|
||||
int dsExists(redisDb *db, robj *key);
|
||||
void dsFlushDb(int dbid);
|
||||
int dsRdbSaveBackground(char *filename);
|
||||
int dsRdbSave(char *filename);
|
||||
|
||||
/* Disk Store Cache */
|
||||
void dsInit(void);
|
||||
void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void vmCancelThreadedIOJob(robj *o);
|
||||
void lockThreadedIO(void);
|
||||
void unlockThreadedIO(void);
|
||||
int vmSwapObjectThreaded(robj *key, robj *val, redisDb *db);
|
||||
void freeIOJob(iojob *j);
|
||||
void queueIOJob(iojob *j);
|
||||
int vmWriteObjectOnSwap(robj *o, off_t page);
|
||||
robj *vmReadObjectFromSwap(off_t page, int type);
|
||||
void waitEmptyIOJobsQueue(void);
|
||||
void vmReopenSwapFile(void);
|
||||
int vmFreePage(off_t page);
|
||||
void processAllPendingIOJobs(void);
|
||||
void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
|
||||
void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
|
||||
int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd);
|
||||
int dontWaitForSwappedKey(redisClient *c, robj *key);
|
||||
void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
|
||||
vmpointer *vmSwapObjectBlocking(robj *val);
|
||||
int cacheFreeOneEntry(void);
|
||||
void cacheScheduleIOAddFlag(redisDb *db, robj *key, long flag);
|
||||
void cacheScheduleIODelFlag(redisDb *db, robj *key, long flag);
|
||||
int cacheScheduleIOGetFlags(redisDb *db, robj *key);
|
||||
void cacheScheduleIO(redisDb *db, robj *key, int type);
|
||||
void cacheCron(void);
|
||||
int cacheKeyMayExist(redisDb *db, robj *key);
|
||||
void cacheSetKeyMayExist(redisDb *db, robj *key);
|
||||
void cacheSetKeyDoesNotExist(redisDb *db, robj *key);
|
||||
void cacheForcePointInTime(void);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -887,6 +905,8 @@ robj *dbRandomKey(redisDb *db);
|
||||
int dbDelete(redisDb *db, robj *key);
|
||||
long long emptyDb();
|
||||
int selectDb(redisClient *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
|
||||
+1
-1
@@ -368,7 +368,7 @@ void sortCommand(redisClient *c) {
|
||||
* SORT result is empty a new key is set and maybe the old content
|
||||
* replaced. */
|
||||
server.dirty += 1+outputlen;
|
||||
touchWatchedKey(c->db,storekey);
|
||||
signalModifiedKey(c->db,storekey);
|
||||
addReplyLongLong(c,outputlen);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -279,7 +279,7 @@ void hsetCommand(redisClient *c) {
|
||||
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
|
||||
update = hashTypeSet(o,c->argv[2],c->argv[3]);
|
||||
addReply(c, update ? shared.czero : shared.cone);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ void hsetnxCommand(redisClient *c) {
|
||||
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
|
||||
hashTypeSet(o,c->argv[2],c->argv[3]);
|
||||
addReply(c, shared.cone);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -315,7 +315,7 @@ void hmsetCommand(redisClient *c) {
|
||||
hashTypeSet(o,c->argv[i],c->argv[i+1]);
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ void hincrbyCommand(redisClient *c) {
|
||||
hashTypeSet(o,c->argv[2],new);
|
||||
decrRefCount(new);
|
||||
addReplyLongLong(c,value);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ void hdelCommand(redisClient *c) {
|
||||
if (hashTypeDelete(o,c->argv[2])) {
|
||||
if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
addReply(c,shared.cone);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
|
||||
+13
-12
@@ -274,14 +274,14 @@ void pushGenericCommand(redisClient *c, int where) {
|
||||
return;
|
||||
}
|
||||
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
addReply(c,shared.cone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
listTypePush(lobj,c->argv[2],where);
|
||||
addReplyLongLong(c,listTypeLength(lobj));
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
|
||||
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
} else {
|
||||
/* Notify client of a failed insert */
|
||||
@@ -339,7 +339,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
}
|
||||
} else {
|
||||
listTypePush(subject,val,where);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ void lsetCommand(redisClient *c) {
|
||||
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
|
||||
decrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
@@ -439,7 +439,7 @@ void lsetCommand(redisClient *c) {
|
||||
listNodeValue(ln) = value;
|
||||
incrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
} else {
|
||||
@@ -458,7 +458,7 @@ void popGenericCommand(redisClient *c, int where) {
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -573,7 +573,7 @@ void ltrimCommand(redisClient *c) {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -616,7 +616,7 @@ void lremCommand(redisClient *c) {
|
||||
|
||||
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
|
||||
addReplyLongLong(c,removed);
|
||||
if (removed) touchWatchedKey(c->db,c->argv[1]);
|
||||
if (removed) signalModifiedKey(c->db,c->argv[1]);
|
||||
}
|
||||
|
||||
/* This is the semantic of this command:
|
||||
@@ -642,7 +642,7 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
|
||||
dstobj = createZiplistObject();
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
} else {
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
listTypePush(dstobj,value,REDIS_HEAD);
|
||||
@@ -670,7 +670,7 @@ void rpoplpushCommand(redisClient *c) {
|
||||
|
||||
/* Delete the source list when it is empty */
|
||||
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -773,7 +773,8 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
zfree(c->bpop.keys);
|
||||
c->bpop.keys = NULL;
|
||||
c->bpop.target = NULL;
|
||||
c->flags &= (~REDIS_BLOCKED);
|
||||
c->flags &= ~REDIS_BLOCKED;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
server.bpop_blocked_clients--;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
|
||||
+8
-8
@@ -231,7 +231,7 @@ void saddCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
if (setTypeAdd(set,c->argv[2])) {
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.cone);
|
||||
} else {
|
||||
@@ -248,7 +248,7 @@ void sremCommand(redisClient *c) {
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
if (setTypeRemove(set,c->argv[2])) {
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.cone);
|
||||
} else {
|
||||
@@ -287,8 +287,8 @@ void smoveCommand(redisClient *c) {
|
||||
|
||||
/* Remove the src set from the database when empty */
|
||||
if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[2]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
|
||||
/* Create the destination set when it doesn't exist */
|
||||
@@ -341,7 +341,7 @@ void spopCommand(redisClient *c) {
|
||||
setTypeRemove(set,ele);
|
||||
}
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
@@ -486,7 +486,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
decrRefCount(dstset);
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
} else {
|
||||
setDeferredMultiBulkLength(c,replylen,cardinality);
|
||||
@@ -578,7 +578,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
decrRefCount(dstset);
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
zfree(sets);
|
||||
|
||||
+13
-9
@@ -1,4 +1,3 @@
|
||||
#include <limits.h>
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -38,7 +37,7 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
|
||||
} else {
|
||||
incrRefCount(val);
|
||||
}
|
||||
touchWatchedKey(c->db,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty++;
|
||||
removeExpire(c->db,key);
|
||||
if (expire) setExpire(c->db,key,time(NULL)+seconds);
|
||||
@@ -84,7 +83,7 @@ void getsetCommand(redisClient *c) {
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
dbReplace(c->db,c->argv[1],c->argv[2]);
|
||||
incrRefCount(c->argv[2]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
removeExpire(c->db,c->argv[1]);
|
||||
}
|
||||
@@ -156,7 +155,7 @@ void setbitCommand(redisClient *c) {
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((char*)o->ptr)[byte] = byteval;
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
@@ -244,7 +243,7 @@ void setrangeCommand(redisClient *c) {
|
||||
if (sdslen(value) > 0) {
|
||||
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
||||
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,sdslen(o->ptr));
|
||||
@@ -331,7 +330,7 @@ void msetGenericCommand(redisClient *c, int nx) {
|
||||
dbReplace(c->db,c->argv[j],c->argv[j+1]);
|
||||
incrRefCount(c->argv[j+1]);
|
||||
removeExpire(c->db,c->argv[j]);
|
||||
touchWatchedKey(c->db,c->argv[j]);
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
}
|
||||
server.dirty += (c->argc-1)/2;
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
@@ -346,17 +345,22 @@ void msetnxCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void incrDecrCommand(redisClient *c, long long incr) {
|
||||
long long value;
|
||||
long long value, oldvalue;
|
||||
robj *o;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
|
||||
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
|
||||
|
||||
oldvalue = value;
|
||||
value += incr;
|
||||
if ((incr < 0 && value > oldvalue) || (incr > 0 && value < oldvalue)) {
|
||||
addReplyError(c,"increment or decrement would overflow");
|
||||
return;
|
||||
}
|
||||
o = createStringObjectFromLongLong(value);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.colon);
|
||||
addReply(c,o);
|
||||
@@ -419,7 +423,7 @@ void appendCommand(redisClient *c) {
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c,totlen);
|
||||
}
|
||||
|
||||
+7
-7
@@ -399,7 +399,7 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int
|
||||
de = dictFind(zs->dict,ele);
|
||||
redisAssert(de != NULL);
|
||||
dictGetEntryVal(de) = &znode->score;
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
if (incr)
|
||||
addReplyDouble(c,score);
|
||||
@@ -427,7 +427,7 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int
|
||||
|
||||
/* Update the score in the current dict entry */
|
||||
dictGetEntryVal(de) = &znode->score;
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
if (incr)
|
||||
@@ -477,7 +477,7 @@ void zremCommand(redisClient *c) {
|
||||
dictDelete(zs->dict,c->argv[2]);
|
||||
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
||||
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
@@ -501,7 +501,7 @@ void zremrangebyscoreCommand(redisClient *c) {
|
||||
deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
|
||||
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
||||
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
|
||||
if (deleted) touchWatchedKey(c->db,c->argv[1]);
|
||||
if (deleted) signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -540,7 +540,7 @@ void zremrangebyrankCommand(redisClient *c) {
|
||||
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
|
||||
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
||||
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
|
||||
if (deleted) touchWatchedKey(c->db,c->argv[1]);
|
||||
if (deleted) signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c, deleted);
|
||||
}
|
||||
@@ -741,14 +741,14 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
}
|
||||
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touched = 1;
|
||||
server.dirty++;
|
||||
}
|
||||
if (dstzset->zsl->length) {
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
addReplyLongLong(c, dstzset->zsl->length);
|
||||
if (!touched) touchWatchedKey(c->db,dstkey);
|
||||
if (!touched) signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
} else {
|
||||
decrRefCount(dstobj);
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.1.8"
|
||||
#define REDIS_VERSION "2.3.0"
|
||||
|
||||
+4
-2
@@ -119,6 +119,7 @@ static unsigned int zipEntryEncoding(unsigned char *p) {
|
||||
return p[0] & 0xf0;
|
||||
}
|
||||
assert(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return bytes needed to store integer encoded by 'encoding' */
|
||||
@@ -129,13 +130,14 @@ static unsigned int zipIntSize(unsigned char encoding) {
|
||||
case ZIP_INT_64B: return sizeof(int64_t);
|
||||
}
|
||||
assert(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode the encoded length pointed by 'p'. If a pointer to 'lensize' is
|
||||
* provided, it is set to the number of bytes required to encode the length. */
|
||||
static unsigned int zipDecodeLength(unsigned char *p, unsigned int *lensize) {
|
||||
unsigned char encoding = zipEntryEncoding(p);
|
||||
unsigned int len;
|
||||
unsigned int len = 0;
|
||||
|
||||
if (ZIP_IS_STR(encoding)) {
|
||||
switch(encoding) {
|
||||
@@ -300,7 +302,7 @@ static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encodi
|
||||
static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
|
||||
int16_t i16;
|
||||
int32_t i32;
|
||||
int64_t i64, ret;
|
||||
int64_t i64, ret = 0;
|
||||
if (encoding == ZIP_INT_16B) {
|
||||
memcpy(&i16,p,sizeof(i16));
|
||||
ret = i16;
|
||||
|
||||
+23
-12
@@ -33,6 +33,7 @@
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include "config.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
#ifdef HAVE_MALLOC_SIZE
|
||||
#define PREFIX_SIZE (0)
|
||||
@@ -52,19 +53,22 @@
|
||||
#define free(ptr) tc_free(ptr)
|
||||
#endif
|
||||
|
||||
#define increment_used_memory(__n) do { \
|
||||
#define update_zmalloc_stat_alloc(__n,__size) do { \
|
||||
size_t _n = (__n); \
|
||||
size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT; \
|
||||
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
|
||||
if (zmalloc_thread_safe) { \
|
||||
pthread_mutex_lock(&used_memory_mutex); \
|
||||
used_memory += _n; \
|
||||
zmalloc_allocations[_stat_slot]++; \
|
||||
pthread_mutex_unlock(&used_memory_mutex); \
|
||||
} else { \
|
||||
used_memory += _n; \
|
||||
zmalloc_allocations[_stat_slot]++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define decrement_used_memory(__n) do { \
|
||||
#define update_zmalloc_stat_free(__n) do { \
|
||||
size_t _n = (__n); \
|
||||
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
|
||||
if (zmalloc_thread_safe) { \
|
||||
@@ -79,6 +83,8 @@
|
||||
static size_t used_memory = 0;
|
||||
static int zmalloc_thread_safe = 0;
|
||||
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
/* Note that malloc_allocations elements are initialized to zero by C */
|
||||
size_t zmalloc_allocations[ZMALLOC_MAX_ALLOC_STAT+1];
|
||||
|
||||
static void zmalloc_oom(size_t size) {
|
||||
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
|
||||
@@ -92,11 +98,11 @@ void *zmalloc(size_t size) {
|
||||
|
||||
if (!ptr) zmalloc_oom(size);
|
||||
#ifdef HAVE_MALLOC_SIZE
|
||||
increment_used_memory(redis_malloc_size(ptr));
|
||||
update_zmalloc_stat_alloc(redis_malloc_size(ptr),size);
|
||||
return ptr;
|
||||
#else
|
||||
*((size_t*)ptr) = size;
|
||||
increment_used_memory(size+PREFIX_SIZE);
|
||||
update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
|
||||
return (char*)ptr+PREFIX_SIZE;
|
||||
#endif
|
||||
}
|
||||
@@ -106,11 +112,11 @@ void *zcalloc(size_t size) {
|
||||
|
||||
if (!ptr) zmalloc_oom(size);
|
||||
#ifdef HAVE_MALLOC_SIZE
|
||||
increment_used_memory(redis_malloc_size(ptr));
|
||||
update_zmalloc_stat_alloc(redis_malloc_size(ptr),size);
|
||||
return ptr;
|
||||
#else
|
||||
*((size_t*)ptr) = size;
|
||||
increment_used_memory(size+PREFIX_SIZE);
|
||||
update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
|
||||
return (char*)ptr+PREFIX_SIZE;
|
||||
#endif
|
||||
}
|
||||
@@ -128,8 +134,8 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
newptr = realloc(ptr,size);
|
||||
if (!newptr) zmalloc_oom(size);
|
||||
|
||||
decrement_used_memory(oldsize);
|
||||
increment_used_memory(redis_malloc_size(newptr));
|
||||
update_zmalloc_stat_free(oldsize);
|
||||
update_zmalloc_stat_alloc(redis_malloc_size(newptr),size);
|
||||
return newptr;
|
||||
#else
|
||||
realptr = (char*)ptr-PREFIX_SIZE;
|
||||
@@ -138,8 +144,8 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
if (!newptr) zmalloc_oom(size);
|
||||
|
||||
*((size_t*)newptr) = size;
|
||||
decrement_used_memory(oldsize);
|
||||
increment_used_memory(size);
|
||||
update_zmalloc_stat_free(oldsize);
|
||||
update_zmalloc_stat_alloc(size,size);
|
||||
return (char*)newptr+PREFIX_SIZE;
|
||||
#endif
|
||||
}
|
||||
@@ -152,12 +158,12 @@ void zfree(void *ptr) {
|
||||
|
||||
if (ptr == NULL) return;
|
||||
#ifdef HAVE_MALLOC_SIZE
|
||||
decrement_used_memory(redis_malloc_size(ptr));
|
||||
update_zmalloc_stat_free(redis_malloc_size(ptr));
|
||||
free(ptr);
|
||||
#else
|
||||
realptr = (char*)ptr-PREFIX_SIZE;
|
||||
oldsize = *((size_t*)realptr);
|
||||
decrement_used_memory(oldsize+PREFIX_SIZE);
|
||||
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
|
||||
free(realptr);
|
||||
#endif
|
||||
}
|
||||
@@ -179,6 +185,11 @@ size_t zmalloc_used_memory(void) {
|
||||
return um;
|
||||
}
|
||||
|
||||
size_t zmalloc_allocations_for_size(size_t size) {
|
||||
if (size > ZMALLOC_MAX_ALLOC_STAT) return 0;
|
||||
return zmalloc_allocations[size];
|
||||
}
|
||||
|
||||
void zmalloc_enable_thread_safeness(void) {
|
||||
zmalloc_thread_safe = 1;
|
||||
}
|
||||
|
||||
@@ -40,5 +40,8 @@ size_t zmalloc_used_memory(void);
|
||||
void zmalloc_enable_thread_safeness(void);
|
||||
float zmalloc_get_fragmentation_ratio(void);
|
||||
size_t zmalloc_get_rss(void);
|
||||
size_t zmalloc_allocations_for_size(size_t size);
|
||||
|
||||
#define ZMALLOC_MAX_ALLOC_STAT 256
|
||||
|
||||
#endif /* _ZMALLOC_H */
|
||||
|
||||
+134
-72
@@ -18,9 +18,9 @@ daemonize no
|
||||
|
||||
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
|
||||
# default. You can specify a custom pid file location here.
|
||||
pidfile redis.pid
|
||||
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
|
||||
|
||||
@@ -44,6 +50,16 @@ loglevel verbose
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile stdout
|
||||
|
||||
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||
# and optionally update the other syslog parameters to suit your needs.
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident redis
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
|
||||
# Set the number of databases. The default database is DB 0, you can select
|
||||
# a different one on a per-connection basis using SELECT <dbid> where
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
@@ -86,7 +102,7 @@ dbfilename dump.rdb
|
||||
# Also the Append Only File will be created inside this directory.
|
||||
#
|
||||
# Note that you must specify a directory here, not a file name.
|
||||
dir ./test/tmp
|
||||
dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
@@ -104,6 +120,19 @@ dir ./test/tmp
|
||||
#
|
||||
# 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 +148,22 @@ dir ./test/tmp
|
||||
#
|
||||
# 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 +193,37 @@ dir ./test/tmp
|
||||
#
|
||||
# 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,81 +271,54 @@ appendonly no
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
|
||||
################################ VIRTUAL MEMORY ###############################
|
||||
# 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 allows Redis to work with datasets bigger than the actual
|
||||
# amount of RAM needed to hold the whole dataset in memory.
|
||||
# In order to do so very used keys are taken in memory while the other keys
|
||||
# are swapped into a swap file, similarly to what operating systems do
|
||||
# with memory pages.
|
||||
#
|
||||
# To enable VM just set 'vm-enabled' to yes, and set the following three
|
||||
# VM parameters accordingly to your needs.
|
||||
#################################### DISK STORE ###############################
|
||||
|
||||
vm-enabled no
|
||||
# vm-enabled yes
|
||||
# When disk store is active Redis works as an on-disk database, where memory
|
||||
# is only used as a object cache.
|
||||
#
|
||||
# This mode is good for datasets that are bigger than memory, and in general
|
||||
# when you want to trade speed for:
|
||||
#
|
||||
# - less memory used
|
||||
# - immediate server restart
|
||||
# - per key durability, without need for backgrond savig
|
||||
#
|
||||
# On the other hand, with disk store enabled MULTI/EXEC are no longer
|
||||
# transactional from the point of view of the persistence on disk, that is,
|
||||
# Redis transactions will still guarantee that commands are either processed
|
||||
# all or nothing, but there is no guarantee that all the keys are flushed
|
||||
# on disk in an atomic way.
|
||||
#
|
||||
# Of course with disk store enabled Redis is not as fast as it is when
|
||||
# working with just the memory back end.
|
||||
|
||||
# This is the path of the Redis swap file. As you can guess, swap files
|
||||
# can't be shared by different Redis instances, so make sure to use a swap
|
||||
# file for every redis process you are running. Redis will complain if the
|
||||
# swap file is already in use.
|
||||
#
|
||||
# The best kind of storage for the Redis swap file (that's accessed at random)
|
||||
# is a Solid State Disk (SSD).
|
||||
#
|
||||
# *** WARNING *** if you are using a shared hosting the default of putting
|
||||
# the swap file under /tmp is not secure. Create a dir with access granted
|
||||
# only to Redis user and configure Redis to create the swap file there.
|
||||
vm-swap-file redis.swap
|
||||
|
||||
# vm-max-memory configures the VM to use at max the specified amount of
|
||||
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
|
||||
# is, if there is still enough contiguous space in the swap file.
|
||||
#
|
||||
# With vm-max-memory 0 the system will swap everything it can. Not a good
|
||||
# default, just specify the max amount of RAM you can in bytes, but it's
|
||||
# better to leave some margin. For instance specify an amount of RAM
|
||||
# that's more or less between 60 and 80% of your free RAM.
|
||||
vm-max-memory 0
|
||||
|
||||
# Redis swap files is split into pages. An object can be saved using multiple
|
||||
# contiguous pages, but pages can't be shared between different objects.
|
||||
# So if your page is too big, small objects swapped out on disk will waste
|
||||
# a lot of space. If you page is too small, there is less space in the swap
|
||||
# file (assuming you configured the same number of total swap file pages).
|
||||
#
|
||||
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
|
||||
# If you use a lot of big objects, use a bigger page size.
|
||||
# If unsure, use the default :)
|
||||
vm-page-size 32
|
||||
|
||||
# Number of total memory pages in the swap file.
|
||||
# Given that the page table (a bitmap of free/used pages) is taken in memory,
|
||||
# every 8 pages on disk will consume 1 byte of RAM.
|
||||
#
|
||||
# The total swap size is vm-page-size * vm-pages
|
||||
#
|
||||
# 32M swap should be enough for testing.
|
||||
vm-pages 1048576
|
||||
|
||||
# Max number of VM I/O threads running at the same time.
|
||||
# This threads are used to read/write data from/to swap file, since they
|
||||
# also encode and decode objects from disk to memory or the reverse, a bigger
|
||||
# number of threads can help with big objects even if they can't help with
|
||||
# I/O itself as the physical device may not be able to couple with many
|
||||
# reads/writes operations at the same time.
|
||||
#
|
||||
# The special value of 0 turn off threaded I/O and enables the blocking
|
||||
# Virtual Memory implementation.
|
||||
vm-max-threads 4
|
||||
diskstore-enabled no
|
||||
diskstore-path redis.ds
|
||||
cache-max-memory 0
|
||||
cache-flush-delay 0
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Glue small output buffers together in order to send small replies in a
|
||||
# single TCP packet. Uses a bit more CPU but most of the times it is a win
|
||||
# in terms of number of queries per second. Use 'yes' if unsure.
|
||||
glueoutputbuf yes
|
||||
|
||||
# Hashes are encoded in a special way (much more memory efficient) when they
|
||||
# have at max a given numer of elements, and the biggest element does not
|
||||
# exceed a given threshold. You can configure this limits with the following
|
||||
@@ -277,6 +326,19 @@ glueoutputbuf yes
|
||||
hash-max-zipmap-entries 64
|
||||
hash-max-zipmap-value 512
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
# you are under the following limits:
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
|
||||
# Sets have a special encoding in just one case: when a set is composed
|
||||
# of just strings that happens to be integers in radix 10 in the range
|
||||
# of 64 bit signed integers.
|
||||
# The following configuration setting sets the limit in the size of the
|
||||
# set in order to use this special memory saving encoding.
|
||||
set-max-intset-entries 512
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation redis uses (see dict.c)
|
||||
|
||||
@@ -4,7 +4,13 @@ file mkdir $::tmproot
|
||||
|
||||
# returns a dirname unique to this process to write to
|
||||
proc tmpdir {basename} {
|
||||
set dir [file join $::tmproot $basename.[pid].[incr ::tmpcounter]]
|
||||
if {$::diskstore} {
|
||||
# For diskstore we want to use the same dir again and again
|
||||
# otherwise everything is too slow.
|
||||
set dir [file join $::tmproot $basename.diskstore]
|
||||
} else {
|
||||
set dir [file join $::tmproot $basename.[pid].[incr ::tmpcounter]]
|
||||
}
|
||||
file mkdir $dir
|
||||
set _ $dir
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ set ::allowtags {}
|
||||
set ::external 0; # If "1" this means, we are running against external instance
|
||||
set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
set ::diskstore 0; # Don't touch this by hand. The test itself will toggle it.
|
||||
|
||||
proc execute_tests name {
|
||||
set path "tests/$name.tcl"
|
||||
@@ -103,6 +104,7 @@ proc s {args} {
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleanup: warning may take some time..."
|
||||
catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
|
||||
catch {exec rm -rf {*}[glob tests/tmp/server.*]}
|
||||
}
|
||||
@@ -125,8 +127,11 @@ proc execute_everything {} {
|
||||
# execute_tests "integration/redis-cli"
|
||||
execute_tests "unit/pubsub"
|
||||
|
||||
# run tests with VM enabled
|
||||
set ::global_overrides {vm-enabled yes}
|
||||
# run tests with diskstore enabled
|
||||
puts "\nRunning diskstore tests... this is slow, press Ctrl+C if not interested.."
|
||||
set ::diskstore 1
|
||||
lappend ::denytags nodiskstore
|
||||
set ::global_overrides {diskstore-enabled yes}
|
||||
execute_tests "unit/protocol"
|
||||
execute_tests "unit/basic"
|
||||
execute_tests "unit/type/list"
|
||||
|
||||
+41
-32
@@ -12,7 +12,7 @@ start_server {tags {"other"}} {
|
||||
r save
|
||||
} {OK}
|
||||
|
||||
tags {"slow"} {
|
||||
tags {slow nodiskstore} {
|
||||
foreach fuzztype {binary alpha compr} {
|
||||
test "FUZZ stresser with data model $fuzztype" {
|
||||
set err 0
|
||||
@@ -46,7 +46,7 @@ start_server {tags {"other"}} {
|
||||
set _ $err
|
||||
} {*invalid*}
|
||||
|
||||
tags {consistency} {
|
||||
tags {consistency nodiskstore} {
|
||||
if {![catch {package require sha1}]} {
|
||||
test {Check consistency of different data types after a reload} {
|
||||
r flushdb
|
||||
@@ -102,45 +102,54 @@ start_server {tags {"other"}} {
|
||||
r flushdb
|
||||
r set x 10
|
||||
r expire x 1000
|
||||
r save
|
||||
r debug reload
|
||||
if {$::diskstore} {
|
||||
r debug flushcache
|
||||
} else {
|
||||
r save
|
||||
r debug reload
|
||||
}
|
||||
set ttl [r ttl x]
|
||||
set e1 [expr {$ttl > 900 && $ttl <= 1000}]
|
||||
r bgrewriteaof
|
||||
waitForBgrewriteaof r
|
||||
if {!$::diskstore} {
|
||||
r bgrewriteaof
|
||||
waitForBgrewriteaof r
|
||||
r debug loadaof
|
||||
}
|
||||
set ttl [r ttl x]
|
||||
set e2 [expr {$ttl > 900 && $ttl <= 1000}]
|
||||
list $e1 $e2
|
||||
} {1 1}
|
||||
|
||||
test {PIPELINING stresser (also a regression for the old epoll bug)} {
|
||||
set fd2 [socket $::host $::port]
|
||||
fconfigure $fd2 -encoding binary -translation binary
|
||||
puts -nonewline $fd2 "SELECT 9\r\n"
|
||||
flush $fd2
|
||||
gets $fd2
|
||||
tags {protocol nodiskstore} {
|
||||
test {PIPELINING stresser (also a regression for the old epoll bug)} {
|
||||
set fd2 [socket $::host $::port]
|
||||
fconfigure $fd2 -encoding binary -translation binary
|
||||
puts -nonewline $fd2 "SELECT 9\r\n"
|
||||
flush $fd2
|
||||
gets $fd2
|
||||
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
set q {}
|
||||
set val "0000${i}0000"
|
||||
append q "SET key:$i $val\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
set q {}
|
||||
append q "GET key:$i\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
}
|
||||
flush $fd2
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
set q {}
|
||||
set val "0000${i}0000"
|
||||
append q "SET key:$i $val\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
set q {}
|
||||
append q "GET key:$i\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
}
|
||||
flush $fd2
|
||||
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
gets $fd2 line
|
||||
gets $fd2 count
|
||||
set count [string range $count 1 end]
|
||||
set val [read $fd2 $count]
|
||||
read $fd2 2
|
||||
}
|
||||
close $fd2
|
||||
set _ 1
|
||||
} {1}
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
gets $fd2 line
|
||||
gets $fd2 count
|
||||
set count [string range $count 1 end]
|
||||
set val [read $fd2 $count]
|
||||
read $fd2 2
|
||||
}
|
||||
close $fd2
|
||||
set _ 1
|
||||
} {1}
|
||||
}
|
||||
|
||||
test {MUTLI / EXEC basics} {
|
||||
r del mylist
|
||||
|
||||
@@ -42,6 +42,7 @@ end
|
||||
def commands
|
||||
return @commands if @commands
|
||||
|
||||
require "rubygems"
|
||||
require "net/http"
|
||||
require "net/https"
|
||||
require "json"
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
if [ $# != "1" ]
|
||||
then
|
||||
echo "Usage: ./mkrelease.sh <git-ref>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG=$1
|
||||
TARNAME="redis-${TAG}.tar"
|
||||
echo "Generating /tmp/${TARNAME}"
|
||||
git archive $TAG --prefix redis-${TAG}/ > /tmp/$TARNAME || exit 1
|
||||
echo "Gizipping the archive"
|
||||
rm -f /tmp/$TARNAME.gz
|
||||
gzip -9 /tmp/$TARNAME
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
# This script is from http://poormansprofiler.org/
|
||||
|
||||
#!/bin/bash
|
||||
nsamples=1
|
||||
sleeptime=0
|
||||
pid=$(pidof redis-server)
|
||||
|
||||
for x in $(seq 1 $nsamples)
|
||||
do
|
||||
gdb -ex "set pagination 0" -ex "thread apply all bt" -batch -p $pid
|
||||
sleep $sleeptime
|
||||
done | \
|
||||
awk '
|
||||
BEGIN { s = ""; }
|
||||
/Thread/ { print s; s = ""; }
|
||||
/^\#/ { if (s != "" ) { s = s "," $4} else { s = $4 } }
|
||||
END { print s }' | \
|
||||
sort | uniq -c | sort -r -n -k 1,1
|
||||
Reference in New Issue
Block a user