Compare commits

...
92 Commits
Author SHA1 Message Date
Pieter Noordhuis 3bcffcbe5b Remove client from list of unblocked clients when it is free'd 2011-01-17 10:04:13 +01:00
antirez b8cfcea424 default hash encoding settings also fixed in redis.conf 2011-01-15 22:31:07 +01:00
antirez 52dc87bb18 lame typo causing bad hashes memory characteristic 2011-01-15 22:24:22 +01:00
antirez 7a1fd61e3d implemented two new INFO fields showing the size of clients max input and output buffers. 2011-01-14 10:20:02 +01:00
antirez 3a73be7524 master-slave replication fixed, it was not listing any key using KEYS command in the slave. 2011-01-14 09:53:57 +01:00
antirez 5b831607e7 redisServer structure fields reordered for clarity 2011-01-14 00:19:19 +01:00
antirez 2d9fdb9d84 TODO modified 2011-01-10 21:45:08 +01:00
antirez 69cb546223 minor aesthetic change in test suite messages 2011-01-09 22:17:08 +01:00
antirez 322ea972fe run both inmemory and diskstore tests. 2011-01-09 19:42:56 +01:00
antirez a5062bbab0 fixed bgsave_in_progress in INFO when BGSAVEing with diskstore enabled, don't DEBUG FLUSHCACHE when bgsave is in progress. 2011-01-09 19:25:34 +01:00
antirez 0a0f83ab2c DEBUG FLUSHCACHE needs to wait that everything was synched on disk 2011-01-09 19:01:44 +01:00
antirez 69bfffb4a7 test adapted to run with diskstore, and a few bugs fixed 2011-01-09 18:25:34 +01:00
antirez 5e1d2d30f7 initial fix of the test suite to run both in in-memory and diskstore mode 2011-01-09 16:49:52 +01:00
antirez 67a1810b32 allocation stats in INFO 2011-01-09 15:56:50 +01:00
antirez 7cca366bdf TODO updated 2011-01-08 02:07:48 +01:00
antirez b39619d8e7 DEBUG OBJECT fixed with diskstore, force loading 2011-01-08 02:06:01 +01:00
antirez f1df1739e3 resolved a problem with delayed loading of keys 2011-01-08 01:42:08 +01:00
antirez cc2750670f blocking SAVE implemented 2011-01-08 00:09:57 +01:00
antirez 43574a72c3 avoid bgsaving temp files 2011-01-07 23:51:31 +01:00
antirez f03fe802b7 diskstore BGSAVE should work now 2011-01-07 23:41:00 +01:00
antirez 5b8ce85378 more work towards diskstore bgsave 2011-01-07 19:31:42 +01:00
antirez 36c17a53b6 source reshaped a bit to play well with a bgsaving thread, still work to do, does not compile. 2011-01-07 18:15:14 +01:00
antirez cb9b35c8ca Merge branch 'unstable' of github.com:antirez/redis-private into unstable 2011-01-07 16:43:51 +01:00
antirez 44898603aa TODO updated 2011-01-07 16:43:36 +01:00
antirez 249ad25f4f BGSAVE work in progress 2011-01-05 18:38:31 +01:00
antirez 51335102ac Merge remote branch 'pietern/writev-unstable' into unstable 2011-01-05 13:51:37 +01:00
antirez f771dc23a0 IO performances greatly improved under high writes load 2011-01-05 12:34:14 +01:00
Pieter Noordhuis 9b1d738f96 Remove newlines for networking errors 2011-01-05 10:51:37 +01:00
Pieter Noordhuis a2b33f2f93 Remove glueoutputbuf from redis.conf 2011-01-05 10:42:37 +01:00
Pieter Noordhuis a510cb0c03 Remove glueoutputbuf option and broken code 2011-01-05 10:39:41 +01:00
antirez 6eaad66373 Merge branch 'master' into unstable 2011-01-04 19:07:15 +01:00
antirez 1190c6cbf7 handle end of space on device in a better way 2011-01-04 19:03:23 +01:00
antirez 418d5eaf50 strictly honour memory limit 2011-01-04 18:53:09 +01:00
antirez 779fa2af7a rename(2) used in diskstore.c for atomic updates of keys 2011-01-04 18:28:04 +01:00
antirez fad97fbeea TODO list updated 2011-01-04 13:20:04 +01:00
antirez 5ab7bbfc27 fixed logging level for debugging message 2011-01-03 17:40:10 +01:00
antirez bafa88c8b7 stupid bug fixed 2011-01-03 17:37:20 +01:00
antirez 9b24d8adbe serious performance enhancement of diskstore 2011-01-03 17:27:47 +01:00
antirez 5d46e370b7 diskstore more fixes 2011-01-03 17:18:37 +01:00
antirez d158dc28f6 comments on top of dscache.c updated 2011-01-03 10:58:59 +01:00
antirez 0b305fcfd9 diskstore FLUSH* fixed 2011-01-03 10:51:45 +01:00
antirez c15a3887e0 diskstore bug fixing and negative cache proper implementation 2011-01-03 10:47:39 +01:00
antirez 120b9ba8f8 FLUSHALL / FLUSHDB for diskstore implemented 2011-01-03 10:17:39 +01:00
antirez d2033feb5c fixed cache-flush-delay configuration parsing 2011-01-02 22:27:26 +01:00
antirez 4942145d72 fixed a bug in diskstore 2011-01-02 21:39:17 +01:00
antirez e37efb0d8b diskstore race condition fixed 2011-01-02 17:42:04 +01:00
antirez 133cf28ee8 minor comment change of dscache.c 2011-01-01 21:44:14 +01:00
antirez 9a3730289f blocking load fixed with the new design 2011-01-01 21:39:48 +01:00
antirez 3be00d7ed6 implemented a different approach to IO scheduling, so object->storage is no longer used, instead there is a queue and hash table of IO tasks to process, and it is always possible to know what are the scheduled and acrtive IO operations against every single key. 2011-01-01 21:35:56 +01:00
antirez aa81e4d5f4 minor changes to doc and comments 2010-12-31 18:23:31 +01:00
antirez d934e1e85b negative caching implemented 2010-12-31 17:32:59 +01:00
antirez c4b64a1395 don't overload the IO job queue if there are alrady too much entries 2010-12-31 16:10:09 +01:00
antirez a440ecf0d3 major bug and a dead lock fixed 2010-12-31 14:30:24 +01:00
antirez 8e6bb67108 brainstorming with myself in dscache.c comments 2010-12-31 02:39:00 +01:00
antirez ad01a25553 blocking load of keys on lookup -- nor tested, nor finished 2010-12-31 00:18:17 +01:00
antirez 4e941ecacb short but important comment added 2010-12-30 19:20:23 +01:00
antirez 3122229228 handled DEL command as a special optimized case for disk store 2010-12-30 19:16:59 +01:00
antirez 8d51fb6a80 diskstore cache bug fixing 2010-12-30 18:37:46 +01:00
antirez 98a9abb66d don't use small shared integer objects when disk store is enabled 2010-12-30 18:17:01 +01:00
antirez 82ef6ebf73 added cache-flush-delay configuration directive. Don't BGSAVE when using diskstore 2010-12-30 17:53:28 +01:00
antirez eea15afe5f fixed bugs on diskstore cache 2010-12-30 17:21:54 +01:00
antirez 1fce320114 filesystem based KV store mostly implemented in diskstore.c 2010-12-30 17:07:06 +01:00
antirez 0b470ebdaa new gitingore 2010-12-30 16:42:11 +01:00
antirez 4ab988238f more work done for diskstore without trying to compile, more work needed to build again. 2010-12-30 16:41:36 +01:00
antirez 1609a1c42d fixed a few bugs in DS store, now it's somewhat about able to reply to 'GET foo' with a bogus value. 2010-12-30 15:55:24 +01:00
antirez 67b0b41c87 disk store logged messages improved 2010-12-29 23:08:18 +01:00
antirez ddbc81af33 diskstore directory structure creation 2010-12-29 23:00:00 +01:00
antirez f63f0928c3 cron part of disk store object cache implemented. Objects are pushed as IO jobs if needed, so that the IO thread will process them. 2010-12-29 22:18:20 +01:00
antirez cea8c5cd75 touched key for WATCH refactored into a more general thing that can be used also for the cache system. Some more changes towards diskstore working. 2010-12-29 19:39:42 +01:00
antirez d021221086 version set to 2.3.0 2010-12-29 17:02:43 +01:00
antirez 5ef640986b more step forwards for disk store to be able to run 2010-12-29 16:58:57 +01:00
antirez 5f6e1183e7 implemented complete job handling 2010-12-29 15:57:27 +01:00
antirez f34a6cd85e still more work for diskstore 2010-12-29 03:57:35 +01:00
antirez f2da3a620c more work converting vm into object cache 2010-12-28 20:03:21 +01:00
antirez f081eaf1c0 removed parts of VM that probably will be of no use with object cache 2010-12-28 18:46:34 +01:00
antirez 16d778780e a lot of code reworked/removed to implement object caching 2010-12-28 18:06:40 +01:00
antirez 697af434fb initial changes needed to turn the current VM code into a cache system. Tons of work to do still. 2010-12-28 15:20:20 +01:00
antirez 33388d4304 added diskstore.c in Makefile and prototypes in redis.h 2010-12-28 14:42:09 +01:00
antirez 52970711cf added diskstore.c, currently just a stub 2010-12-26 18:46:25 +01:00
antirez 04a2ade90d Merge remote branch 'pietern/bench-fix' 2010-12-23 13:18:07 +01:00
Pieter Noordhuis 8ce39260a4 Fix compiler warnings on Solaris 2010-12-23 11:26:11 +00:00
Pieter Noordhuis 89191613f1 limits.h is already included from redis.h 2010-12-23 11:14:05 +00:00
Pieter Noordhuis 65b472acce Solaris 10 doesn't know AF_LOCAL 2010-12-23 11:09:07 +00:00
Pieter Noordhuis ba55932674 Solaris doesn't support -rdynamic 2010-12-23 11:08:50 +00:00
Pieter Noordhuis a1e97d692e Update hiredis to 0.9.2 2010-12-23 11:01:11 +00:00
antirez d3e5e28804 added mkrelease.sh script into utils. gitignore modified accordingly since this script was originally ignored 2010-12-20 15:47:33 +01:00
antirez d841dedb5f Merge branch 'master' of github.com:antirez/redis 2010-12-20 13:14:12 +01:00
antirez 4c22fd3409 TODO udpated 2010-12-20 13:13:46 +01:00
antirez 180596766b help.h updated 2010-12-19 15:19:08 +01:00
antirez f21779ff36 removed some dead code, added evicted keys counter separated from expired keys. 2010-12-19 15:15:08 +01:00
antirez 9d7165e885 overflow detection in INCR family functions 2010-12-19 12:22:12 +01:00
antirez 6418b4c790 help.h updated 2010-12-15 17:39:40 +01:00
37 changed files with 2467 additions and 2043 deletions
+2 -1
View File
@@ -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
+44 -52
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+4
View File
@@ -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);
+7 -32
View File
@@ -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
View File
@@ -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) {
+136 -62
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+42 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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];
+71 -93
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.1.8"
#define REDIS_VERSION "2.3.0"
-1149
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -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
View File
@@ -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;
}
+3
View File
@@ -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
View File
@@ -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)
+7 -1
View File
@@ -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
}
+7 -2
View File
@@ -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
View File
@@ -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
+1
View File
@@ -42,6 +42,7 @@ end
def commands
return @commands if @commands
require "rubygems"
require "net/http"
require "net/https"
require "json"
+14
View File
@@ -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
+18
View File
@@ -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