├── .gitignore ├── AUTHORS ├── COPYING ├── LICENSE ├── Makefile ├── README.md ├── devtools ├── bench_noreply.pl ├── gdbline └── stress-memcached.pl ├── doc ├── protocol-binary.txt └── protocol.txt ├── include ├── config.h ├── connector.h ├── kmodtest.h ├── memcache.h └── sasl.h ├── kmod ├── Makefile ├── kmodtest.c ├── mc.h ├── mc_buffer.c ├── mc_buffer.h ├── mc_connector.c ├── mc_dispatcher.c ├── mc_dispatcher.h ├── mc_hash.c ├── mc_hashtable.c ├── mc_items.c ├── mc_main.c ├── mc_messenger.c ├── mc_messenger.h ├── mc_msg.c ├── mc_msg.h ├── mc_oom.c ├── mc_oom.h ├── mc_proto.c ├── mc_proto.h ├── mc_proto_bin.c ├── mc_proto_bin.h ├── mc_proto_txt.c ├── mc_sasl.c ├── mc_slabs.c ├── mc_slabs.h ├── mc_stats.c ├── mc_strops.c ├── mc_uparam.c ├── mc_uparam.h ├── mc_worker.c └── mc_worker.h ├── t ├── 000_startup.t ├── 001_64bit.t ├── 002_binary-get.t ├── 003_binary.t ├── 005_bogus-commands.t ├── 006_cas.t ├── 007_dash-M.t ├── 008_evictions.t ├── 009_expirations.t ├── 010_flags.t ├── 011_flush-all.t ├── 012_getset.t ├── 013_incrdecr.t ├── 014_issue_104.t ├── 015_issue_108.t ├── 016_issue_14.t ├── 017_issue_140.t ├── 018_issue_152.t ├── 019_issue_163.t ├── 020_issue_183.t ├── 021_issue_22.t ├── 022_issue_29.t ├── 023_issue_3.t ├── 024_issue_41.t ├── 025_issue_42.t ├── 026_issue_50.t ├── 027_issue_61.t ├── 028_issue_67.t ├── 029_issue_68.t ├── 030_issue_70.t ├── 031_item_size_max.t ├── 032_line-lengths.t ├── 033_lru.t ├── 034_maxconns.t ├── 035_multiversioning.t ├── 036_noreply.t ├── 037_slabs_reassign.t ├── 038_stats-detail.t ├── 039_stats.t ├── 040_touch.t ├── 041_udp.t ├── 042_unixsocket.t ├── auto.pl ├── binary-sasl.pl ├── daemonize.pl ├── lib │ └── MemcachedTest.pm ├── memslap.pl ├── sasl │ └── memcached.conf └── whitespace.pl ├── test ├── Makefile ├── kmcstart.c ├── kmcstop.c ├── memcached-tool ├── testapp.c ├── timedrun.c ├── util.c └── util.h └── user ├── Makefile ├── cache_bh.c ├── daemon.c ├── environment.c ├── settings.c ├── shutdown.c ├── umemcached.c └── umemcached.h /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Normal rules 3 | # 4 | .* 5 | *.o 6 | *.o.* 7 | *.a 8 | *.s 9 | *.ko 10 | *.so 11 | *.so.dbg 12 | *.mod.c 13 | *.i 14 | *.lst 15 | *.symtypes 16 | *.order 17 | modules.builtin 18 | *.elf 19 | *.bin 20 | *.gz 21 | *.bz2 22 | *.lzma 23 | *.xz 24 | *.lzo 25 | *.patch 26 | *.gcno 27 | 28 | # 29 | # generic files 30 | # 31 | tags 32 | Module.markers 33 | Module.symvers 34 | user/umemcached 35 | 36 | # 37 | # git files that we don't want to ignore even it they are dot-files 38 | # 39 | !.gitignore 40 | !.mailmap 41 | 42 | 43 | # stgit generated dirs 44 | patches-* 45 | 46 | # quilt's files 47 | patches 48 | series 49 | 50 | # cscope files 51 | cscope.* 52 | ncscope.* 53 | 54 | # gnu global files 55 | GPATH 56 | GRTAGS 57 | GSYMS 58 | GTAGS 59 | 60 | *.orig 61 | *~ 62 | \#*# 63 | 64 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Li Jianguo 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, SOHU Infrastructure Department 2 | Copyright (c) 2012, Li Jianguo 3 | Copyright (c) 2012, memcached team 4 | Copyright (c) 2003, Danga Interactive, Inc. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above 15 | copyright notice, this list of conditions and the following disclaimer 16 | in the documentation and/or other materials provided with the 17 | distribution. 18 | 19 | * Neither the name of the Danga Interactive nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | INSTALLDIR ?= $(PREFIX)/kmemcache 3 | KERNELDIR ?= /lib/modules/$(shell uname -r)/build 4 | 5 | all: utils module 6 | 7 | .PHONY: utils 8 | utils: 9 | $(MAKE) -C user PWD=$(shell pwd)/user all 10 | $(MAKE) -C test PWD=$(shell pwd)/test all 11 | 12 | .PHONY: module 13 | module: 14 | $(MAKE) -C kmod KERNELDIR=$(KERNELDIR) PWD=$(shell pwd)/kmod all 15 | 16 | clean: 17 | $(MAKE) -C user clean 18 | $(MAKE) -C test clean 19 | $(MAKE) -C kmod clean 20 | 21 | install: 22 | $(MAKE) -C user INSTALLDIR=$(INSTALLDIR) install 23 | $(MAKE) -C kmod INSTALLDIR=$(INSTALLDIR) install 24 | 25 | uninstall: 26 | $(MAKE) -C user INSTALLDIR=$(INSTALLDIR) uninstall 27 | $(MAKE) -C kmod INSTALLDIR=$(INSTALLDIR) uninstall 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kmemcache 2 | ========= 3 | 4 | kmemcache is a high-performance, distributed memory object caching system, generic in nature, 5 | but intended for use in speeding up dynamic web applications by alleviating database load. 6 | 7 | kmemcache is derived from memcached-v1.4.15, exactly it is a linux kernel memcached, 8 | and aims at quicker response and higher performance. 9 | 10 | Current Limitations 11 | ------------------- 12 | kmemcache has now implemented all most major features of memcached, including the complete binary 13 | and text protocols, based on tcp, udp and unix domain communication protocols, slab allocation 14 | dynamically rebalanced, hash table expansion and so on. 15 | 16 | The programming interface remains consistent with memcached. Clients using memcached can easily 17 | connected to kmemcache, without modification. You could also easily add the kmemcache server to 18 | your cluster of memcached servers. 19 | 20 | The following are some features that have not been implemented yet: 21 | * SASL 22 | 23 | Environment 24 | ----------- 25 | x86_32/x86_64 26 | kernel: [2.6.32, 3.2] 27 | other versions have not been tested 28 | 29 | Building, Running & Testing 30 | --------------------------- 31 | 1) Clone kmemcache from github 32 | 33 | [jgli@linux]$ git clone https://github.com/jgli/kmemcache.git 34 | 35 | 2) Compiling kmemcache 36 | 37 | [jgli@linux]$ cd kmemcache 38 | [jgli@linux]$ make 39 | 40 | 3) Running kmemcache 41 | 42 | 3.1 Change user to root 43 | 44 | [jgli@linux]$ su root 45 | Password: 46 | 47 | 3.2 Insert kernel module and start server 48 | 49 | [root@linux]# insmod kmod/kmemcache.ko 50 | [root@linux]# user/umemcached -h 51 | [root@linux]# user/umemcached -p 11213 52 | 53 | 4) Stopping kmemcache 54 | 55 | [root@linux]# rmmod kmemcache 56 | 57 | 5) Testing kmemcache 58 | 59 | 5.1 Case 1, using libmemcached 60 | 61 | [root@linux]# apt-get install libmemcached 62 | [root@linux]# memcapable -h localhost -p 11213 63 | [root@linux]# memslap --servers=localhost:11213 64 | 65 | 5.2 Case 2, using testapp 66 | 67 | [root@linux]# insmod kmod/kmctest.ko 68 | [root@linux]# ./test/testapp 69 | 70 | 5.3 Case 3, using perl scripts 71 | 72 | [root@linux]# ./t/auto.pl 73 | 74 | 5.4 More cases refer to memcached 75 | 76 | Contributing 77 | ------------ 78 | Want to contribute? You are so welcome! Any reporting bugs, feedback, and pulling requests are encouraged! 79 | 80 | Website 81 | ------- 82 | Official memcached: http://www.memcached.org/ 83 | Test tools: http://libmemcached.org/libMemcached.html 84 | Something about kmemcache from my blog: http://blog.sina.com.cn/u/3289939872 85 | -------------------------------------------------------------------------------- /devtools/bench_noreply.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl 2 | # 3 | use warnings; 4 | use strict; 5 | 6 | use IO::Socket::INET; 7 | 8 | use FindBin; 9 | 10 | @ARGV == 1 or @ARGV == 2 11 | or die "Usage: $FindBin::Script HOST:PORT [COUNT]\n"; 12 | 13 | # Note that it's better to run the test over the wire, because for 14 | # localhost the task may become CPU bound. 15 | my $addr = $ARGV[0]; 16 | my $count = $ARGV[1] || 10_000; 17 | 18 | my $sock = IO::Socket::INET->new(PeerAddr => $addr, 19 | Timeout => 3); 20 | die "$!\n" unless $sock; 21 | 22 | 23 | # By running 'noreply' test first we also ensure there are no reply 24 | # packets left in the network. 25 | foreach my $noreply (1, 0) { 26 | use Time::HiRes qw(gettimeofday tv_interval); 27 | 28 | print "'noreply' is ", $noreply ? "enabled" : "disabled", ":\n"; 29 | my $param = $noreply ? 'noreply' : ''; 30 | my $start = [gettimeofday]; 31 | foreach (1 .. $count) { 32 | print $sock "add foo 0 0 1 $param\r\n1\r\n"; 33 | scalar<$sock> unless $noreply; 34 | print $sock "set foo 0 0 1 $param\r\n1\r\n"; 35 | scalar<$sock> unless $noreply; 36 | print $sock "replace foo 0 0 1 $param\r\n1\r\n"; 37 | scalar<$sock> unless $noreply; 38 | print $sock "append foo 0 0 1 $param\r\n1\r\n"; 39 | scalar<$sock> unless $noreply; 40 | print $sock "prepend foo 0 0 1 $param\r\n1\r\n"; 41 | scalar<$sock> unless $noreply; 42 | print $sock "incr foo 1 $param\r\n"; 43 | scalar<$sock> unless $noreply; 44 | print $sock "decr foo 1 $param\r\n"; 45 | scalar<$sock> unless $noreply; 46 | print $sock "delete foo $param\r\n"; 47 | scalar<$sock> unless $noreply; 48 | } 49 | my $end = [gettimeofday]; 50 | printf("update commands: %.2f secs\n\n", tv_interval($start, $end)); 51 | } 52 | -------------------------------------------------------------------------------- /devtools/gdbline: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # gdbline module image 4 | # 5 | # Outputs an add-symbol-file line suitable for pasting into gdb to examine 6 | # a loaded module. 7 | # 8 | cd /sys/module/$1/sections 9 | echo -n add-symbol-file $2 `/bin/cat .text` 10 | 11 | for section in .[a-z]* *; do 12 | if [ $section != ".text" ]; then 13 | echo " \\" 14 | echo -n " -s" $section `/bin/cat $section` 15 | fi 16 | done 17 | echo 18 | -------------------------------------------------------------------------------- /devtools/stress-memcached.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | 4 | use strict; 5 | use lib '../../api/perl/lib'; 6 | use Cache::Memcached; 7 | use Time::HiRes qw(time); 8 | 9 | unless (@ARGV == 2) { 10 | die "Usage: stress-memcached.pl ip:port threads\n"; 11 | } 12 | 13 | my $host = shift; 14 | my $threads = shift; 15 | 16 | my $memc = new Cache::Memcached; 17 | $memc->set_servers([$host]); 18 | 19 | unless ($memc->set("foo", "bar") && 20 | $memc->get("foo") eq "bar") { 21 | die "memcached not running at $host ?\n"; 22 | } 23 | $memc->disconnect_all(); 24 | 25 | 26 | my $running = 0; 27 | while (1) { 28 | if ($running < $threads) { 29 | my $cpid = fork(); 30 | if ($cpid) { 31 | $running++; 32 | #print "Launched $cpid. Running $running threads.\n"; 33 | } else { 34 | stress(); 35 | exit 0; 36 | } 37 | } else { 38 | wait(); 39 | $running--; 40 | } 41 | } 42 | 43 | sub stress { 44 | undef $memc; 45 | $memc = new Cache::Memcached; 46 | $memc->set_servers([$host]); 47 | 48 | my ($t1, $t2); 49 | my $start = sub { $t1 = time(); }; 50 | my $stop = sub { 51 | my $op = shift; 52 | $t2 = time(); 53 | my $td = sprintf("%0.3f", $t2 - $t1); 54 | if ($td > 0.25) { print "Took $td seconds for: $op\n"; } 55 | }; 56 | 57 | my $max = rand(50); 58 | my $sets = 0; 59 | 60 | for (my $i = 0; $i < $max; $i++) { 61 | my $key = key($i); 62 | my $set = $memc->set($key, $key); 63 | $sets++ if $set; 64 | } 65 | 66 | for (1..int(rand(500))) { 67 | my $rand = int(rand($max)); 68 | my $key = key($rand); 69 | my $meth = int(rand(3)); 70 | my $exp = int(rand(3)); 71 | undef $exp unless $exp; 72 | $start->(); 73 | if ($meth == 0) { 74 | $memc->add($key, $key, $exp); 75 | $stop->("add"); 76 | } elsif ($meth == 1) { 77 | $memc->delete($key); 78 | $stop->("delete"); 79 | } else { 80 | $memc->set($key, $key, $exp); 81 | $stop->("set"); 82 | } 83 | $rand = int(rand($max)); 84 | $key = key($rand); 85 | $start->(); 86 | my $v = $memc->get($key); 87 | $stop->("get"); 88 | if ($v && $v ne $key) { die "Bogus: $v for key $rand\n"; } 89 | } 90 | 91 | $start->(); 92 | my $multi = $memc->get_multi(map { key(int(rand($max))) } (1..$max)); 93 | $stop->("get_multi"); 94 | } 95 | 96 | sub key { 97 | my $n = shift; 98 | $_ = sprintf("%04d", $n); 99 | if ($n % 2) { $_ .= "a"x20; } 100 | $_; 101 | } 102 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | /* Name of package */ 2 | #define PACKAGE "kmemcache" 3 | 4 | /* Define to the address where bug reports for this package should be sent. */ 5 | #define PACKAGE_BUGREPORT "byjgli@gmail.com" 6 | 7 | /* Define to the full name of this package. */ 8 | #define PACKAGE_NAME "kmemcache" 9 | 10 | /* Define to the full name and version of this package. */ 11 | #define PACKAGE_STRING "kmemcache 0.4" 12 | 13 | /* Define to the one symbol short name of this package. */ 14 | #define PACKAGE_TARNAME "kmemcache" 15 | 16 | /* Define to the home page for this package. */ 17 | #define PACKAGE_URL "" 18 | 19 | /* Define to the version of this package. */ 20 | #define PACKAGE_VERSION "0.4" 21 | 22 | #define VERSION_STRING "VERSION 0.4" 23 | 24 | /* Version number of package */ 25 | #define VERSION "0.4" 26 | 27 | /* Release number of package */ 28 | #define REL_VERSION "May 25, 2013" 29 | 30 | #define LOG_PREFIX "kmemcache: " 31 | -------------------------------------------------------------------------------- /include/connector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * transport helper for user space and kernel space. 3 | * 4 | * Parts derived from drivers/connector, copyright of 5 | * their respective owners. 6 | * 7 | * usages: 8 | * 9 | * kernel space 10 | * connector_init 11 | * mc_get_unique_val 12 | * mc_add_callback(xx, xx, 1) 13 | * mc_send_msg_sync/mc_send_msg_timeout 14 | * mc_del_callback(xx, 1) 15 | * mc_put_unique_val 16 | * ... 17 | * mc_get_unique_val 18 | * mc_add_callback(xx, xx, 0) 19 | * mc_send_msg 20 | * mc_del_callback(xx, 0) 21 | * mc_put_unique_val 22 | * ... 23 | * mc_get_unique_val 24 | * mc_add_callback(xx, xx, 0) 25 | * mc_del_callback(xx, 0) 26 | * mc_put_unique_val 27 | * connector_exit 28 | * 29 | * user space 30 | * socket 31 | * bind 32 | * select 33 | * ... 34 | * close 35 | */ 36 | 37 | #ifndef __MC_CONNECTOR_H 38 | #define __MC_CONNECTOR_H 39 | 40 | #include 41 | 42 | #define NETLINK_MEMCACHE 20 43 | #define NETLINK_MEMCACHE_GRP 2 44 | #define NETLINK_PAYLOAD 4096 45 | 46 | #define CN_IDX_CACHE_BH 0x01 47 | #define CN_IDX_INIT_SET 0x02 48 | #define CN_IDX_ENV 0x03 49 | #define CN_IDX_SASL_DIS 0x10 50 | #define CN_IDX_SASL_SER_NEW 0x11 51 | #define CN_IDX_SASL_LIST_MECH 0x12 52 | #define CN_IDX_SASL_SER_START 0x13 53 | #define CN_IDX_SASL_SER_STEP 0x14 54 | #define CN_IDX_SASL_GET_PROP 0x15 55 | #define CN_IDX_CACHE_BH_STATUS 0x30 56 | #define CN_IDX_SHUTDOWN 0x50 57 | 58 | #define CN_VAL_INIT 0x1 59 | #define CN_VAL_CACHE_BH CN_VAL_INIT 60 | 61 | struct cn_id { 62 | __u32 idx; 63 | __u32 val; 64 | }; 65 | 66 | struct cn_msg { 67 | struct cn_id id; 68 | 69 | __u16 len; 70 | __u8 data[0]; 71 | }; 72 | 73 | #ifdef __KERNEL__ 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | 82 | typedef void* (cn_callback_fn)(struct cn_msg *, struct netlink_skb_parms *); 83 | 84 | struct cn_callback { 85 | struct sk_buff *skb; 86 | 87 | cn_callback_fn *f; 88 | 89 | void *out; 90 | }; 91 | 92 | struct cn_entry { 93 | #define ENTRY_NEW (0x1 << 0) 94 | #define ENTRY_RUNNING (0x1 << 1) 95 | #define ENTRY_FINISHED (0x1 << 2) 96 | u32 flags:4; 97 | u32 unused:28; 98 | struct cn_id id; 99 | struct list_head list_entry; 100 | 101 | struct cn_callback callback; 102 | struct work_struct work; 103 | struct completion comp; 104 | }; 105 | 106 | extern int connector_init(void); 107 | extern void connector_exit(void); 108 | 109 | extern u32 mc_get_unique_val(void); 110 | extern void mc_put_unique_val(u32 val); 111 | extern int mc_add_callback(struct cn_id *id, cn_callback_fn *f, int sync); 112 | extern void mc_del_callback(struct cn_id *id, int sync); 113 | extern void* mc_send_msg(struct cn_msg *msg); 114 | extern void* mc_send_msg_sync(struct cn_msg *msg); 115 | extern void* mc_send_msg_timeout(struct cn_msg *msg, unsigned long timeout); 116 | 117 | #endif /* __KERNEL__ */ 118 | #endif /* __MC_CONNECTOR_H */ 119 | -------------------------------------------------------------------------------- /include/kmodtest.h: -------------------------------------------------------------------------------- 1 | #ifndef __KMOD_TEST_H 2 | #define __KMOD_TEST_H 3 | 4 | #include 5 | #include 6 | 7 | struct __str { 8 | size_t len; 9 | const char *p; 10 | }; 11 | 12 | struct str_ull { 13 | struct __str str; 14 | __u64 out; 15 | }; 16 | 17 | struct str_ll { 18 | struct __str str; 19 | __s64 out; 20 | }; 21 | 22 | struct str_ul { 23 | struct __str str; 24 | __u32 out; 25 | }; 26 | 27 | struct str_l { 28 | struct __str str; 29 | __s32 out; 30 | }; 31 | 32 | #define KMC_MAGIC 0xff 33 | 34 | #define KMC_STR_ULL _IOWR(KMC_MAGIC, 0, struct str_ull) 35 | #define KMC_STR_LL _IOWR(KMC_MAGIC, 1, struct str_ll) 36 | #define KMC_STR_UL _IOWR(KMC_MAGIC, 2, struct str_ul) 37 | #define KMC_STR_L _IOWR(KMC_MAGIC, 3, struct str_l) 38 | 39 | #endif /* __KMOD_TEST_H */ 40 | -------------------------------------------------------------------------------- /include/memcache.h: -------------------------------------------------------------------------------- 1 | #ifndef __MEMCACHE_H 2 | #define __MEMCACHE_H 3 | 4 | #include 5 | 6 | /* time relative to server start. */ 7 | typedef unsigned int rel_time_t; 8 | 9 | typedef enum { 10 | local_transport, 11 | tcp_transport, 12 | udp_transport 13 | } net_transport_t; 14 | 15 | #define IS_UDP(x) (x == udp_transport) 16 | 17 | typedef enum { 18 | ascii_prot, 19 | binary_prot, 20 | negotiating_prot 21 | } protocol_t; 22 | 23 | typedef enum { 24 | T_MEMD_INITIAL_MALLOC, 25 | T_MEMD_SLABS_LIMIT, 26 | MEMCACHED_PORT_FILENAME, 27 | MEMCACHED_HASH_BULK_MOVE, 28 | MEMCACHED_SLAB_BULK_CHECK, 29 | MEMCACHED_GROWTH_FACTOR, 30 | } env_t; 31 | 32 | typedef env_t ask_env_t; 33 | 34 | typedef struct { 35 | env_t env; 36 | char data[0]; 37 | } ack_env_t; 38 | 39 | #define MAX_VERBOSITY_LEVEL 2 40 | #define DEFAULT_HASH_BULK_MOVE 1 41 | #define DEFAULT_SLAB_BULK_CHECK 1 42 | 43 | typedef struct { 44 | __u16 len; 45 | __u8 buf[0]; 46 | } str_t; 47 | 48 | typedef struct { 49 | net_transport_t trans; 50 | 51 | __s32 family; 52 | __s32 type; 53 | __s32 protocol; 54 | 55 | __s32 addrlen; 56 | __u8 addr[0]; 57 | } sock_entry_t __attribute__((aligned(sizeof(int)))); 58 | 59 | typedef struct { 60 | __u16 len; 61 | __u8 buf[0]; 62 | } inet_t __attribute__((aligned(sizeof(int)))); 63 | 64 | typedef struct { 65 | __s32 port; 66 | __s32 udpport; 67 | __s32 access; 68 | __s32 backlog; 69 | 70 | __s32 verbose; 71 | __u64 maxbytes; 72 | __s32 maxconns; 73 | __s32 num_threads_per_udp; 74 | __s32 reqs_per_event; 75 | __s32 evict_to_free; 76 | 77 | __s32 chunk_size; 78 | __s32 item_size_max; 79 | __s32 slab_automove; 80 | __s32 hashpower_init; 81 | __s32 hash_bulk_move; 82 | __s32 slab_bulk_check; 83 | rel_time_t oldest_live; 84 | protocol_t binding_protocol; 85 | __s32 factor_numerator; 86 | __s32 factor_denominator; 87 | 88 | char *factor; 89 | char *socketpath; 90 | char *inter; 91 | 92 | __u8 use_cas; 93 | __u8 sasl; 94 | __u8 maxconns_fast; 95 | __u8 slab_reassign; 96 | __s8 prefix_delimiter; 97 | __u8 detail_enabled; 98 | __u8 shutdown_command; 99 | __u8 preallocate; 100 | } base_set_t __attribute__((aligned(sizeof(int)))); 101 | 102 | typedef struct { 103 | base_set_t base; 104 | 105 | #define SLAB_FACTOR (0x1 << 0) 106 | #define INET_INTER (0x1 << 1) 107 | #define UNIX_SOCK (0x1 << 2) 108 | #define INET_SOCK (0x1 << 3) 109 | /* 110 | * flags --- describe the data's value 111 | * SLAB_FACTOR : slab allocator's growth factor 112 | * INET_INTER : interface to listen on 113 | * UNIX_SOCK : unix domain's absolute path 114 | * INET_SOCK : n * sock_entry_t 115 | * 116 | * layout of data[n]: 117 | * str_t : SLAB_FACTOR 118 | * str_t : INET_INTER 119 | * str_t : UNIX_SOCK 120 | * inet_t : INET_SOCK 121 | */ 122 | __s8 flags; 123 | __u16 len; 124 | __s8 data[0]; 125 | } settings_init_t __attribute__((aligned(sizeof(int)))); 126 | 127 | #define KMC_V_MSG (sizeof(struct cn_msg)) 128 | #define KMC_V_ASK_ENV (KMC_V_MSG + sizeof(ask_env_t)) 129 | #define KMC_V_ACK_ENV (KMC_V_MSG + sizeof(ack_env_t)) 130 | #define KMC_V_BH_STATUS (KMC_V_MSG + sizeof(__s32)) 131 | 132 | #ifdef __KERNEL__ 133 | typedef base_set_t settings_t; 134 | 135 | extern settings_t settings; 136 | 137 | #endif /* __KERNEL__ */ 138 | #endif /* __MEMCACHE_H */ 139 | -------------------------------------------------------------------------------- /include/sasl.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_SASL_H 2 | #define __MC_SASL_H 3 | 4 | typedef struct { 5 | void ** pconn; 6 | } sasl_dispose_t; 7 | 8 | #ifdef __KERNEL__ 9 | 10 | // Longest one I could find was ``9798-U-RSA-SHA1-ENC'' 11 | #define MAX_SASL_MECH_LEN 32 12 | 13 | #define SASL_OK 0 14 | #define SASL_CONTINUE 1 15 | #define SASL_USERNAME 0 16 | 17 | typedef void sasl_conn_t; 18 | typedef struct sasl_callback { 19 | unsigned long id; 20 | int (*proc)(void); 21 | void *context; 22 | } sasl_callback_t; 23 | 24 | #ifdef CONFIG_SASL 25 | void mc_sasl_dispose(sasl_conn_t **pconn); 26 | int mc_sasl_server_new(const char *service, 27 | const char *serverFQDN, 28 | const char *user_realm, 29 | const char *iplocalport, 30 | const char *ipremoteport, 31 | const sasl_callback_t *callbacks, 32 | unsigned flags, 33 | sasl_conn_t **pconn); 34 | int mc_sasl_listmech(sasl_conn_t *conn, 35 | const char *user, 36 | const char *prefix, 37 | const char *sep, 38 | const char *suffix, 39 | const char **result, 40 | unsigned *plen, 41 | int *pcount); 42 | int mc_sasl_server_start(sasl_conn_t *conn, 43 | const char *mech, 44 | const char *clientin, 45 | unsigned clientinlen, 46 | const char **serverout, 47 | unsigned *serveroutlen); 48 | int mc_sasl_server_step(sasl_conn_t *conn, 49 | const char *clientin, 50 | unsigned clientinlen, 51 | const char **serverout, 52 | unsigned *serveroutlen); 53 | int mc_sasl_getprop(sasl_conn_t *conn, 54 | int propnum, 55 | const void **pvalue); 56 | 57 | extern char my_sasl_hostname[1025]; 58 | #else 59 | #define mc_init_sasl() {} 60 | #define mc_sasl_dispose(x) {} 61 | #define mc_sasl_server_new(a, b, c, d, e, f, g, h) 1 62 | #define mc_sasl_listmech(a, b, c, d, e, f, g, h) 1 63 | #define mc_sasl_server_start(a, b, c, d, e, f) 1 64 | #define mc_sasl_server_step(a, b, c, d, e) 1 65 | #define mc_sasl_getprop(a, b, c) {} 66 | #endif 67 | 68 | #endif /* __KERNEL__ */ 69 | #endif /* __MC_SASL_H */ 70 | -------------------------------------------------------------------------------- /kmod/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=n 2 | 3 | # sasl, not implementation 4 | CONFIG_SASL=n 5 | 6 | # connector request cache 7 | CONFIG_CN_CACHE=n 8 | 9 | # out of memory managment 10 | CONFIG_OOM=y 11 | 12 | # verbose log level control 13 | CONFIG_VERBOSE=y 14 | 15 | # thread statistics lock 16 | CONFIG_SLOCK=n 17 | 18 | ifeq ($(CONFIG_DEBUG),y) 19 | DEBFLAGS = -O -Wall -g -DCONFIG_DEBUG 20 | endif 21 | 22 | EXTRA_CFLAGS += $(DEBFLAGS) 23 | EXTRA_CFLAGS += -I$(PWD)/../include 24 | 25 | ifeq ($(CONFIG_CN_CACHE),y) 26 | EXTRA_CFLAGS += -DCONFIG_CN_CACHE 27 | endif 28 | 29 | ifeq ($(CONFIG_OOM),y) 30 | EXTRA_CFLAGS += -DCONFIG_OOM 31 | endif 32 | 33 | ifeq ($(CONFIG_VERBOSE),y) 34 | EXTRA_CFLAGS += -DCONFIG_VERBOSE 35 | endif 36 | 37 | ifeq ($(CONFIG_SLOCK),y) 38 | EXTRA_CFLAGS += -DCONFIG_SLOCK 39 | endif 40 | 41 | obj-m += kmctest.o kmemcache.o 42 | 43 | kmctest-y := mc_strops.o kmodtest.o 44 | 45 | kmemcache-y := mc_strops.o \ 46 | mc_buffer.o \ 47 | mc_hash.o \ 48 | mc_slabs.o \ 49 | mc_items.o \ 50 | mc_hashtable.o \ 51 | mc_stats.o \ 52 | mc_main.o \ 53 | mc_worker.o \ 54 | mc_messenger.o \ 55 | mc_dispatcher.o \ 56 | mc_proto_bin.o \ 57 | mc_proto_txt.o \ 58 | mc_proto.o \ 59 | mc_connector.o \ 60 | mc_uparam.o \ 61 | mc_msg.o 62 | 63 | kmemcache-$(CONFIG_SASL) += mc_sasl.o 64 | kmemcache-$(CONFIG_OOM) += mc_oom.o 65 | 66 | 67 | all: 68 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 69 | 70 | clean: 71 | rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order 72 | 73 | install: 74 | install -d $(INSTALLDIR) 75 | install -m 755 *.ko $(INSTALLDIR) 76 | 77 | uninstall: 78 | rm -f $(INSTALLDIR)/*.ko 79 | 80 | depend .depend dep: 81 | $(CC) $(CFLAGS) -M *.c > .depend 82 | 83 | ifeq (.depend,$(wildcard .depend)) 84 | include .depend 85 | endif 86 | -------------------------------------------------------------------------------- /kmod/kmodtest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mc.h" 8 | #include "kmodtest.h" 9 | 10 | #define COPY_FROM_USER(type) \ 11 | do { \ 12 | if (unlikely(!access_ok(VERIFY_WRITE, user, sizeof(type)))) \ 13 | return -EFAULT; \ 14 | if (copy_from_user(&kern, user, sizeof(type))) \ 15 | return -EFAULT; \ 16 | if (unlikely(!access_ok(VERIFY_READ, kern.str.p, kern.str.len)))\ 17 | return -EFAULT; \ 18 | str = strndup_user(kern.str.p, kern.str.len); \ 19 | if (IS_ERR(str)) \ 20 | return -EFAULT; \ 21 | } while (0) 22 | 23 | static int str_ull_helper(unsigned long __user arg) 24 | { 25 | int ret; 26 | char *str; 27 | struct str_ull kern, *user; 28 | 29 | user = (struct str_ull *)arg; 30 | COPY_FROM_USER(struct str_ull); 31 | 32 | ret = safe_strtoull(str, &kern.out); 33 | if (!ret && __copy_to_user(&user->out, &kern.out, sizeof(__u64))) { 34 | ret = -EFAULT; 35 | } 36 | 37 | kfree(str); 38 | return ret; 39 | } 40 | 41 | static int str_ll_helper(unsigned long __user arg) 42 | { 43 | int ret; 44 | char *str; 45 | struct str_ll kern, *user; 46 | 47 | user = (struct str_ll *)arg; 48 | COPY_FROM_USER(struct str_ll); 49 | 50 | ret = safe_strtoll(str, &kern.out); 51 | if (!ret && __copy_to_user(&user->out, &kern.out, sizeof(__s64))) { 52 | ret = -EFAULT; 53 | } 54 | 55 | kfree(str); 56 | return ret; 57 | } 58 | 59 | static int str_ul_helper(unsigned long __user arg) 60 | { 61 | int ret; 62 | char *str; 63 | struct str_ul kern, *user; 64 | 65 | user = (struct str_ul *)arg; 66 | COPY_FROM_USER(struct str_ul); 67 | 68 | ret = safe_strtoul(str, &kern.out); 69 | if (!ret && __copy_to_user(&user->out, &kern.out, sizeof(__u32))) { 70 | ret = -EFAULT; 71 | } 72 | 73 | kfree(str); 74 | return ret; 75 | } 76 | 77 | static int str_l_helper(unsigned long __user arg) 78 | { 79 | int ret; 80 | char *str; 81 | struct str_l kern, *user; 82 | 83 | user = (struct str_l *)arg; 84 | COPY_FROM_USER(struct str_l); 85 | 86 | ret = safe_strtol(str, &kern.out); 87 | if (!ret && __copy_to_user(&user->out, &kern.out, sizeof(__s32))) { 88 | ret = -EFAULT; 89 | } 90 | 91 | kfree(str); 92 | return ret; 93 | } 94 | 95 | static long kmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 96 | { 97 | int ret = 0; 98 | 99 | switch (cmd) { 100 | case KMC_STR_ULL: 101 | ret = str_ull_helper(arg); 102 | break; 103 | case KMC_STR_LL: 104 | ret = str_ll_helper(arg); 105 | break; 106 | case KMC_STR_UL: 107 | ret = str_ul_helper(arg); 108 | break; 109 | case KMC_STR_L: 110 | ret = str_l_helper(arg); 111 | break; 112 | default: 113 | BUG(); 114 | break; 115 | } 116 | 117 | return ret; 118 | } 119 | 120 | static int kmc_open(struct inode *inode, struct file *file) 121 | { 122 | return 0; 123 | } 124 | 125 | static int kmc_release(struct inode *inode, struct file *file) 126 | { 127 | return 0; 128 | } 129 | 130 | static struct file_operations kmc_miscdev_fops = { 131 | .owner = THIS_MODULE, 132 | .open = kmc_open, 133 | .unlocked_ioctl = kmc_ioctl, 134 | .release = kmc_release, 135 | }; 136 | 137 | static struct miscdevice kmc_miscdev = { 138 | .minor = MISC_DYNAMIC_MINOR, 139 | .name = "kmemcache", 140 | .fops = &kmc_miscdev_fops, 141 | }; 142 | 143 | static int __init kmc_test_init(void) 144 | { 145 | int ret = 0; 146 | 147 | ret = misc_register(&kmc_miscdev); 148 | if (ret) { 149 | PRINTK("register kmc_miscdev error\n"); 150 | } 151 | 152 | return ret; 153 | } 154 | 155 | static void __exit kmc_test_exit(void) 156 | { 157 | misc_deregister(&kmc_miscdev); 158 | } 159 | 160 | module_init(kmc_test_init); 161 | module_exit(kmc_test_exit); 162 | 163 | MODULE_AUTHOR("Li Jianguo "); 164 | MODULE_DESCRIPTION("kmemcache test"); 165 | MODULE_LICENSE("GPL v2"); 166 | -------------------------------------------------------------------------------- /kmod/mc_buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mc.h" 7 | 8 | #define alloc_policy(len) \ 9 | ({ \ 10 | unsigned int flags = 0; \ 11 | if ((len) <= BUF_KMALLOC_MAX) \ 12 | flags = BUF_KMALLOC; \ 13 | else if ( len > BUF_PAGES_MAX) \ 14 | flags = BUF_VMALLOC; \ 15 | else \ 16 | flags = BUF_PAGES; \ 17 | flags; \ 18 | }) 19 | 20 | static inline int _alloc_buffer(struct buffer *buf, size_t len, gfp_t mask) 21 | { 22 | int ret = 0; 23 | unsigned int order; 24 | 25 | order = get_order(len); 26 | if (unlikely(order >= MAX_ORDER)) { 27 | ret = -EFBIG; 28 | goto out; 29 | } 30 | 31 | #ifdef CONFIG_X86_32 32 | buf->_page = alloc_pages(mask | GFP_PAGES, order); 33 | if (!buf->_page) { 34 | ret = -ENOMEM; 35 | goto out; 36 | } 37 | buf->buf = kmap(buf->_page); 38 | #else 39 | buf->buf = (void *)__get_free_pages(mask | GFP_PAGES, order); 40 | if (!buf->buf) { 41 | ret = -ENOMEM; 42 | goto out; 43 | } 44 | #endif 45 | buf->room = (_AC(1,UL) << (order + PAGE_SHIFT)); 46 | 47 | out: 48 | return ret; 49 | } 50 | 51 | int alloc_buffer(struct buffer *buf, size_t len, gfp_t mask) 52 | { 53 | int ret = 0; 54 | unsigned int which; 55 | 56 | which = alloc_policy(len); 57 | xchg: 58 | switch (which) { 59 | case BUF_KMALLOC: 60 | buf->buf = kmalloc(len, mask | GFP_KERNEL); 61 | if (!buf->buf) { 62 | PRINFO("kmalloc - alloc buffer error"); 63 | ret = -ENOMEM; 64 | goto out; 65 | } 66 | buf->room = ksize(buf->buf); 67 | break; 68 | case BUF_VMALLOC: 69 | buf->buf = __vmalloc(len, mask | GFP_PAGES, PAGE_KERNEL); 70 | if (!buf->buf) { 71 | PRINFO("vmalloc - alloc buffer error"); 72 | ret = -ENOMEM; 73 | goto out; 74 | } 75 | buf->room = len; 76 | break; 77 | case BUF_PAGES: 78 | ret = _alloc_buffer(buf, len, mask); 79 | if (ret) { 80 | PRINFO("alloc_pages - alloc buffer error"); 81 | ret = 0; 82 | which = BUF_VMALLOC; 83 | goto xchg; 84 | } 85 | break; 86 | default: 87 | BUG(); 88 | break; 89 | } 90 | 91 | if (ret == 0) { 92 | buf->flags = which; 93 | } 94 | out: 95 | return ret; 96 | } 97 | 98 | static inline void _memcpy(void *dst, struct buffer *buf, size_t valid) 99 | { 100 | void *src; 101 | 102 | src = buf->buf; 103 | memcpy(dst, src, valid); 104 | } 105 | 106 | static inline void _memmove(struct buffer *dst, struct buffer *src, size_t valid) 107 | { 108 | void *_dst; 109 | 110 | _dst = dst->buf; 111 | _memcpy(_dst, src, valid); 112 | } 113 | 114 | static inline int _realloc_buffer(struct buffer *buf, size_t len, size_t valid, gfp_t mask) 115 | { 116 | struct buffer new_buf; 117 | 118 | if (alloc_buffer(&new_buf, len, mask)) 119 | return -ENOMEM; 120 | 121 | _memmove(&new_buf, buf, valid); 122 | free_buffer(buf); 123 | memcpy(buf, &new_buf, sizeof(*buf)); 124 | 125 | return 0; 126 | } 127 | 128 | static inline int _realloc_buffer_more(struct buffer *buf, size_t more, size_t valid, gfp_t mask) 129 | { 130 | return _realloc_buffer(buf, more, valid, mask); 131 | } 132 | 133 | static inline int _realloc_buffer_less(struct buffer *buf, size_t less, size_t valid, gfp_t mask) 134 | { 135 | if (buf->flags == BUF_KMALLOC) 136 | return 0; 137 | 138 | if (buf->flags == BUF_PAGES && alloc_policy(less) == BUF_PAGES) { 139 | unsigned int neworder, oldorder; 140 | neworder = get_order(less); 141 | oldorder = get_order(buf->room); 142 | if (neworder == oldorder) 143 | return 0; 144 | } 145 | 146 | return _realloc_buffer(buf, less, valid, mask); 147 | } 148 | 149 | int realloc_buffer(struct buffer *buf, size_t len, size_t valid, gfp_t mask) 150 | { 151 | BUG_ON(buf->flags >= BUF_FLAGS_MAX); 152 | if (buf->flags == BUF_NEGATIVE) 153 | return alloc_buffer(buf, len, mask); 154 | if (len > buf->room) { 155 | return _realloc_buffer_more(buf, len, valid, mask); 156 | } 157 | if (len < buf->room) { 158 | return _realloc_buffer_less(buf, len, valid, mask); 159 | } 160 | return 0; 161 | } 162 | 163 | static inline void __free_buffer(struct buffer *buf) 164 | { 165 | unsigned int order; 166 | 167 | order = get_order(buf->room); 168 | 169 | #ifdef CONFIG_X86_32 170 | kunmap(buf->_page); 171 | __free_pages(buf->_page, order); 172 | #else 173 | free_pages((unsigned long)buf->buf, order); 174 | #endif 175 | } 176 | 177 | static inline void _free_buffer(struct buffer *buf) 178 | { 179 | switch (buf->flags) { 180 | case BUF_KMALLOC: 181 | kfree(buf->buf); 182 | break; 183 | case BUF_VMALLOC: 184 | vfree(buf->buf); 185 | break; 186 | case BUF_PAGES: 187 | __free_buffer(buf); 188 | break; 189 | case BUF_NEGATIVE: 190 | break; 191 | default: 192 | BUG(); 193 | break; 194 | } 195 | } 196 | 197 | void free_buffer(struct buffer *buf) 198 | { 199 | _free_buffer(buf); 200 | } 201 | 202 | void free_buffer_init(struct buffer *buf) 203 | { 204 | _free_buffer(buf); 205 | init_buffer(buf); 206 | } 207 | -------------------------------------------------------------------------------- /kmod/mc_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_BUFFER_H 2 | #define __MC_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef CONFIG_X86_32 11 | #define GFP_PAGES (GFP_KERNEL | __GFP_HIGHMEM) 12 | #else 13 | #define GFP_PAGES GFP_KERNEL 14 | #endif 15 | 16 | #define BUF_NEGATIVE 0 17 | #define BUF_KMALLOC 1 18 | #define BUF_VMALLOC 2 19 | #define BUF_PAGES 3 20 | #define BUF_FLAGS_MAX 4 21 | 22 | #define BUF_KMALLOC_MAX PAGE_SIZE 23 | #define BUF_PAGES_MAX 1024 * 1024 /* 1M */ 24 | 25 | struct buffer { 26 | /* 27 | * BUF_NEGATIVE: not alloced 28 | * BUF_KMALLOC : kmalloc 29 | * BUF_VMALLOC : vmalloc 30 | * BUF_PAGES : alloc_pages 31 | */ 32 | unsigned int flags: 3; 33 | 34 | /* 35 | * for kmalloc, ksize(p) 36 | * for alloc_pages, bytes size 37 | * for vmalloc, request size 38 | */ 39 | unsigned int room: 29; 40 | 41 | void *buf; 42 | #ifdef CONFIG_X86_32 43 | struct page *_page; 44 | #endif 45 | }; 46 | 47 | #ifdef CONFIG_X86_32 48 | #define __INIT_BUFFER(buf) { \ 49 | .flags = BUF_NEGATIVE, \ 50 | .room = 0, \ 51 | .buf = NULL, \ 52 | ._page = NULL } 53 | #else 54 | #define __INIT_BUFFER(buf) { \ 55 | .flags = BUF_NEGATIVE, \ 56 | .room = 0, \ 57 | .buf = NULL } 58 | 59 | #endif 60 | 61 | #define DECLEARE_BUFFER(name) \ 62 | struct buffer name = __INIT_BUFFER(name) 63 | 64 | #define init_buffer(buf) \ 65 | do { \ 66 | memset((buf), 0, sizeof(*(buf)));\ 67 | } while (0) 68 | 69 | #define BUFFER(b) \ 70 | ({ \ 71 | void *_buf; \ 72 | switch ((b)->flags) { \ 73 | case BUF_KMALLOC: \ 74 | case BUF_VMALLOC: \ 75 | case BUF_PAGES: \ 76 | _buf = (b)->buf; \ 77 | break; \ 78 | default: \ 79 | BUG(); \ 80 | break; \ 81 | } \ 82 | _buf; \ 83 | }) 84 | 85 | #define BUFFER_PTR(buf, ptr) \ 86 | do { \ 87 | void *_buf; \ 88 | _buf = BUFFER(buf); \ 89 | (ptr) = (typeof(*(ptr)) *)_buf; \ 90 | } while (0) 91 | 92 | /** 93 | * alloc_buffer() - wapper of kmalloc/vmalloc/pages 94 | * @buf : !null 95 | * @len : request buffer size 96 | * @mask : gfp mask 97 | * 98 | * return 0 success, errno otherwise 99 | */ 100 | extern int alloc_buffer(struct buffer *buf, size_t len, gfp_t mask); 101 | 102 | /** 103 | * realloc_buffer() - realloc buffer or alloc new buffer, dumping on buffer's flags 104 | * @buf : !null 105 | * @len : request new size, greater or less than previous size 106 | * @mask : gfp mask 107 | * 108 | * return 0 success, errno otherwise 109 | */ 110 | extern int realloc_buffer(struct buffer *buf, size_t len, size_t valid, gfp_t mask); 111 | 112 | extern void free_buffer(struct buffer *buf); 113 | extern void free_buffer_init(struct buffer *buf); 114 | 115 | #endif /* __MC_BUFFER_H */ 116 | -------------------------------------------------------------------------------- /kmod/mc_connector.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "mc.h" 11 | 12 | static void mc_cn_work(struct work_struct *work); 13 | static void mc_cn_work_del(struct work_struct *work); 14 | 15 | #ifdef CONFIG_CN_CACHE 16 | static struct kmem_cache *cn_cachep; 17 | #endif 18 | 19 | struct cn_queue { 20 | #ifdef CONFIG_CN_CACHE 21 | struct workqueue_struct *workqueue; 22 | #endif 23 | 24 | struct list_head list; 25 | spinlock_t lock; 26 | }; 27 | 28 | static struct cn { 29 | struct sock *sock; 30 | struct cn_queue *queue; 31 | } cn; 32 | 33 | static u32 unique_val = CN_VAL_INIT; 34 | static DEFINE_SPINLOCK(unique_val_lock); 35 | 36 | u32 mc_get_unique_val(void) 37 | { 38 | u32 val; 39 | 40 | spin_lock_bh(&unique_val_lock); 41 | val = ++unique_val; 42 | unique_val &= val; 43 | spin_unlock_bh(&unique_val_lock); 44 | 45 | return val; 46 | } 47 | EXPORT_SYMBOL(mc_get_unique_val); 48 | 49 | void mc_put_unique_val(u32 val) 50 | { 51 | spin_lock_bh(&unique_val_lock); 52 | unique_val &= ~val; 53 | spin_unlock_bh(&unique_val_lock); 54 | } 55 | EXPORT_SYMBOL(mc_put_unique_val); 56 | 57 | static void __mc_del_callback(struct cn_id *id) 58 | { 59 | struct cn_entry *pos, *n; 60 | struct cn_queue *queue = cn.queue; 61 | 62 | spin_lock_bh(&queue->lock); 63 | list_for_each_entry_safe(pos, n, &queue->list, list_entry) { 64 | if (pos->id.idx == id->idx && pos->id.val == id->val) { 65 | list_del(&pos->list_entry); 66 | break; 67 | } 68 | } 69 | spin_unlock_bh(&queue->lock); 70 | 71 | if (&pos->list_entry != &queue->list) { 72 | #ifdef CONFIG_CN_CACHE 73 | kmem_cache_free(cn_cachep, pos); 74 | #else 75 | kfree(pos); 76 | #endif 77 | } 78 | } 79 | 80 | void mc_del_callback(struct cn_id *id, int sync) 81 | { 82 | if (likely(sync)) 83 | __mc_del_callback(id); 84 | } 85 | EXPORT_SYMBOL(mc_del_callback); 86 | 87 | int mc_add_callback(struct cn_id *id, cn_callback_fn *f, int sync) 88 | { 89 | int ret = 0; 90 | struct cn_entry *entry, *pos; 91 | struct cn_queue *queue = cn.queue; 92 | 93 | #ifdef CONFIG_CN_CACHE 94 | entry = kmem_cache_zalloc(cn_cachep, GFP_KERNEL); 95 | #else 96 | entry = kzalloc(sizeof(*entry), GFP_KERNEL); 97 | #endif 98 | if (!entry) { 99 | ret = -ENOMEM; 100 | goto out; 101 | } 102 | entry->flags = ENTRY_NEW; 103 | entry->id.idx = id->idx; 104 | entry->id.val = id->val; 105 | entry->callback.skb = NULL; 106 | entry->callback.out = NULL; 107 | entry->callback.f = f; 108 | if (likely(sync)) { 109 | INIT_WORK(&entry->work, mc_cn_work); 110 | init_completion(&entry->comp); 111 | } else { 112 | INIT_WORK(&entry->work, mc_cn_work_del); 113 | } 114 | 115 | spin_lock_bh(&queue->lock); 116 | list_for_each_entry(pos, &queue->list, list_entry) { 117 | if (pos->id.idx == id->idx && pos->id.val == id->val) { 118 | break; 119 | } 120 | } 121 | if (&pos->list_entry == &queue->list) { 122 | list_add_tail(&entry->list_entry, &queue->list); 123 | spin_unlock_bh(&queue->lock); 124 | } else { 125 | spin_unlock_bh(&queue->lock); 126 | ret = -EFAULT; 127 | goto free_entry; 128 | } 129 | 130 | return 0; 131 | 132 | free_entry: 133 | #ifdef CONFIG_CN_CACHE 134 | kmem_cache_free(cn_cachep, entry); 135 | #else 136 | kfree(entry); 137 | #endif 138 | out: 139 | return ret; 140 | } 141 | EXPORT_SYMBOL(mc_add_callback); 142 | 143 | static void mc_cn_work(struct work_struct *work) 144 | { 145 | struct cn_entry *entry; 146 | struct cn_callback *callback; 147 | struct cn_msg *msg; 148 | struct netlink_skb_parms *parms; 149 | 150 | entry = container_of(work, struct cn_entry, work); 151 | callback = &entry->callback; 152 | msg = NLMSG_DATA(nlmsg_hdr(callback->skb)); 153 | parms = &NETLINK_CB(callback->skb); 154 | 155 | callback->out = callback->f(msg, parms); 156 | kfree_skb(callback->skb); 157 | complete(&entry->comp); 158 | } 159 | 160 | static void mc_cn_work_del(struct work_struct *work) 161 | { 162 | struct cn_entry *entry; 163 | struct cn_callback *callback; 164 | struct cn_msg *msg; 165 | struct netlink_skb_parms *parms; 166 | 167 | entry = container_of(work, struct cn_entry, work); 168 | callback = &entry->callback; 169 | msg = NLMSG_DATA(nlmsg_hdr(callback->skb)); 170 | parms = &NETLINK_CB(callback->skb); 171 | 172 | callback->out = callback->f(msg, parms); 173 | kfree_skb(callback->skb); 174 | 175 | __mc_del_callback(&entry->id); 176 | } 177 | 178 | static void* __send_msg_sync(struct cn_msg *msg, unsigned long timeout) 179 | { 180 | int ret = 0; 181 | size_t size; 182 | struct sk_buff *skb; 183 | struct nlmsghdr *nlh; 184 | struct cn_msg *data; 185 | struct cn_entry *entry; 186 | struct cn_queue *queue = cn.queue; 187 | 188 | spin_lock_bh(&queue->lock); 189 | list_for_each_entry(entry, &queue->list, list_entry) { 190 | if (entry->id.idx == msg->id.idx && 191 | entry->id.val == msg->id.val) { 192 | entry->flags = ENTRY_RUNNING; 193 | break; 194 | } 195 | } 196 | spin_unlock_bh(&queue->lock); 197 | 198 | if (unlikely(&entry->list_entry == &queue->list)) 199 | return NULL; 200 | 201 | if (!netlink_has_listeners(cn.sock, NETLINK_MEMCACHE_GRP)) { 202 | PRINTK("netlink hasn't got a listener\n"); 203 | ret = -ESRCH; 204 | goto out; 205 | } 206 | 207 | size = NLMSG_SPACE(sizeof(*msg) + msg->len); 208 | skb = alloc_skb(size, GFP_KERNEL); 209 | if (!skb) { 210 | PRINTK("alloc skb error\n"); 211 | ret = -ENOMEM; 212 | goto out; 213 | } 214 | 215 | nlh = NLMSG_PUT(skb, 0, 0, NLMSG_DONE, size - sizeof(*nlh)); 216 | 217 | data = NLMSG_DATA(nlh); 218 | memcpy(data, msg, sizeof(*data) + msg->len); 219 | 220 | NETLINK_CB(skb).dst_group = 0; 221 | 222 | if ((ret = netlink_broadcast(cn.sock, skb, 0, 223 | NETLINK_MEMCACHE_GRP, 224 | GFP_KERNEL))) { 225 | PRINTK("netlink broadcast error\n"); 226 | goto out; 227 | } 228 | 229 | if (unlikely(!timeout)) { 230 | entry->flags = ENTRY_FINISHED; 231 | return NULL; 232 | } 233 | 234 | ret = wait_for_completion_timeout(&entry->comp, timeout); 235 | if (!ret) { 236 | PRINTK("__send_msg_sync timeout\n"); 237 | ret = -EFAULT; 238 | goto out; 239 | } 240 | entry->flags = ENTRY_FINISHED; 241 | return entry->callback.out; 242 | 243 | nlmsg_failure: 244 | kfree_skb(skb); 245 | ret = -EFAULT; 246 | out: 247 | return ERR_PTR(ret); 248 | } 249 | 250 | void* mc_send_msg(struct cn_msg *msg) 251 | { 252 | return __send_msg_sync(msg, 0); 253 | } 254 | EXPORT_SYMBOL(mc_send_msg); 255 | 256 | void* mc_send_msg_sync(struct cn_msg *msg) 257 | { 258 | return __send_msg_sync(msg, MAX_SCHEDULE_TIMEOUT); 259 | } 260 | EXPORT_SYMBOL(mc_send_msg_sync); 261 | 262 | void* mc_send_msg_timeout(struct cn_msg *msg, unsigned long timeout) 263 | { 264 | return __send_msg_sync(msg, timeout); 265 | } 266 | EXPORT_SYMBOL(mc_send_msg_timeout); 267 | 268 | static void mc_nl_callback(struct sk_buff *_skb) 269 | { 270 | struct sk_buff *skb; 271 | struct nlmsghdr *nlh; 272 | struct cn_msg *msg; 273 | struct cn_entry *entry; 274 | struct cn_queue *queue = cn.queue; 275 | 276 | skb = skb_get(_skb); 277 | if (skb->len < NLMSG_SPACE(0)) 278 | goto out; 279 | 280 | nlh = nlmsg_hdr(skb); 281 | if (nlh->nlmsg_len < sizeof(struct cn_msg) || 282 | skb->len < nlh->nlmsg_len || 283 | nlh->nlmsg_len > NETLINK_PAYLOAD) { 284 | kfree_skb(skb); 285 | goto out; 286 | } 287 | 288 | msg = NLMSG_DATA(nlh); 289 | spin_lock_bh(&queue->lock); 290 | list_for_each_entry(entry, &queue->list, list_entry) { 291 | if (entry->id.idx == msg->id.idx && 292 | entry->id.val == msg->id.val) { 293 | entry->callback.skb = skb; 294 | #ifdef CONFIG_CN_CACHE 295 | if (!queue_work(queue->workqueue, &entry->work)) { 296 | #else 297 | if (!schedule_work(&entry->work)) { 298 | #endif 299 | spin_unlock_bh(&queue->lock); 300 | entry->callback.skb = NULL; 301 | PRINTK("may be dead lock, check callback\n"); 302 | goto free_skb; 303 | } 304 | break; 305 | } 306 | } 307 | spin_unlock_bh(&queue->lock); 308 | 309 | if (unlikely(&entry->list_entry == &queue->list)) { 310 | kfree_skb(skb); 311 | } 312 | 313 | return; 314 | 315 | free_skb: 316 | kfree_skb(skb); 317 | out: 318 | return; 319 | } 320 | 321 | int connector_init(void) 322 | { 323 | int ret = 0; 324 | 325 | #ifdef CONFIG_CN_CACHE 326 | cn_cachep = kmem_cache_create("mc_cn_cache", 327 | sizeof(struct cn_entry), 328 | 0, 329 | SLAB_HWCACHE_ALIGN, 330 | NULL); 331 | if (!cn_cachep) { 332 | PRINTK("create connector cache error\n"); 333 | ret = -ENOMEM; 334 | goto out; 335 | } 336 | #endif 337 | 338 | cn.sock = netlink_kernel_create(&init_net, 339 | NETLINK_MEMCACHE, 340 | NETLINK_MEMCACHE_GRP, 341 | mc_nl_callback, 342 | NULL, 343 | THIS_MODULE); 344 | if (!cn.sock) { 345 | PRINTK("create netlink error\n"); 346 | ret = -EIO; 347 | goto free_cache; 348 | } 349 | 350 | cn.queue = kzalloc(sizeof(struct cn_queue), GFP_KERNEL); 351 | if (!cn.queue) { 352 | PRINTK("alloc connetctor queue error\n"); 353 | ret = -ENOMEM; 354 | goto free_netlink; 355 | } 356 | #ifdef CONFIG_CN_CACHE 357 | cn.queue->workqueue = create_singlethread_workqueue("kmccn"); 358 | if (!cn.queue->workqueue) { 359 | PRINTK("create connetctor queue error\n"); 360 | ret = -ENOMEM; 361 | goto free_queue; 362 | } 363 | #endif 364 | INIT_LIST_HEAD(&cn.queue->list); 365 | spin_lock_init(&cn.queue->lock); 366 | 367 | return 0; 368 | 369 | #ifdef CONFIG_CN_CACHE 370 | free_queue: 371 | kfree(cn.queue); 372 | cn.queue = NULL; 373 | #endif 374 | free_netlink: 375 | netlink_kernel_release(cn.sock); 376 | cn.sock = NULL; 377 | free_cache: 378 | #ifdef CONFIG_CN_CACHE 379 | kmem_cache_destroy(cn_cachep); 380 | out: 381 | #endif 382 | return ret; 383 | } 384 | 385 | void connector_exit(void) 386 | { 387 | struct cn_entry *pos, *n; 388 | struct cn_queue *queue = cn.queue; 389 | 390 | retry: 391 | spin_lock_bh(&queue->lock); 392 | list_for_each_entry_safe(pos, n, &queue->list, list_entry) { 393 | if (pos->flags != ENTRY_RUNNING) { 394 | list_del(&pos->list_entry); 395 | #ifdef CONFIG_CN_CACHE 396 | kmem_cache_free(cn_cachep, pos); 397 | #else 398 | kfree(pos); 399 | #endif 400 | } else { 401 | spin_unlock_bh(&queue->lock); 402 | flush_work(&pos->work); 403 | goto retry; 404 | } 405 | } 406 | spin_unlock_bh(&queue->lock); 407 | 408 | #ifdef CONFIG_CN_CACHE 409 | destroy_workqueue(queue->workqueue); 410 | #endif 411 | kfree(queue); 412 | netlink_kernel_release(cn.sock); 413 | #ifdef CONFIG_CN_CACHE 414 | kmem_cache_destroy(cn_cachep); 415 | #endif 416 | } 417 | -------------------------------------------------------------------------------- /kmod/mc_dispatcher.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_DISPATCHER_H 2 | #define __MC_DISPATCHER_H 3 | 4 | extern void mc_accept_new_conns(int enable); 5 | extern int dispatcher_init(void); 6 | extern void dispatcher_exit(void); 7 | 8 | #endif /* __MC_DISPATCHER_H */ 9 | -------------------------------------------------------------------------------- /kmod/mc_hashtable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mc.h" 7 | 8 | typedef struct buffer hash_table_storage_t; 9 | 10 | static DECLARE_WAIT_QUEUE_HEAD(hash_wait_queue); 11 | static struct task_struct *hash_kthread; 12 | 13 | /* how many powers of 2's worth of buckets we use */ 14 | unsigned int hashpower = HASHPOWER_DEFAULT; 15 | 16 | #define hashsize(n) ((u32)1 << (n)) 17 | #define hashmask(n) (hashsize(n) -1) 18 | 19 | /* main hash table. This is where we look except during expansion. */ 20 | static hash_table_storage_t primary_hts; 21 | static item** primary_hashtable = 0; 22 | 23 | /* 24 | * previous hash table. During expansion, we look here for keys that 25 | * haven't been moved over to the primary yet. 26 | */ 27 | static hash_table_storage_t old_hts; 28 | static item** old_hashtable = 0; 29 | 30 | /* number of items in the hash table */ 31 | static unsigned int hash_items = 0; 32 | 33 | /* flag: are we in the middle of expanding now? */ 34 | #define ZOMBIE 0 35 | #define EXPANDING 1 36 | #define SEXPANDING 2 37 | static unsigned long hashflags; 38 | 39 | /* 40 | * during expansion we migrate values with bucket granularity; this is how 41 | * far we've gotten so far. Ranges from 0 .. hashsize(hashpower - 1) - 1. 42 | */ 43 | static unsigned int expand_bucket = 0; 44 | 45 | int hash_init(int power) 46 | { 47 | size_t bytes; 48 | int ret = 0; 49 | 50 | if (power) 51 | hashpower = power; 52 | bytes = hashsize(hashpower) * sizeof(void *); 53 | ret = alloc_buffer(&primary_hts, bytes, __GFP_ZERO); 54 | if (ret) { 55 | PRINTK("alloc primary_hashtable error\n"); 56 | goto out; 57 | } else { 58 | BUFFER_PTR(&primary_hts, primary_hashtable); 59 | } 60 | 61 | ATOMIC32_SET(stats.hash_power_level, hashpower); 62 | ATOMIC64_SET(stats.hash_bytes, bytes); 63 | 64 | out: 65 | return ret; 66 | } 67 | 68 | void hash_exit(void) 69 | { 70 | if (test_bit(EXPANDING, &hashflags)) { 71 | free_buffer(&old_hts); 72 | old_hashtable = NULL; 73 | } 74 | free_buffer(&primary_hts); 75 | primary_hashtable = NULL; 76 | } 77 | 78 | item* mc_hash_find(const char *key, u32 nkey, u32 hv) 79 | { 80 | item *it, *ret = NULL; 81 | unsigned int oldbucket; 82 | 83 | if (test_bit(EXPANDING, &hashflags) && 84 | (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) { 85 | it = old_hashtable[oldbucket]; 86 | } else { 87 | it = primary_hashtable[hv & hashmask(hashpower)]; 88 | } 89 | 90 | while (it) { 91 | if ((nkey == it->nkey) && 92 | !memcmp(key, ITEM_key(it), nkey)) { 93 | ret = it; 94 | break; 95 | } 96 | it = it->h_next; 97 | } 98 | 99 | return ret; 100 | } 101 | 102 | /** 103 | * returns the address of the item pointer before the key, 104 | * if *item == 0, the item wasn't found. 105 | */ 106 | static item** _mc_hashitem_before(const char *key, size_t nkey, u32 hv) 107 | { 108 | item **pos; 109 | unsigned int oldbucket; 110 | 111 | if (test_bit(EXPANDING, &hashflags) && 112 | (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) { 113 | pos = &old_hashtable[oldbucket]; 114 | } else { 115 | pos = &primary_hashtable[hv & hashmask(hashpower)]; 116 | } 117 | 118 | while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) { 119 | pos = &(*pos)->h_next; 120 | } 121 | return pos; 122 | } 123 | 124 | /** 125 | * grows the hashtable to the next power of 2 126 | */ 127 | static void mc_hash_expand(void) 128 | { 129 | size_t bytes; 130 | int ret = 0; 131 | 132 | old_hashtable = primary_hashtable; 133 | memcpy(&old_hts, &primary_hts, sizeof(old_hts)); 134 | 135 | bytes = hashsize(hashpower + 1) * sizeof(void *); 136 | ret = alloc_buffer(&primary_hts, bytes, __GFP_ZERO); 137 | if (!ret) { 138 | PVERBOSE(1, "hash table expansion starting\n"); 139 | BUFFER_PTR(&primary_hts, primary_hashtable); 140 | hashpower++; 141 | set_bit(EXPANDING, &hashflags); 142 | expand_bucket = 0; 143 | 144 | ATOMIC32_SET(stats.hash_power_level, hashpower); 145 | ATOMIC64_ADD(stats.hash_bytes, bytes); 146 | set_bit(STATS_HASH_EXP, &stats.flags); 147 | } else { 148 | /* bad news, but we can keep running */ 149 | PRINTK("hash table expansion error\n"); 150 | memcpy(&primary_hts, &old_hts, sizeof(old_hts)); 151 | primary_hashtable = old_hashtable; 152 | } 153 | } 154 | 155 | static void mc_hash_start_expand(void) 156 | { 157 | if (test_and_set_bit(SEXPANDING, &hashflags)) 158 | return; 159 | wake_up(&hash_wait_queue); 160 | } 161 | 162 | /* 163 | * Note: this isn't an hash_update. The key must not already exist 164 | * to call this. 165 | */ 166 | int mc_hash_insert(item *it, u32 hv) 167 | { 168 | unsigned int oldbucket; 169 | 170 | /* shouldn't have duplicately named things defined */ 171 | //BUG_ON(mc_hash_find(ITEM_key(it), it->nkey)); 172 | 173 | if (test_bit(EXPANDING, &hashflags) && 174 | (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) { 175 | it->h_next = old_hashtable[oldbucket]; 176 | old_hashtable[oldbucket] = it; 177 | } else { 178 | it->h_next = primary_hashtable[hv & hashmask(hashpower)]; 179 | primary_hashtable[hv & hashmask(hashpower)] = it; 180 | } 181 | 182 | hash_items++; 183 | if (!test_bit(EXPANDING, &hashflags) && hash_items > (hashsize(hashpower) * 3) / 2) { 184 | mc_hash_start_expand(); 185 | } 186 | 187 | return 0; 188 | } 189 | 190 | void mc_hash_delete(const char *key, size_t nkey, u32 hv) 191 | { 192 | item **before = _mc_hashitem_before(key, nkey, hv); 193 | 194 | if (*before) { 195 | item *nxt; 196 | hash_items--; 197 | 198 | /* 199 | * The DTrace probe can't be triggered as the last instruction 200 | * due to possible tail-optimization by the compiler 201 | */ 202 | nxt = (*before)->h_next; 203 | (*before)->h_next = 0; /* probably pointless, but whatever */ 204 | *before = nxt; 205 | return; 206 | } 207 | 208 | /* 209 | * Note: we never actually get here, the callers don't delete things 210 | * they can't find. 211 | */ 212 | BUG_ON(!*before); 213 | } 214 | 215 | static int mc_hash_thread(void *ignore) 216 | { 217 | set_freezable(); 218 | mc_slabs_rebalancer_pause(); 219 | 220 | while (!test_bit(ZOMBIE, &hashflags)) { 221 | int ii = 0; 222 | 223 | /* 224 | * Lock the cache, and bulk move multiple buckets to 225 | * the new hash table. 226 | */ 227 | mc_item_lock_global(); 228 | mutex_lock(&cache_lock); 229 | 230 | for (ii = 0; ii < settings.hash_bulk_move && test_bit(EXPANDING, &hashflags); ii++) { 231 | item *it, *next; 232 | int bucket; 233 | 234 | for (it = old_hashtable[expand_bucket]; it; it = next) { 235 | next = it->h_next; 236 | 237 | bucket = hash(ITEM_key(it), it->nkey, 0) & 238 | hashmask(hashpower); 239 | it->h_next = primary_hashtable[bucket]; 240 | primary_hashtable[bucket] = it; 241 | } 242 | 243 | old_hashtable[expand_bucket] = NULL; 244 | expand_bucket++; 245 | 246 | if (expand_bucket == hashsize(hashpower - 1)) { 247 | clear_bit(EXPANDING, &hashflags); 248 | clear_bit(SEXPANDING, &hashflags); 249 | free_buffer(&old_hts); 250 | 251 | ATOMIC64_SUB(stats.hash_bytes, 252 | hashsize(hashpower - 1) * 253 | sizeof(void *)); 254 | clear_bit(STATS_HASH_EXP, &stats.flags); 255 | 256 | PVERBOSE(1, "hash table expansion done\n"); 257 | } 258 | } 259 | 260 | mutex_unlock(&cache_lock); 261 | mc_item_unlock_global(); 262 | 263 | if (!test_bit(EXPANDING, &hashflags)) { 264 | /* 265 | * finished expanding. tell all threads to use 266 | * fine-grained locks. 267 | */ 268 | mc_switch_item_lock_type(ITEM_LOCK_GRANULAR); 269 | mc_slabs_rebalancer_resume(); 270 | 271 | /* 272 | * We are done expanding.. just wait for next invocation 273 | */ 274 | wait_event_freezable(hash_wait_queue, 275 | test_bit(SEXPANDING, &hashflags) || 276 | kthread_should_stop()); 277 | if (test_bit(ZOMBIE, &hashflags)) { 278 | goto out; 279 | } 280 | /* before doing anything, tell threads to use a global lock */ 281 | mc_slabs_rebalancer_pause(); 282 | mc_switch_item_lock_type(ITEM_LOCK_GLOBAL); 283 | mutex_lock(&cache_lock); 284 | mc_hash_expand(); 285 | mutex_unlock(&cache_lock); 286 | } 287 | } 288 | 289 | out: 290 | return 0; 291 | } 292 | 293 | int start_hash_thread(void) 294 | { 295 | int ret = 0; 296 | 297 | hash_kthread = kthread_run(mc_hash_thread, 298 | NULL, "kmchash"); 299 | if (IS_ERR(hash_kthread)) { 300 | ret = PTR_ERR(hash_kthread); 301 | PRINTK("create hash kthread error\n"); 302 | goto out; 303 | } 304 | 305 | out: 306 | return ret; 307 | } 308 | 309 | void stop_hash_thread(void) 310 | { 311 | set_bit(ZOMBIE, &hashflags); 312 | kthread_stop(hash_kthread); 313 | } 314 | 315 | -------------------------------------------------------------------------------- /kmod/mc_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mc.h" 14 | 15 | int timeout __read_mostly = 10; 16 | module_param(timeout, int, 0); 17 | MODULE_PARM_DESC(timeout, "timeout in seconds for msg from umemcached"); 18 | 19 | unsigned long slabsize __read_mostly = 95; 20 | module_param(slabsize, ulong, 0); 21 | MODULE_PARM_DESC(slabsize, "percent of totalram that slabs could use"); 22 | 23 | volatile rel_time_t current_time; 24 | time_t process_started __read_mostly; 25 | 26 | rel_time_t realtime(rel_time_t exptime) 27 | { 28 | /* no. of seconds in 30 days - largest possible delta exptime */ 29 | 30 | if (exptime == 0) 31 | return 0; /* 0 means never expire */ 32 | 33 | if (exptime > REALTIME_MAXDELTA) { 34 | /* 35 | * if item expiration is at/before the server started, give it an 36 | * expiration time of 1 second after the server started. 37 | * (because 0 means don't expire). without this, we'd 38 | * underflow and wrap around to some large value way in the 39 | * future, effectively making items expiring in the past 40 | * really expiring never 41 | */ 42 | if (exptime <= process_started) 43 | return (rel_time_t)1; 44 | return (rel_time_t)(exptime - process_started); 45 | } else { 46 | return (rel_time_t)(exptime + current_time); 47 | } 48 | } 49 | 50 | #define TIMER_CYCLE ((unsigned long) ~0) 51 | static struct time_updater { 52 | #define TIMER_DEL 0x1 53 | u32 flags; 54 | struct timer_list timer; 55 | } time_updater; 56 | 57 | static void mc_timer_update(unsigned long arg) 58 | { 59 | struct time_updater *t = 60 | (struct time_updater *)arg; 61 | 62 | if (unlikely(t->flags & TIMER_DEL)) 63 | return; 64 | current_time = get_seconds() - process_started; 65 | t->timer.expires = jiffies + HZ; 66 | add_timer(&t->timer); 67 | } 68 | 69 | static int timer_init(void) 70 | { 71 | process_started = get_seconds() - 2; 72 | current_time = 2; 73 | 74 | init_timer(&time_updater.timer); 75 | 76 | time_updater.timer.expires = jiffies + HZ; 77 | time_updater.timer.data = (unsigned long)&time_updater; 78 | time_updater.timer.function= mc_timer_update; 79 | 80 | add_timer(&time_updater.timer); 81 | 82 | return 0; 83 | } 84 | 85 | static void timer_exit(void) 86 | { 87 | time_updater.flags |= TIMER_DEL; 88 | del_timer_sync(&time_updater.timer); 89 | } 90 | 91 | static struct cache_info { 92 | struct kmem_cache **cachep; 93 | char *name; 94 | size_t size; 95 | void (*ctor)(void *); 96 | } caches_info[] = { 97 | { 98 | .cachep = &prefix_cachep, 99 | .name = "mc_prefix_cache", 100 | .size = sizeof(struct prefix_stats), 101 | .ctor = NULL 102 | }, 103 | { 104 | .cachep = &suffix_cachep, 105 | .name = "mc_suffix_cache", 106 | .size = SUFFIX_SIZE, 107 | .ctor = NULL 108 | }, 109 | { 110 | .cachep = &conn_req_cachep, 111 | .name = "mc_conn_req_cache", 112 | .size = sizeof(struct conn_req), 113 | .ctor = NULL 114 | }, 115 | { 116 | .cachep = &lock_xchg_req_cachep, 117 | .name = "mc_lock_xchg_req_cache", 118 | .size = sizeof(struct lock_xchg_req), 119 | .ctor = NULL 120 | }, 121 | { 122 | .cachep = &conn_cachep, 123 | .name = "mc_conn_cache", 124 | .size = sizeof(struct conn), 125 | .ctor = NULL 126 | }, 127 | }; 128 | 129 | static void caches_info_exit(void) 130 | { 131 | int i; 132 | struct cache_info *cache; 133 | 134 | for (i = 0; i < ARRAY_SIZE(caches_info); i++) { 135 | cache = &caches_info[i]; 136 | if (*cache->cachep) { 137 | kmem_cache_destroy(*cache->cachep); 138 | } 139 | } 140 | } 141 | 142 | static int caches_info_init(void) 143 | { 144 | int i; 145 | 146 | for (i = 0; i < ARRAY_SIZE(caches_info); i++) { 147 | struct cache_info *cache = &caches_info[i]; 148 | 149 | *cache->cachep = kmem_cache_create(cache->name, 150 | cache->size, 151 | 0, 152 | SLAB_HWCACHE_ALIGN, 153 | cache->ctor); 154 | if (!*cache->cachep) { 155 | PRINTK("create kmem cache error\n"); 156 | goto out; 157 | } 158 | } 159 | 160 | return 0; 161 | out: 162 | caches_info_exit(); 163 | return -ENOMEM; 164 | } 165 | 166 | static int __kmemcache_bh_init(void *unused) 167 | { 168 | int ret = 0; 169 | 170 | if ((ret = settings_init())) { 171 | PRINTK("init settings error\n"); 172 | goto out; 173 | } 174 | if ((ret = caches_info_init())) { 175 | PRINTK("init caches error\n"); 176 | goto out; 177 | } 178 | if ((ret = stats_init())) { 179 | PRINTK("init stats error\n"); 180 | goto del_caches; 181 | } 182 | if ((ret = slabs_init(settings.maxbytes, 183 | settings.factor_numerator, 184 | settings.factor_denominator, 185 | settings.preallocate))) { 186 | PRINTK("init slabs error\n"); 187 | goto del_stats; 188 | } 189 | if ((ret = hash_init(settings.hashpower_init))) { 190 | PRINTK("init hashtable error\n"); 191 | goto del_slabs; 192 | } 193 | if ((ret = workers_init())) { 194 | PRINTK("init workers error\n"); 195 | goto del_hash; 196 | } 197 | if ((ret = start_slab_thread())) { 198 | PRINTK("init slab kthread error\n"); 199 | goto del_workers; 200 | } 201 | if ((ret = start_hash_thread())) { 202 | PRINTK("init hashtable kthread error\n"); 203 | goto del_slab_thread; 204 | } 205 | if ((ret = timer_init())) { 206 | PRINTK("init timer error\n"); 207 | goto del_hash_thread; 208 | } 209 | if ((ret = dispatcher_init())) { 210 | PRINTK("init dispatcher error\n"); 211 | goto del_timer; 212 | } 213 | if ((ret = oom_init())) { 214 | PRINTK("init oom error\n"); 215 | goto del_dispatcher; 216 | } 217 | 218 | goto out; 219 | 220 | del_dispatcher: 221 | dispatcher_exit(); 222 | del_timer: 223 | timer_exit(); 224 | del_hash_thread: 225 | stop_hash_thread(); 226 | del_slab_thread: 227 | stop_slab_thread(); 228 | del_workers: 229 | workers_exit(); 230 | del_hash: 231 | hash_exit(); 232 | del_slabs: 233 | slabs_exit(); 234 | del_stats: 235 | stats_exit(); 236 | del_caches: 237 | caches_info_exit(); 238 | out: 239 | 240 | __settings_exit(); 241 | if (ret) { 242 | sock_info.status = FAILURE; 243 | PRINTK("start server error\n"); 244 | } else { 245 | sock_info.status = SUCCESS; 246 | PRINTK("start server success\n"); 247 | } 248 | report_cache_bh_status(ret == 0); 249 | 250 | return ret; 251 | } 252 | 253 | static void* kmemcache_bh_init(struct cn_msg *msg, 254 | struct netlink_skb_parms *pm) 255 | { 256 | struct task_struct *helper; 257 | 258 | helper = kthread_run(__kmemcache_bh_init, NULL, "kmcbh"); 259 | if (IS_ERR(helper)) { 260 | PRINTK("create kmemcache bh kthread error\n"); 261 | } 262 | 263 | return NULL; 264 | } 265 | 266 | static inline void unregister_kmemcache_bh(void) 267 | { 268 | mc_del_callback(&cache_bh_id, 0); 269 | } 270 | 271 | static inline int register_kmemcache_bh(void) 272 | { 273 | return mc_add_callback(&cache_bh_id, kmemcache_bh_init, 0); 274 | } 275 | 276 | static int __init kmemcache_init(void) 277 | { 278 | int ret = 0; 279 | 280 | msg_init(); 281 | 282 | ret = connector_init(); 283 | if (ret) { 284 | PRINTK("init connector error\n"); 285 | goto out; 286 | } 287 | ret = register_kmemcache_bh(); 288 | if (ret) { 289 | PRINTK("register kmemcache bh error\n"); 290 | goto cn_exit; 291 | } 292 | 293 | PRINTK("insert kmod success\n"); 294 | return 0; 295 | 296 | cn_exit: 297 | connector_exit(); 298 | out: 299 | return ret; 300 | } 301 | 302 | static void __exit kmemcache_exit(void) 303 | { 304 | if (sock_info.status == SUCCESS) { 305 | dispatcher_exit(); 306 | timer_exit(); 307 | stop_hash_thread(); 308 | stop_slab_thread(); 309 | workers_exit(); 310 | hash_exit(); 311 | slabs_exit(); 312 | stats_exit(); 313 | caches_info_exit(); 314 | settings_exit(); 315 | oom_exit(); 316 | 317 | PRINTK("stop server success\n"); 318 | } else if (sock_info.status != FAILURE) { 319 | unregister_kmemcache_bh(); 320 | } 321 | connector_exit(); 322 | PRINTK("remove kmod success\n"); 323 | } 324 | 325 | module_init(kmemcache_init); 326 | module_exit(kmemcache_exit); 327 | 328 | MODULE_AUTHOR("Li Jianguo "); 329 | MODULE_DESCRIPTION("kmemcache"); 330 | MODULE_LICENSE("GPL v2"); 331 | -------------------------------------------------------------------------------- /kmod/mc_messenger.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_MESSENGER_H 2 | #define __MC_MESSENGER_H 3 | 4 | #include 5 | 6 | struct proto_operations; 7 | struct conn_req; 8 | 9 | struct simpbuf { 10 | struct buffer _buf; 11 | char *buf; 12 | char *cur; 13 | int len; 14 | int bytes; 15 | }; 16 | 17 | struct kvecbuf { 18 | struct buffer _buf; 19 | struct kvec *iov; 20 | int iovsize; /* number of elements allocated in iov[] */ 21 | int iovused; /* number of elements used in iov[] */ 22 | }; 23 | 24 | struct msghdrbuf { 25 | struct buffer _buf; 26 | struct msghdr *msglist; 27 | int msgsize; /* number of elements allocated in msglist[] */ 28 | int msgused; /* number of elements used in msglist[] */ 29 | int msgcurr; /* element in msglist[] being transmitted now */ 30 | int msgbytes; /* number of bytes in current msg */ 31 | }; 32 | 33 | struct ilistbuf { 34 | struct buffer _buf; 35 | item **ilist; /* list of items to write out */ 36 | int isize; 37 | item **icurr; 38 | int ileft; 39 | }; 40 | 41 | struct slistbuf { 42 | struct buffer _buf; 43 | char **suffixlist; 44 | int suffixsize; 45 | char **suffixcurr; 46 | int suffixleft; 47 | }; 48 | 49 | /* socket event bit flags */ 50 | #define EV_READ 2 /* read event, not used */ 51 | #define EV_WRITE 3 /* write event, note used */ 52 | #define EV_RDWR 4 /* 1 for READ, 0 for WRITE */ 53 | #define EV_BUSY 5 /* in process */ 54 | #define EV_CLOSE 7 /* sock closing */ 55 | #define EV_DEAD 9 /* about to free */ 56 | 57 | #define CONN_READ (POLLIN | POLLRDNORM | POLLRDBAND) 58 | #define CONN_WRITE (POLLOUT | POLLWRNORM | POLLWRBAND) 59 | 60 | struct conn { 61 | unsigned long event; 62 | atomic_t nref; 63 | struct worker_storage *who; 64 | struct work_struct work; 65 | struct list_head list; 66 | 67 | struct socket *sock; 68 | sasl_conn_t *sasl_conn; 69 | bin_substate_t substate; 70 | conn_state_t state; 71 | net_transport_t transport; 72 | 73 | const struct proto_operations *proto_ops; 74 | #define cn_protocol proto_ops->proto 75 | 76 | struct simpbuf _rbuf; 77 | #define cn_rbuf _rbuf.buf /* read commands into */ 78 | #define cn_rcurr _rbuf.cur /* parse commands here */ 79 | #define cn_rsize _rbuf.len /* sizeof cn_rbuf */ 80 | #define cn_rbytes _rbuf.bytes /* sizeof of unparsed form cn_rcurr */ 81 | 82 | struct simpbuf _wbuf; 83 | #define cn_wbuf _wbuf.buf 84 | #define cn_wcurr _wbuf.cur 85 | #define cn_wsize _wbuf.len 86 | #define cn_wbytes _wbuf.bytes 87 | 88 | /* which state to go into after finishing current write */ 89 | conn_state_t write_and_go; 90 | /* free this memory after finishing writing */ 91 | struct buffer write_and_free; 92 | 93 | /* when we read in an item's value, it goes here */ 94 | char *ritem; 95 | int rlbytes; 96 | 97 | /* data for the nread state */ 98 | 99 | /* 100 | * item is used to hold an item structure created after reading the command 101 | * line of set/add/replace commands, but before we finished reading the actual 102 | * data. The data is read into ITEM_data(item) to avoid extra copying. 103 | */ 104 | 105 | /* for commands set/add/replace */ 106 | void *item; 107 | 108 | /* data for the swallow state */ 109 | int sbytes; 110 | 111 | /* data for the mwrite state */ 112 | struct kvecbuf _kvecbuf; 113 | #define cn_iov _kvecbuf.iov 114 | #define cn_iovsize _kvecbuf.iovsize 115 | #define cn_iovused _kvecbuf.iovused 116 | 117 | struct msghdrbuf _msghdrbuf; 118 | #define cn_msglist _msghdrbuf.msglist 119 | #define cn_msgsize _msghdrbuf.msgsize 120 | #define cn_msgused _msghdrbuf.msgused 121 | #define cn_msgcurr _msghdrbuf.msgcurr 122 | #define cn_msgbytes _msghdrbuf.msgbytes 123 | 124 | struct ilistbuf _ilistbuf; 125 | #define cn_ilist _ilistbuf.ilist 126 | #define cn_isize _ilistbuf.isize 127 | #define cn_icurr _ilistbuf.icurr 128 | #define cn_ileft _ilistbuf.ileft 129 | 130 | struct slistbuf _slistbuf; 131 | #define cn_suffixlist _slistbuf.suffixlist 132 | #define cn_suffixsize _slistbuf.suffixsize 133 | #define cn_suffixcurr _slistbuf.suffixcurr 134 | #define cn_suffixleft _slistbuf.suffixleft 135 | 136 | /* for UDP clients */ 137 | int request_id; /* incoming UDP request ID */ 138 | struct sockaddr request_addr; /* who sent the most recent request */ 139 | size_t request_addr_size; 140 | unsigned char *hdrbuf; /* udp packet headers */ 141 | int hdrsize; /* number of headers' worth of space */ 142 | 143 | u8 noreply; /* the reply should be sent? */ 144 | /* current stats command */ 145 | struct buffer stats; 146 | size_t offset; 147 | size_t stats_len; 148 | 149 | /* binary protocol stuff */ 150 | protocol_binary_request_header bin_header; 151 | u64 cas; /* the cas to return */ 152 | short cmd; /* current command being processed */ 153 | int opaque; 154 | int keylen; 155 | }; 156 | 157 | extern struct kmem_cache *conn_cachep; 158 | 159 | conn* mc_conn_new(struct conn_req *rq); 160 | void mc_conn_close(conn *c); 161 | void mc_conn_cleanup(conn *c); 162 | conn* mc_conn_get(conn *c); 163 | void mc_conn_put(conn *c); 164 | void mc_queue_conn(conn *c); 165 | void mc_requeue_conn(conn *c); 166 | int update_event(conn *c, int flag); 167 | 168 | void worker_set_sock_callbacks(struct socket *sock, conn *c); 169 | int mc_recvfrom(struct socket *sock, void *buf, size_t len, int flags, 170 | struct sockaddr *addr, size_t *addrlen); 171 | int mc_recv(struct socket *sock, void *buf, size_t len); 172 | int mc_send(struct socket *sock, void *buf, size_t len); 173 | int mc_sendmsg(struct socket *sock, struct msghdr *msg); 174 | int mc_s2clog(struct socket *sock, int type); 175 | 176 | static inline int realloc_simpbuf(struct simpbuf *sbuf, 177 | size_t len, size_t valid, int move) 178 | { 179 | int res = 0; 180 | struct buffer *buf = &sbuf->_buf; 181 | 182 | if (move && sbuf->cur != sbuf->buf) 183 | memmove(sbuf->buf, sbuf->cur, sbuf->bytes); 184 | if (realloc_buffer(buf, len, valid, 0)) { 185 | res = -ENOMEM; 186 | } else { 187 | BUFFER_PTR(buf, sbuf->buf); 188 | sbuf->len = len; 189 | } 190 | if (move) { 191 | sbuf->cur = sbuf->buf; 192 | } 193 | 194 | return res; 195 | } 196 | 197 | static inline int realloc_ilistbuf(struct ilistbuf *ibuf, 198 | size_t len, size_t valid) 199 | { 200 | int res = 0; 201 | struct buffer *buf = &ibuf->_buf; 202 | 203 | if (realloc_buffer(buf, 204 | len * sizeof(item *), 205 | valid * sizeof(item *), 206 | 0)) { 207 | res = -ENOMEM; 208 | } else { 209 | BUFFER_PTR(buf, ibuf->ilist); 210 | ibuf->isize = len; 211 | } 212 | 213 | return res; 214 | } 215 | 216 | static inline int realloc_slistbuf(struct slistbuf *sbuf, 217 | size_t len, size_t valid) 218 | { 219 | int res = 0; 220 | struct buffer *buf = &sbuf->_buf; 221 | 222 | if (realloc_buffer(buf, 223 | len * sizeof(char *), 224 | valid * sizeof(char *), 225 | 0)) { 226 | res = -ENOMEM; 227 | } else { 228 | BUFFER_PTR(buf, sbuf->suffixlist); 229 | sbuf->suffixsize = len; 230 | } 231 | 232 | return res; 233 | } 234 | 235 | static inline int realloc_msghdrbuf(struct msghdrbuf *mbuf, 236 | size_t len, size_t valid) 237 | { 238 | int res = 0; 239 | struct buffer *buf = &mbuf->_buf; 240 | 241 | if (realloc_buffer(buf, 242 | len * sizeof(struct msghdr), 243 | valid * sizeof(struct msghdr), 244 | 0)) { 245 | res = -ENOMEM; 246 | } else { 247 | BUFFER_PTR(buf, mbuf->msglist); 248 | mbuf->msgsize = len; 249 | } 250 | 251 | return res; 252 | } 253 | 254 | static inline int realloc_kvecbuf(struct kvecbuf *vbuf, 255 | size_t len, size_t valid) 256 | { 257 | int res = 0; 258 | struct buffer *buf = &vbuf->_buf; 259 | 260 | if (realloc_buffer(buf, 261 | len * sizeof(struct kvec), 262 | valid * sizeof(struct kvec), 263 | 0)) { 264 | res = -ENOMEM; 265 | } else { 266 | BUFFER_PTR(buf, vbuf->iov); 267 | vbuf->iovsize = len; 268 | } 269 | 270 | return res; 271 | } 272 | 273 | #endif /* __MC_MESSENGER_H */ 274 | -------------------------------------------------------------------------------- /kmod/mc_msg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "config.h" 4 | #include "mc_msg.h" 5 | 6 | #define MSG_ENTRY(type, msg) (msg) 7 | 8 | char* s2c_msg[MSG_MAX] __read_mostly = { 9 | MSG_ENTRY(MSG_SYS_CONNS, "ERROR Too many open connections\r\n"), 10 | MSG_ENTRY(MSG_SYS_SHUT, "ERROR: shutdown not enabled"), 11 | MSG_ENTRY(MSG_SYS_BUSY, "BUSY currently processing reassign request"), 12 | MSG_ENTRY(MSG_SYS_BADCLS, "BADCLASS invalid src or dst class id"), 13 | MSG_ENTRY(MSG_SYS_NOSPACE, "NOSPARE source class has no spare pages"), 14 | MSG_ENTRY(MSG_SYS_SAMECLS, "SAME src and dst class are identical"), 15 | MSG_ENTRY(MSG_SYS_VERSION, VERSION_STRING), 16 | 17 | MSG_ENTRY(MSG_SER_OOM, "SERVER_ERROR out of memory"), 18 | MSG_ENTRY(MSG_SER_OOM_STAT, "SERVER_ERROR out of memory writing stats"), 19 | MSG_ENTRY(MSG_SER_OOM_RREQ, "SERVER_ERROR out of memory reading request"), 20 | MSG_ENTRY(MSG_SER_OOM_WRES, "SERVER_ERROR out of memory writing get response"), 21 | MSG_ENTRY(MSG_SER_OOM_PRES, "SERVER_ERROR out of memory preparing response"), 22 | MSG_ENTRY(MSG_SER_OOM_SOBJ, "SERVER_ERROR out of memory storing object"), 23 | MSG_ENTRY(MSG_SER_OOM_CAS, "SERVER_ERROR out of memory making CAS suffix"), 24 | MSG_ENTRY(MSG_SER_MUL_PACK, "SERVER_ERROR multi-packet request not supported"), 25 | MSG_ENTRY(MSG_SER_LAROBJ, "SERVER_ERROR object too large for cache"), 26 | MSG_ENTRY(MSG_SER_STYPE, "SERVER_ERROR Unhandled storage type."), 27 | MSG_ENTRY(MSG_SER_LNGOUT, "SERVER_ERROR output line too long"), 28 | 29 | MSG_ENTRY(MSG_BIN_AUTHED, "Authenticated"), 30 | MSG_ENTRY(MSG_BIN_OOM, "Out of memory"), 31 | MSG_ENTRY(MSG_BIN_NCMD, "Unknown command"), 32 | MSG_ENTRY(MSG_BIN_NFD, "Not found"), 33 | MSG_ENTRY(MSG_BIN_NARG, "Invalid arguments"), 34 | MSG_ENTRY(MSG_BIN_XKEY, "Data exists for key."), 35 | MSG_ENTRY(MSG_BIN_LARG, "Too large."), 36 | MSG_ENTRY(MSG_BIN_NNUM, "Non-numeric server-side value for incr or decr"), 37 | MSG_ENTRY(MSG_BIN_NSTO, "Not stored."), 38 | MSG_ENTRY(MSG_BIN_AUTH, "Auth failure."), 39 | MSG_ENTRY(MSG_BIN_UHND, "UNHANDLED ERROR"), 40 | MSG_ENTRY(MSG_BIN_UKNW, "Unknown error"), 41 | 42 | MSG_ENTRY(MSG_TXT_OK, "OK"), 43 | MSG_ENTRY(MSG_TXT_RESET, "RESET"), 44 | MSG_ENTRY(MSG_TXT_ERROR, "ERROR"), 45 | MSG_ENTRY(MSG_TXT_STORED, "STORED"), 46 | MSG_ENTRY(MSG_TXT_EXISTS, "EXISTS"), 47 | MSG_ENTRY(MSG_TXT_TOUCHED, "TOUCHED"), 48 | MSG_ENTRY(MSG_TXT_DELETED, "DELETED"), 49 | MSG_ENTRY(MSG_TXT_NFOUND, "NOT_FOUND"), 50 | MSG_ENTRY(MSG_TXT_NSTORED, "NOT_STORED"), 51 | 52 | MSG_ENTRY(MSG_TXT_BAD_CHUNK, "CLIENT_ERROR bad data chunk"), 53 | MSG_ENTRY(MSG_TXT_BAD_CMDLIN, "CLIENT_ERROR bad command line"), 54 | MSG_ENTRY(MSG_TXT_BAD_CMDFMT, "CLIENT_ERROR bad command line format"), 55 | MSG_ENTRY(MSG_TXT_BAD_CMDUSG, "CLIENT_ERROR bad command line format. Usage: delete [noreply]"), 56 | MSG_ENTRY(MSG_TXT_ILL_SLAB, "CLIENT_ERROR Illegal slab id"), 57 | MSG_ENTRY(MSG_TXT_ILL_TIME, "CLIENT_ERROR invalid exptime argument"), 58 | MSG_ENTRY(MSG_TXT_ILL_NUM, "CLIENT_ERROR invalid numeric delta argument"), 59 | MSG_ENTRY(MSG_TXT_USG_STAT, "CLIENT_ERROR usage: stats detail on|off|dump"), 60 | MSG_ENTRY(MSG_TXT_CRE_VAL, "CLIENT_ERROR cannot increment or decrement non-numeric value"), 61 | MSG_ENTRY(MSG_TXT_SLAB_DIS, "CLIENT_ERROR slab reassignment disabled"), 62 | }; 63 | 64 | u8 s2c_len[MSG_MAX] __read_mostly; 65 | 66 | void msg_init(void) 67 | { 68 | int i; 69 | 70 | for (i = 0; i < MSG_MAX; i++) { 71 | s2c_len[i] = strlen(s2c_msg[i]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /kmod/mc_msg.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_MSG_H 2 | #define __MC_MSG_H 3 | 4 | enum { 5 | MSG_SYS_CONNS =0, 6 | MSG_SYS_SHUT, 7 | MSG_SYS_BUSY, 8 | MSG_SYS_BADCLS, 9 | MSG_SYS_NOSPACE, 10 | MSG_SYS_SAMECLS, 11 | MSG_SYS_VERSION, 12 | 13 | MSG_SER_OOM, 14 | MSG_SER_OOM_STAT, 15 | MSG_SER_OOM_RREQ, 16 | MSG_SER_OOM_WRES, 17 | MSG_SER_OOM_PRES, 18 | MSG_SER_OOM_SOBJ, 19 | MSG_SER_OOM_CAS, 20 | MSG_SER_MUL_PACK, 21 | MSG_SER_LAROBJ, 22 | MSG_SER_STYPE, 23 | MSG_SER_LNGOUT, 24 | 25 | MSG_BIN_AUTHED, 26 | MSG_BIN_OOM, 27 | MSG_BIN_NCMD, 28 | MSG_BIN_NFD, 29 | MSG_BIN_NARG, 30 | MSG_BIN_XKEY, 31 | MSG_BIN_LARG, 32 | MSG_BIN_NNUM, 33 | MSG_BIN_NSTO, 34 | MSG_BIN_AUTH, 35 | MSG_BIN_UHND, 36 | MSG_BIN_UKNW, 37 | 38 | MSG_TXT_OK, 39 | MSG_TXT_RESET, 40 | MSG_TXT_ERROR, 41 | MSG_TXT_STORED, 42 | MSG_TXT_EXISTS, 43 | MSG_TXT_TOUCHED, 44 | MSG_TXT_DELETED, 45 | MSG_TXT_NFOUND, 46 | MSG_TXT_NSTORED, 47 | MSG_TXT_BAD_CHUNK, 48 | MSG_TXT_BAD_CMDLIN, 49 | MSG_TXT_BAD_CMDFMT, 50 | MSG_TXT_BAD_CMDUSG, 51 | MSG_TXT_ILL_SLAB, 52 | MSG_TXT_ILL_TIME, 53 | MSG_TXT_ILL_NUM, 54 | MSG_TXT_USG_STAT, 55 | MSG_TXT_CRE_VAL, 56 | MSG_TXT_SLAB_DIS, 57 | 58 | MSG_MAX 59 | }; 60 | 61 | extern char* s2c_msg[MSG_MAX]; 62 | extern u8 s2c_len[MSG_MAX]; 63 | 64 | extern void msg_init(void); 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /kmod/mc_oom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "mc.h" 4 | 5 | /* must run in process context */ 6 | static int mc_oom_notify(struct notifier_block *nb, unsigned long dummy, void *v) 7 | { 8 | unsigned long *freed = v; 9 | 10 | /* simple treatment now */ 11 | *freed += 1; 12 | 13 | return NOTIFY_OK; 14 | } 15 | 16 | static struct notifier_block mc_oom_nb = { 17 | .notifier_call = mc_oom_notify, 18 | }; 19 | 20 | int oom_init(void) 21 | { 22 | return register_oom_notifier(&mc_oom_nb); 23 | } 24 | 25 | void oom_exit(void) 26 | { 27 | unregister_oom_notifier(&mc_oom_nb); 28 | } 29 | -------------------------------------------------------------------------------- /kmod/mc_oom.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_OOM_H 2 | #define __MC_OOM_H 3 | 4 | #ifdef CONFIG_OOM 5 | extern int oom_init(void); 6 | extern void oom_exit(void); 7 | #else 8 | static inline int oom_init(void) { return 0; } 9 | static inline void oom_exit(void) { } 10 | #endif 11 | 12 | #endif /* __MC_OOM_H */ 13 | -------------------------------------------------------------------------------- /kmod/mc_proto.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_PROTO_H 2 | #define __MC_PROTO_H 3 | 4 | #include "mc_proto_bin.h" 5 | 6 | struct proto_operations { 7 | protocol_t proto; 8 | 9 | void (*dispatch)(conn *c, char *cmd); 10 | void (*complete_nread)(conn *c); 11 | void (*append_stats)(const char *key, u16 klen, 12 | const char *val, u32 vlen, conn *c); 13 | }; 14 | 15 | extern const struct proto_operations bin_proto_ops; 16 | extern const struct proto_operations txt_proto_ops; 17 | extern const struct proto_operations def_proto_ops; 18 | 19 | extern struct kmem_cache *suffix_cachep; 20 | 21 | static inline void* _suffix_new(void) 22 | { 23 | return kmem_cache_alloc(suffix_cachep, GFP_KERNEL); 24 | } 25 | 26 | static inline void _suffix_free(void *objp) 27 | { 28 | kmem_cache_free(suffix_cachep, objp); 29 | } 30 | 31 | void mc_out_string(conn *c, const char *str, size_t len); 32 | #define OSTRING(c, type) \ 33 | do { \ 34 | mc_out_string((c), \ 35 | s2c_msg[(type)], \ 36 | s2c_len[(type)]); \ 37 | } while (0) 38 | 39 | //mc_out_string((c), s2c_msg[(type)], s2c_len[(type)]); 40 | delta_result_t mc_do_add_delta(conn *c, const char *key, size_t nkey, u8 incr, 41 | s64 delta, char *buf, u64 *cas, u32 hv); 42 | store_item_t mc_do_store_item(item *item, int comm, conn* c, u32 hv); 43 | void mc_append_stat(const char *name, add_stat_fn f, 44 | conn *c, const char *fmt, ...); 45 | void mc_append_stats(const char *key, const u16 klen, const char *val, 46 | u32 vlen, const void *cookie); 47 | void mc_server_stats(add_stat_fn f, conn *c); 48 | void conn_set_state(conn *c, conn_state_t state); 49 | void mc_stat_settings(add_stat_fn f, void *c); 50 | void write_and_free(conn *c, struct buffer *buf, int bytes); 51 | int mc_add_msghdr(conn *c); 52 | int mc_build_udp_headers(conn *c); 53 | int mc_add_iov(conn *c, const void *buf, int len); 54 | void mc_worker_machine(conn *c); 55 | 56 | #endif /* __MC_PROTO_H */ 57 | -------------------------------------------------------------------------------- /kmod/mc_sasl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "mc.h" 4 | 5 | static void* mc_sasl_dispose_callback(struct cn_msg *msg, 6 | struct netlink_skb_parms *pm) 7 | { 8 | return msg; 9 | } 10 | 11 | void mc_sasl_dispose(sasl_conn_t **pconn) 12 | { 13 | size_t size; 14 | struct cn_msg *msg; 15 | sasl_dispose_t *data; 16 | void *out; 17 | 18 | size = sizeof(struct cn_msg) + sizeof(sasl_dispose_t); 19 | 20 | msg = kmalloc(size, GFP_KERNEL); 21 | if (!msg) { 22 | PRINTK("alloc sasl dispose msg error\n"); 23 | goto out; 24 | } 25 | msg->id.idx = CN_IDX_SASL_DIS; 26 | msg->id.val = mc_get_unique_val(); 27 | 28 | data = (sasl_dispose_t *)msg->data; 29 | data->pconn = pconn; 30 | 31 | if (mc_add_callback(&msg->id, mc_sasl_dispose_callback)) { 32 | PRINTK("add sasl dispose callback error\n"); 33 | goto free_id; 34 | } 35 | if (IS_ERR((out = mc_send_msg_sync(msg)))) { 36 | PRINTK("send sasl dispose error\n"); 37 | } 38 | if (out) { 39 | mc_del_callback(&msg->id); 40 | } 41 | 42 | free_id: 43 | mc_put_unique_val(msg->id.val); 44 | kfree(msg); 45 | out: 46 | return; 47 | } 48 | -------------------------------------------------------------------------------- /kmod/mc_slabs.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_SLAB_H 2 | #define __MC_SLAB_H 3 | 4 | /* percent of totalram that slabs could use */ 5 | extern unsigned long slabsize; 6 | 7 | /* init the subsystem */ 8 | extern int slabs_init(size_t limit, int factor_nume, int factor_deno, bool prealloc); 9 | 10 | /* clear up the subsystem */ 11 | extern void slabs_exit(void); 12 | 13 | /* figures out which slab class (chunk size) is required */ 14 | extern unsigned int mc_slabs_clsid(size_t size); 15 | 16 | /* allocate object of given length */ 17 | extern void* mc_slabs_alloc(size_t size, unsigned int id); 18 | 19 | /* free previously allocated object */ 20 | extern void mc_slabs_free(void *ptr, size_t size, unsigned int id); 21 | 22 | /* adjust the stats for memory requested */ 23 | extern void mc_slabs_adjust_mem_requested(unsigned int id, size_t old, size_t ntotal); 24 | 25 | /* return a datum for stats in binary protocol */ 26 | extern int mc_get_stats(const char *stat_type, int nkey, add_stat_fn f, void *c); 27 | 28 | /* fill buffer with stats */ 29 | extern void mc_slabs_stats(add_stat_fn f, void *c); 30 | 31 | #define REASSIGN_OK 0x0 32 | #define REASSIGN_RUNNING 0x1 33 | #define REASSIGN_BADCLASS 0x2 34 | #define REASSIGN_NOSPACE 0x3 35 | #define REASSIGN_SRC_DST_SAME 0x4 36 | 37 | #define SLAB_TIMER_ACTIVE 0 38 | 39 | struct slab_rebal { 40 | unsigned long flags; 41 | struct task_struct *tsk; 42 | wait_queue_head_t wq; 43 | struct mutex lock; 44 | 45 | void *slab_start; 46 | void *slab_end; 47 | void *slab_pos; 48 | int s_clsid; 49 | int d_clsid; 50 | int busy_items; 51 | unsigned int done:16; 52 | unsigned int signal:16; 53 | }; 54 | 55 | extern struct slab_rebal slab_rebal; 56 | 57 | extern int start_slab_thread(void); 58 | extern void stop_slab_thread(void); 59 | 60 | extern int mc_slabs_reassign(int src, int dst); 61 | 62 | static inline void mc_slabs_rebalancer_pause(void) 63 | { 64 | if (settings.slab_reassign) 65 | mutex_lock(&slab_rebal.lock); 66 | } 67 | 68 | static inline void mc_slabs_rebalancer_resume(void) 69 | { 70 | if (settings.slab_reassign) 71 | mutex_unlock(&slab_rebal.lock); 72 | } 73 | 74 | #endif /* __MC_SLAB_H */ 75 | -------------------------------------------------------------------------------- /kmod/mc_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Detailed statistics management. For simple stats like total number of 3 | * "get" requests, we use inline code in memcached.c, but when 4 | * stats detail mode is activated, the code here records more information. 5 | */ 6 | 7 | #include 8 | #include "mc.h" 9 | 10 | struct stats stats; 11 | 12 | #define PREFIX_HASH_SIZE 256 13 | 14 | struct kmem_cache *prefix_cachep; 15 | 16 | /* lock for prefix_stats */ 17 | static DEFINE_MUTEX(prefix_stats_lock); 18 | 19 | static prefix_stats_t *prefix_stats[PREFIX_HASH_SIZE]; 20 | static int num_prefixes = 0; 21 | static int total_prefix_size = 0; 22 | 23 | /** 24 | * cleans up all our previously collected stats. 25 | * 26 | * NOTE: caller must hold the stats lock. 27 | */ 28 | static void mc_stats_prefix_clear(void) 29 | { 30 | int i; 31 | 32 | for (i = 0; i < PREFIX_HASH_SIZE; i++) { 33 | prefix_stats_t *cur, *next; 34 | for (cur = prefix_stats[i]; cur; cur = next) { 35 | next = cur->next; 36 | kfree(cur->prefix); 37 | kmem_cache_free(prefix_cachep, cur); 38 | } 39 | prefix_stats[i] = NULL; 40 | } 41 | num_prefixes = 0; 42 | total_prefix_size = 0; 43 | } 44 | 45 | /** 46 | * returns the stats structure for a prefix, creating it if it's 47 | * not already in the list. 48 | * 49 | * NOTE: caller must hold the stats lock. 50 | */ 51 | static prefix_stats_t* mc_stats_prefix_find(const char *key, size_t nkey) 52 | { 53 | prefix_stats_t *pfs; 54 | u32 hashval; 55 | size_t len; 56 | int bailout = 1; 57 | 58 | BUG_ON(!key); 59 | 60 | for (len = 0; len < nkey && key[len] != '\0'; len++) { 61 | if (key[len] == settings.prefix_delimiter) { 62 | bailout = 0; 63 | break; 64 | } 65 | } 66 | 67 | if (bailout) 68 | return NULL; 69 | hashval = hash(key, len, 0) % PREFIX_HASH_SIZE; 70 | 71 | for (pfs = prefix_stats[hashval]; pfs; pfs = pfs->next) { 72 | if (strncmp(pfs->prefix, key, len) == 0) { 73 | return pfs; 74 | } 75 | } 76 | 77 | pfs = kmem_cache_zalloc(prefix_cachep, GFP_KERNEL); 78 | if (!pfs) { 79 | PRINTK("allocate space for stats structure error\n"); 80 | return NULL; 81 | } 82 | pfs->prefix = kmalloc(len + 1, GFP_KERNEL); 83 | if (!pfs->prefix) { 84 | PRINTK("allocate space for prefix of prefix_stats structure error\n"); 85 | kmem_cache_free(prefix_cachep, pfs); 86 | return NULL; 87 | } 88 | 89 | strncpy(pfs->prefix, key, len); 90 | pfs->prefix[len] = '\0'; 91 | pfs->len = len; 92 | pfs->next = prefix_stats[hashval]; 93 | prefix_stats[hashval] = pfs; 94 | 95 | num_prefixes++; 96 | total_prefix_size += len; 97 | 98 | return pfs; 99 | } 100 | 101 | /** 102 | * records a "get" of a key 103 | */ 104 | void mc_stats_prefix_record_get(const char *key, size_t nkey, int is_hit) 105 | { 106 | prefix_stats_t *pfs; 107 | 108 | mutex_lock(&prefix_stats_lock); 109 | pfs = mc_stats_prefix_find(key, nkey); 110 | if (pfs) { 111 | pfs->num_gets++; 112 | if (is_hit) 113 | pfs->num_hits++; 114 | } 115 | mutex_unlock(&prefix_stats_lock); 116 | } 117 | 118 | /** 119 | * records a "delete" of a key 120 | */ 121 | void mc_stats_prefix_record_delete(const char *key, size_t nkey) 122 | { 123 | prefix_stats_t *pfs; 124 | 125 | mutex_lock(&prefix_stats_lock); 126 | pfs = mc_stats_prefix_find(key, nkey); 127 | if (pfs) { 128 | pfs->num_deletes++; 129 | } 130 | mutex_unlock(&prefix_stats_lock); 131 | } 132 | 133 | /** 134 | * records a "set" of a key 135 | */ 136 | void mc_stats_prefix_record_set(const char *key, size_t nkey) 137 | { 138 | prefix_stats_t *pfs; 139 | 140 | mutex_lock(&prefix_stats_lock); 141 | pfs = mc_stats_prefix_find(key, nkey); 142 | if (pfs) { 143 | pfs->num_sets++; 144 | } 145 | mutex_unlock(&prefix_stats_lock); 146 | 147 | } 148 | 149 | /** 150 | * stats in textual form suitable for writing to client. 151 | * 152 | * returns dump size on success, errno otherwise 153 | */ 154 | int mc_stats_prefix_dump(struct buffer *buf) 155 | { 156 | const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n"; 157 | char *dumpstr; 158 | prefix_stats_t *pfs; 159 | int i, pos, res = 0; 160 | size_t size = 0, written = 0, total_written = 0; 161 | 162 | /* 163 | * Figure out how big the buffer needs to be. This is the sum of the 164 | * lengths of the prefixes themselves, plus the size of one copy of 165 | * the per-prefix output with 20-digit values for all the counts, 166 | * plus space for the "END" at the end. 167 | */ 168 | mutex_lock(&prefix_stats_lock); 169 | size = strlen(format) + total_prefix_size + 170 | num_prefixes * (strlen(format) - 2 /* %s */ 171 | + 4 * (20 - 4)) /* %llu replaced by 20-digit num */ 172 | + sizeof("END\r\n"); 173 | res = alloc_buffer(buf, size, 0); 174 | if (res) { 175 | mutex_unlock(&prefix_stats_lock); 176 | PRINTK("can't allocate stats response\n"); 177 | goto out; 178 | } 179 | BUFFER_PTR(buf, dumpstr); 180 | 181 | pos = 0; 182 | for (i = 0; i < PREFIX_HASH_SIZE; i++) { 183 | for (pfs = prefix_stats[i]; NULL != pfs; pfs = pfs->next) { 184 | written = snprintf(dumpstr + pos, size-pos, format, 185 | pfs->prefix, pfs->num_gets, pfs->num_hits, 186 | pfs->num_sets, pfs->num_deletes); 187 | pos += written; 188 | total_written += written; 189 | BUG_ON(total_written >= size); 190 | } 191 | } 192 | 193 | mutex_unlock(&prefix_stats_lock); 194 | memcpy(dumpstr + pos, "END\r\n", 6); 195 | 196 | res = pos + 5; 197 | out: 198 | return res; 199 | } 200 | 201 | int stats_init(void) 202 | { 203 | /* assuming we start in this state. */ 204 | #ifdef CONFIG_GSLOCK 205 | stats.accepting_conns = 1; 206 | #else 207 | set_bit(STATS_ACCEPT, &stats.flags); 208 | #endif 209 | 210 | return 0; 211 | } 212 | 213 | void stats_exit(void) 214 | { 215 | mutex_lock(&prefix_stats_lock); 216 | mc_stats_prefix_clear(); 217 | mutex_unlock(&prefix_stats_lock); 218 | } 219 | 220 | void mc_stats_reset(void) 221 | { 222 | #ifdef CONFIG_GSLOCK 223 | spin_lock(&stats_lock); 224 | stats.total_items = 0; 225 | stats.total_conns = 0; 226 | stats.rejected_conns = 0; 227 | stats.evictions = 0; 228 | stats.reclaimed = 0; 229 | stats.listen_disabled_num = 0; 230 | spin_unlock(&stats_lock); 231 | #else 232 | ATOMIC32_SET(stats.total_items, 0); 233 | ATOMIC32_SET(stats.total_conns, 0); 234 | ATOMIC64_SET(stats.rejected_conns, 0); 235 | ATOMIC64_SET(stats.evictions, 0); 236 | ATOMIC64_SET(stats.reclaimed, 0); 237 | ATOMIC64_SET(stats.listen_disabled_num, 0); 238 | #endif 239 | 240 | mutex_lock(&prefix_stats_lock); 241 | mc_stats_prefix_clear(); 242 | mutex_unlock(&prefix_stats_lock); 243 | 244 | mc_threadlocal_stats_reset(); 245 | mc_item_stats_reset(); 246 | } 247 | -------------------------------------------------------------------------------- /kmod/mc_strops.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* POSIX isspace */ 9 | static unsigned char __read_mostly type[] = {'\0', '\r', '\n', ' ', '\t', '\f', '\v'}; 10 | #define ISSPACE(c) \ 11 | ({ \ 12 | int i, res = 0; \ 13 | for (i = 0; i < sizeof(type); i++) { \ 14 | if (type[i] == c) { \ 15 | res = 1; \ 16 | break; \ 17 | } \ 18 | } \ 19 | res; \ 20 | }) 21 | 22 | int safe_strtoull(const char *str, u64 *out) 23 | { 24 | char *endptr = NULL; 25 | char temp[65] = {'\0'}; 26 | unsigned long long ull; 27 | 28 | while (*str == ' ') 29 | str++; 30 | if (unlikely(*str == '+')) 31 | str++; 32 | ull = simple_strtoull(str, &endptr, 10); 33 | if (endptr != str && ISSPACE(*endptr)) { 34 | if ((long long)ull < 0 && strchr(str, '-')) { 35 | return -EINVAL; 36 | } 37 | snprintf(temp, 64, "%llu", (u64)ull); 38 | if (!memcmp(temp, str, strlen(temp))) { 39 | *out = ull; 40 | return 0; 41 | } 42 | } 43 | return -EINVAL; 44 | } 45 | 46 | #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32) 47 | static long long __simple_strtoll(const char *cp, char **endp, unsigned int base) 48 | { 49 | if (*cp == '-') 50 | return -simple_strtoull(cp + 1, endp, base); 51 | return simple_strtoull(cp, endp, base); 52 | } 53 | 54 | #define simple_strtoll __simple_strtoll 55 | #endif 56 | 57 | int safe_strtoll(const char *str, s64 *out) 58 | { 59 | char *endptr = NULL; 60 | char temp[65] = {'\0'}; 61 | long long ll; 62 | 63 | while (*str == ' ') 64 | str++; 65 | if (unlikely(*str == '+')) 66 | str++; 67 | ll = simple_strtoll(str, &endptr, 10); 68 | if (endptr != str && ISSPACE(*endptr)) { 69 | snprintf(temp, 64, "%lld", (s64)ll); 70 | if (!memcmp(temp, str, strlen(temp))) { 71 | *out = ll; 72 | return 0; 73 | } 74 | } 75 | return -EINVAL; 76 | } 77 | 78 | int safe_strtoul(const char *str, u32 *out) 79 | { 80 | char *endptr = NULL; 81 | char temp[33] = {'\0'}; 82 | unsigned long ul; 83 | 84 | while (*str == ' ') 85 | str++; 86 | if (unlikely(*str == '+')) 87 | str++; 88 | ul = simple_strtoul(str, &endptr, 10); 89 | if (endptr != str && ISSPACE(*endptr)) { 90 | if ((long)ul < 0 && strchr(str, '-')) { 91 | return -EINVAL; 92 | } 93 | snprintf(temp, 32, "%u", (u32)ul); 94 | if (!memcmp(temp, str, strlen(temp))) { 95 | *out = ul; 96 | return 0; 97 | } 98 | } 99 | return -EINVAL; 100 | } 101 | 102 | int safe_strtol(const char *str, s32 *out) 103 | { 104 | char *endptr = NULL; 105 | char temp[33] = {'\0'}; 106 | long l; 107 | 108 | while (*str == ' ') 109 | str++; 110 | if (unlikely(*str == '+')) 111 | str++; 112 | l = simple_strtol(str, &endptr, 10); 113 | if (endptr != str && ISSPACE(*endptr)) { 114 | snprintf(temp, 32, "%d", (s32)l); 115 | if (!memcmp(temp, str, strlen(temp))) { 116 | *out = l; 117 | return 0; 118 | } 119 | } 120 | return -EINVAL; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /kmod/mc_uparam.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "mc.h" 6 | 7 | struct cn_id cache_bh_id = { 8 | .idx = CN_IDX_CACHE_BH, 9 | .val = CN_VAL_CACHE_BH 10 | }; 11 | 12 | settings_t settings __read_mostly; 13 | parser_sock_t sock_info; 14 | 15 | static void* default_callback(struct cn_msg *msg, 16 | struct netlink_skb_parms *pm) 17 | { 18 | return (void *)1; 19 | } 20 | 21 | static void* settings_init_callback(struct cn_msg *msg, 22 | struct netlink_skb_parms *pm) 23 | { 24 | size_t size; 25 | int pos = 0; 26 | str_t *str = NULL; 27 | char *factor = NULL, *inter = NULL, *domain = NULL; 28 | settings_init_t *data = (settings_init_t *)msg->data; 29 | 30 | if (!data->len) 31 | goto err; 32 | 33 | /* parse slab allocator's factor */ 34 | if (data->flags & SLAB_FACTOR) { 35 | str = (str_t *)(data->data + pos); 36 | factor = kzalloc(str->len + 1, GFP_KERNEL); 37 | if (!factor) { 38 | PRINTK("alloc slab growth factor error\n"); 39 | goto err; 40 | } 41 | memcpy(factor, str->buf, str->len); 42 | pos += str->len + sizeof(str_t); 43 | } 44 | 45 | /* parse listen interface */ 46 | if (data->flags & INET_INTER) { 47 | str = (str_t *)(data->data + pos); 48 | inter = kzalloc(str->len + 1, GFP_KERNEL); 49 | if (!inter) { 50 | PRINTK("alloc listen interface error\n"); 51 | goto free_factor; 52 | } 53 | memcpy(inter, str->buf, str->len); 54 | pos += str->len + sizeof(str_t); 55 | } 56 | 57 | if (data->flags & UNIX_SOCK) { 58 | /* parse unix domain path */ 59 | str = (str_t *)(data->data + pos); 60 | domain = kzalloc(str->len + 1, GFP_KERNEL); 61 | if (!domain) { 62 | PRINTK("alloc unix domain path error\n"); 63 | goto free_inter; 64 | } 65 | memcpy(domain, str->buf, str->len); 66 | 67 | sock_info.flags |= UNIX_SOCK; 68 | sock_info.local = domain; 69 | } else if (data->flags & INET_SOCK) { 70 | /* parse inet socket */ 71 | inet_t *inet = (inet_t *)(data->data + pos); 72 | size = inet->len + sizeof(inet_t); 73 | 74 | sock_info.inet = kmalloc(size, GFP_KERNEL); 75 | if (!sock_info.inet) { 76 | PRINTK("alloc inet socket error\n"); 77 | goto free_inter; 78 | } 79 | memcpy(sock_info.inet, inet, size); 80 | sock_info.flags |= INET_SOCK; 81 | } else { 82 | PRINTK("no sock type defined\n"); 83 | goto free_inter; 84 | } 85 | 86 | /* init struct settings */ 87 | memcpy(&settings, &data->base, sizeof(settings_t)); 88 | settings.factor = factor; 89 | settings.inter = inter; 90 | settings.socketpath = domain; 91 | 92 | return &settings; 93 | 94 | free_inter: 95 | kfree(inter); 96 | free_factor: 97 | kfree(factor); 98 | err: 99 | return ERR_PTR(-ENOMEM); 100 | } 101 | 102 | int settings_init(void) 103 | { 104 | int ret = 0; 105 | void *out; 106 | struct cn_msg msg; 107 | 108 | msg.id.idx = CN_IDX_INIT_SET; 109 | msg.id.val = mc_get_unique_val(); 110 | msg.len = 0; 111 | 112 | ret = mc_add_callback(&msg.id, settings_init_callback, 1); 113 | if (unlikely(ret)) { 114 | PRINTK("add settings init callback error\n"); 115 | goto out; 116 | } 117 | out = mc_send_msg_timeout(&msg, msecs_to_jiffies(timeout * 1000)); 118 | if (IS_ERR_OR_NULL(out)) { 119 | PRINTK("send settings init error\n"); 120 | ret = -EFAULT; 121 | } 122 | 123 | mc_del_callback(&msg.id, 1); 124 | out: 125 | mc_put_unique_val(msg.id.val); 126 | return ret; 127 | } 128 | 129 | void __settings_exit(void) 130 | { 131 | if (sock_info.flags & INET_SOCK) 132 | kfree(sock_info.inet); 133 | } 134 | 135 | void settings_exit(void) 136 | { 137 | kfree(settings.factor); 138 | kfree(settings.inter); 139 | kfree(settings.socketpath); 140 | } 141 | 142 | static void* user_env_callback(struct cn_msg *msg, 143 | struct netlink_skb_parms *pm) 144 | { 145 | void *res = NULL; 146 | ack_env_t *penv = (ack_env_t *)msg->data; 147 | 148 | union { 149 | str_t *_str; 150 | int *_int; 151 | unsigned long *_ul; 152 | } data; 153 | 154 | switch (penv->env) { 155 | case T_MEMD_INITIAL_MALLOC: 156 | data._ul = (unsigned long *)penv->data; 157 | res = (void *)*data._ul; 158 | break; 159 | case T_MEMD_SLABS_LIMIT: 160 | data._int = (int *)penv->data; 161 | res = (void *)(long)*data._int; 162 | break; 163 | case MEMCACHED_PORT_FILENAME: 164 | data._str = (str_t *)penv->data; 165 | if (data._str->len == 0) { 166 | res = NULL; 167 | } else { 168 | res = kzalloc(data._str->len + 1, GFP_KERNEL); 169 | if (!res) { 170 | PRINTK("alloc port_filename env vector error\n"); 171 | break; 172 | } 173 | memcpy(res, data._str->buf, data._str->len); 174 | } 175 | break; 176 | default: 177 | WARN(1, "not define environment: %d\n", penv->env); 178 | break; 179 | } 180 | 181 | return res; 182 | } 183 | 184 | void* user_env(ask_env_t env) 185 | { 186 | int ret; 187 | void *res = NULL; 188 | struct cn_msg *msg; 189 | ask_env_t *penv; 190 | char buf[KMC_V_ASK_ENV]; 191 | 192 | msg = (struct cn_msg *)buf; 193 | penv = (env_t *)msg->data; 194 | 195 | msg->id.idx = CN_IDX_ENV; 196 | msg->id.val = mc_get_unique_val(); 197 | msg->len = sizeof(ask_env_t); 198 | *penv = env; 199 | 200 | ret = mc_add_callback(&msg->id, user_env_callback, 1); 201 | if (unlikely(ret)) { 202 | PRINTK("add user_env callback error\n"); 203 | goto out; 204 | } 205 | res = mc_send_msg_timeout(msg, msecs_to_jiffies(timeout * 1000)); 206 | if (IS_ERR(res)) { 207 | PRINTK("send ask user_env error\n"); 208 | res = NULL; 209 | } 210 | 211 | mc_del_callback(&msg->id, 1); 212 | out: 213 | mc_put_unique_val(msg->id.val); 214 | return res; 215 | } 216 | 217 | void report_cache_bh_status(bool success) 218 | { 219 | int ret = 0; 220 | struct cn_msg *msg; 221 | __s32 *status; 222 | char buf[KMC_V_BH_STATUS]; 223 | 224 | msg = (struct cn_msg *)buf; 225 | status = (__s32 *)msg->data; 226 | 227 | msg->id.idx = CN_IDX_CACHE_BH_STATUS; 228 | msg->id.val = mc_get_unique_val(); 229 | msg->len = sizeof(__s32); 230 | *status = success; 231 | 232 | ret = mc_add_callback(&msg->id, default_callback, 0); 233 | if (unlikely(ret)) { 234 | PRINTK("add report cache bh callback error\n"); 235 | goto out; 236 | } 237 | if (IS_ERR(mc_send_msg(msg))) { 238 | PRINTK("send cache bh status error\n"); 239 | } 240 | 241 | mc_del_callback(&msg->id, 0); 242 | out: 243 | mc_put_unique_val(msg->id.val); 244 | } 245 | 246 | static void try_shutdown(void) 247 | { 248 | char *envp[] = { 249 | "HOME=/", 250 | "TERM=linux", 251 | "PATH=/sbin:/usr/sbin:/bin:/usr/bin", 252 | NULL 253 | }; 254 | char *argv[] = { 255 | "/sbin/rmmod", 256 | "kmemcache", 257 | NULL 258 | }; 259 | 260 | call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); 261 | } 262 | 263 | void shutdown_cmd(void) 264 | { 265 | int ret; 266 | struct cn_msg msg; 267 | 268 | msg.id.idx = CN_IDX_SHUTDOWN; 269 | msg.id.val = mc_get_unique_val(); 270 | msg.len = 0; 271 | 272 | ret = mc_add_callback(&msg.id, default_callback, 1); 273 | if (unlikely(ret)) { 274 | PRINTK("add shutdown callback error\n"); 275 | goto out; 276 | } 277 | mc_send_msg_timeout(&msg, msecs_to_jiffies(timeout * 1000)); 278 | 279 | mc_del_callback(&msg.id, 1); 280 | out: 281 | mc_put_unique_val(msg.id.val); 282 | 283 | try_shutdown(); 284 | } 285 | -------------------------------------------------------------------------------- /kmod/mc_uparam.h: -------------------------------------------------------------------------------- 1 | #ifndef __UPARAM_H 2 | #define __UPARAM_H 3 | 4 | /* msg timeout from umemcached in seconds */ 5 | extern int timeout; 6 | 7 | /* init settings callback return */ 8 | typedef struct { 9 | #define SUCCESS 1 10 | #define FAILURE -1 11 | 12 | __s16 status: 8; 13 | __s16 flags : 8; 14 | 15 | union { 16 | char *local; 17 | inet_t *inet; 18 | }; 19 | } parser_sock_t; 20 | 21 | /* used only once, then free */ 22 | extern parser_sock_t sock_info; 23 | 24 | /* initialize command from umemcached */ 25 | extern struct cn_id cache_bh_id; 26 | 27 | extern int settings_init(void); 28 | extern void __settings_exit(void); 29 | extern void settings_exit(void); 30 | 31 | extern void* user_env(ask_env_t env); 32 | 33 | extern void report_cache_bh_status(bool success); 34 | 35 | extern void shutdown_cmd(void); 36 | 37 | #endif /* __UPARAM_H */ 38 | -------------------------------------------------------------------------------- /kmod/mc_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef __MC_WORKER_H 2 | #define __MC_WORKER_H 3 | 4 | /* stats stored per-slab and per-thread */ 5 | struct slab_stats { 6 | u64 set_cmds; 7 | u64 get_hits; 8 | u64 touch_hits; 9 | u64 delete_hits; 10 | u64 cas_hits; 11 | u64 cas_badval; 12 | u64 incr_hits; 13 | u64 decr_hits; 14 | }; 15 | 16 | /* stats stored per-thread */ 17 | struct thread_stats { 18 | u64 get_cmds; 19 | u64 get_misses; 20 | u64 touch_cmds; 21 | u64 touch_misses; 22 | u64 delete_misses; 23 | u64 incr_misses; 24 | u64 decr_misses; 25 | u64 cas_misses; 26 | u64 bytes_read; 27 | u64 bytes_written; 28 | u64 flush_cmds; 29 | u64 conn_yields; /* # of yields for connections (-R option)*/ 30 | u64 auth_cmds; 31 | u64 auth_errors; 32 | struct slab_stats slab_stats[MAX_SLAB_CLASSES]; 33 | }; 34 | 35 | #define ATOMIC64_SET(ptr, val) atomic64_set((atomic64_t *)&(ptr), (val)) 36 | #define ATOMIC64_INC(ptr) atomic64_inc((atomic64_t *)&(ptr)) 37 | #define ATOMIC64_DEC(ptr) atomic64_dec((atomic64_t *)&(ptr)) 38 | #define ATOMIC64_ADD(ptr, val) atomic64_add((val), (atomic64_t *)&(ptr)) 39 | #define ATOMIC64_SUB(ptr, val) atomic64_sub((val), (atomic64_t *)&(ptr)) 40 | #define ATOMIC64_READ(ptr) atomic64_read((atomic64_t *)&(ptr)) 41 | #define ATOMIC32_SET(ptr, val) atomic_set((atomic_t *)&(ptr), (val)) 42 | #define ATOMIC32_INC(ptr) atomic_inc((atomic_t *)&(ptr)) 43 | #define ATOMIC32_DEC(ptr) atomic_dec((atomic_t *)&(ptr)) 44 | #define ATOMIC32_ADD(ptr, val) atomic_add((val), (atomic_t *)&(ptr)) 45 | #define ATOMIC32_SUB(ptr, val) atomic_sub((val), (atomic_t *)&(ptr)) 46 | #define ATOMIC32_READ(ptr) atomic_read((atomic_t *)&(ptr)) 47 | 48 | /* slaved info storage */ 49 | struct worker_storage { 50 | spinlock_t lock; 51 | struct list_head list; /* conn list */ 52 | 53 | #ifdef CONFIG_SLOCK 54 | spinlock_t slock; /* thread_stats */ 55 | #endif 56 | struct thread_stats stats; 57 | item_lock_t lock_type; 58 | }; 59 | 60 | extern struct worker_storage *storage __percpu; 61 | extern struct workqueue_struct *slaved; 62 | 63 | /* new conn from master */ 64 | struct conn_req { 65 | conn_state_t state; 66 | net_transport_t transport; 67 | struct socket *sock; 68 | int rsize; 69 | struct work_struct work; 70 | }; 71 | extern struct kmem_cache *conn_req_cachep; 72 | 73 | /* items lock for slaved */ 74 | struct lock_xchg_req { 75 | item_lock_t type; 76 | struct work_struct work; 77 | }; 78 | extern struct kmem_cache *lock_xchg_req_cachep; 79 | 80 | static inline void* new_conn_req(void) 81 | { 82 | return kmem_cache_alloc(conn_req_cachep, GFP_ATOMIC); 83 | } 84 | 85 | static inline void free_conn_req(void *obj) 86 | { 87 | kmem_cache_free(conn_req_cachep, obj); 88 | } 89 | 90 | static inline void* new_lock_xchg_req(void) 91 | { 92 | return kmem_cache_alloc(lock_xchg_req_cachep, GFP_KERNEL); 93 | } 94 | 95 | static inline void free_lock_xchg_req(void *obj) 96 | { 97 | kmem_cache_free(lock_xchg_req_cachep, obj); 98 | } 99 | 100 | item* mc_item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes); 101 | int mc_item_cachedump(unsigned int slabs_clsid, unsigned int limit, struct buffer *buf); 102 | void mc_item_flush_expired(void); 103 | item* mc_item_get(const char *key, size_t nkey); 104 | item* mc_item_touch(const char *key, size_t nkey, u32 exptime); 105 | int mc_item_link(item *it); 106 | void mc_item_remove(item *it); 107 | int mc_item_replace(item *it, item *new_it, u32 hv); 108 | void mc_item_stats(add_stat_fn f, void *c); 109 | void mc_item_stats_totals(add_stat_fn f, void *c); 110 | void mc_item_stats_sizes(add_stat_fn f, void *c); 111 | void mc_item_unlink(item *it); 112 | void mc_item_update(item *it); 113 | delta_result_t mc_add_delta(conn *c, const char *key, size_t nkey, 114 | int incr, s64 delta, char *buf, u64 *cas); 115 | store_item_t mc_store_item(item *item, int comm, conn *c); 116 | void mc_item_lock_global(void); 117 | void mc_item_unlock_global(void); 118 | void* mc_item_trylock(u32 hv); 119 | void mc_item_trylock_unlock(void *arg); 120 | void mc_threadlocal_stats_reset(void); 121 | void mc_threadlocal_stats_aggregate(struct thread_stats *stats); 122 | void mc_slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out); 123 | void mc_switch_item_lock_type(item_lock_t type); 124 | int mc_dispatch_conn_udp(struct socket *sock, conn_state_t state, 125 | int rbuflen, int udp); 126 | int mc_dispatch_conn_new(struct socket *sock, conn_state_t state, 127 | int rbuflen, net_transport_t transport); 128 | void mc_conn_work(struct work_struct *work); 129 | int workers_init(void); 130 | void workers_exit(void); 131 | 132 | #endif /* __MC_WORKER_H */ 133 | -------------------------------------------------------------------------------- /t/000_startup.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 17; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | eval { 10 | my $server = start_kmemcache(); 11 | ok($server, "started the server"); 12 | stop_kmemcache(); 13 | }; 14 | is($@, '', 'Basic startup works'); 15 | 16 | eval { 17 | my $server = start_kmemcache("-l fooble"); 18 | stop_kmemcache(); 19 | }; 20 | ok($@, "Died with illegal -l args"); 21 | 22 | eval { 23 | my $server = start_kmemcache("-l 127.0.0.1"); 24 | stop_kmemcache(); 25 | }; 26 | is($@,'', "-l 127.0.0.1 works"); 27 | 28 | eval { 29 | my $server = start_kmemcache('-C'); 30 | my $stats = mem_stats($server->sock, 'settings'); 31 | is('no', $stats->{'cas_enabled'}); 32 | stop_kmemcache(); 33 | }; 34 | is($@, '', "-C works"); 35 | 36 | eval { 37 | my $server = start_kmemcache('-b 8675'); 38 | my $stats = mem_stats($server->sock, 'settings'); 39 | is('8675', $stats->{'tcp_backlog'}); 40 | stop_kmemcache(); 41 | }; 42 | is($@, '', "-b works"); 43 | 44 | foreach my $val ('auto', 'ascii') { 45 | eval { 46 | my $server = start_kmemcache("-B $val"); 47 | my $stats = mem_stats($server->sock, 'settings'); 48 | ok($stats->{'binding_protocol'} =~ /$val/, "$val works"); 49 | stop_kmemcache(); 50 | }; 51 | is($@, '', "$val works"); 52 | } 53 | 54 | # For the binary test, we just verify it starts since we don't have an easy bin client. 55 | eval { 56 | my $server = start_kmemcache("-B binary"); 57 | stop_kmemcache(); 58 | }; 59 | is($@, '', "binary works"); 60 | 61 | eval { 62 | my $server = start_kmemcache("-vv -B auto"); 63 | stop_kmemcache(); 64 | }; 65 | is($@, '', "auto works"); 66 | 67 | eval { 68 | my $server = start_kmemcache("-vv -B ascii"); 69 | stop_kmemcache(); 70 | }; 71 | is($@, '', "ascii works"); 72 | 73 | 74 | # For the binary test, we just verify it starts since we don't have an easy bin client. 75 | eval { 76 | my $server = start_kmemcache("-vv -B binary"); 77 | stop_kmemcache(); 78 | }; 79 | is($@, '', "binary works"); 80 | 81 | 82 | # Should blow up with something invalid. 83 | eval { 84 | my $server = start_kmemcache("-B http"); 85 | stop_kmemcache(); 86 | }; 87 | ok($@, "Died with illegal -B arg."); 88 | 89 | # kmemcache not use '-t' 90 | # Should not allow -t 0 91 | #eval { 92 | # my $server = start_kmemcache("-t 0"); 93 | # stop_kmemcache(); 94 | #}; 95 | #ok($@, "Died with illegal 0 thread count"); 96 | -------------------------------------------------------------------------------- /t/001_64bit.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | $ENV{T_MEMD_INITIAL_MALLOC} = "4294967328"; # 2**32 + 32 , just over 4GB 10 | $ENV{T_MEMD_SLABS_ALLOC} = 0; # don't preallocate slabs 11 | $ENV{T_MEMD_SLABS_LIMIT} = 0; # enable kmod/mc_main.c:slabsize 12 | 13 | my $server = start_kmemcache("-m 4098 -M"); 14 | my $sock = $server->sock; 15 | 16 | my ($stats, $slabs) = @_; 17 | 18 | $stats = mem_stats($sock); 19 | 20 | if ($stats->{'pointer_size'} eq "32") { 21 | plan skip_all => 'Skipping 64-bit tests on 32-bit build'; 22 | stop_kmemcache(); 23 | exit 0; 24 | } else { 25 | plan tests => 6; 26 | } 27 | 28 | is($stats->{'pointer_size'}, 64, "is 64 bit"); 29 | is($stats->{'limit_maxbytes'}, "4297064448", "max bytes is 4098 MB"); 30 | 31 | $slabs = mem_stats($sock, 'slabs'); 32 | is($slabs->{'total_malloced'}, "4294967328", "expected (faked) value of total_malloced"); 33 | is($slabs->{'active_slabs'}, 0, "no active slabs"); 34 | 35 | my $hit_limit = 0; 36 | for (1..5) { 37 | my $size = 400 * 1024; 38 | my $data = "a" x $size; 39 | print $sock "set big$_ 0 0 $size\r\n$data\r\n"; 40 | my $res = <$sock>; 41 | $hit_limit = 1 if $res ne "STORED\r\n"; 42 | } 43 | ok($hit_limit, "hit size limit"); 44 | 45 | $slabs = mem_stats($sock, 'slabs'); 46 | is($slabs->{'active_slabs'}, 1, "1 active slab"); 47 | 48 | stop_kmemcache(); 49 | -------------------------------------------------------------------------------- /t/002_binary-get.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 8; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | my $count = 1; 13 | 14 | foreach my $blob ("mooo\0", "mumble\0\0\0\0\r\rblarg", "\0", "\r") { 15 | my $key = "foo$count"; 16 | my $len = length($blob); 17 | print "len is $len\n"; 18 | print $sock "set $key 0 0 $len\r\n$blob\r\n"; 19 | is(scalar <$sock>, "STORED\r\n", "stored $key"); 20 | mem_get_is($sock, $key, $blob); 21 | $count++; 22 | } 23 | 24 | stop_kmemcache(); 25 | -------------------------------------------------------------------------------- /t/005_bogus-commands.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 1; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | print $sock "boguscommand slkdsldkfjsd\r\n"; 13 | is(scalar <$sock>, "ERROR\r\n", "got error back"); 14 | 15 | stop_kmemcache(); 16 | -------------------------------------------------------------------------------- /t/006_cas.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 43; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | 10 | my $server = start_kmemcache(); 11 | my $sock = $server->sock; 12 | my $sock2 = $server->new_sock; 13 | 14 | my @result; 15 | my @result2; 16 | 17 | ok($sock != $sock2, "have two different connections open"); 18 | 19 | stop_kmemcache(); 20 | 21 | sub check_args { 22 | my ($line, $name) = @_; 23 | 24 | my $svr = start_kmemcache(); 25 | my $s = $svr->sock; 26 | 27 | print $s $line; 28 | is(scalar <$s>, "CLIENT_ERROR bad command line format\r\n", $name); 29 | undef $svr; 30 | stop_kmemcache(); 31 | } 32 | 33 | check_args "cas bad blah 0 0 0\r\n\r\n", "bad flags"; 34 | check_args "cas bad 0 blah 0 0\r\n\r\n", "bad exp"; 35 | check_args "cas bad 0 0 blah 0\r\n\r\n", "bad cas"; 36 | check_args "cas bad 0 0 0 blah\r\n\r\n", "bad size"; 37 | 38 | my $server = start_kmemcache(); 39 | my $sock = $server->sock; 40 | my $sock2 = $server->new_sock; 41 | 42 | # gets foo (should not exist) 43 | print $sock "gets foo\r\n"; 44 | is(scalar <$sock>, "END\r\n", "gets failed"); 45 | 46 | # set foo 47 | print $sock "set foo 0 0 6\r\nbarval\r\n"; 48 | is(scalar <$sock>, "STORED\r\n", "stored barval"); 49 | 50 | # gets foo and verify identifier exists 51 | @result = mem_gets($sock, "foo"); 52 | mem_gets_is($sock,$result[0],"foo","barval"); 53 | 54 | # cas fail 55 | print $sock "cas foo 0 0 6 123\r\nbarva2\r\n"; 56 | is(scalar <$sock>, "EXISTS\r\n", "cas failed for foo"); 57 | 58 | # gets foo - success 59 | @result = mem_gets($sock, "foo"); 60 | mem_gets_is($sock,$result[0],"foo","barval"); 61 | 62 | # cas success 63 | print $sock "cas foo 0 0 6 $result[0]\r\nbarva2\r\n"; 64 | is(scalar <$sock>, "STORED\r\n", "cas success, set foo"); 65 | 66 | # cas failure (reusing the same key) 67 | print $sock "cas foo 0 0 6 $result[0]\r\nbarva2\r\n"; 68 | is(scalar <$sock>, "EXISTS\r\n", "reusing a CAS ID"); 69 | 70 | # delete foo 71 | print $sock "delete foo\r\n"; 72 | is(scalar <$sock>, "DELETED\r\n", "deleted foo"); 73 | 74 | # cas missing 75 | print $sock "cas foo 0 0 6 $result[0]\r\nbarva2\r\n"; 76 | is(scalar <$sock>, "NOT_FOUND\r\n", "cas failed, foo does not exist"); 77 | 78 | # cas empty 79 | print $sock "cas foo 0 0 6 \r\nbarva2\r\n"; 80 | is(scalar <$sock>, "ERROR\r\n", "cas empty, throw error"); 81 | # cant parse barval2\r\n 82 | is(scalar <$sock>, "ERROR\r\n", "error out on barval2 parsing"); 83 | 84 | # set foo1 85 | print $sock "set foo1 0 0 1\r\n1\r\n"; 86 | is(scalar <$sock>, "STORED\r\n", "set foo1"); 87 | # set foo2 88 | print $sock "set foo2 0 0 1\r\n2\r\n"; 89 | is(scalar <$sock>, "STORED\r\n", "set foo2"); 90 | 91 | # gets foo1 check 92 | print $sock "gets foo1\r\n"; 93 | ok(scalar <$sock> =~ /VALUE foo1 0 1 (\d+)\r\n/, "gets foo1 regexp success"); 94 | my $foo1_cas = $1; 95 | is(scalar <$sock>, "1\r\n","gets foo1 data is 1"); 96 | is(scalar <$sock>, "END\r\n","gets foo1 END"); 97 | 98 | # gets foo2 check 99 | print $sock "gets foo2\r\n"; 100 | ok(scalar <$sock> =~ /VALUE foo2 0 1 (\d+)\r\n/,"gets foo2 regexp success"); 101 | my $foo2_cas = $1; 102 | is(scalar <$sock>, "2\r\n","gets foo2 data is 2"); 103 | is(scalar <$sock>, "END\r\n","gets foo2 END"); 104 | 105 | # validate foo1 != foo2 106 | ok($foo1_cas != $foo2_cas,"foo1 != foo2 single-gets success"); 107 | 108 | # multi-gets 109 | print $sock "gets foo1 foo2\r\n"; 110 | ok(scalar <$sock> =~ /VALUE foo1 0 1 (\d+)\r\n/, "validating first set of data is foo1"); 111 | $foo1_cas = $1; 112 | is(scalar <$sock>, "1\r\n", "validating foo1 set of data is 1"); 113 | ok(scalar <$sock> =~ /VALUE foo2 0 1 (\d+)\r\n/, "validating second set of data is foo2"); 114 | $foo2_cas = $1; 115 | is(scalar <$sock>, "2\r\n", "validating foo2 set of data is 2"); 116 | is(scalar <$sock>, "END\r\n","validating foo1,foo2 gets is over - END"); 117 | 118 | # validate foo1 != foo2 119 | ok($foo1_cas != $foo2_cas, "foo1 != foo2 multi-gets success"); 120 | 121 | ### simulate race condition with cas 122 | 123 | # gets foo1 - success 124 | @result = mem_gets($sock, "foo1"); 125 | ok($result[0] != "", "sock - gets foo1 is not empty"); 126 | 127 | # gets foo2 - success 128 | @result2 = mem_gets($sock2, "foo1"); 129 | ok($result2[0] != "","sock2 - gets foo1 is not empty"); 130 | 131 | print $sock "cas foo1 0 0 6 $result[0]\r\nbarva2\r\n"; 132 | print $sock2 "cas foo1 0 0 5 $result2[0]\r\napple\r\n"; 133 | 134 | my $res1 = <$sock>; 135 | my $res2 = <$sock2>; 136 | 137 | ok( ( $res1 eq "STORED\r\n" && $res2 eq "EXISTS\r\n") || 138 | ( $res1 eq "EXISTS\r\n" && $res2 eq "STORED\r\n"), 139 | "cas on same item from two sockets"); 140 | 141 | ### bug 15: http://code.google.com/p/memcached/issues/detail?id=15 142 | 143 | # set foo 144 | print $sock "set bug15 0 0 1\r\n0\r\n"; 145 | is(scalar <$sock>, "STORED\r\n", "stored 0"); 146 | 147 | # Check out the first gets. 148 | print $sock "gets bug15\r\n"; 149 | ok(scalar <$sock> =~ /VALUE bug15 0 1 (\d+)\r\n/, "gets bug15 regexp success"); 150 | my $bug15_cas = $1; 151 | is(scalar <$sock>, "0\r\n", "gets bug15 data is 0"); 152 | is(scalar <$sock>, "END\r\n","gets bug15 END"); 153 | 154 | # Increment 155 | print $sock "incr bug15 1\r\n"; 156 | is(scalar <$sock>, "1\r\n", "incr worked"); 157 | 158 | # Validate a changed CAS 159 | print $sock "gets bug15\r\n"; 160 | ok(scalar <$sock> =~ /VALUE bug15 0 1 (\d+)\r\n/, "gets bug15 regexp success"); 161 | my $next_bug15_cas = $1; 162 | is(scalar <$sock>, "1\r\n", "gets bug15 data is 0"); 163 | is(scalar <$sock>, "END\r\n","gets bug15 END"); 164 | 165 | ok($bug15_cas != $next_bug15_cas, "CAS changed"); 166 | 167 | stop_kmemcache(); 168 | -------------------------------------------------------------------------------- /t/007_dash-M.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache('-M -m 1'); 10 | my $sock = $server->sock; 11 | 12 | my $value = "B" x 8192; 13 | my $vallen = length($value); 14 | 15 | my $resp = "STORED\r\n"; 16 | my $key = 0; 17 | 18 | while($resp eq "STORED\r\n") { 19 | print $sock "set dash$key 0 0 $vallen\r\n$value\r\n"; 20 | $key++; 21 | $resp = scalar <$sock>; 22 | } 23 | 24 | my $max_stored = $key - 1; 25 | 26 | plan tests => $max_stored + 1; 27 | 28 | print $sock "set dash$key 0 0 $vallen\r\n$value\r\n"; 29 | is(scalar <$sock>, "SERVER_ERROR out of memory storing object\r\n", 30 | "failed to add another one."); 31 | 32 | for($key = 0; $key < $max_stored; $key++) { 33 | mem_get_is $sock, "dash$key", $value, "Failed at dash$key"; 34 | } 35 | 36 | stop_kmemcache(); 37 | -------------------------------------------------------------------------------- /t/008_evictions.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # Test the 'stats items' evictions counters. 3 | 4 | use strict; 5 | use Test::More tests => 92; 6 | use FindBin qw($Bin); 7 | use lib "$Bin/lib"; 8 | use MemcachedTest; 9 | 10 | my $server = start_kmemcache("-m 3"); 11 | my $sock = $server->sock; 12 | my $value = "B"x66560; 13 | my $key = 0; 14 | 15 | # These aren't set to expire. 16 | for ($key = 0; $key < 40; $key++) { 17 | print $sock "set key$key 0 0 66560\r\n$value\r\n"; 18 | is(scalar <$sock>, "STORED\r\n", "stored key$key"); 19 | } 20 | 21 | # These ones would expire in 600 seconds. 22 | for ($key = 0; $key < 50; $key++) { 23 | print $sock "set key$key 0 600 66560\r\n$value\r\n"; 24 | is(scalar <$sock>, "STORED\r\n", "stored key$key"); 25 | } 26 | 27 | my $stats = mem_stats($sock, "items"); 28 | my $evicted = $stats->{"items:31:evicted"}; 29 | isnt($evicted, "0", "check evicted"); 30 | my $evicted_nonzero = $stats->{"items:31:evicted_nonzero"}; 31 | isnt($evicted_nonzero, "0", "check evicted_nonzero"); 32 | 33 | stop_kmemcache(); 34 | -------------------------------------------------------------------------------- /t/009_expirations.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 15; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $expire; 12 | 13 | sub wait_for_early_second { 14 | my $have_hires = eval "use Time::HiRes (); 1"; 15 | if ($have_hires) { 16 | my $tsh = Time::HiRes::time(); 17 | my $ts = int($tsh); 18 | return if ($tsh - $ts) < 0.5; 19 | } 20 | 21 | my $ts = int(time()); 22 | while (1) { 23 | my $t = int(time()); 24 | return if $t != $ts; 25 | select undef, undef, undef, 0.10; # 1/10th of a second sleeps until time changes. 26 | } 27 | } 28 | 29 | wait_for_early_second(); 30 | 31 | print $sock "set foo 0 1 6\r\nfooval\r\n"; 32 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 33 | 34 | mem_get_is($sock, "foo", "fooval"); 35 | sleep(1.5); 36 | mem_get_is($sock, "foo", undef); 37 | 38 | $expire = time() - 1; 39 | print $sock "set foo 0 $expire 6\r\nfooval\r\n"; 40 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 41 | mem_get_is($sock, "foo", undef, "already expired"); 42 | 43 | $expire = time() + 1; 44 | print $sock "set foo 0 $expire 6\r\nfoov+1\r\n"; 45 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 46 | mem_get_is($sock, "foo", "foov+1"); 47 | sleep(2.2); 48 | mem_get_is($sock, "foo", undef, "now expired"); 49 | 50 | $expire = time() - 20; 51 | print $sock "set boo 0 $expire 6\r\nbooval\r\n"; 52 | is(scalar <$sock>, "STORED\r\n", "stored boo"); 53 | mem_get_is($sock, "boo", undef, "now expired"); 54 | 55 | print $sock "add add 0 2 6\r\naddval\r\n"; 56 | is(scalar <$sock>, "STORED\r\n", "stored add"); 57 | mem_get_is($sock, "add", "addval"); 58 | # second add fails 59 | print $sock "add add 0 2 7\r\naddval2\r\n"; 60 | is(scalar <$sock>, "NOT_STORED\r\n", "add failure"); 61 | sleep(2.3); 62 | print $sock "add add 0 2 7\r\naddval3\r\n"; 63 | is(scalar <$sock>, "STORED\r\n", "stored add again"); 64 | mem_get_is($sock, "add", "addval3"); 65 | 66 | stop_kmemcache(); 67 | -------------------------------------------------------------------------------- /t/010_flags.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 6; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | # set foo (and should get it) 13 | for my $flags (0, 123, 2**16-1) { 14 | print $sock "set foo $flags 0 6\r\nfooval\r\n"; 15 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 16 | mem_get_is({ sock => $sock, 17 | flags => $flags }, "foo", "fooval", "got flags $flags back"); 18 | } 19 | 20 | stop_kmemcache(); 21 | -------------------------------------------------------------------------------- /t/011_flush-all.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 21; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $expire; 12 | 13 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 14 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 15 | 16 | mem_get_is($sock, "foo", "fooval"); 17 | print $sock "flush_all\r\n"; 18 | is(scalar <$sock>, "OK\r\n", "did flush_all"); 19 | mem_get_is($sock, "foo", undef); 20 | 21 | # Test flush_all with zero delay. 22 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 23 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 24 | 25 | mem_get_is($sock, "foo", "fooval"); 26 | print $sock "flush_all 0\r\n"; 27 | is(scalar <$sock>, "OK\r\n", "did flush_all"); 28 | mem_get_is($sock, "foo", undef); 29 | 30 | # check that flush_all doesn't blow away items that immediately get set 31 | print $sock "set foo 0 0 3\r\nnew\r\n"; 32 | is(scalar <$sock>, "STORED\r\n", "stored foo = 'new'"); 33 | mem_get_is($sock, "foo", 'new'); 34 | 35 | # and the other form, specifying a flush_all time... 36 | my $expire = time() + 2; 37 | print $sock "flush_all $expire\r\n"; 38 | is(scalar <$sock>, "OK\r\n", "did flush_all in future"); 39 | 40 | print $sock "set foo 0 0 4\r\n1234\r\n"; 41 | is(scalar <$sock>, "STORED\r\n", "stored foo = '1234'"); 42 | mem_get_is($sock, "foo", '1234'); 43 | sleep(3); 44 | mem_get_is($sock, "foo", undef); 45 | 46 | print $sock "set foo 0 0 5\r\n12345\r\n"; 47 | is(scalar <$sock>, "STORED\r\n", "stored foo = '12345'"); 48 | mem_get_is($sock, "foo", '12345'); 49 | print $sock "flush_all 86400\r\n"; 50 | is(scalar <$sock>, "OK\r\n", "did flush_all for far future"); 51 | # Check foo still exists. 52 | mem_get_is($sock, "foo", '12345'); 53 | print $sock "set foo2 0 0 5\r\n54321\r\n"; 54 | is(scalar <$sock>, "STORED\r\n", "stored foo2 = '54321'"); 55 | mem_get_is($sock, "foo", '12345'); 56 | mem_get_is($sock, "foo2", '54321'); 57 | 58 | stop_kmemcache(); 59 | -------------------------------------------------------------------------------- /t/012_getset.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 539; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | 10 | my $server = start_kmemcache(); 11 | my $sock = $server->sock; 12 | 13 | 14 | # set foo (and should get it) 15 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 16 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 17 | mem_get_is($sock, "foo", "fooval"); 18 | 19 | # add bar (and should get it) 20 | print $sock "add bar 0 0 6\r\nbarval\r\n"; 21 | is(scalar <$sock>, "STORED\r\n", "stored barval"); 22 | mem_get_is($sock, "bar", "barval"); 23 | 24 | # add foo (but shouldn't get new value) 25 | print $sock "add foo 0 0 5\r\nfoov2\r\n"; 26 | is(scalar <$sock>, "NOT_STORED\r\n", "not stored"); 27 | mem_get_is($sock, "foo", "fooval"); 28 | 29 | # replace bar (should work) 30 | print $sock "replace bar 0 0 6\r\nbarva2\r\n"; 31 | is(scalar <$sock>, "STORED\r\n", "replaced barval 2"); 32 | 33 | # replace notexist (shouldn't work) 34 | print $sock "replace notexist 0 0 6\r\nbarva2\r\n"; 35 | is(scalar <$sock>, "NOT_STORED\r\n", "didn't replace notexist"); 36 | 37 | # delete foo. 38 | print $sock "delete foo\r\n"; 39 | is(scalar <$sock>, "DELETED\r\n", "deleted foo"); 40 | 41 | # delete foo again. not found this time. 42 | print $sock "delete foo\r\n"; 43 | is(scalar <$sock>, "NOT_FOUND\r\n", "deleted foo, but not found"); 44 | 45 | # add moo 46 | # 47 | print $sock "add moo 0 0 6\r\nmooval\r\n"; 48 | is(scalar <$sock>, "STORED\r\n", "stored barval"); 49 | mem_get_is($sock, "moo", "mooval"); 50 | 51 | # check-and-set (cas) failure case, try to set value with incorrect cas unique val 52 | print $sock "cas moo 0 0 6 0\r\nMOOVAL\r\n"; 53 | is(scalar <$sock>, "EXISTS\r\n", "check and set with invalid id"); 54 | 55 | # test "gets", grab unique ID 56 | print $sock "gets moo\r\n"; 57 | # VALUE moo 0 6 3084947704 58 | # 59 | my @retvals = split(/ /, scalar <$sock>); 60 | my $data = scalar <$sock>; # grab data 61 | my $dot = scalar <$sock>; # grab dot on line by itself 62 | is($retvals[0], "VALUE", "get value using 'gets'"); 63 | my $unique_id = $retvals[4]; 64 | # clean off \r\n 65 | $unique_id =~ s/\r\n$//; 66 | ok($unique_id =~ /^\d+$/, "unique ID '$unique_id' is an integer"); 67 | # now test that we can store moo with the correct unique id 68 | print $sock "cas moo 0 0 6 $unique_id\r\nMOOVAL\r\n"; 69 | is(scalar <$sock>, "STORED\r\n"); 70 | mem_get_is($sock, "moo", "MOOVAL"); 71 | 72 | # pipeling is okay 73 | print $sock "set foo 0 0 6\r\nfooval\r\ndelete foo\r\nset foo 0 0 6\r\nfooval\r\ndelete foo\r\n"; 74 | is(scalar <$sock>, "STORED\r\n", "pipeline set"); 75 | is(scalar <$sock>, "DELETED\r\n", "pipeline delete"); 76 | is(scalar <$sock>, "STORED\r\n", "pipeline set"); 77 | is(scalar <$sock>, "DELETED\r\n", "pipeline delete"); 78 | 79 | 80 | # Test sets up to a large size around 1MB. 81 | # Everything up to 1MB - 1k should succeed, everything 1MB +1k should fail. 82 | 83 | my $len = 1024; 84 | while ($len < 1024*1028) { 85 | my $val = "B"x$len; 86 | if ($len > (1024*1024)) { 87 | # Ensure causing a memory overflow doesn't leave stale data. 88 | print $sock "set foo_$len 0 0 3\r\nMOO\r\n"; 89 | is(scalar <$sock>, "STORED\r\n"); 90 | print $sock "set foo_$len 0 0 $len\r\n$val\r\n"; 91 | is(scalar <$sock>, "SERVER_ERROR object too large for cache\r\n", "failed to store size $len"); 92 | mem_get_is($sock, "foo_$len"); 93 | } else { 94 | print $sock "set foo_$len 0 0 $len\r\n$val\r\n"; 95 | is(scalar <$sock>, "STORED\r\n", "stored size $len"); 96 | } 97 | $len += 2048; 98 | } 99 | 100 | stop_kmemcache(); 101 | -------------------------------------------------------------------------------- /t/013_incrdecr.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 23; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | # Bug 21 13 | print $sock "set bug21 0 0 19\r\n9223372036854775807\r\n"; 14 | is(scalar <$sock>, "STORED\r\n", "stored text"); 15 | print $sock "incr bug21 1\r\n"; 16 | is(scalar <$sock>, "9223372036854775808\r\n", "bug21 incr 1"); 17 | print $sock "incr bug21 1\r\n"; 18 | is(scalar <$sock>, "9223372036854775809\r\n", "bug21 incr 2"); 19 | print $sock "decr bug21 1\r\n"; 20 | is(scalar <$sock>, "9223372036854775808\r\n", "bug21 decr"); 21 | 22 | print $sock "set num 0 0 1\r\n1\r\n"; 23 | is(scalar <$sock>, "STORED\r\n", "stored num"); 24 | mem_get_is($sock, "num", 1, "stored 1"); 25 | 26 | print $sock "incr num 1\r\n"; 27 | is(scalar <$sock>, "2\r\n", "+ 1 = 2"); 28 | mem_get_is($sock, "num", 2); 29 | 30 | print $sock "incr num 8\r\n"; 31 | is(scalar <$sock>, "10\r\n", "+ 8 = 10"); 32 | mem_get_is($sock, "num", 10); 33 | 34 | print $sock "decr num 1\r\n"; 35 | is(scalar <$sock>, "9\r\n", "- 1 = 9"); 36 | 37 | print $sock "decr num 9\r\n"; 38 | is(scalar <$sock>, "0\r\n", "- 9 = 0"); 39 | 40 | print $sock "decr num 5\r\n"; 41 | is(scalar <$sock>, "0\r\n", "- 5 = 0"); 42 | 43 | printf $sock "set num 0 0 10\r\n4294967296\r\n"; 44 | is(scalar <$sock>, "STORED\r\n", "stored 2**32"); 45 | 46 | print $sock "incr num 1\r\n"; 47 | is(scalar <$sock>, "4294967297\r\n", "4294967296 + 1 = 4294967297"); 48 | 49 | printf $sock "set num 0 0 %d\r\n18446744073709551615\r\n", length("18446744073709551615"); 50 | is(scalar <$sock>, "STORED\r\n", "stored 2**64-1"); 51 | 52 | print $sock "incr num 1\r\n"; 53 | is(scalar <$sock>, "0\r\n", "(2**64 - 1) + 1 = 0"); 54 | 55 | print $sock "decr bogus 5\r\n"; 56 | is(scalar <$sock>, "NOT_FOUND\r\n", "can't decr bogus key"); 57 | 58 | print $sock "decr incr 5\r\n"; 59 | is(scalar <$sock>, "NOT_FOUND\r\n", "can't incr bogus key"); 60 | 61 | print $sock "set bigincr 0 0 1\r\n0\r\n"; 62 | is(scalar <$sock>, "STORED\r\n", "stored bigincr"); 63 | print $sock "incr bigincr 18446744073709551610\r\n"; 64 | is(scalar <$sock>, "18446744073709551610\r\n"); 65 | 66 | print $sock "set text 0 0 2\r\nhi\r\n"; 67 | is(scalar <$sock>, "STORED\r\n", "stored hi"); 68 | print $sock "incr text 1\r\n"; 69 | is(scalar <$sock>, 70 | "CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 71 | "hi - 1 = 0"); 72 | 73 | stop_kmemcache(); 74 | -------------------------------------------------------------------------------- /t/014_issue_104.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 6; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | # first get should miss 13 | print $sock "get foo\r\n"; 14 | is(scalar <$sock>, "END\r\n", "get foo"); 15 | 16 | # Now set and get (should hit) 17 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 18 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 19 | mem_get_is($sock, "foo", "fooval"); 20 | 21 | my $stats = mem_stats($sock); 22 | is($stats->{cmd_get}, 2, "Should have 2 get requests"); 23 | is($stats->{get_hits}, 1, "Should have 1 hit"); 24 | is($stats->{get_misses}, 1, "Should have 1 miss"); 25 | 26 | stop_kmemcache(); 27 | -------------------------------------------------------------------------------- /t/015_issue_108.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 4; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $key = "del_key"; 12 | 13 | print $sock "add $key 0 0 1\r\nx\r\n"; 14 | is (scalar <$sock>, "STORED\r\n", "Added a key"); 15 | 16 | print $sock "delete $key 0\r\n"; 17 | is (scalar <$sock>, "DELETED\r\n", "Properly deleted with 0"); 18 | 19 | print $sock "add $key 0 0 1\r\nx\r\n"; 20 | is (scalar <$sock>, "STORED\r\n", "Added again a key"); 21 | 22 | print $sock "delete $key 0 noreply\r\n"; 23 | # will not reply, but a subsequent add will succeed 24 | 25 | print $sock "add $key 0 0 1\r\nx\r\n"; 26 | is (scalar <$sock>, "STORED\r\n", "Add succeeded after quiet deletion."); 27 | 28 | stop_kmemcache(); 29 | -------------------------------------------------------------------------------- /t/016_issue_14.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 21; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $value = "B"x66560; 12 | my $key = 0; 13 | 14 | for ($key = 0; $key < 10; $key++) { 15 | print $sock "set key$key 0 2 66560\r\n$value\r\n"; 16 | is (scalar <$sock>, "STORED\r\n", "stored key$key"); 17 | } 18 | 19 | #print $sock "stats slabs" 20 | my $first_stats = mem_stats($sock, "slabs"); 21 | my $first_malloc = $first_stats->{total_malloced}; 22 | 23 | sleep(4); 24 | 25 | for ($key = 10; $key < 20; $key++) { 26 | print $sock "set key$key 0 2 66560\r\n$value\r\n"; 27 | is (scalar <$sock>, "STORED\r\n", "stored key$key"); 28 | } 29 | 30 | my $second_stats = mem_stats($sock, "slabs"); 31 | my $second_malloc = $second_stats->{total_malloced}; 32 | 33 | 34 | is ($second_malloc, $first_malloc, "Memory grows.."); 35 | 36 | stop_kmemcache(); 37 | -------------------------------------------------------------------------------- /t/017_issue_140.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | plan skip_all => 'Fix for Issue 140 was only an illusion'; 10 | 11 | plan tests => 7; 12 | 13 | my $server = start_kmemcache(); 14 | my $sock = $server->sock; 15 | 16 | print $sock "set a 0 0 1\r\na\r\n"; 17 | is (scalar <$sock>, "STORED\r\n", "stored key"); 18 | 19 | my $stats = mem_stats($sock, "items"); 20 | my $age = $stats->{"items:1:age"}; 21 | isnt ($age, "0", "Age should not be zero"); 22 | 23 | print $sock "flush_all\r\n"; 24 | is (scalar <$sock>, "OK\r\n", "items flushed"); 25 | 26 | my $stats = mem_stats($sock, "items"); 27 | my $age = $stats->{"items:1:age"}; 28 | is ($age, undef, "all should be gone"); 29 | 30 | print $sock "set a 0 1 1\r\na\r\n"; 31 | is (scalar <$sock>, "STORED\r\n", "stored key"); 32 | 33 | my $stats = mem_stats($sock, "items"); 34 | my $age = $stats->{"items:1:age"}; 35 | isnt ($age, "0", "Age should not be zero"); 36 | 37 | sleep(3); 38 | 39 | my $stats = mem_stats($sock, "items"); 40 | my $age = $stats->{"items:1:age"}; 41 | is ($age, undef, "all should be gone"); 42 | 43 | stop_kmemcache(); 44 | -------------------------------------------------------------------------------- /t/018_issue_152.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 2; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $key = "a"x251; 12 | 13 | print $sock "set a 1 0 1\r\na\r\n"; 14 | is (scalar <$sock>, "STORED\r\n", "Stored key"); 15 | 16 | print $sock "get a $key\r\n"; 17 | is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n", "illegal key"); 18 | 19 | stop_kmemcache(); 20 | -------------------------------------------------------------------------------- /t/019_issue_163.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 7; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $value1 = "A"x66560; 12 | my $value2 = "B"x66570; 13 | 14 | print $sock "set key 0 1 66560\r\n$value1\r\n"; 15 | is (scalar <$sock>, "STORED\r\n", "stored key"); 16 | 17 | my $stats = mem_stats($sock, "slabs"); 18 | my $requested = $stats->{"31:mem_requested"}; 19 | isnt ($requested, "0", "We should have requested some memory"); 20 | 21 | sleep(3); 22 | print $sock "set key 0 0 66570\r\n$value2\r\n"; 23 | is (scalar <$sock>, "STORED\r\n", "stored key"); 24 | 25 | my $stats = mem_stats($sock, "items"); 26 | my $reclaimed = $stats->{"items:31:reclaimed"}; 27 | is ($reclaimed, "1", "Objects should be reclaimed"); 28 | 29 | print $sock "delete key\r\n"; 30 | is (scalar <$sock>, "DELETED\r\n", "deleted key"); 31 | 32 | print $sock "set key 0 0 66560\r\n$value1\r\n"; 33 | is (scalar <$sock>, "STORED\r\n", "stored key"); 34 | 35 | my $stats = mem_stats($sock, "slabs"); 36 | my $requested2 = $stats->{"31:mem_requested"}; 37 | is ($requested2, $requested, "we've not allocated and freed the same amont"); 38 | 39 | stop_kmemcache(); 40 | -------------------------------------------------------------------------------- /t/020_issue_183.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 5; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | print $sock "set key 0 0 1\r\n1\r\n"; 12 | is (scalar <$sock>, "STORED\r\n", "stored key"); 13 | my $s1 = mem_stats($sock); 14 | my $r1 = $s1->{"reclaimed"}; 15 | is ($r1, "0", "Objects should not be reclaimed"); 16 | sleep(2); 17 | print $sock "flush_all\r\n"; 18 | is (scalar <$sock>, "OK\r\n", "Cache flushed"); 19 | print $sock "set key 0 0 1\r\n1\r\n"; 20 | is (scalar <$sock>, "STORED\r\n", "stored key"); 21 | my $s2 = mem_stats($sock); 22 | my $r2 = $s2->{"reclaimed"}; 23 | is ($r2, "1", "Objects should be reclaimed"); 24 | 25 | stop_kmemcache(); 26 | -------------------------------------------------------------------------------- /t/021_issue_22.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 84; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache("-m 3"); 10 | my $sock = $server->sock; 11 | my $value = "B"x66560; 12 | my $key = 0; 13 | 14 | for ($key = 0; $key < 40; $key++) { 15 | print $sock "set key$key 0 0 66560\r\n$value\r\n"; 16 | is (scalar <$sock>, "STORED\r\n", "stored key$key"); 17 | } 18 | 19 | my $first_stats = mem_stats($sock, "items"); 20 | my $first_evicted = $first_stats->{"items:31:evicted"}; 21 | # I get 1 eviction on a 32 bit binary, but 4 on a 64 binary.. 22 | # Just check that I have evictions... 23 | isnt ($first_evicted, "0", "check evicted"); 24 | 25 | print $sock "stats reset\r\n"; 26 | is (scalar <$sock>, "RESET\r\n", "Stats reset"); 27 | 28 | my $second_stats = mem_stats($sock, "items"); 29 | my $second_evicted = $second_stats->{"items:31:evicted"}; 30 | is ($second_evicted, "0", "check evicted"); 31 | 32 | for ($key = 40; $key < 80; $key++) { 33 | print $sock "set key$key 0 0 66560\r\n$value\r\n"; 34 | is (scalar <$sock>, "STORED\r\n", "stored key$key"); 35 | } 36 | 37 | my $last_stats = mem_stats($sock, "items"); 38 | my $last_evicted = $last_stats->{"items:31:evicted"}; 39 | is ($last_evicted, "40", "check evicted"); 40 | 41 | stop_kmemcache(); 42 | -------------------------------------------------------------------------------- /t/022_issue_29.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 4; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | print $sock "set issue29 0 0 0\r\n\r\n"; 13 | is (scalar <$sock>, "STORED\r\n", "stored issue29"); 14 | 15 | my $first_stats = mem_stats($sock, "slabs"); 16 | my $first_used = $first_stats->{"1:used_chunks"}; 17 | 18 | is(1, $first_used, "Used one"); 19 | 20 | print $sock "set issue29_b 0 0 0\r\n\r\n"; 21 | is (scalar <$sock>, "STORED\r\n", "stored issue29_b"); 22 | 23 | my $second_stats = mem_stats($sock, "slabs"); 24 | my $second_used = $second_stats->{"1:used_chunks"}; 25 | 26 | is(2, $second_used, "Used two"); 27 | 28 | stop_kmemcache(); 29 | -------------------------------------------------------------------------------- /t/023_issue_3.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 8; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $key = "del_key"; 12 | 13 | print $sock "delete $key\r\n"; 14 | is (scalar <$sock>, "NOT_FOUND\r\n", "not found on delete"); 15 | 16 | print $sock "delete $key 10\r\n"; 17 | is (scalar <$sock>, "CLIENT_ERROR bad command line format." 18 | . " Usage: delete [noreply]\r\n", "invalid delete"); 19 | 20 | print $sock "add $key 0 0 1\r\nx\r\n"; 21 | is (scalar <$sock>, "STORED\r\n", "Add before a broken delete."); 22 | 23 | print $sock "delete $key 10 noreply\r\n"; 24 | # Does not reply 25 | # is (scalar <$sock>, "ERROR\r\n", "Even more invalid delete"); 26 | 27 | print $sock "add $key 0 0 1\r\nx\r\n"; 28 | is (scalar <$sock>, "NOT_STORED\r\n", "Failed to add after failed silent delete."); 29 | 30 | print $sock "delete $key noreply\r\n"; 31 | # Will not reply, so let's do a set and check that. 32 | 33 | print $sock "set $key 0 0 1\r\nx\r\n"; 34 | is (scalar <$sock>, "STORED\r\n", "Stored a key"); 35 | 36 | print $sock "delete $key\r\n"; 37 | is (scalar <$sock>, "DELETED\r\n", "Properly deleted"); 38 | 39 | print $sock "set $key 0 0 1\r\nx\r\n"; 40 | is (scalar <$sock>, "STORED\r\n", "Stored a key"); 41 | 42 | print $sock "delete $key noreply\r\n"; 43 | # will not reply, but a subsequent add will succeed 44 | 45 | print $sock "add $key 0 0 1\r\nx\r\n"; 46 | is (scalar <$sock>, "STORED\r\n", "Add succeeded after deletion."); 47 | 48 | stop_kmemcache(); 49 | -------------------------------------------------------------------------------- /t/024_issue_41.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use POSIX qw(ceil); 6 | use Test::More tests => 691; 7 | use FindBin qw($Bin); 8 | use lib "$Bin/lib"; 9 | use MemcachedTest; 10 | 11 | my $server = start_kmemcache(); 12 | my $sock = $server->sock; 13 | 14 | my $factor = 2; 15 | my $val = "x" x $factor; 16 | my $key = ''; 17 | 18 | # SET items of diverse size to the daemon so it can attempt 19 | # to return a large stats output for slabs 20 | for (my $i=0; $i<69; $i++) { 21 | for (my $j=0; $j<10; $j++) { 22 | $key = "$i:$j"; 23 | print $sock "set key$key 0 0 $factor\r\n$val\r\n"; 24 | is (scalar <$sock>, "STORED\r\n", "stored key$key"); 25 | } 26 | $factor *= 1.2; 27 | $factor = ceil($factor); 28 | $val = "x" x $factor; 29 | } 30 | 31 | # This request will kill the daemon if it has not allocated 32 | # enough memory internally. 33 | my $stats = mem_stats($sock, "slabs"); 34 | 35 | # Verify whether the daemon is still running or not by asking 36 | # it for statistics. 37 | print $sock "version\r\n"; 38 | my $v = scalar <$sock>; 39 | ok(defined $v && length($v), "memcached didn't respond"); 40 | 41 | stop_kmemcache(); 42 | -------------------------------------------------------------------------------- /t/025_issue_42.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 11; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $value = "B"x10; 12 | my $key = 0; 13 | 14 | for ($key = 0; $key < 10; $key++) { 15 | print $sock "set key$key 0 0 10\r\n$value\r\n"; 16 | is (scalar <$sock>, "STORED\r\n", "stored key$key"); 17 | } 18 | 19 | my $first_stats = mem_stats($sock, "slabs"); 20 | my $req = $first_stats->{"1:mem_requested"}; 21 | ok ($req == "640" || $req == "800", "Check allocated size"); 22 | 23 | stop_kmemcache(); 24 | -------------------------------------------------------------------------------- /t/026_issue_50.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 1; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache('-B binary'); 10 | my $sock = $server->sock; 11 | 12 | $SIG{ALRM} = sub { die "alarm\n" }; 13 | alarm(2); 14 | print $sock "Here's a bunch of garbage that doesn't look like the bin prot."; 15 | my $rv = <$sock>; 16 | ok(1, "Either the above worked and quit, or hung forever."); 17 | 18 | stop_kmemcache(); 19 | -------------------------------------------------------------------------------- /t/027_issue_61.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 7; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache("-R 1"); 10 | my $sock = $server->sock; 11 | 12 | print $sock "set foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\nset foobar 0 0 5\r\nBubba\r\n"; 13 | is (scalar <$sock>, "STORED\r\n", "stored foobar"); 14 | is (scalar <$sock>, "STORED\r\n", "stored foobar"); 15 | is (scalar <$sock>, "STORED\r\n", "stored foobar"); 16 | is (scalar <$sock>, "STORED\r\n", "stored foobar"); 17 | is (scalar <$sock>, "STORED\r\n", "stored foobar"); 18 | is (scalar <$sock>, "STORED\r\n", "stored foobar"); 19 | my $stats = mem_stats($sock); 20 | is ($stats->{"conn_yields"}, "5", "Got a decent number of yields"); 21 | 22 | stop_kmemcache(); 23 | -------------------------------------------------------------------------------- /t/028_issue_67.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 22; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | use Carp qw(croak); 9 | 10 | use Cwd; 11 | my $builddir = getcwd; 12 | 13 | $ENV{'MEMCACHED_PORT_FILENAME'} = "/tmp/ports.$$"; 14 | 15 | sub read_ports { 16 | my %rv = (); 17 | open(my $f, "/tmp/ports.$$") || die("Can't open ports file."); 18 | while(<$f>) { 19 | my ($type, $port) = split(/:\s+/); 20 | $rv{$type} = $port + 0; 21 | } 22 | unlink "/tmp/ports.$$"; 23 | return %rv; 24 | } 25 | 26 | sub validate_port { 27 | my ($name, $got, $expected) = @_; 28 | # diag "Wanted $expected, got $got"; 29 | if ($expected == -1) { 30 | ok(!defined($got), "$name expected no port, got $got"); 31 | } elsif ($expected == 0) { 32 | ok($got != 11211, "$name expected random port (got $got)"); 33 | } else { 34 | is($got, $expected, "$name"); 35 | } 36 | } 37 | 38 | sub run_server { 39 | my ($args) = @_; 40 | 41 | my $exe = "$builddir/user/umemcached"; 42 | my $mod = "$builddir/kmod/kmemcache.ko"; 43 | croak("memcached binary doesn't exist. Haven't run 'make' ?\n") unless -e $exe; 44 | 45 | my $childpid = fork(); 46 | 47 | # my $root = ''; 48 | # $root = "-u root" if ($< == 0); 49 | my $cmd = "$builddir/test/kmcstart 1 $mod $exe $args"; 50 | 51 | unless($childpid) { 52 | exec $cmd; 53 | exit; # NOTREACHED 54 | } 55 | 56 | waitpid($childpid, 0); 57 | 58 | for (1..20) { 59 | if (-f "/tmp/ports.$$") { 60 | return Memcached::Handle->new(pid => $childpid); 61 | } 62 | select undef, undef, undef, 0.10; 63 | } 64 | croak "Failed to start server."; 65 | } 66 | 67 | sub stop_server { 68 | stop_kmemcache(); 69 | } 70 | 71 | sub when { 72 | my ($name, $params, $expected_tcp, $expected_udp) = @_; 73 | 74 | my $server = run_server($params); 75 | my %ports = read_ports(); 76 | 77 | validate_port($name, $ports{'TCP INET'}, $expected_tcp); 78 | validate_port($name, $ports{'UDP INET'}, $expected_udp); 79 | 80 | stop_server(); 81 | } 82 | 83 | # Disabling the defaults since it conflicts with a running instance. 84 | # when('no arguments', '', 11211, 11211); 85 | when('specifying tcp port', '-p 11212', 11212, 11212); 86 | when('specifying udp port', '-U 11222', 11222, 11222); 87 | when('specifying tcp ephemeral port', '-p -1', 0, 0); 88 | when('specifying udp ephemeral port', '-U -1', 0, 0); 89 | when('tcp port disabled', '-p 0', -1, -1); 90 | when('udp port disabled', '-U 0', -1, -1); 91 | when('specifying tcp and udp ports', '-p 11232 -U 11233', 11232, 11233); 92 | when('specifying tcp and disabling udp', '-p 11242 -U 0', 11242, -1); 93 | when('specifying udp and disabling tcp', '-p -1 -U 11252', 0, 11252); 94 | when('specifying tcp and ephemeral udp', '-p 11262 -U -1', 11262, 0); 95 | when('specifying udp and ephemeral tcp', '-p -1 -U 11272', 0, 11272); 96 | -------------------------------------------------------------------------------- /t/029_issue_68.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 996; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | for (my $keyi = 1; $keyi < 250; $keyi++) { 13 | my $key = "x" x $keyi; 14 | print $sock "set $key 0 0 1\r\n9\r\n"; 15 | is (scalar <$sock>, "STORED\r\n", "stored $key"); 16 | mem_get_is($sock, $key, "9"); 17 | print $sock "incr $key 1\r\n"; 18 | is (scalar <$sock>, "10\r\n", "incr $key to 10"); 19 | mem_get_is($sock, $key, "10"); 20 | } 21 | 22 | stop_kmemcache(); 23 | -------------------------------------------------------------------------------- /t/030_issue_70.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 4; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | print $sock "set issue70 0 0 0\r\n\r\n"; 13 | is (scalar <$sock>, "STORED\r\n", "stored issue70"); 14 | 15 | print $sock "set issue70 0 0 -1\r\n"; 16 | is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n"); 17 | 18 | print $sock "set issue70 0 0 4294967295\r\n"; 19 | is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n"); 20 | 21 | print $sock "set issue70 0 0 2147483647\r\nscoobyscoobydoo"; 22 | is (scalar <$sock>, "CLIENT_ERROR bad command line format\r\n"); 23 | 24 | stop_kmemcache(); 25 | -------------------------------------------------------------------------------- /t/031_item_size_max.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 7; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | my $stats = mem_stats($sock, ' settings'); 13 | 14 | # Ensure default still works. 15 | is($stats->{item_size_max}, 1024 * 1024); 16 | $server->stop(); 17 | 18 | # Should die. 19 | eval { 20 | $server = start_kmemcache('-I 1000'); 21 | }; 22 | ok($@ && $@ =~ m/^Failed/, "Shouldn't start with < 1k item max"); 23 | 24 | eval { 25 | $server = start_kmemcache('-I 256m'); 26 | }; 27 | ok($@ && $@ =~ m/^Failed/, "Shouldn't start with > 128m item max"); 28 | 29 | # Minimum. 30 | my $server = start_kmemcache('-I 1024'); 31 | my $stats = mem_stats($server->sock, ' settings'); 32 | is($stats->{item_size_max}, 1024); 33 | $server->stop(); 34 | 35 | # Reasonable but unreasonable. 36 | my $server = start_kmemcache('-I 1049600'); 37 | my $stats = mem_stats($server->sock, ' settings'); 38 | is($stats->{item_size_max}, 1049600); 39 | $server->stop(); 40 | 41 | # Suffix kilobytes. 42 | my $server = start_kmemcache('-I 512k'); 43 | my $stats = mem_stats($server->sock, ' settings'); 44 | is($stats->{item_size_max}, 524288); 45 | $server->stop(); 46 | 47 | # Suffix megabytes. 48 | my $server = start_kmemcache('-I 32m'); 49 | my $stats = mem_stats($server->sock, ' settings'); 50 | is($stats->{item_size_max}, 33554432); 51 | $server->stop(); 52 | 53 | -------------------------------------------------------------------------------- /t/032_line-lengths.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use FindBin qw($Bin); 4 | our @files; 5 | 6 | BEGIN { 7 | chdir "$Bin/.." or die; 8 | @files = ( "doc/protocol.txt" ); 9 | } 10 | 11 | use Test::More tests => scalar(@files); 12 | 13 | foreach my $f (@files) { 14 | open(my $fh, $f) or die("Can't open $f"); 15 | my @long_lines = (); 16 | my $line_number = 0; 17 | while(<$fh>) { 18 | $line_number++; 19 | if(length($_) > 80) { 20 | push(@long_lines, $line_number); 21 | } 22 | } 23 | close($fh); 24 | ok(@long_lines == 0, "$f has a long lines: @long_lines"); 25 | } 26 | -------------------------------------------------------------------------------- /t/033_lru.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 149; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | # assuming max slab is 1M and default mem is 64M 10 | my $server = start_kmemcache(); 11 | my $sock = $server->sock; 12 | 13 | # create a big value for the largest slab 14 | my $max = 1024 * 1024; 15 | my $big = 'x' x (1024 * 1024 - 250); 16 | 17 | ok(length($big) > 512 * 1024); 18 | ok(length($big) < 1024 * 1024); 19 | 20 | # test that an even bigger value is rejected while we're here 21 | my $too_big = $big . $big . $big; 22 | my $len = length($too_big); 23 | print $sock "set too_big 0 0 $len\r\n$too_big\r\n"; 24 | is(scalar <$sock>, "SERVER_ERROR object too large for cache\r\n", "too_big not stored"); 25 | 26 | # set the big value 27 | my $len = length($big); 28 | print $sock "set big 0 0 $len\r\n$big\r\n"; 29 | is(scalar <$sock>, "STORED\r\n", "stored big"); 30 | mem_get_is($sock, "big", $big); 31 | 32 | # no evictions yet 33 | my $stats = mem_stats($sock); 34 | is($stats->{"evictions"}, "0", "no evictions to start"); 35 | 36 | # set many big items, enough to get evictions 37 | for (my $i = 0; $i < 100; $i++) { 38 | print $sock "set item_$i 0 0 $len\r\n$big\r\n"; 39 | is(scalar <$sock>, "STORED\r\n", "stored item_$i"); 40 | } 41 | 42 | # some evictions should have happened 43 | my $stats = mem_stats($sock); 44 | my $evictions = int($stats->{"evictions"}); 45 | ok($evictions == 37, "some evictions happened"); 46 | 47 | # the first big value should be gone 48 | mem_get_is($sock, "big", undef); 49 | 50 | # the earliest items should be gone too 51 | for (my $i = 0; $i < $evictions - 1; $i++) { 52 | mem_get_is($sock, "item_$i", undef); 53 | } 54 | 55 | # check that the non-evicted are the right ones 56 | for (my $i = $evictions - 1; $i < $evictions + 4; $i++) { 57 | mem_get_is($sock, "item_$i", $big); 58 | } 59 | 60 | stop_kmemcache(); 61 | -------------------------------------------------------------------------------- /t/034_maxconns.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # NOTE: This test never worked. Memcached would ignore maxconns requests lower 3 | # than the current ulimit. Test needs to be updated. 4 | 5 | use strict; 6 | use warnings; 7 | 8 | use Test::More tests => 11; 9 | 10 | use FindBin qw($Bin); 11 | use lib "$Bin/lib"; 12 | use MemcachedTest; 13 | 14 | 15 | # start up a server with 10 maximum connections 16 | my $server = start_kmemcache('-c 100'); 17 | my $sock = $server->sock; 18 | my @sockets; 19 | 20 | ok(defined($sock), 'Connection 0'); 21 | push (@sockets, $sock); 22 | 23 | 24 | foreach my $conn (1..10) { 25 | $sock = $server->new_sock; 26 | ok(defined($sock), "Made connection $conn"); 27 | push(@sockets, $sock); 28 | } 29 | 30 | stop_kmemcache(); 31 | -------------------------------------------------------------------------------- /t/035_multiversioning.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 13; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $sock2 = $server->new_sock; 12 | 13 | ok($sock != $sock2, "have two different connections open"); 14 | 15 | # set large value 16 | my $size = 256 * 1024; # 256 kB 17 | my $bigval = "0123456789abcdef" x ($size / 16); 18 | $bigval =~ s/^0/\[/; $bigval =~ s/f$/\]/; 19 | my $bigval2 = uc($bigval); 20 | 21 | print $sock "set big 0 0 $size\r\n$bigval\r\n"; 22 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 23 | mem_get_is($sock, "big", $bigval, "big value got correctly"); 24 | 25 | print $sock "get big\r\n"; 26 | my $buf; 27 | is(read($sock, $buf, $size / 2), $size / 2, "read half the answer back"); 28 | like($buf, qr/VALUE big/, "buf has big value header in it"); 29 | like($buf, qr/abcdef/, "buf has some data in it"); 30 | unlike($buf, qr/abcde\]/, "buf doesn't yet close"); 31 | 32 | # sock2 interrupts (maybe sock1 is slow) and deletes stuff: 33 | print $sock2 "delete big\r\n"; 34 | is(scalar <$sock2>, "DELETED\r\n", "deleted big from sock2 while sock1's still reading it"); 35 | mem_get_is($sock2, "big", undef, "nothing from sock2 now. gone from namespace."); 36 | print $sock2 "set big 0 0 $size\r\n$bigval2\r\n"; 37 | is(scalar <$sock2>, "STORED\r\n", "stored big w/ val2"); 38 | mem_get_is($sock2, "big", $bigval2, "big value2 got correctly"); 39 | 40 | # sock1 resumes reading... 41 | $buf .= <$sock>; 42 | $buf .= <$sock>; 43 | like($buf, qr/abcde\]/, "buf now closes"); 44 | 45 | # and if sock1 reads again, it's the uppercase version: 46 | mem_get_is($sock, "big", $bigval2, "big value2 got correctly from sock1"); 47 | 48 | stop_kmemcache(); 49 | -------------------------------------------------------------------------------- /t/036_noreply.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 9; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | 10 | my $server = start_kmemcache(); 11 | my $sock = $server->sock; 12 | 13 | 14 | # Test that commands can take 'noreply' parameter. 15 | print $sock "flush_all noreply\r\n"; 16 | print $sock "flush_all 0 noreply\r\n"; 17 | 18 | print $sock "verbosity 0 noreply\r\n"; 19 | 20 | print $sock "add noreply:foo 0 0 1 noreply\r\n1\r\n"; 21 | mem_get_is($sock, "noreply:foo", "1"); 22 | 23 | print $sock "set noreply:foo 0 0 1 noreply\r\n2\r\n"; 24 | mem_get_is($sock, "noreply:foo", "2"); 25 | 26 | print $sock "replace noreply:foo 0 0 1 noreply\r\n3\r\n"; 27 | mem_get_is($sock, "noreply:foo", "3"); 28 | 29 | print $sock "append noreply:foo 0 0 1 noreply\r\n4\r\n"; 30 | mem_get_is($sock, "noreply:foo", "34"); 31 | 32 | print $sock "prepend noreply:foo 0 0 1 noreply\r\n5\r\n"; 33 | my @result = mem_gets($sock, "noreply:foo"); 34 | ok($result[1] eq "534"); 35 | 36 | print $sock "cas noreply:foo 0 0 1 $result[0] noreply\r\n6\r\n"; 37 | mem_get_is($sock, "noreply:foo", "6"); 38 | 39 | print $sock "incr noreply:foo 3 noreply\r\n"; 40 | mem_get_is($sock, "noreply:foo", "9"); 41 | 42 | print $sock "decr noreply:foo 2 noreply\r\n"; 43 | mem_get_is($sock, "noreply:foo", "7"); 44 | 45 | print $sock "delete noreply:foo noreply\r\n"; 46 | mem_get_is($sock, "noreply:foo"); 47 | 48 | stop_kmemcache(); 49 | -------------------------------------------------------------------------------- /t/037_slabs_reassign.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use Test::More tests => 130; 6 | use FindBin qw($Bin); 7 | use lib "$Bin/lib"; 8 | use MemcachedTest; 9 | 10 | # Enable manual slab reassign, cap at 6 slabs 11 | my $server = start_kmemcache('-o slab_reassign -m 4'); 12 | my $stats = mem_stats($server->sock, ' settings'); 13 | is($stats->{slab_reassign}, "yes"); 14 | 15 | my $sock = $server->sock; 16 | 17 | # Fill a largeish slab until it evicts (honors the -m 6) 18 | my $bigdata = 'x' x 70000; # slab 31 19 | for (1 .. 60) { 20 | print $sock "set bfoo$_ 0 0 70000\r\n", $bigdata, "\r\n"; 21 | is(scalar <$sock>, "STORED\r\n", "stored key"); 22 | } 23 | 24 | # Fill a smaller slab until it evicts 25 | my $smalldata = 'y' x 20000; # slab 25 26 | for (1 .. 60) { 27 | print $sock "set sfoo$_ 0 0 20000\r\n", $smalldata, "\r\n"; 28 | is(scalar <$sock>, "STORED\r\n", "stored key"); 29 | } 30 | 31 | my $items_before = mem_stats($sock, "items"); 32 | isnt($items_before->{"items:31:evicted"}, 0, "slab 31 evicted is nonzero"); 33 | isnt($items_before->{"items:25:evicted"}, 0, "slab 25 evicted is nonzero"); 34 | 35 | my $slabs_before = mem_stats($sock, "slabs"); 36 | # Move a large slab to the smaller slab 37 | print $sock "slabs reassign 31 25\r\n"; 38 | is(scalar <$sock>, "OK\r\n", "slab rebalancer started"); 39 | 40 | # Still working out how/if to signal the thread. For now, just sleep. 41 | sleep 2; 42 | 43 | # Check that stats counters increased 44 | my $slabs_after = mem_stats($sock, "slabs"); 45 | $stats = mem_stats($sock); 46 | 47 | isnt($stats->{slabs_moved}, 0, "slabs moved is nonzero"); 48 | 49 | # Check that slab stats reflect the change 50 | ok($slabs_before->{"31:total_pages"} != $slabs_after->{"31:total_pages"}, 51 | "slab 31 pagecount changed"); 52 | ok($slabs_before->{"25:total_pages"} != $slabs_after->{"25:total_pages"}, 53 | "slab 25 pagecount changed"); 54 | 55 | # Try to move another slab, see that you can move two in a row 56 | print $sock "slabs reassign 31 25\r\n"; 57 | like(scalar <$sock>, qr/^OK/, "Cannot re-run against class with empty space"); 58 | 59 | # Try to move a page backwards. Should complain that source class isn't "safe" 60 | # to move from. 61 | # TODO: Wait until the above command completes, then try to move it back? 62 | # Seems pointless... 63 | #print $sock "slabs reassign 25 31\r\n"; 64 | #like(scalar <$sock>, qr/^UNSAFE/, "Cannot move an unsafe slab back"); 65 | 66 | # Try to insert items into both slabs 67 | print $sock "set bfoo51 0 0 70000\r\n", $bigdata, "\r\n"; 68 | is(scalar <$sock>, "STORED\r\n", "stored key"); 69 | 70 | print $sock "set sfoo51 0 0 20000\r\n", $smalldata, "\r\n"; 71 | is(scalar <$sock>, "STORED\r\n", "stored key"); 72 | 73 | # Do need to come up with better automated tests for this. 74 | 75 | stop_kmemcache(); 76 | -------------------------------------------------------------------------------- /t/038_stats-detail.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 24; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | my $expire; 12 | 13 | print $sock "stats detail dump\r\n"; 14 | is(scalar <$sock>, "END\r\n", "verified empty stats at start"); 15 | 16 | print $sock "stats detail on\r\n"; 17 | is(scalar <$sock>, "OK\r\n", "detail collection turned on"); 18 | 19 | print $sock "set foo:123 0 0 6\r\nfooval\r\n"; 20 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 21 | 22 | print $sock "stats detail dump\r\n"; 23 | is(scalar <$sock>, "PREFIX foo get 0 hit 0 set 1 del 0\r\n", "details after set"); 24 | is(scalar <$sock>, "END\r\n", "end of details"); 25 | 26 | mem_get_is($sock, "foo:123", "fooval"); 27 | print $sock "stats detail dump\r\n"; 28 | is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 1 del 0\r\n", "details after get with hit"); 29 | is(scalar <$sock>, "END\r\n", "end of details"); 30 | 31 | mem_get_is($sock, "foo:124", undef); 32 | 33 | print $sock "stats detail dump\r\n"; 34 | is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 0\r\n", "details after get without hit"); 35 | is(scalar <$sock>, "END\r\n", "end of details"); 36 | 37 | print $sock "delete foo:125\r\n"; 38 | is(scalar <$sock>, "NOT_FOUND\r\n", "sent delete command"); 39 | 40 | print $sock "stats detail dump\r\n"; 41 | is(scalar <$sock>, "PREFIX foo get 2 hit 1 set 1 del 1\r\n", "details after delete"); 42 | is(scalar <$sock>, "END\r\n", "end of details"); 43 | 44 | print $sock "stats reset\r\n"; 45 | is(scalar <$sock>, "RESET\r\n", "stats cleared"); 46 | 47 | print $sock "stats detail dump\r\n"; 48 | is(scalar <$sock>, "END\r\n", "empty stats after clear"); 49 | 50 | mem_get_is($sock, "foo:123", "fooval"); 51 | print $sock "stats detail dump\r\n"; 52 | is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0\r\n", "details after clear and get"); 53 | is(scalar <$sock>, "END\r\n", "end of details"); 54 | 55 | print $sock "stats detail off\r\n"; 56 | is(scalar <$sock>, "OK\r\n", "detail collection turned off"); 57 | 58 | mem_get_is($sock, "foo:124", undef); 59 | 60 | mem_get_is($sock, "foo:123", "fooval"); 61 | print $sock "stats detail dump\r\n"; 62 | is(scalar <$sock>, "PREFIX foo get 1 hit 1 set 0 del 0\r\n", "details after stats turned off"); 63 | is(scalar <$sock>, "END\r\n", "end of details"); 64 | 65 | stop_kmemcache(); 66 | -------------------------------------------------------------------------------- /t/039_stats.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 95; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $server = start_kmemcache(); 10 | my $sock = $server->sock; 11 | 12 | 13 | ## Output looks like this: 14 | ## 15 | ## STAT pid 22969 16 | ## STAT uptime 13 17 | ## STAT time 1259170891 18 | ## STAT version 1.4.3 19 | ## STAT libevent 1.4.13-stable. 20 | ## STAT pointer_size 32 21 | ## STAT rusage_user 0.001198 22 | ## STAT rusage_system 0.003523 23 | ## STAT curr_connections 10 24 | ## STAT total_connections 11 25 | ## STAT connection_structures 11 26 | ## STAT cmd_get 0 27 | ## STAT cmd_set 0 28 | ## STAT cmd_flush 0 29 | ## STAT get_hits 0 30 | ## STAT get_misses 0 31 | ## STAT delete_misses 0 32 | ## STAT delete_hits 0 33 | ## STAT incr_misses 0 34 | ## STAT incr_hits 0 35 | ## STAT decr_misses 0 36 | ## STAT decr_hits 0 37 | ## STAT cas_misses 0 38 | ## STAT cas_hits 0 39 | ## STAT cas_badval 0 40 | ## STAT auth_cmds 0 41 | ## STAT auth_unknowns 0 42 | ## STAT bytes_read 7 43 | ## STAT bytes_written 0 44 | ## STAT limit_maxbytes 67108864 45 | ## STAT accepting_conns 1 46 | ## STAT listen_disabled_num 0 47 | ## STAT threads 4 48 | ## STAT conn_yields 0 49 | ## STAT bytes 0 50 | ## STAT curr_items 0 51 | ## STAT total_items 0 52 | ## STAT evictions 0 53 | ## STAT reclaimed 0 54 | 55 | # note that auth stats are tested in auth specfic tests 56 | 57 | 58 | my $stats = mem_stats($sock); 59 | 60 | # Test number of keys 61 | # kmemcache doesn't use these value 62 | # @STAT libevent 63 | # @STAT rusage_user 64 | # @STAT rusage_system 65 | # @STAT reserved_fds 66 | is(scalar(keys(%$stats)), 44, "44 stats values"); 67 | 68 | # Test initial state 69 | foreach my $key (qw(curr_items total_items bytes cmd_get cmd_set get_hits evictions get_misses 70 | bytes_written delete_hits delete_misses incr_hits incr_misses decr_hits 71 | decr_misses listen_disabled_num)) { 72 | is($stats->{$key}, 0, "initial $key is zero"); 73 | } 74 | is($stats->{accepting_conns}, 1, "initial accepting_conns is one"); 75 | 76 | # Do some operations 77 | 78 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 79 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 80 | mem_get_is($sock, "foo", "fooval"); 81 | 82 | my $stats = mem_stats($sock); 83 | 84 | foreach my $key (qw(total_items curr_items cmd_get cmd_set get_hits)) { 85 | is($stats->{$key}, 1, "after one set/one get $key is 1"); 86 | } 87 | 88 | my $cache_dump = mem_stats($sock, " cachedump 1 100"); 89 | ok(defined $cache_dump->{'foo'}, "got foo from cachedump"); 90 | 91 | print $sock "delete foo\r\n"; 92 | is(scalar <$sock>, "DELETED\r\n", "deleted foo"); 93 | 94 | my $stats = mem_stats($sock); 95 | is($stats->{delete_hits}, 1); 96 | is($stats->{delete_misses}, 0); 97 | 98 | print $sock "delete foo\r\n"; 99 | is(scalar <$sock>, "NOT_FOUND\r\n", "shouldn't delete foo again"); 100 | 101 | my $stats = mem_stats($sock); 102 | is($stats->{delete_hits}, 1); 103 | is($stats->{delete_misses}, 1); 104 | 105 | # incr stats 106 | 107 | sub check_incr_stats { 108 | my ($ih, $im, $dh, $dm) = @_; 109 | my $stats = mem_stats($sock); 110 | 111 | is($stats->{incr_hits}, $ih); 112 | is($stats->{incr_misses}, $im); 113 | is($stats->{decr_hits}, $dh); 114 | is($stats->{decr_misses}, $dm); 115 | } 116 | 117 | print $sock "incr i 1\r\n"; 118 | is(scalar <$sock>, "NOT_FOUND\r\n", "shouldn't incr a missing thing"); 119 | check_incr_stats(0, 1, 0, 0); 120 | 121 | print $sock "decr d 1\r\n"; 122 | is(scalar <$sock>, "NOT_FOUND\r\n", "shouldn't decr a missing thing"); 123 | check_incr_stats(0, 1, 0, 1); 124 | 125 | print $sock "set n 0 0 1\r\n0\r\n"; 126 | is(scalar <$sock>, "STORED\r\n", "stored n"); 127 | 128 | print $sock "incr n 3\r\n"; 129 | is(scalar <$sock>, "3\r\n", "incr works"); 130 | check_incr_stats(1, 1, 0, 1); 131 | 132 | print $sock "decr n 1\r\n"; 133 | is(scalar <$sock>, "2\r\n", "decr works"); 134 | check_incr_stats(1, 1, 1, 1); 135 | 136 | # cas stats 137 | 138 | sub check_cas_stats { 139 | my ($ch, $cm, $cb) = @_; 140 | my $stats = mem_stats($sock); 141 | 142 | is($stats->{cas_hits}, $ch); 143 | is($stats->{cas_misses}, $cm); 144 | is($stats->{cas_badval}, $cb); 145 | } 146 | 147 | check_cas_stats(0, 0, 0); 148 | 149 | print $sock "cas c 0 0 1 99999999\r\nz\r\n"; 150 | is(scalar <$sock>, "NOT_FOUND\r\n", "missed cas"); 151 | check_cas_stats(0, 1, 0); 152 | 153 | print $sock "set c 0 0 1\r\nx\r\n"; 154 | is(scalar <$sock>, "STORED\r\n", "stored c"); 155 | my ($id, $v) = mem_gets($sock, 'c'); 156 | is('x', $v, 'got the expected value'); 157 | 158 | print $sock "cas c 0 0 1 99999999\r\nz\r\n"; 159 | is(scalar <$sock>, "EXISTS\r\n", "missed cas"); 160 | check_cas_stats(0, 1, 1); 161 | my ($newid, $v) = mem_gets($sock, 'c'); 162 | is('x', $v, 'got the expected value'); 163 | 164 | print $sock "cas c 0 0 1 $id\r\nz\r\n"; 165 | is(scalar <$sock>, "STORED\r\n", "good cas"); 166 | check_cas_stats(1, 1, 1); 167 | my ($newid, $v) = mem_gets($sock, 'c'); 168 | is('z', $v, 'got the expected value'); 169 | 170 | my $settings = mem_stats($sock, ' settings'); 171 | is(1024, $settings->{'maxconns'}); 172 | is('NULL', $settings->{'domain_socket'}); 173 | is('on', $settings->{'evictions'}); 174 | is('yes', $settings->{'cas_enabled'}); 175 | is('no', $settings->{'auth_enabled_sasl'}); 176 | 177 | print $sock "stats reset\r\n"; 178 | is(scalar <$sock>, "RESET\r\n", "good stats reset"); 179 | 180 | my $stats = mem_stats($sock); 181 | is(0, $stats->{'cmd_get'}); 182 | is(0, $stats->{'cmd_set'}); 183 | is(0, $stats->{'get_hits'}); 184 | is(0, $stats->{'get_misses'}); 185 | is(0, $stats->{'delete_misses'}); 186 | is(0, $stats->{'delete_hits'}); 187 | is(0, $stats->{'incr_misses'}); 188 | is(0, $stats->{'incr_hits'}); 189 | is(0, $stats->{'decr_misses'}); 190 | is(0, $stats->{'decr_hits'}); 191 | is(0, $stats->{'cas_misses'}); 192 | is(0, $stats->{'cas_hits'}); 193 | is(0, $stats->{'cas_badval'}); 194 | is(0, $stats->{'evictions'}); 195 | is(0, $stats->{'reclaimed'}); 196 | 197 | print $sock "flush_all\r\n"; 198 | is(scalar <$sock>, "OK\r\n", "flushed"); 199 | 200 | my $stats = mem_stats($sock); 201 | is($stats->{cmd_flush}, 1, "after one flush cmd_flush is 1"); 202 | 203 | stop_kmemcache(); 204 | -------------------------------------------------------------------------------- /t/040_touch.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 4; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | 10 | my $server = start_kmemcache(); 11 | my $sock = $server->sock; 12 | 13 | # set foo (and should get it) 14 | print $sock "set foo 0 2 6\r\nfooval\r\n"; 15 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 16 | mem_get_is($sock, "foo", "fooval"); 17 | 18 | # touch it 19 | print $sock "touch foo 10\r\n"; 20 | is(scalar <$sock>, "TOUCHED\r\n", "touched foo"); 21 | 22 | sleep 2; 23 | mem_get_is($sock, "foo", "fooval"); 24 | 25 | stop_kmemcache(); 26 | -------------------------------------------------------------------------------- /t/041_udp.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 48; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | use constant IS_ASCII => 0; 10 | use constant IS_BINARY => 1; 11 | use constant ENTRY_EXISTS => 0; 12 | use constant ENTRY_MISSING => 1; 13 | use constant BIN_REQ_MAGIC => 0x80; 14 | use constant BIN_RES_MAGIC => 0x81; 15 | use constant CMD_GET => 0x00; 16 | use constant CMD_SET => 0x01; 17 | use constant CMD_ADD => 0x02; 18 | use constant CMD_REPLACE => 0x03; 19 | use constant CMD_DELETE => 0x04; 20 | use constant CMD_INCR => 0x05; 21 | use constant CMD_DECR => 0x06; 22 | use constant CMD_APPEND => 0x0E; 23 | use constant CMD_PREPEND => 0x0F; 24 | use constant REQ_PKT_FMT => "CCnCCnNNNN"; 25 | use constant RES_PKT_FMT => "CCnCCnNNNN"; 26 | use constant INCRDECR_PKT_FMT => "NNNNN"; 27 | use constant MIN_RECV_BYTES => length(pack(RES_PKT_FMT)); 28 | 29 | 30 | my $server = start_kmemcache(); 31 | my $sock = $server->sock; 32 | 33 | # set foo (and should get it) 34 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 35 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 36 | mem_get_is($sock, "foo", "fooval"); 37 | 38 | my $usock = $server->new_udp_sock 39 | or die "Can't bind : $@\n"; 40 | 41 | # testing sequence of request ids 42 | for my $offt (1, 1, 2) { 43 | my $req = 160 + $offt; 44 | my $res = send_udp_request($usock, $req, "get foo\r\n"); 45 | ok($res, "got result"); 46 | is(keys %$res, 1, "one key (one packet)"); 47 | ok($res->{0}, "only got seq number 0"); 48 | is(substr($res->{0}, 8), "VALUE foo 0 6\r\nfooval\r\nEND\r\n"); 49 | is(hexify(substr($res->{0}, 0, 2)), hexify(pack("n", $req)), "udp request number in response ($req) is correct"); 50 | } 51 | 52 | # op tests 53 | for my $prot (::IS_ASCII,::IS_BINARY) { 54 | udp_set_test($prot,45,"aval$prot","1",0,0); 55 | udp_set_test($prot,45,"bval$prot","abcd" x 1024,0,0); 56 | udp_get_test($prot,45,"aval$prot","1",::ENTRY_EXISTS); 57 | udp_get_test($prot,45,"404$prot","1",::ENTRY_MISSING); 58 | udp_incr_decr_test($prot,45,"aval$prot","1","incr",1); 59 | udp_incr_decr_test($prot,45,"aval$prot","1","decr",2); 60 | udp_delete_test($prot,45,"aval$prot"); 61 | } 62 | 63 | sub udp_set_test { 64 | my ($protocol, $req_id, $key, $value, $flags, $exp) = @_; 65 | my $req = ""; 66 | my $val_len = length($value); 67 | 68 | if ($protocol == ::IS_ASCII) { 69 | $req = "set $key $flags $exp $val_len\r\n$value\r\n"; 70 | } elsif ($protocol == ::IS_BINARY) { 71 | my $key_len = length($key); 72 | my $extra = pack "NN",$flags,$exp; 73 | my $extra_len = length($extra); 74 | my $total_len = $val_len + $extra_len + $key_len; 75 | $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, ::CMD_SET, $key_len, $extra_len, 0, 0, $total_len, 0, 0, 0); 76 | $req .= $extra . $key . $value; 77 | } 78 | 79 | my $datagrams = send_udp_request($usock, $req_id, $req); 80 | my $resp = construct_udp_message($datagrams); 81 | 82 | if ($protocol == ::IS_ASCII) { 83 | is($resp,"STORED\r\n","Store key $key using ASCII protocol"); 84 | } elsif ($protocol == ::IS_BINARY) { 85 | my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len, 86 | $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp); 87 | is($resp_status,"0","Store key $key using binary protocol"); 88 | } 89 | } 90 | 91 | sub udp_get_test { 92 | my ($protocol, $req_id, $key, $value, $exists) = @_; 93 | my $key_len = length($key); 94 | my $value_len = length($value); 95 | my $req = ""; 96 | 97 | if ($protocol == ::IS_ASCII) { 98 | $req = "get $key\r\n"; 99 | } elsif ($protocol == ::IS_BINARY) { 100 | $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, ::CMD_GET, $key_len, 0, 0, 0, $key_len, 0, 0, 0); 101 | $req .= $key; 102 | } 103 | 104 | my $datagrams = send_udp_request($usock, $req_id, $req); 105 | my $resp = construct_udp_message($datagrams); 106 | 107 | if ($protocol == ::IS_ASCII) { 108 | if ($exists == ::ENTRY_EXISTS) { 109 | is($resp,"VALUE $key 0 $value_len\r\n$value\r\nEND\r\n","Retrieve entry with key $key using ASCII protocol"); 110 | } else { 111 | is($resp,"END\r\n","Retrieve non existing entry with key $key using ASCII protocol"); 112 | } 113 | } elsif ($protocol == ::IS_BINARY) { 114 | my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len, 115 | $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp); 116 | if ($exists == ::ENTRY_EXISTS) { 117 | is($resp_status,"0","Retrieve entry with key $key using binary protocol"); 118 | is(substr($resp,::MIN_RECV_BYTES + $resp_extra_len + $resp_key_len, $value_len),$value,"Value for key $key retrieved with binary protocol matches"); 119 | } else { 120 | is($resp_status,"1","Retrieve non existing entry with key $key using binary protocol"); 121 | } 122 | } 123 | } 124 | 125 | sub udp_delete_test { 126 | my ($protocol, $req_id, $key) = @_; 127 | my $req = ""; 128 | my $key_len = length($key); 129 | 130 | if ($protocol == ::IS_ASCII) { 131 | $req = "delete $key\r\n"; 132 | } elsif ($protocol == ::IS_BINARY) { 133 | $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, ::CMD_DELETE, $key_len, 0, 0, 0, $key_len, 0, 0, 0); 134 | $req .= $key; 135 | } 136 | 137 | my $datagrams = send_udp_request($usock, $req_id, $req); 138 | my $resp = construct_udp_message($datagrams); 139 | 140 | if ($protocol == ::IS_ASCII) { 141 | is($resp,"DELETED\r\n","Delete key $key using ASCII protocol"); 142 | } elsif ($protocol == ::IS_BINARY) { 143 | my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len, 144 | $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp); 145 | is($resp_status,"0","Delete key $key using binary protocol"); 146 | } 147 | } 148 | 149 | sub udp_incr_decr_test { 150 | my ($protocol, $req_id, $key, $val, $optype, $init_val) = @_; 151 | my $req = ""; 152 | my $key_len = length($key); 153 | my $expected_value = 0; 154 | my $acmd = "incr"; 155 | my $bcmd = ::CMD_INCR; 156 | if ($optype eq "incr") { 157 | $expected_value = $init_val + $val; 158 | } else { 159 | $acmd = "decr"; 160 | $bcmd = ::CMD_DECR; 161 | $expected_value = $init_val - $val; 162 | } 163 | 164 | if ($protocol == ::IS_ASCII) { 165 | $req = "$acmd $key $val\r\n"; 166 | } elsif ($protocol == ::IS_BINARY) { 167 | my $extra = pack(::INCRDECR_PKT_FMT, ($val / 2 ** 32),($val % 2 ** 32), 0, 0, 0); 168 | my $extra_len = length($extra); 169 | $req = pack(::REQ_PKT_FMT, ::BIN_REQ_MAGIC, $bcmd, $key_len, $extra_len, 0, 0, $key_len + $extra_len, 0, 0, 0); 170 | $req .= $extra . $key; 171 | } 172 | 173 | my $datagrams = send_udp_request($usock, $req_id, $req); 174 | my $resp = construct_udp_message($datagrams); 175 | 176 | if ($protocol == ::IS_ASCII) { 177 | is($resp,"$expected_value\r\n","perform $acmd math operation on key $key with ASCII protocol"); 178 | } elsif ($protocol == ::IS_BINARY) { 179 | my ($resp_magic, $resp_op_code, $resp_key_len, $resp_extra_len, $resp_data_type, $resp_status, $resp_total_len, 180 | $resp_opaque, $resp_ident_hi, $resp_ident_lo) = unpack(::RES_PKT_FMT, $resp); 181 | is($resp_status,"0","perform $acmd math operation on key $key with binary protocol"); 182 | my ($resp_hi,$resp_lo) = unpack("NN",substr($resp,::MIN_RECV_BYTES + $resp_extra_len + $resp_key_len, 183 | $resp_total_len - $resp_extra_len - $resp_key_len)); 184 | is(($resp_hi * 2 ** 32) + $resp_lo,$expected_value,"validate result of binary protocol math operation $acmd . Expected value $expected_value") 185 | } 186 | } 187 | 188 | sub construct_udp_message { 189 | my $datagrams = shift; 190 | my $num_datagram = keys (%$datagrams); 191 | my $msg = ""; 192 | my $cur_dg =""; 193 | my $cur_udp_header =""; 194 | for (my $cur_dg_index = 0; $cur_dg_index < $num_datagram; $cur_dg_index++) { 195 | $cur_dg = $datagrams->{$cur_dg_index}; 196 | isnt($cur_dg,"","missing datagram for segment $cur_dg_index"); 197 | $cur_udp_header=substr($cur_dg, 0, 8); 198 | $msg .= substr($cur_dg,8); 199 | } 200 | return $msg; 201 | } 202 | 203 | sub hexify { 204 | my $val = shift; 205 | $val =~ s/(.)/sprintf("%02x", ord($1))/egs; 206 | return $val; 207 | } 208 | 209 | # returns undef on select timeout, or hashref of "seqnum" -> payload (including headers) 210 | # verifies that resp_id is equal to id sent in request 211 | # ensures consistency in num packets that make up response 212 | sub send_udp_request { 213 | my ($sock, $reqid, $req) = @_; 214 | 215 | my $pkt = pack("nnnn", $reqid, 0, 1, 0); # request id (opaque), seq num, #packets, reserved (must be 0) 216 | $pkt .= $req; 217 | my $fail = sub { 218 | my $msg = shift; 219 | warn " FAILING send_udp because: $msg\n"; 220 | return undef; 221 | }; 222 | return $fail->("send") unless send($sock, $pkt, 0); 223 | 224 | my $ret = {}; 225 | 226 | my $got = 0; # packets got 227 | my $numpkts = undef; 228 | 229 | while (!defined($numpkts) || $got < $numpkts) { 230 | my $rin = ''; 231 | vec($rin, fileno($sock), 1) = 1; 232 | my $rout; 233 | return $fail->("timeout after $got packets") unless 234 | select($rout = $rin, undef, undef, 1.5); 235 | 236 | my $res; 237 | my $sender = $sock->recv($res, 1500, 0); 238 | my ($resid, $seq, $this_numpkts, $resv) = unpack("nnnn", substr($res, 0, 8)); 239 | die "Response ID of $resid doesn't match request if of $reqid" unless $resid == $reqid; 240 | die "Reserved area not zero" unless $resv == 0; 241 | die "num packets changed midstream!" if defined $numpkts && $this_numpkts != $numpkts; 242 | $numpkts = $this_numpkts; 243 | $ret->{$seq} = $res; 244 | $got++; 245 | } 246 | return $ret; 247 | } 248 | 249 | 250 | __END__ 251 | $sender = recv($usock, $ans, 1050, 0); 252 | 253 | __END__ 254 | $usock->send 255 | 256 | 257 | ($hispaddr = recv(SOCKET, $rtime, 4, 0)) || die "recv: $!"; 258 | ($port, $hisiaddr) = sockaddr_in($hispaddr); 259 | $host = gethostbyaddr($hisiaddr, AF_INET); 260 | $histime = unpack("N", $rtime) - $SECS_of_70_YEARS ; 261 | 262 | stop_kmemcache(); 263 | -------------------------------------------------------------------------------- /t/042_unixsocket.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 3; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | my $filename = "/tmp/memcachetest$$"; 10 | 11 | my $server = start_kmemcache("-s $filename"); 12 | my $sock = $server->sock; 13 | 14 | ok(-S $filename, "creating unix domain socket $filename"); 15 | 16 | # set foo (and should get it) 17 | print $sock "set foo 0 0 6\r\nfooval\r\n"; 18 | 19 | is(scalar <$sock>, "STORED\r\n", "stored foo"); 20 | mem_get_is($sock, "foo", "fooval"); 21 | 22 | $sock->close(); 23 | stop_kmemcache(); 24 | 25 | unlink($filename); 26 | 27 | ## Just some basic stuff for now... 28 | -------------------------------------------------------------------------------- /t/auto.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use Cwd; 4 | 5 | my $builddir = getcwd; 6 | my @test_files = <$builddir/t/*.t>; 7 | 8 | foreach $file (@test_files) { 9 | print "-------------------------------------- begin ----------------------------------\n"; 10 | print "file: $file\n"; 11 | &test($file); 12 | print "--------------------------------------- end -----------------------------------\n"; 13 | } 14 | 15 | sub test { 16 | my ($file) = @_; 17 | my $childpid = fork(); 18 | 19 | unless ($childpid) { 20 | exec "perl $file"; 21 | exit; 22 | } 23 | 24 | waitpid($childpid, 0); 25 | } 26 | -------------------------------------------------------------------------------- /t/daemonize.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Test::More tests => 7; 5 | use FindBin qw($Bin); 6 | use lib "$Bin/lib"; 7 | use MemcachedTest; 8 | 9 | use File::Temp qw(tempfile); 10 | 11 | my (undef, $tmpfn) = tempfile(); 12 | 13 | my $server = new_memcached("-d -P $tmpfn"); 14 | my $sock = $server->sock; 15 | sleep 0.5; 16 | 17 | ok(-e $tmpfn, "pid file exists"); 18 | ok(-s $tmpfn, "pid file has length"); 19 | 20 | open (my $fh, $tmpfn) or die; 21 | my $readpid = do { local $/; <$fh>; }; 22 | chomp $readpid; 23 | close ($fh); 24 | 25 | ok(kill(0, $readpid), "process is still running"); 26 | 27 | my $stats = mem_stats($sock); 28 | is($stats->{pid}, $readpid, "memcached reports same pid as file"); 29 | 30 | ok($server->new_sock, "opened new socket"); 31 | ok(kill(9, $readpid), "sent KILL signal"); 32 | sleep 0.5; 33 | ok(! $server->new_sock, "failed to open new socket"); 34 | -------------------------------------------------------------------------------- /t/lib/MemcachedTest.pm: -------------------------------------------------------------------------------- 1 | package MemcachedTest; 2 | use strict; 3 | use IO::Socket::INET; 4 | use IO::Socket::UNIX; 5 | use Exporter 'import'; 6 | use Carp qw(croak); 7 | use vars qw(@EXPORT); 8 | 9 | # Instead of doing the substitution with Autoconf, we assume that 10 | # cwd == builddir. 11 | use Cwd; 12 | my $builddir = getcwd; 13 | 14 | 15 | @EXPORT = qw(start_kmemcache stop_kmemcache sleep mem_get_is mem_gets mem_gets_is mem_stats 16 | supports_sasl free_port); 17 | 18 | sub sleep { 19 | my $n = shift; 20 | select undef, undef, undef, $n; 21 | } 22 | 23 | sub mem_stats { 24 | my ($sock, $type) = @_; 25 | $type = $type ? " $type" : ""; 26 | print $sock "stats$type\r\n"; 27 | my $stats = {}; 28 | while (<$sock>) { 29 | last if /^(\.|END)/; 30 | /^(STAT|ITEM) (\S+)\s+([^\r\n]+)/; 31 | #print " slabs: $_"; 32 | $stats->{$2} = $3; 33 | } 34 | return $stats; 35 | } 36 | 37 | sub mem_get_is { 38 | # works on single-line values only. no newlines in value. 39 | my ($sock_opts, $key, $val, $msg) = @_; 40 | my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {}; 41 | my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts; 42 | 43 | my $expect_flags = $opts->{flags} || 0; 44 | my $dval = defined $val ? "'$val'" : ""; 45 | $msg ||= "$key == $dval"; 46 | 47 | print $sock "get $key\r\n"; 48 | if (! defined $val) { 49 | my $line = scalar <$sock>; 50 | if ($line =~ /^VALUE/) { 51 | $line .= scalar(<$sock>) . scalar(<$sock>); 52 | } 53 | Test::More::is($line, "END\r\n", $msg); 54 | } else { 55 | my $len = length($val); 56 | my $body = scalar(<$sock>); 57 | my $expected = "VALUE $key $expect_flags $len\r\n$val\r\nEND\r\n"; 58 | if (!$body || $body =~ /^END/) { 59 | Test::More::is($body, $expected, $msg); 60 | return; 61 | } 62 | $body .= scalar(<$sock>) . scalar(<$sock>); 63 | Test::More::is($body, $expected, $msg); 64 | } 65 | } 66 | 67 | sub mem_gets { 68 | # works on single-line values only. no newlines in value. 69 | my ($sock_opts, $key) = @_; 70 | my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {}; 71 | my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts; 72 | my $val; 73 | my $expect_flags = $opts->{flags} || 0; 74 | 75 | print $sock "gets $key\r\n"; 76 | my $response = <$sock>; 77 | if ($response =~ /^END/) { 78 | return "NOT_FOUND"; 79 | } 80 | else 81 | { 82 | $response =~ /VALUE (.*) (\d+) (\d+) (\d+)/; 83 | my $flags = $2; 84 | my $len = $3; 85 | my $identifier = $4; 86 | read $sock, $val , $len; 87 | # get the END 88 | $_ = <$sock>; 89 | $_ = <$sock>; 90 | 91 | return ($identifier,$val); 92 | } 93 | 94 | } 95 | sub mem_gets_is { 96 | # works on single-line values only. no newlines in value. 97 | my ($sock_opts, $identifier, $key, $val, $msg) = @_; 98 | my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {}; 99 | my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts; 100 | 101 | my $expect_flags = $opts->{flags} || 0; 102 | my $dval = defined $val ? "'$val'" : ""; 103 | $msg ||= "$key == $dval"; 104 | 105 | print $sock "gets $key\r\n"; 106 | if (! defined $val) { 107 | my $line = scalar <$sock>; 108 | if ($line =~ /^VALUE/) { 109 | $line .= scalar(<$sock>) . scalar(<$sock>); 110 | } 111 | Test::More::is($line, "END\r\n", $msg); 112 | } else { 113 | my $len = length($val); 114 | my $body = scalar(<$sock>); 115 | my $expected = "VALUE $key $expect_flags $len $identifier\r\n$val\r\nEND\r\n"; 116 | if (!$body || $body =~ /^END/) { 117 | Test::More::is($body, $expected, $msg); 118 | return; 119 | } 120 | $body .= scalar(<$sock>) . scalar(<$sock>); 121 | Test::More::is($body, $expected, $msg); 122 | } 123 | } 124 | 125 | sub free_port { 126 | my $type = shift || "tcp"; 127 | my $sock; 128 | my $port; 129 | while (!$sock) { 130 | $port = int(rand(20000)) + 30000; 131 | $sock = IO::Socket::INET->new(LocalAddr => '127.0.0.1', 132 | LocalPort => $port, 133 | Proto => $type, 134 | ReuseAddr => 1); 135 | } 136 | return $port; 137 | } 138 | 139 | sub supports_udp { 140 | # my $output = `$builddir/user/umemcached -h`; 141 | # return 0 if $output =~ /^memcached 1\.1\./; 142 | return 1; 143 | } 144 | 145 | sub supports_sasl { 146 | # my $output = `$builddir/user/umemcached -h`; 147 | # return 1 if $output =~ /sasl/i; 148 | return 0; 149 | } 150 | 151 | sub start_kmemcache { 152 | my ($args, $passed_port) = @_; 153 | my $port = $passed_port || free_port(); 154 | my $host = '127.0.0.1'; 155 | 156 | if ($ENV{T_MEMD_USE_DAEMON}) { 157 | my ($host, $port) = ($ENV{T_MEMD_USE_DAEMON} =~ m/^([^:]+):(\d+)$/); 158 | my $conn = IO::Socket::INET->new(PeerAddr => "$host:$port"); 159 | if ($conn) { 160 | return Memcached::Handle->new(conn => $conn, 161 | host => $host, 162 | port => $port); 163 | } 164 | croak("Failed to connect to specified kmemcache server.") unless $conn; 165 | } 166 | 167 | my $udpport = free_port("udp"); 168 | $args .= " -p $port"; 169 | if (supports_udp()) { 170 | $args .= " -U $udpport"; 171 | } 172 | 173 | my $childpid = fork(); 174 | 175 | my $exe = "$builddir/user/umemcached"; 176 | my $mod = "$builddir/kmod/kmemcache.ko"; 177 | croak("kmemcache binary doesn't exist. Haven't run 'make' ?\n") unless -e $exe; 178 | croak("kmemcache binary not executable\n") unless -x _; 179 | 180 | unless ($childpid) { 181 | exec "$builddir/test/kmcstart 1 $mod $exe $args"; 182 | exit; # never gets here. 183 | } 184 | 185 | waitpid($childpid, 0); 186 | 187 | # unix domain sockets 188 | if ($args =~ /-s (\S+)/) { 189 | my $filename = $1; 190 | my $conn = IO::Socket::UNIX->new(Peer => $filename) || 191 | croak("Failed to connect to unix domain socket: $! '$filename'"); 192 | 193 | return Memcached::Handle->new(pid => $childpid, 194 | conn => $conn, 195 | domainsocket => $filename, 196 | host => $host, 197 | port => $port); 198 | } 199 | 200 | # try to connect / find open port, only if we're not using unix domain 201 | # sockets 202 | 203 | for (1..20) { 204 | my $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port"); 205 | if ($conn) { 206 | return Memcached::Handle->new(pid => $childpid, 207 | conn => $conn, 208 | udpport => $udpport, 209 | host => $host, 210 | port => $port); 211 | } 212 | select undef, undef, undef, 0.10; 213 | } 214 | croak("Failed to startup/connect to kmemcache server."); 215 | } 216 | 217 | sub stop_kmemcache { 218 | my $childpid = fork(); 219 | 220 | unless ($childpid) { 221 | exec "$builddir/test/kmcstop 1"; 222 | exit; # never gets here. 223 | } 224 | 225 | waitpid($childpid, 0); 226 | 227 | return 0; 228 | } 229 | 230 | ############################################################################ 231 | package Memcached::Handle; 232 | sub new { 233 | my ($class, %params) = @_; 234 | return bless \%params, $class; 235 | } 236 | 237 | sub DESTROY { 238 | # my $self = shift; 239 | # kill 2, $self->{pid}; 240 | MemcachedTest::stop_kmemcache(); 241 | } 242 | 243 | sub stop { 244 | # my $self = shift; 245 | # kill 15, $self->{pid}; 246 | MemcachedTest::stop_kmemcache(); 247 | } 248 | 249 | sub host { $_[0]{host} } 250 | sub port { $_[0]{port} } 251 | sub udpport { $_[0]{udpport} } 252 | 253 | sub sock { 254 | my $self = shift; 255 | 256 | if ($self->{conn} && ($self->{domainsocket} || getpeername($self->{conn}))) { 257 | return $self->{conn}; 258 | } 259 | return $self->new_sock; 260 | } 261 | 262 | sub new_sock { 263 | my $self = shift; 264 | if ($self->{domainsocket}) { 265 | return IO::Socket::UNIX->new(Peer => $self->{domainsocket}); 266 | } else { 267 | return IO::Socket::INET->new(PeerAddr => "$self->{host}:$self->{port}"); 268 | } 269 | } 270 | 271 | sub new_udp_sock { 272 | my $self = shift; 273 | return IO::Socket::INET->new(PeerAddr => '127.0.0.1', 274 | PeerPort => $self->{udpport}, 275 | Proto => 'udp', 276 | LocalAddr => '127.0.0.1', 277 | LocalPort => MemcachedTest::free_port('udp'), 278 | ); 279 | 280 | } 281 | 282 | 1; 283 | -------------------------------------------------------------------------------- /t/memslap.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use Cwd; 4 | 5 | my $usage = "Usage: memslap.pl host:port get/set prefix"; 6 | 7 | if (@ARGV != 3) { 8 | die $usage; 9 | } 10 | 11 | my ($server, $test, $prefix) = @ARGV; 12 | 13 | printf("server=%s test=%s outputfix=%s\n", $server, $test, $prefix); 14 | 15 | if ($test eq 'get') { 16 | for ($conn = 1000; $conn <= 10000; $conn += 500) { 17 | memslap($conn, 1000); 18 | } 19 | } elsif ($test eq 'set') { 20 | for ($conn = 1000; $conn <= 10000; $conn += 500) { 21 | for ($num = 1000; $num <= 10000; $num += 1000) { 22 | memslap($conn, $num); 23 | } 24 | } 25 | } else { 26 | die $usage; 27 | } 28 | 29 | sub memslap { 30 | my ($conn, $num) = @_; 31 | my ($file) = sprintf("%s_%s_%s_%s", $prefix, $test, $conn, $num); 32 | my ($args) = sprintf("--servers=%s --test=%s --concurrency=%s --execute-number=%s >%s 2>&1", $server, $test, $conn, $num, $file); 33 | 34 | my $childpid = fork(); 35 | 36 | unless ($childpid) { 37 | exec "memslap $args"; 38 | exit; 39 | } 40 | 41 | waitpid($childpid, 0); 42 | } 43 | -------------------------------------------------------------------------------- /t/sasl/memcached.conf: -------------------------------------------------------------------------------- 1 | mech_list: plain cram-md5 2 | log_level: 5 3 | sasldb_path: /tmp/test-memcached.sasldb 4 | -------------------------------------------------------------------------------- /t/whitespace.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use FindBin qw($Bin); 4 | our @files; 5 | 6 | BEGIN { 7 | chdir "$Bin/.." or die; 8 | 9 | my @exempted = qw(Makefile.am ChangeLog doc/Makefile.am README README.md); 10 | push(@exempted, glob("doc/*.xml")); 11 | push(@exempted, glob("doc/xml2rfc/*.xsl")); 12 | push(@exempted, glob("m4/*backport*m4")); 13 | push(@exempted, glob("*.orig")); 14 | my %exempted_hash = map { $_ => 1 } @exempted; 15 | 16 | my @stuff = split /\0/, `git ls-files -z -c -m -o --exclude-standard`; 17 | @files = grep { ! $exempted_hash{$_} } @stuff; 18 | 19 | # We won't find any files if git isn't installed. If git isn't 20 | # installed, they're probably not doing any useful development, or 21 | # at the very least am will clean up whitespace when we receive 22 | # their patch. 23 | unless (@files) { 24 | use Test::More; 25 | plan skip_all => "Skipping tests probably because you don't have git."; 26 | exit 0; 27 | } 28 | } 29 | 30 | use Test::More tests => scalar(@files); 31 | 32 | foreach my $f (@files) { 33 | open(my $fh, $f) or die "Cannot open file $f: $!"; 34 | my $before = do { local $/; <$fh>; }; 35 | close ($fh); 36 | my $after = $before; 37 | $after =~ s/\t/ /g; 38 | $after =~ s/ +$//mg; 39 | $after .= "\n" unless $after =~ /\n$/; 40 | ok ($after eq $before, "$f (see devtools/clean-whitespace.pl)"); 41 | } 42 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -g -O2 -Wall -std=gnu99 3 | LDFLAGS = -lpthread 4 | 5 | CFLAGS += -I$(PWD)/../include 6 | 7 | testapp-obj = testapp.o util.o 8 | 9 | timedrun-obj = timedrun.o 10 | 11 | kmcstart-obj = kmcstart.o util.o 12 | 13 | kmcstop-obj = kmcstop.o util.o 14 | 15 | all: testapp timedrun kmcstart kmcstop 16 | 17 | testapp: $(testapp-obj) 18 | $(CC) $(LDFLAGS) -o $@ $^ 19 | 20 | timedrun: $(timedrun-obj) 21 | $(CC) -o $@ $^ 22 | 23 | kmcstart: $(kmcstart-obj) 24 | $(CC) -o $@ $^ 25 | 26 | kmcstop: $(kmcstop-obj) 27 | $(CC) -o $@ $^ 28 | 29 | clean: 30 | rm -f testapp timedrun kmcstart kmcstop *.o 31 | -------------------------------------------------------------------------------- /test/kmcstart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "util.h" 6 | 7 | /* 8 | * argv[0]: command 9 | * argv[1]: for detail output 10 | * argv[2]: kmemcache.ko path 11 | * argv[3]: umemcached command 12 | * argv[4]: args of umemcached 13 | */ 14 | int main(int argc, char *argv[]) 15 | { 16 | assert(argc > 4); 17 | 18 | if (!strcmp(argv[1], "0")) 19 | close_terminal(); 20 | 21 | start_kmc_server(argv + 2); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /test/kmcstop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "util.h" 5 | 6 | /* 7 | * argv[0]: command 8 | * argv[1]: for detail output 9 | */ 10 | int main(int argc, char *argv[]) 11 | { 12 | if (argc >= 2 && !strcmp(argv[1], "0")) 13 | close_terminal(); 14 | 15 | stop_kmc_server(NULL); 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /test/memcached-tool: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # memcached-tool: 4 | # stats/management tool for memcached. 5 | # 6 | # Author: 7 | # Brad Fitzpatrick 8 | # 9 | # License: 10 | # public domain. I give up all rights to this 11 | # tool. modify and copy at will. 12 | # 13 | 14 | use strict; 15 | use IO::Socket::INET; 16 | 17 | my $addr = shift; 18 | my $mode = shift || "display"; 19 | my ($from, $to); 20 | 21 | if ($mode eq "display") { 22 | undef $mode if @ARGV; 23 | } elsif ($mode eq "move") { 24 | $from = shift; 25 | $to = shift; 26 | undef $mode if $from < 6 || $from > 17; 27 | undef $mode if $to < 6 || $to > 17; 28 | print STDERR "ERROR: parameters out of range\n\n" unless $mode; 29 | } elsif ($mode eq 'dump') { 30 | ; 31 | } elsif ($mode eq 'stats') { 32 | ; 33 | } else { 34 | undef $mode; 35 | } 36 | 37 | undef $mode if @ARGV; 38 | 39 | die 40 | "Usage: memcached-tool [mode]\n 41 | memcached-tool 10.0.0.5:11211 display # shows slabs 42 | memcached-tool 10.0.0.5:11211 # same. (default is display) 43 | memcached-tool 10.0.0.5:11211 stats # shows general stats 44 | memcached-tool 10.0.0.5:11211 dump # dumps keys and values 45 | " unless $addr && $mode; 46 | 47 | 48 | my $sock; 49 | if ($addr =~ m:/:) { 50 | $sock = IO::Socket::UNIX->new( 51 | Peer => $addr, 52 | ); 53 | } 54 | else { 55 | $addr .= ':11211' unless $addr =~ /:\d+$/; 56 | 57 | $sock = IO::Socket::INET->new( 58 | PeerAddr => $addr, 59 | Proto => 'tcp', 60 | ); 61 | } 62 | die "Couldn't connect to $addr\n" unless $sock; 63 | 64 | if ($mode eq 'dump') { 65 | my %items; 66 | my $totalitems; 67 | 68 | print $sock "stats items\r\n"; 69 | 70 | while (<$sock>) { 71 | last if /^END/; 72 | if (/^STAT items:(\d*):number (\d*)/) { 73 | $items{$1} = $2; 74 | $totalitems += $2; 75 | } 76 | } 77 | print STDERR "Dumping memcache contents\n"; 78 | print STDERR " Number of buckets: " . scalar(keys(%items)) . "\n"; 79 | print STDERR " Number of items : $totalitems\n"; 80 | 81 | foreach my $bucket (sort(keys(%items))) { 82 | print STDERR "Dumping bucket $bucket - " . $items{$bucket} . " total items\n"; 83 | print $sock "stats cachedump $bucket $items{$bucket}\r\n"; 84 | my %keyexp; 85 | while (<$sock>) { 86 | last if /^END/; 87 | # return format looks like this 88 | # ITEM foo [6 b; 1176415152 s] 89 | if (/^ITEM (\S+) \[.* (\d+) s\]/) { 90 | $keyexp{$1} = $2; 91 | } 92 | } 93 | 94 | foreach my $k (keys(%keyexp)) { 95 | print $sock "get $k\r\n"; 96 | my $response = <$sock>; 97 | if ($response =~ /VALUE (\S+) (\d+) (\d+)/) { 98 | my $flags = $2; 99 | my $len = $3; 100 | my $val; 101 | read $sock, $val, $len; 102 | print "add $k $flags $keyexp{$k} $len\r\n$val\r\n"; 103 | # get the END 104 | $_ = <$sock>; 105 | $_ = <$sock>; 106 | } 107 | } 108 | } 109 | exit; 110 | } 111 | 112 | if ($mode eq 'stats') { 113 | my %items; 114 | 115 | print $sock "stats\r\n"; 116 | 117 | while (<$sock>) { 118 | last if /^END/; 119 | chomp; 120 | if (/^STAT\s+(\S*)\s+(.*)/) { 121 | $items{$1} = $2; 122 | } 123 | } 124 | printf ("#%-17s %5s %11s\n", $addr, "Field", "Value"); 125 | foreach my $name (sort(keys(%items))) { 126 | printf ("%24s %12s\n", $name, $items{$name}); 127 | 128 | } 129 | exit; 130 | } 131 | 132 | # display mode: 133 | 134 | my %items; # class -> { number, age, chunk_size, chunks_per_page, 135 | # total_pages, total_chunks, used_chunks, 136 | # free_chunks, free_chunks_end } 137 | 138 | print $sock "stats items\r\n"; 139 | my $max = 0; 140 | while (<$sock>) { 141 | last if /^END/; 142 | if (/^STAT items:(\d+):(\w+) (\d+)/) { 143 | $items{$1}{$2} = $3; 144 | $max = $1; 145 | } 146 | } 147 | 148 | print $sock "stats slabs\r\n"; 149 | while (<$sock>) { 150 | last if /^END/; 151 | if (/^STAT (\d+):(\w+) (\d+)/) { 152 | $items{$1}{$2} = $3; 153 | } 154 | } 155 | 156 | print " # Item_Size Max_age Pages Count Full? Evicted Evict_Time OOM\n"; 157 | foreach my $n (1..$max) { 158 | my $it = $items{$n}; 159 | next if (0 == $it->{total_pages}); 160 | my $size = $it->{chunk_size} < 1024 ? 161 | "$it->{chunk_size}B" : 162 | sprintf("%.1fK", $it->{chunk_size} / 1024.0); 163 | my $full = $it->{free_chunks_end} == 0 ? "yes" : " no"; 164 | printf("%3d %8s %9ds %7d %7d %7s %8d %8d %4d\n", 165 | $n, $size, $it->{age}, $it->{total_pages}, 166 | $it->{number}, $full, $it->{evicted}, 167 | $it->{evicted_time}, $it->{outofmemory}); 168 | } 169 | 170 | -------------------------------------------------------------------------------- /test/timedrun.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | static int caught = 0; 11 | 12 | static void caught_signal(int which) 13 | { 14 | caught = which; 15 | } 16 | 17 | static int wait_for_process(pid_t pid) 18 | { 19 | int rv = EX_SOFTWARE; 20 | int stats = 0; 21 | int i = 0; 22 | struct sigaction sig_handler; 23 | 24 | sig_handler.sa_handler = caught_signal; 25 | sig_handler.sa_flags = 0; 26 | 27 | sigaction(SIGALRM, &sig_handler, NULL); 28 | sigaction(SIGHUP, &sig_handler, NULL); 29 | sigaction(SIGINT, &sig_handler, NULL); 30 | sigaction(SIGTERM, &sig_handler, NULL); 31 | sigaction(SIGPIPE, &sig_handler, NULL); 32 | 33 | /* Loop forever waiting for the process to quit */ 34 | for (i = 0; ;i++) { 35 | pid_t p = waitpid(pid, &stats, 0); 36 | if (p == pid) { 37 | /* child exited. Let's get out of here */ 38 | rv = WIFEXITED(stats) ? 39 | WEXITSTATUS(stats) : 40 | (0x80 | WTERMSIG(stats)); 41 | break; 42 | } else { 43 | int sig = 0; 44 | switch (i) { 45 | case 0: 46 | /* On the first iteration, pass the signal through */ 47 | sig = caught > 0 ? caught : SIGTERM; 48 | if (caught == SIGALRM) { 49 | fprintf(stderr, "Timeout.. killing the process\n"); 50 | } 51 | break; 52 | case 1: 53 | sig = SIGTERM; 54 | break; 55 | default: 56 | sig = SIGKILL; 57 | break; 58 | } 59 | if (kill(pid, sig) < 0) { 60 | /* Kill failed. Must have lost the process. :/ */ 61 | perror("lost child when trying to kill"); 62 | } 63 | /* Wait up to 5 seconds for the pid */ 64 | alarm(5); 65 | } 66 | } 67 | return rv; 68 | } 69 | 70 | static int spawn_and_wait(char **argv) 71 | { 72 | int rv = EX_SOFTWARE; 73 | pid_t pid = fork(); 74 | 75 | switch (pid) { 76 | case -1: 77 | perror("fork"); 78 | rv = EX_OSERR; 79 | break; /* NOTREACHED */ 80 | case 0: 81 | execvp(argv[0], argv); 82 | perror("exec"); 83 | rv = EX_SOFTWARE; 84 | break; /* NOTREACHED */ 85 | default: 86 | rv = wait_for_process(pid); 87 | } 88 | return rv; 89 | } 90 | 91 | int main(int argc, char **argv) 92 | { 93 | int naptime = 0; 94 | assert(argc > 2); 95 | 96 | naptime = atoi(argv[1]); 97 | assert(naptime > 0 && naptime < 1800); 98 | 99 | alarm(naptime); 100 | 101 | return spawn_and_wait(argv+2); 102 | } 103 | -------------------------------------------------------------------------------- /test/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "util.h" 12 | 13 | void close_terminal(void) 14 | { 15 | int fd; 16 | 17 | fd = open("/dev/null", O_RDWR, 0); 18 | if (fd == -1) { 19 | perror("open /dev/null"); 20 | return; 21 | } 22 | if(dup2(fd, STDIN_FILENO) < 0) { 23 | perror("dup2 stdin"); 24 | return; 25 | } 26 | if(dup2(fd, STDOUT_FILENO) < 0) { 27 | perror("dup2 stdout"); 28 | return; 29 | } 30 | if(dup2(fd, STDERR_FILENO) < 0) { 31 | perror("dup2 stderr"); 32 | return; 33 | } 34 | if (fd > STDERR_FILENO && close(fd) < 0) { 35 | perror("close"); 36 | return; 37 | } 38 | } 39 | 40 | void insert_kmod(const char *mod) 41 | { 42 | pid_t pid; 43 | 44 | pid = fork(); 45 | assert(pid != -1); 46 | 47 | if (pid > 0) { 48 | int stat; 49 | pid_t c; 50 | 51 | while ((c = waitpid(pid, &stat, 0)) == (pid_t)-1 && errno == EINTR); 52 | assert(c == pid); 53 | assert(stat == 0); 54 | } else { 55 | char *argv[5]; 56 | 57 | argv[0] = "/sbin/insmod"; 58 | argv[1] = (char *)mod; 59 | argv[2] = NULL; 60 | 61 | assert(execv(argv[0], argv) == 0); 62 | } 63 | } 64 | 65 | int check_kmod(const char *mod) 66 | { 67 | int fd, size = 0, ret = 0; 68 | char *buf = NULL; 69 | 70 | if ((fd = open("/proc/modules", O_RDONLY, 0)) == -1) { 71 | perror("open /proc/modules"); 72 | ret = -1; 73 | goto out; 74 | } 75 | retry: 76 | size += 1000; 77 | if (!(buf = (char *)realloc(buf, size))) { 78 | perror("malloc"); 79 | ret = -1; 80 | goto close; 81 | } 82 | ret = read(fd, buf + size - 1000, 1000); 83 | if (ret == -1) { 84 | perror("read"); 85 | goto free; 86 | } else if (ret == 1000) { 87 | goto retry; 88 | } 89 | if (strstr(buf, mod)) { 90 | ret = 1; 91 | } else { 92 | ret = 0; 93 | } 94 | 95 | free: 96 | free(buf); 97 | close: 98 | close(fd); 99 | out: 100 | return ret; 101 | } 102 | 103 | void remove_kmod(const char *mod) 104 | { 105 | pid_t pid; 106 | 107 | pid = fork(); 108 | assert(pid != -1); 109 | 110 | if (pid > 0) { 111 | int stat; 112 | pid_t c; 113 | 114 | while ((c = waitpid(pid, &stat, 0)) == (pid_t)-1 && errno == EINTR); 115 | assert(c == pid); 116 | assert(stat == 0); 117 | } else { 118 | char *argv[5]; 119 | 120 | argv[0] = "/sbin/rmmod"; 121 | argv[1] = (char *)mod; 122 | argv[2] = NULL; 123 | 124 | assert(execv(argv[0], argv) == 0); 125 | } 126 | } 127 | 128 | void __start_kmc_server(char *argv[]) 129 | { 130 | pid_t pid; 131 | 132 | pid = fork(); 133 | assert(pid != -1); 134 | 135 | if (pid > 0) { 136 | int stat; 137 | pid_t c; 138 | 139 | while ((c = waitpid(pid, &stat, 0)) == (pid_t)-1 && errno == EINTR); 140 | assert(c == pid); 141 | assert(stat == 0); 142 | } else { 143 | assert(execv(argv[0], argv) == 0); 144 | } 145 | } 146 | 147 | void __stop_kmc_server(char *argv[]) 148 | { 149 | pid_t pid; 150 | 151 | pid = fork(); 152 | assert(pid != -1); 153 | 154 | if (pid > 0) { 155 | int stat; 156 | pid_t c; 157 | 158 | while ((c = waitpid(pid, &stat, 0)) == (pid_t)-1 && errno == EINTR); 159 | assert(c == pid); 160 | assert(stat == 0); 161 | } else { 162 | char *argv[5]; 163 | 164 | argv[0] = "/sbin/rmmod"; 165 | argv[1] = "kmemcache"; 166 | argv[2] = NULL; 167 | 168 | assert(execv(argv[0], argv) == 0); 169 | } 170 | } 171 | 172 | void start_kmc_server(char *argv[]) 173 | { 174 | int status; 175 | 176 | status = check_kmod("kmemcache"); 177 | retry: 178 | switch (status) { 179 | case 0: 180 | insert_kmod(argv[0]); 181 | __start_kmc_server(argv + 1); 182 | break; 183 | case -1: 184 | default: 185 | perror("start_kmc_server"); 186 | break; 187 | case 1: 188 | __stop_kmc_server(NULL); 189 | status = 0; 190 | goto retry; 191 | } 192 | } 193 | 194 | void stop_kmc_server(char *argv[]) 195 | { 196 | int status; 197 | 198 | status = check_kmod("kmemcache"); 199 | switch (status) { 200 | case 1: 201 | __stop_kmc_server(argv); 202 | break; 203 | case -1: 204 | default: 205 | perror("stop_kmc_server"); 206 | case 0: 207 | break; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /test/util.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_UTIL_H 2 | #define __TEST_UTIL_H 3 | 4 | extern void close_terminal(void); 5 | 6 | extern void insert_kmod(const char *mod); 7 | extern int check_kmod(const char *mod); 8 | extern void remove_kmod(const char *mod); 9 | 10 | extern void __start_kmc_server(char *argv[]); 11 | extern void __stop_kmc_server(char *argv[]); 12 | 13 | extern void start_kmc_server(char *argv[]); 14 | extern void stop_kmc_server(char *argv[]); 15 | 16 | #endif /* __TEST_UTIL_H */ 17 | -------------------------------------------------------------------------------- /user/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -g -O2 -Wall 3 | LDFLAGS = 4 | 5 | CFLAGS += -I$(PWD)/../include 6 | 7 | umemcached-obj = umemcached.o settings.o environment.o cache_bh.o shutdown.o daemon.o 8 | 9 | all: umemcached 10 | 11 | umemcached: $(umemcached-obj) 12 | $(CC) $(LDFLAGS) -o $@ $^ 13 | 14 | clean: 15 | rm -f umemcached *.o 16 | 17 | install: 18 | install -d $(INSTALLDIR) 19 | install -m 755 umemcached $(INSTALLDIR) 20 | 21 | uninstall: 22 | rm -f $(INSTALLDIR)/umemcached 23 | -------------------------------------------------------------------------------- /user/cache_bh.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "umemcached.h" 4 | 5 | /* 6 | * enable kmemcache bottom half 7 | */ 8 | int netlink_send_cache_bh(int sock) 9 | { 10 | struct cn_msg msg = { 11 | .id = { 12 | .idx = CN_IDX_CACHE_BH, 13 | .val = CN_VAL_CACHE_BH 14 | }, 15 | .len = 0 16 | }; 17 | 18 | return netlink_send(sock, &msg); 19 | } 20 | -------------------------------------------------------------------------------- /user/daemon.c: -------------------------------------------------------------------------------- 1 | /* $Header: /cvsroot/wikipedia/willow/src/bin/willow/daemon.c,v 1.1 2005/05/02 19:15:21 kateturner Exp $ */ 2 | /* $NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $ */ 3 | /*- 4 | * Copyright (c) 1990, 1993 5 | * The Regents of the University of California. All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the University nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #if defined __SUNPRO_C || defined __DECC || defined __HP_cc 33 | # pragma ident "@(#)$Header: /cvsroot/wikipedia/willow/src/bin/willow/daemon.c,v 1.1 2005/05/02 19:15:21 kateturner Exp $" 34 | # pragma ident "$NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $" 35 | #endif 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "umemcached.h" 43 | 44 | int daemonize(int nochdir, int noclose) 45 | { 46 | int fd; 47 | 48 | switch (fork()) { 49 | case -1: 50 | return (-1); 51 | case 0: 52 | break; 53 | default: 54 | _exit(EXIT_SUCCESS); 55 | } 56 | 57 | if (setsid() == -1) 58 | return (-1); 59 | 60 | if (nochdir == 0) { 61 | if(chdir("/") != 0) { 62 | perror("chdir"); 63 | return (-1); 64 | } 65 | } 66 | 67 | if (noclose == 0 && (fd = open("/dev/null", O_RDWR, 0)) != -1) { 68 | if(dup2(fd, STDIN_FILENO) < 0) { 69 | perror("dup2 stdin"); 70 | return (-1); 71 | } 72 | if(dup2(fd, STDOUT_FILENO) < 0) { 73 | perror("dup2 stdout"); 74 | return (-1); 75 | } 76 | if(dup2(fd, STDERR_FILENO) < 0) { 77 | perror("dup2 stderr"); 78 | return (-1); 79 | } 80 | 81 | if (fd > STDERR_FILENO) { 82 | if(close(fd) < 0) { 83 | perror("close"); 84 | return (-1); 85 | } 86 | } 87 | } 88 | return (0); 89 | } 90 | -------------------------------------------------------------------------------- /user/environment.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "umemcached.h" 6 | 7 | int netlink_send_env(int sock, struct cn_msg *rcv) 8 | { 9 | ask_env_t *ask; 10 | ack_env_t *ack; 11 | struct cn_msg *snd; 12 | char *env; 13 | char buf[KMC_V_ACK_ENV + PATH_MAX]; 14 | 15 | union { 16 | str_t *_str; 17 | int *_int; 18 | unsigned long *_ul; 19 | } data; 20 | 21 | ask = (ask_env_t *)rcv->data; 22 | 23 | snd = (struct cn_msg *)buf; 24 | ack = (ack_env_t *)snd->data; 25 | snd->id.idx = rcv->id.idx; 26 | snd->id.val = rcv->id.val; 27 | ack->env = *ask; 28 | 29 | switch (*ask) { 30 | case T_MEMD_INITIAL_MALLOC: 31 | data._ul = (unsigned long *)ack->data; 32 | env = getenv("T_MEMD_INITIAL_MALLOC"); 33 | if (env) { 34 | *data._ul = atol(env); 35 | } else { 36 | *data._ul = 0; 37 | } 38 | snd->len = sizeof(ack_env_t) + sizeof(unsigned long); 39 | break; 40 | case T_MEMD_SLABS_LIMIT: 41 | data._int = (int *)ack->data; 42 | env = getenv("T_MEMD_SLABS_LIMIT"); 43 | if (env && atoi(env)) { 44 | *data._int = 1; 45 | } else { 46 | *data._int = 0; 47 | } 48 | snd->len = sizeof(ack_env_t) + sizeof(int); 49 | break; 50 | case MEMCACHED_PORT_FILENAME: 51 | data._str = (str_t *)ack->data; 52 | env = getenv("MEMCACHED_PORT_FILENAME"); 53 | if (env) { 54 | data._str->len = strlen(env); 55 | memcpy(data._str->buf, env, data._str->len); 56 | } else { 57 | data._str->len = 0; 58 | } 59 | snd->len = sizeof(ack_env_t) + data._str->len; 60 | break; 61 | default: 62 | printf("kernel env error\n"); 63 | return -1; 64 | } 65 | 66 | return netlink_send(sock, snd); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /user/settings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "umemcached.h" 11 | 12 | static struct addrinfo_pair { 13 | struct addrinfo *ai; 14 | net_transport_t trans; 15 | struct addrinfo_pair *next; 16 | } ainfo = { 17 | .ai = NULL, 18 | .next = &ainfo 19 | }; 20 | 21 | static void unregister_addrinfo(void) 22 | { 23 | struct addrinfo_pair *p, *q; 24 | 25 | for (p = ainfo.next; p != &ainfo;) { 26 | freeaddrinfo(p->ai); 27 | q = p->next; 28 | free(p); 29 | p = q; 30 | } 31 | ainfo.next = &ainfo; 32 | } 33 | 34 | static int register_addrinfo(struct addrinfo *ai, net_transport_t trans) 35 | { 36 | struct addrinfo_pair *pair; 37 | 38 | pair = malloc(sizeof(*pair)); 39 | if (!pair) { 40 | unregister_addrinfo(); 41 | return 1; 42 | } 43 | 44 | pair->ai = ai; 45 | pair->trans = trans; 46 | pair->next = ainfo.next; 47 | ainfo.next = pair; 48 | 49 | return 0; 50 | } 51 | 52 | #define xisspace(c) isspace((unsigned char)c) 53 | 54 | int safe_strtol(const char *str, int *out) { 55 | errno = 0; 56 | *out = 0; 57 | char *endptr; 58 | long l = strtol(str, &endptr, 10); 59 | 60 | if ((errno == ERANGE) || (str == endptr)) { 61 | return -1; 62 | } 63 | 64 | if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) { 65 | *out = l; 66 | return 0; 67 | } 68 | return -1; 69 | } 70 | 71 | static int parse_server_socket(const char *interface, int port, 72 | net_transport_t transport) 73 | { 74 | int ret = 0; 75 | char port_buf[NI_MAXSERV]; 76 | struct addrinfo *ai; 77 | struct addrinfo hints = { 78 | .ai_flags = AI_PASSIVE, 79 | .ai_family = AF_UNSPEC 80 | }; 81 | 82 | hints.ai_socktype = IS_UDP(transport) ? SOCK_DGRAM : SOCK_STREAM; 83 | 84 | if (port == -1) 85 | port = 0; 86 | snprintf(port_buf, sizeof(port_buf), "%d", port); 87 | 88 | ret = getaddrinfo(interface, port_buf, &hints, &ai); 89 | if (ret != 0) { 90 | if (ret != EAI_SYSTEM) 91 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); 92 | else 93 | perror("getaddrinfo"); 94 | return 1; 95 | } 96 | 97 | return register_addrinfo(ai, transport); 98 | } 99 | 100 | static int parse_server_sockets(int port, net_transport_t transport) 101 | { 102 | if (!settings.inter) { 103 | return parse_server_socket(settings.inter, port, transport); 104 | } else { 105 | char *b, *p; 106 | int ret = 0; 107 | char *list; 108 | 109 | list = strdup(settings.inter); 110 | if (list == NULL) { 111 | fprintf(stderr, "Failed to allocate memory" 112 | "for parsing server interface string\n"); 113 | return 1; 114 | } 115 | 116 | for (p = strtok_r(list, ";,", &b); 117 | p != NULL; 118 | p = strtok_r(NULL, ";,", &b)) { 119 | int the_port = port; 120 | char *s = strchr(p, ':'); 121 | 122 | if (s != NULL) { 123 | *s = '\0'; 124 | ++s; 125 | if (safe_strtol(s, &the_port)) { 126 | fprintf(stderr, "Invalid port number: \"%s\"", s); 127 | return 1; 128 | } 129 | } 130 | if (strcmp(p, "*") == 0) { 131 | p = NULL; 132 | } 133 | 134 | ret |= parse_server_socket(p, the_port, transport); 135 | if (ret) { 136 | fprintf(stderr, "parse socket error\n"); 137 | return 1; 138 | } 139 | } 140 | 141 | free(list); 142 | return ret; 143 | } 144 | } 145 | 146 | static int construct_inet(void *_addr) 147 | { 148 | int res = 0; 149 | struct addrinfo *addr; 150 | struct addrinfo_pair *p; 151 | sock_entry_t *entry; 152 | inet_t *inet = (inet_t *)_addr; 153 | 154 | if (settings.port && parse_server_sockets(settings.port, 155 | tcp_transport)) { 156 | res = -1; 157 | goto out; 158 | } 159 | if (settings.udpport && parse_server_sockets(settings.udpport, 160 | udp_transport)) { 161 | res = -1; 162 | goto out; 163 | } 164 | 165 | entry = (sock_entry_t *)inet->buf; 166 | for (p = ainfo.next; p != &ainfo; p = p->next) { 167 | for (addr = p->ai; addr; addr = addr->ai_next) { 168 | entry->trans = p->trans; 169 | entry->family = addr->ai_family; 170 | entry->type = addr->ai_socktype; 171 | entry->protocol = addr->ai_protocol; 172 | entry->addrlen = addr->ai_addrlen; 173 | memcpy(entry->addr, addr->ai_addr, addr->ai_addrlen); 174 | 175 | res += entry->addrlen + sizeof(sock_entry_t); 176 | entry = (sock_entry_t *)((char *)inet->buf + res); 177 | } 178 | } 179 | inet->len = res; 180 | 181 | out: 182 | unregister_addrinfo(); 183 | 184 | //keep issue_67.t test passed 185 | //return (res <= 0 ? res : res + sizeof(inet_t)); 186 | return (res + sizeof(inet_t)); 187 | } 188 | 189 | static int construct_unix(void *addr) 190 | { 191 | str_t *str = (str_t *)addr; 192 | 193 | str->len = strlen(settings.socketpath); 194 | memcpy(str->buf, settings.socketpath, str->len); 195 | 196 | return (str->len + sizeof(str_t)); 197 | } 198 | 199 | static int construct_inter(void *addr) 200 | { 201 | str_t *str = (str_t *)addr; 202 | 203 | str->len = strlen(settings.inter); 204 | memcpy((void *)str->buf, settings.inter, str->len); 205 | 206 | return (str->len + sizeof(str_t)); 207 | } 208 | 209 | static int construct_factor(void *addr) 210 | { 211 | str_t *str = (str_t *)addr; 212 | 213 | str->len = strlen(settings.factor); 214 | memcpy((void *)str->buf, settings.factor, str->len); 215 | 216 | return (str->len + sizeof(str_t)); 217 | } 218 | 219 | static int construct_settings(struct cn_msg *msg) 220 | { 221 | int ret = 0; 222 | int res, pos = 0; 223 | settings_init_t *data; 224 | 225 | data = (settings_init_t *)msg->data; 226 | 227 | data->base.port = settings.port; 228 | data->base.udpport = settings.udpport; 229 | data->base.access = settings.access; 230 | data->base.backlog = settings.backlog; 231 | data->base.verbose = settings.verbose; 232 | data->base.maxbytes = settings.maxbytes; 233 | data->base.maxconns = settings.maxconns; 234 | data->base.num_threads_per_udp = settings.num_threads_per_udp; 235 | data->base.reqs_per_event = settings.reqs_per_event; 236 | data->base.evict_to_free = settings.evict_to_free; 237 | data->base.chunk_size = settings.chunk_size; 238 | data->base.item_size_max = settings.item_size_max; 239 | data->base.slab_automove = settings.slab_automove; 240 | data->base.hashpower_init = settings.hashpower_init; 241 | data->base.hash_bulk_move = settings.hash_bulk_move; 242 | data->base.slab_bulk_check = settings.slab_bulk_check; 243 | data->base.oldest_live = settings.oldest_live; 244 | data->base.binding_protocol = settings.binding_protocol; 245 | data->base.factor_numerator = settings.factor_numerator; 246 | data->base.factor_denominator = settings.factor_denominator; 247 | data->base.use_cas = settings.use_cas; 248 | data->base.sasl = settings.sasl; 249 | data->base.maxconns_fast = settings.maxconns_fast; 250 | data->base.slab_reassign = settings.slab_reassign; 251 | data->base.prefix_delimiter = settings.prefix_delimiter; 252 | data->base.detail_enabled = settings.detail_enabled; 253 | data->base.shutdown_command = settings.shutdown_command; 254 | data->base.preallocate = settings.preallocate; 255 | data->base.factor = NULL; 256 | data->base.socketpath = NULL; 257 | data->base.inter = NULL; 258 | 259 | /* SLAB_FACTOR */ 260 | res = construct_factor(data->data + pos); 261 | data->flags |= SLAB_FACTOR; 262 | data->len += res; 263 | pos += res; 264 | 265 | /* INET_INTER */ 266 | if (settings.inter != NULL) { 267 | res = construct_inter(data->data + pos); 268 | data->flags |= INET_INTER; 269 | data->len += res; 270 | pos += res; 271 | } 272 | 273 | if (settings.socketpath != NULL) { 274 | /* UNIX_SOCK */ 275 | res = construct_unix(data->data + pos); 276 | data->flags |= UNIX_SOCK; 277 | data->len += res; 278 | pos += res; 279 | } else { 280 | /* INET_SOCK */ 281 | res = construct_inet(data->data + pos); 282 | if (res <= 0) { 283 | ret = -1; 284 | } else { 285 | data->flags |= INET_SOCK; 286 | data->len += res; 287 | pos += res; 288 | } 289 | } 290 | 291 | msg->len = data->len + sizeof(*data); 292 | 293 | return ret; 294 | } 295 | 296 | int netlink_send_settings(int sock, struct cn_id *id) 297 | { 298 | char buf[NETLINK_PAYLOAD] = {0}; 299 | struct cn_msg *msg = (struct cn_msg *)buf; 300 | 301 | if (construct_settings(msg)) { 302 | printf("construct settings msg error\n"); 303 | return -1; 304 | } 305 | msg->id.idx = id->idx; 306 | msg->id.val = id->val; 307 | 308 | return netlink_send(sock, msg); 309 | } 310 | -------------------------------------------------------------------------------- /user/shutdown.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "umemcached.h" 4 | 5 | int netlink_send_shutdown(int sock, struct cn_id *id) 6 | { 7 | struct cn_msg msg; 8 | 9 | msg.id.idx = id->idx; 10 | msg.id.val = id->val; 11 | msg.len = 0; 12 | 13 | return netlink_send(sock, &msg); 14 | } 15 | -------------------------------------------------------------------------------- /user/umemcached.h: -------------------------------------------------------------------------------- 1 | #ifndef __UMEMCACHED_H 2 | #define __UMEMCACHED_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "memcache.h" 8 | #include "connector.h" 9 | #include "sasl.h" 10 | 11 | //typedef int bool; 12 | 13 | struct settings { 14 | size_t maxbytes; 15 | int maxconns; 16 | int port; 17 | int udpport; 18 | char *inter; 19 | int verbose; 20 | rel_time_t oldest_live; /* ignore existing items older than this */ 21 | int evict_to_free; 22 | char *socketpath; /* path to unix socket if using local socket */ 23 | int access; /* access mask (a la chmod) for unix domain socket */ 24 | char *factor; /* chunk size growth factor */ 25 | int factor_numerator; /* chunk size growth factor */ 26 | int factor_denominator; /* chunk size growth factor */ 27 | int chunk_size; 28 | int num_threads_per_udp; /* number of worker threads serving each udp socket */ 29 | char prefix_delimiter; /* character that marks a key prefix (for stats) */ 30 | int detail_enabled; /* nonzero if we're collecting detailed stats */ 31 | int reqs_per_event; /* Maximum number of io to process on each 32 | io-event. */ 33 | bool use_cas; 34 | protocol_t binding_protocol; 35 | int backlog; 36 | int item_size_max; /* Maximum item size, and upper end for slabs */ 37 | bool sasl; /* SASL on/off */ 38 | bool maxconns_fast; /* Whether or not to early close connections */ 39 | bool slab_reassign; /* Whether or not slab reassignment is allowed */ 40 | int slab_automove; /* Whether or not to automatically move slabs */ 41 | int hashpower_init; /* Starting hash power level */ 42 | bool shutdown_command; /* allow shutdown command */ 43 | int slab_bulk_check; 44 | int hash_bulk_move; 45 | bool preallocate; 46 | }; 47 | 48 | extern struct settings settings; 49 | 50 | extern void wait_for_thread_exit(int nthreads); 51 | extern void notify_thread_exit(void); 52 | 53 | extern int daemonize(int nochdir, int noclose); 54 | 55 | extern int netlink_send(int sock, struct cn_msg *msg); 56 | extern int netlink_send_env(int sock, struct cn_msg *rcv); 57 | extern int netlink_send_settings(int sock, struct cn_id *id); 58 | extern int netlink_send_cache_bh(int sock); 59 | extern int netlink_send_shutdown(int sock, struct cn_id *id); 60 | 61 | #endif /* __UMEMCACHED_H */ 62 | --------------------------------------------------------------------------------