pax_global_header00006660000000000000000000000064124213237600014512gustar00rootroot0000000000000052 comment=8daeb77d5b43535df2ec352d706c4e78945510de nutcracker-0.4.0+dfsg/000077500000000000000000000000001242132376000145735ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/.gitignore000066400000000000000000000012211242132376000165570ustar00rootroot00000000000000# pyc *.pyc # Compiled Object files *.lo *.o # Compiled Dynamic libraries *.so # Compiled Static libraries *.la *.a # Compiled misc *.dep *.gcda *.gcno *.gcov # Packages *.tar.gz *.tar.bz2 # Logs *.log # Temporary *.swp *.~ *.project *.cproject # Core and executable core* nutcracker # extracted yaml !/contrib/yaml-0.1.4.tar.gz # Autotools .deps .libs /aclocal.m4 /autom4te.cache /stamp-h1 /autoscan.log /libtool /config/config.guess /config/config.sub /config/depcomp /config/install-sh /config/ltmain.sh /config/missing /config /config.h /config.h.in /config.h.in~ /config.log /config.status /configure.scan /configure Makefile Makefile.in nutcracker-0.4.0+dfsg/.travis.yml000066400000000000000000000001701242132376000167020ustar00rootroot00000000000000language: c script: CFLAGS="-ggdb3 -O0" autoreconf -fvi && ./configure --enable-debug=log && make && sudo make install nutcracker-0.4.0+dfsg/ChangeLog000066400000000000000000000073631242132376000163560ustar00rootroot000000000000002013-20-12 Manju Rajashekhar * twemproxy: version 0.3.0 release SRANDMEMBER support for the optional count argument (mkhq) Handle case where server responds while the request is still being sent (jdi-tagged) event ports (solaris/smartos) support add timestamp when the server was ejected support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat) kqueue (bsd) support (ferenyx) fix parsing redis response to accept integer reply (charsyam) 2013-23-04 Manju Rajashekhar * twemproxy: version 0.2.4 release redis keys must be less than mbuf_data_size() in length (fifsky) Adds support for DUMP/RESTORE commands in Redis (remotezygote) Use of the weight value in the modula distribution (mezzatto) Add support to unix socket connections to servers (mezzatto) only check for duplicate server name and not 'host:port:weight' when 'name' is configured crc16 hash support added (mezzatto) 2013-31-01 Manju Rajashekhar * twemproxy: version 0.2.3 release RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon) EVAL and EVALSHA support (ferenyx) exit 1 if configuration file is invalid (cofyc) return non-zero exit status when nutcracker cannot start for some reason use server names in stats (charsyam) Fix failure to resolve long FQDN name resolve (conmame) add support for hash tags 2012-18-10 Manju Rajashekhar * twemproxy: version 0.2.2 release fix the off-by-one error when calculating redis key length 2012-12-10 Manju Rajashekhar * twemproxy: version 0.2.1 release don't use buf in conf_add_server allow an optional instance name for consistent hashing (charsyam) add --stats-addr=S option add stats-bind-any -a option (charsyam) 2012-12-03 Manju Rajashekhar * twemproxy: version 0.2.0 release add -D or --describe-stats command-line argument to print stats description redis support in twemproxy setup pre/post splitcopy and pre/post coalesce handlers in msg struct memcache pre_splitcopy, post_splitcopy, pre_coalesce and post_coalesce handlers every fragment of a msg vector keeps track of the first/last fragment, number of fragments and fragment owner set up msg parser handler for memcache connections refactor parsing code and create header file nc_proto.h stats_listen should use st->addr as the listening address string delete stats tracking memcache requests and responses; stats module no longer tracks protocol related stats 2012-10-27 Manju Rajashekhar * twemproxy: version 0.1.20 release on msg_repair, msg->pos should point to nbuf->pos and not nbuf->last refactor memcache parsing code into proto directory add redis option to configuration file fix macro definition strXcmp error for big endian fix log_hexdump and loga_hexdump 2012-07-31 Manju Rajashekhar * twemproxy: version 0.1.19 release close server connection on a stray response (yashh, bmatheny) 2012-06-19 Manju Rajashekhar * twemproxy: version 0.1.18 release command line option to set mbuf chunk size 2012-05-09 Manju Rajashekhar * twemproxy: version 0.1.17 release use _exit(0) instead of exit(0) when daemonizing use loga instead of log_stderr in nc_stacktrace 2012-02-09 Manju Rajashekhar * twemproxy: version 0.1.16 release twemproxy (aka nutcracker) is a fast and lightweight proxy for memcached protocol. nutcracker-0.4.0+dfsg/LICENSE000066400000000000000000000236751242132376000156150ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONSnutcracker-0.4.0+dfsg/Makefile.am000066400000000000000000000003621242132376000166300ustar00rootroot00000000000000MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.h.in config.h.in~ stamp-h.in ACLOCAL_AMFLAGS = -I m4 SUBDIRS = contrib src dist_man_MANS = man/nutcracker.8 EXTRA_DIST = README.md NOTICE LICENSE ChangeLog conf scripts notes nutcracker-0.4.0+dfsg/NOTICE000066400000000000000000000142441242132376000155040ustar00rootroot00000000000000twemproxy is a fast and lightweight proxy for memcached protocol Copyright (C) 2012 Twitter, Inc. Portions of twemproxy were inspired from nginx: http://nginx.org/ The implementation of generic array (nc_array.[ch]) and red black tree (nc_rbtree.[ch]) also comes from nginx-0.8.55. /* * Copyright (C) 2002-2010 Igor Sysoev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ The generic queue implementation comes from BSD /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ The implementation of consistent hashing and individual hash algorithms were borrowed from libmemcached. Copyright (c) 2011, Data Differential (http://datadifferential.com/) Copyright (c) 2007-2010, TangentOrg (Brian Aker) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of TangentOrg nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The source also includes libyaml (yaml-0.1.4) in contrib/ directory Copyright (c) 2006 Kirill Simonov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nutcracker-0.4.0+dfsg/README.md000066400000000000000000000345401242132376000160600ustar00rootroot00000000000000# twemproxy (nutcracker) [![Build Status](https://secure.travis-ci.org/twitter/twemproxy.png)](http://travis-ci.org/twitter/twemproxy) **twemproxy** (pronounced "two-em-proxy"), aka **nutcracker** is a fast and lightweight proxy for [memcached](http://www.memcached.org/) and [redis](http://redis.io/) protocol. It was primarily built to reduce the connection count on the backend caching servers. ## Build To build nutcracker from [distribution tarball](http://code.google.com/p/twemproxy/downloads/list): $ ./configure $ make $ sudo make install To build nutcracker from [distribution tarball](http://code.google.com/p/twemproxy/downloads/list) in _debug mode_: $ CFLAGS="-ggdb3 -O0" ./configure --enable-debug=full $ make $ sudo make install To build nutcracker from source with _debug logs enabled_ and _assertions disabled_: $ git clone git@github.com:twitter/twemproxy.git $ cd twemproxy $ autoreconf -fvi $ ./configure --enable-debug=log $ make $ src/nutcracker -h ## Features + Fast. + Lightweight. + Maintains persistent server connections. + Keeps connection count on the backend caching servers low. + Enables pipelining of requests and responses. + Supports proxying to multiple servers. + Supports multiple server pools simultaneously. + Shard data automatically across multiple servers. + Implements the complete [memcached ascii](notes/memcache.txt) and [redis](notes/redis.md) protocol. + Easy configuration of server pools through a YAML file. + Supports multiple hashing modes including consistent hashing and distribution. + Can be configured to disable nodes on failures. + Observability through stats exposed on stats monitoring port. + Works with Linux, *BSD, OS X and Solaris (SmartOS) ## Help Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file] [-c conf file] [-s stats port] [-a stats addr] [-i stats interval] [-p pid file] [-m mbuf size] Options: -h, --help : this help -V, --version : show version and exit -t, --test-conf : test configuration for syntax errors and exit -d, --daemonize : run as a daemon -D, --describe-stats : print stats description and exit -v, --verbosity=N : set logging level (default: 5, min: 0, max: 11) -o, --output=S : set logging file (default: stderr) -c, --conf-file=S : set configuration file (default: conf/nutcracker.yml) -s, --stats-port=N : set stats monitoring port (default: 22222) -a, --stats-addr=S : set stats monitoring ip (default: 0.0.0.0) -i, --stats-interval=N : set stats aggregation interval in msec (default: 30000 msec) -p, --pid-file=S : set pid file (default: off) -m, --mbuf-size=N : set size of mbuf chunk in bytes (default: 16384 bytes) ## Zero Copy In nutcracker, all the memory for incoming requests and outgoing responses is allocated in mbuf. Mbuf enables zero-copy because the same buffer on which a request was received from the client is used for forwarding it to the server. Similarly the same mbuf on which a response was received from the server is used for forwarding it to the client. Furthermore, memory for mbufs is managed using a reuse pool. This means that once mbuf is allocated, it is not deallocated, but just put back into the reuse pool. By default each mbuf chunk is set to 16K bytes in size. There is a trade-off between the mbuf size and number of concurrent connections nutcracker can support. A large mbuf size reduces the number of read syscalls made by nutcracker when reading requests or responses. However, with large mbuf size, every active connection would use up 16K bytes of buffer which might be an issue when nutcracker is handling large number of concurrent connections from clients. When nutcracker is meant to handle a large number of concurrent client connections, you should set chunk size to a small value like 512 bytes using the -m or --mbuf-size=N argument. ## Configuration nutcracker can be configured through a YAML file specified by the -c or --conf-file command-line argument on process start. The configuration file is used to specify the server pools and the servers within each pool that nutcracker manages. The configuration files parses and understands the following keys: + **listen**: The listening address and port (name:port or ip:port) for this server pool. + **hash**: The name of the hash function. Possible values are: + one_at_a_time + md5 + crc16 + crc32 (crc32 implementation compatible with [libmemcached](http://libmemcached.org/)) + crc32a (correct crc32 implementation as per the spec) + fnv1_64 + fnv1a_64 + fnv1_32 + fnv1a_32 + hsieh + murmur + jenkins + **hash_tag**: A two character string that specifies the part of the key used for hashing. Eg "{}" or "$$". [Hash tag](notes/recommendation.md#hash-tags) enable mapping different keys to the same server as long as the part of the key within the tag is the same. + **distribution**: The key distribution mode. Possible values are: + ketama + modula + random + **timeout**: The timeout value in msec that we wait for to establish a connection to the server or receive a response from a server. By default, we wait indefinitely. + **backlog**: The TCP backlog argument. Defaults to 512. + **preconnect**: A boolean value that controls if nutcracker should preconnect to all the servers in this pool on process start. Defaults to false. + **redis**: A boolean value that controls if a server pool speaks redis or memcached protocol. Defaults to false. + **server_connections**: The maximum number of connections that can be opened to each server. By default, we open at most 1 server connection. + **auto_eject_hosts**: A boolean value that controls if server should be ejected temporarily when it fails consecutively server_failure_limit times. See [liveness recommendations](notes/recommendation.md#liveness) for information. Defaults to false. + **server_retry_timeout**: The timeout value in msec to wait for before retrying on a temporarily ejected server, when auto_eject_host is set to true. Defaults to 30000 msec. + **server_failure_limit**: The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject_host is set to true. Defaults to 2. + **servers**: A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. For example, the configuration file in [conf/nutcracker.yml](conf/nutcracker.yml), also shown below, configures 5 server pools with names - _alpha_, _beta_, _gamma_, _delta_ and omega. Clients that intend to send requests to one of the 10 servers in pool delta connect to port 22124 on 127.0.0.1. Clients that intend to send request to one of 2 servers in pool omega connect to unix path /tmp/gamma. Requests sent to pool alpha and omega have no timeout and might require timeout functionality to be implemented on the client side. On the other hand, requests sent to pool beta, gamma and delta timeout after 400 msec, 400 msec and 100 msec respectively when no response is received from the server. Of the 5 server pools, only pools alpha, gamma and delta are configured to use server ejection and hence are resilient to server failures. All the 5 server pools use ketama consistent hashing for key distribution with the key hasher for pools alpha, beta, gamma and delta set to fnv1a_64 while that for pool omega set to hsieh. Also only pool beta uses [nodes names](notes/recommendation.md#node-names-for-consistent-hashing) for consistent hashing, while pool alpha, gamma, delta and omega use 'host:port:weight' for consistent hashing. Finally, only pool alpha and beta can speak redis protocol, while pool gamma, deta and omega speak memcached protocol. alpha: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true redis: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:6379:1 beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 gamma: listen: 127.0.0.1:22123 hash: fnv1a_64 distribution: ketama timeout: 400 backlog: 1024 preconnect: true auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 3 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 delta: listen: 127.0.0.1:22124 hash: fnv1a_64 distribution: ketama timeout: 100 auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11214:1 - 127.0.0.1:11215:1 - 127.0.0.1:11216:1 - 127.0.0.1:11217:1 - 127.0.0.1:11218:1 - 127.0.0.1:11219:1 - 127.0.0.1:11220:1 - 127.0.0.1:11221:1 - 127.0.0.1:11222:1 - 127.0.0.1:11223:1 omega: listen: /tmp/gamma hash: hsieh distribution: ketama auto_eject_hosts: false servers: - 127.0.0.1:11214:100000 - 127.0.0.1:11215:1 Finally, to make writing syntactically correct configuration file easier, nutcracker provides a command-line argument -t or --test-conf that can be used to test the YAML configuration file for any syntax error. ## Observability Observability in nutcracker is through logs and stats. Nutcracker exposes stats at the granularity of server pool and servers per pool through the stats monitoring port. The stats are essentially JSON formatted key-value pairs, with the keys corresponding to counter names. By default stats are exposed on port 22222 and aggregated every 30 seconds. Both these values can be configured on program start using the -c or --conf-file and -i or --stats-interval command-line arguments respectively. You can print the description of all stats exported by nutcracker using the -D or --describe-stats command-line argument. $ nutcracker --describe-stats pool stats: client_eof "# eof on client connections" client_err "# errors on client connections" client_connections "# active client connections" server_ejects "# times backend server was ejected" forward_error "# times we encountered a forwarding error" fragments "# fragments created from a multi-vector request" server stats: server_eof "# eof on server connections" server_err "# errors on server connections" server_timedout "# timeouts on server connections" server_connections "# active server connections" requests "# requests" request_bytes "total request bytes" responses "# responses" response_bytes "total response bytes" in_queue "# requests in incoming queue" in_queue_bytes "current request bytes in incoming queue" out_queue "# requests in outgoing queue" out_queue_bytes "current request bytes in outgoing queue" Logging in nutcracker is only available when nutcracker is built with logging enabled. By default logs are written to stderr. Nutcracker can also be configured to write logs to a specific file through the -o or --output command-line argument. On a running nutcracker, we can turn log levels up and down by sending it SIGTTIN and SIGTTOU signals respectively and reopen log files by sending it SIGHUP signal. ## Pipelining Nutcracker enables proxying multiple client connections onto one or few server connections. This architectural setup makes it ideal for pipelining requests and responses and hence saving on the round trip time. For example, if nutcracker is proxying three client connections onto a single server and we get requests - 'get key\r\n', 'set key 0 0 3\r\nval\r\n' and 'delete key\r\n' on these three connections respectively, nutcracker would try to batch these requests and send them as a single message onto the server connection as 'get key\r\nset key 0 0 3\r\nval\r\ndelete key\r\n'. Pipelining is the reason why nutcracker ends up doing better in terms of throughput even though it introduces an extra hop between the client and server. ## Deployment If you are deploying nutcracker in production, you might consider reading through the [recommendation document](notes/recommendation.md) to understand the parameters you could tune in nutcracker to run it efficiently in the production environment. ## Utils + [nagios checks](https://github.com/wanelo/nagios-checks/blob/master/check_twemproxy) + [circunous](https://github.com/wanelo-chef/nad-checks/blob/master/recipes/twemproxy.rb) + [puppet module](https://github.com/wuakitv/puppet-twemproxy) + [nutcracker-web](https://github.com/kontera-technologies/nutcracker-web) + [munin-plugin](https://github.com/eveiga/contrib/tree/nutcracker/plugins/nutcracker) + [collectd-plugin](https://github.com/bewie/collectd-twemproxy) + [redis-twemproxy agent](https://github.com/Stono/redis-twemproxy-agent) + [sensu-metrics](https://github.com/sensu/sensu-community-plugins/blob/master/plugins/twemproxy/twemproxy-metrics.rb) + [redis-mgr](https://github.com/idning/redis-mgr) + [smitty for twemproxy failover](https://github.com/areina/smitty) ## Users + [Pinterest](http://pinterest.com/) + [Tumblr](https://www.tumblr.com/) + [Twitter](https://twitter.com/) + [Vine](http://vine.co/) + [Kiip](http://www.kiip.me/) + [Wuaki.tv](https://wuaki.tv/) + [Wanelo](http://wanelo.com/) + [Kontera](http://kontera.com/) + [Wikimedia](http://www.wikimedia.org/) + [Bright](http://www.bright.com/) + [56.com](http://www.56.com/) + [Snapchat](http://www.snapchat.com/) + [Digg](http://digg.com/) + [Gawkermedia](http://advertising.gawker.com/) + [3scale.net](http://3scale.net) + [Ooyala](http://www.ooyala.com) + [Twitch](http://twitch.tv) ## Issues and Support Have a bug or a question? Please create an issue here on GitHub! https://github.com/twitter/twemproxy/issues ## Committers * Manju Rajashekhar ([@manju](https://twitter.com/manju)) * Lin Yang ([@idning](https://github.com/idning)) Thank you to all of our [contributors](https://github.com/twitter/twemproxy/graphs/contributors)! ## License Copyright 2012 Twitter, Inc. Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 nutcracker-0.4.0+dfsg/conf/000077500000000000000000000000001242132376000155205ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/conf/nutcracker.leaf.yml000066400000000000000000000003211242132376000213060ustar00rootroot00000000000000leaf: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 nutcracker-0.4.0+dfsg/conf/nutcracker.root.yml000066400000000000000000000002271242132376000213670ustar00rootroot00000000000000root: listen: 127.0.0.1:22120 hash: fnv1a_64 distribution: ketama preconnect: true auto_eject_hosts: false servers: - 127.0.0.1:22121:1 nutcracker-0.4.0+dfsg/conf/nutcracker.yml000066400000000000000000000024251242132376000204070ustar00rootroot00000000000000alpha: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true redis: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:6379:1 beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 gamma: listen: 127.0.0.1:22123 hash: fnv1a_64 distribution: ketama timeout: 400 backlog: 1024 preconnect: true auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 3 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 delta: listen: 127.0.0.1:22124 hash: fnv1a_64 distribution: ketama timeout: 100 auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11214:1 - 127.0.0.1:11215:1 - 127.0.0.1:11216:1 - 127.0.0.1:11217:1 - 127.0.0.1:11218:1 - 127.0.0.1:11219:1 - 127.0.0.1:11220:1 - 127.0.0.1:11221:1 - 127.0.0.1:11222:1 - 127.0.0.1:11223:1 omega: listen: /tmp/gamma hash: hsieh distribution: ketama auto_eject_hosts: false servers: - 127.0.0.1:11214:100000 - 127.0.0.1:11215:1 nutcracker-0.4.0+dfsg/configure.ac000066400000000000000000000135611242132376000170670ustar00rootroot00000000000000# Define the package version numbers and the bug reporting address m4_define([NC_MAJOR], 0) m4_define([NC_MINOR], 3) m4_define([NC_PATCH], 0) m4_define([NC_BUGS], [manj@cs.stanford.edu]) # Initialize autoconf AC_PREREQ([2.64]) AC_INIT([nutcracker], [NC_MAJOR.NC_MINOR.NC_PATCH], [NC_BUGS]) AC_CONFIG_SRCDIR([src/nc.c]) AC_CONFIG_AUX_DIR([config]) AC_CONFIG_HEADERS([config.h:config.h.in]) AC_CONFIG_MACRO_DIR([m4]) # Initialize automake AM_INIT_AUTOMAKE([1.9 foreign]) # Define macro variables for the package version numbers AC_DEFINE(NC_VERSION_MAJOR, NC_MAJOR, [Define the major version number]) AC_DEFINE(NC_VERSION_MINOR, NC_MINOR, [Define the minor version number]) AC_DEFINE(NC_VERSION_PATCH, NC_PATCH, [Define the patch version number]) AC_DEFINE(NC_VERSION_STRING, "NC_MAJOR.NC_MINOR.NC_PATCH", [Define the version string]) # Checks for language AC_LANG([C]) # Checks for programs AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AC_PROG_CXX AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB AC_PROG_LIBTOOL # Checks for typedefs, structures, and compiler characteristics AC_C_INLINE AC_TYPE_INT8_T AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INTMAX_T AC_TYPE_INTPTR_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINTMAX_T AC_TYPE_UINTPTR_T AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_C_BIGENDIAN( [], [AC_DEFINE(HAVE_LITTLE_ENDIAN, 1, [Define to 1 if machine is little endian])], [AC_MSG_ERROR([endianess of this machine is unknown])], [AC_MSG_ERROR([universial endianess not supported])] ) # Checks for header files AC_HEADER_STDBOOL AC_CHECK_HEADERS([fcntl.h float.h limits.h stddef.h stdlib.h string.h unistd.h]) AC_CHECK_HEADERS([inttypes.h stdint.h]) AC_CHECK_HEADERS([sys/ioctl.h sys/time.h sys/uio.h]) AC_CHECK_HEADERS([sys/socket.h sys/un.h netinet/in.h arpa/inet.h netdb.h]) AC_CHECK_HEADERS([execinfo.h], [AC_DEFINE(HAVE_BACKTRACE, [1], [Define to 1 if backtrace is supported])], []) AC_CHECK_HEADERS([sys/epoll.h], [], []) AC_CHECK_HEADERS([sys/event.h], [], []) # Checks for libraries AC_CHECK_LIB([m], [pow]) AC_CHECK_LIB([pthread], [pthread_create]) # Checks for library functions AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([dup2 gethostname gettimeofday strerror]) AC_CHECK_FUNCS([socket]) AC_CHECK_FUNCS([memchr memmove memset]) AC_CHECK_FUNCS([strchr strndup strtoul]) AC_CACHE_CHECK([if epoll works], [ac_cv_epoll_works], AC_TRY_RUN([ #include #include #include int main(int argc, char **argv) { int fd; fd = epoll_create(256); if (fd < 0) { perror("epoll_create:"); exit(1); } exit(0); } ], [ac_cv_epoll_works=yes], [ac_cv_epoll_works=no])) AS_IF([test "x$ac_cv_epoll_works" = "xyes"], [AC_DEFINE([HAVE_EPOLL], [1], [Define to 1 if epoll is supported])], []) AC_CACHE_CHECK([if kqueue works], [ac_cv_kqueue_works], AC_TRY_RUN([ #include #include #include #include #include int main(int argc, char **argv) { int fd; fd = kqueue(); if (fd < 0) { perror("kqueue:"); exit(1); } exit(0); } ], [ac_cv_kqueue_works=yes], [ac_cv_kqueue_works=no])) AS_IF([test "x$ac_cv_kqueue_works" = "xyes"], [AC_DEFINE([HAVE_KQUEUE], [1], [Define to 1 if kqueue is supported])], []) AC_CACHE_CHECK([if event ports works], [ac_cv_evports_works], AC_TRY_RUN([ #include #include #include int main(int argc, char **argv) { int fd; fd = port_create(); if (fd < 0) { perror("port_create:"); exit(1); } exit(0); } ], [ac_cv_evports_works=yes], [ac_cv_evports_works=no])) AS_IF([test "x$ac_cv_evports_works" = "xyes"], [AC_DEFINE([HAVE_EVENT_PORTS], [1], [Define to 1 if event ports is supported])], []) AS_IF([test "x$ac_cv_epoll_works" = "xno" && test "x$ac_cv_kqueue_works" = "xno" && test "x$ac_cv_evports_works" = "xno"], [AC_MSG_ERROR([either epoll or kqueue or event ports support is required])], []) AM_CONDITIONAL([OS_LINUX], [test "x$ac_cv_epoll_works" = "xyes"]) AM_CONDITIONAL([OS_BSD], [test "x$ac_cv_kqueue_works" = "xyes"]) AM_CONDITIONAL([OS_SOLARIS], [test "x$ac_cv_evports_works" = "xyes"]) # Package options AC_MSG_CHECKING([whether to enable debug logs and asserts]) AC_ARG_ENABLE([debug], [AS_HELP_STRING( [--enable-debug=@<:@full|yes|log|no@:>@], [enable debug logs and asserts @<:@default=no@:>@]) ], [], [enable_debug=no]) AS_CASE([x$enable_debug], [xfull], [AC_DEFINE([HAVE_ASSERT_PANIC], [1], [Define to 1 if panic on an assert is enabled]) AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) ], [xyes], [AC_DEFINE([HAVE_ASSERT_LOG], [1], [Define to 1 if log on an assert is enabled]) AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) ], [xlog], [AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled])], [xno], [], [AC_MSG_FAILURE([invalid value ${enable_debug} for --enable-debug])]) AC_MSG_RESULT($enable_debug) AC_MSG_CHECKING([whether to disable stats]) AC_ARG_ENABLE([stats], [AS_HELP_STRING( [--disable-stats], [disable stats]) ], [disable_stats=yes], [disable_stats=no]) AS_IF([test "x$disable_stats" = xyes], [], [AC_DEFINE([HAVE_STATS], [1], [Define to 1 if stats is not disabled])]) AC_MSG_RESULT($disable_stats) # Untar the yaml-0.1.4 in contrib/ before config.status is rerun AC_CONFIG_COMMANDS_PRE([tar xvfz contrib/yaml-0.1.4.tar.gz -C contrib]) # Call yaml-0.1.4 ./configure recursively AC_CONFIG_SUBDIRS([contrib/yaml-0.1.4]) # Define Makefiles AC_CONFIG_FILES([Makefile contrib/Makefile src/Makefile src/hashkit/Makefile src/proto/Makefile src/event/Makefile]) # Generate the "configure" script AC_OUTPUT nutcracker-0.4.0+dfsg/contrib/000077500000000000000000000000001242132376000162335ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/contrib/Makefile.am000066400000000000000000000000651242132376000202700ustar00rootroot00000000000000SUBDIRS = yaml-0.1.4 EXTRA_DIST = yaml-0.1.4.tar.gz nutcracker-0.4.0+dfsg/m4/000077500000000000000000000000001242132376000151135ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/m4/.gitignore000066400000000000000000000000571242132376000171050ustar00rootroot00000000000000# Ignore everything * # Except me !.gitignore nutcracker-0.4.0+dfsg/man/000077500000000000000000000000001242132376000153465ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/man/nutcracker.8000066400000000000000000000041151242132376000176010ustar00rootroot00000000000000.TH NUTCRACKER 8 "June 13, 2013" .SH NAME nutcracker \- Fast, light-weight proxy for memcached and Redis .SH SYNOPSIS .B nutcracker .RI [ options ] .SH DESCRIPTION \fBnutcracker\fP, also known as \fBtwemproxy\fP (pronounced "two-em-proxy"), is a fast and lightweight proxy for the memcached and Redis protocols. .PP It was primarily built to reduce the connection count on backend caching servers, but it has a number of features, such as: .IP \[bu] Maintains persistent server connections to backend servers. .IP \[bu] Enables pipelining of requests and responses. .IP \[bu] Supports multiple server pools simultaneously. .IP \[bu] Shard data automatically across multiple servers. .IP \[bu] Supports multiple hashing modes including consistent hashing and distribution. .IP \[bu] High-availability by disabling nodes on failures. .IP \[bu] Observability through stats exposed on stats monitoring port. .SH OPTIONS .TP .BR \-h ", " \-\-help Show usage information and exit. .TP .BR \-V ", " \-\-version Show version and exit. .TP .BR \-t ", " \-\-test-conf Test configuration for syntax errors and exit. .TP .BR \-D ", " \-\-describe-stats Print stats description and exit. .TP .BR \-v ", " \-\-verbosity=\fIN\fP Set logging level to \fIN\fP. (default: 5, min: 0, max: 11) .TP .BR \-o ", " \-\-output=\fIfilename\fP Set logging file to \fIfilename\fP. .TP .BR \-c ", " \-\-conf-file=\fIfilename\fP Set configuration file to \fIfilename\fP. .TP .BR \-s ", " \-\-stats-port=\fIport\fP Set stats monitoring port to \fIport\fP. (default: 22222) .TP .BR \-a ", " \-\-stats-addr=\fIaddress\fP Set stats monitoring IP to \fIaddress\fP. (default: 0.0.0.0) .TP .BR \-i ", " \-\-stats-interval=\fIinterval\fP Set stats aggregation interval in msec to \fIinterval\fP. (default: 30000 msec) .TP .BR \-m ", " \-\-mbuf-size=\fIsize\fP Set size of mbuf chunk in bytes to \fIsize\fP. (default: 16384 bytes) .TP .BR \-d ", " \-\-daemonize Run as a daemon. .TP .BR \-p ", " \-\-pid-file=\fIfilename\fP Set pid file to \fIfilename\fP. .SH SEE ALSO .BR memcached (8), .BR redis-server (1) .br .SH AUTHOR nutcracker was written by Twitter, Inc. nutcracker-0.4.0+dfsg/notes/000077500000000000000000000000001242132376000157235ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/notes/c-styleguide.txt000066400000000000000000000362761242132376000211000ustar00rootroot00000000000000- No literal tabs. Expand tabs to 4 spaces. - Indentation is 4 spaces. - No more than 3 levels of indentation, otherwise you should think about refactoring your code. - Use one statement per line. - Make sure that your editor does not leave space at the end of the line. - snake_case for variable, function and file names. - Use your own judgment when naming variables and functions. Be as Spartan as possible. Eg: Using name like this_variable_is_a_temporary_counter will usually be frowned upon. - Don’t use local variables or parameters that shadow global identifiers. GCC’s ‘-Wshadow’ option can help you to detect this problem. - Avoid using int, char, short, long. Instead use int8_t uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, which are available in . However, when interfacing with system calls and libraries you cannot get away from using int and char. - Use bool for boolean variables. You have to include - Avoid using a bool as type for struct member names. Instead use unsigned 1-bit bit field. Eg: struct foo { unsigned is_bar:1; }; - Always use size_t type when dealing with sizes of objects or memory ranges. - Your code should be 64-bit and 32-bit friendly. Bear in mind problems of printing, comparisons, and structure alignment. You have to include to get generic format specifier macros for printing. - 80 column line limit. - If you have to wrap a long statement (> 80 column), put the operator at the end of the line and indent the next line at the same column as the arguments in the previous column. Eg: while (cnt < 20 && this_variable_name_is_too_long && ep != NULL) { z = a + really + long + statement + that + needs + three + lines + gets + indented + on + the + same + column + as + the + previous + column } and: int a = function(param_a, param_b, param_c, param_d, param_e, param_f, param_g, param_h, param_i, param_j, param_k, param_l); - Always use braces for all conditional blocks (if, switch, for, while, do). This holds good even for single statement conditional blocks. Eg: if (cond) { stmt; } - Placement of braces for non-function statement blocks - put opening brace last on the line and closing brace first. Eg: if (x is true) { we do y } - Placement of brace for functions - put the opening brace at the beginning of the next line and closing brace first. This is useful because several tools look for opening brace in column one to find beginning of C functions. Eg: int function(int x) { body of the function } - Closing brace is empty on a line of its own, except in cases where it is followed by a continuation of the same statement, i.e. a "while" in a do-statement or an "else" in an if-statement, like this: do { body of do-loop } while (condition); and, if (x == y) { .. } else if (x > y) { ... } else { .... } - Column align switch keyword and the corresponding case/default keyword. Eg: switch (alphabet) { case 'a': case 'b': printf("I am a or b\n"); break; default: break; } - Forever loops are done with for, and not while. Eg: for (;;) { stmt; } - Don't use a space after a function name. - Do not needlessly surround the return expression with parentheses. - Use space after keywords. Exceptions are sizeof, typeof, alignof and __attribute__, which look like functions. - Do not add spaces around (inside) parenthesized expressions. s = sizeof( sizeof(*p)) ); /* bad example */ s = sizeof(sizeof(*p)); /* good example */ - Casts should not be followed by space. Eg: int q = *(int *)&p - There is no need to type cast when assigning a void pointer to a non-void pointer, or vice versa. - Avoid using goto statements. However there are some exceptions to this rule when a single goto label within a function and one or more goto statements come in handy when a function exits from multiple locations and some common work such as cleanup has to be done. Eg: int fun(void) { int result = 0; char *buffer; buffer = malloc(1024); if (buffer == NULL) { return -1; } if (condition1) { while (loop1) { ... } result = 1; goto out; } ... out: free(buffer); return result; } - When declaring pointer data, use '*' adjacent to the data name and not adjacent to the type name. Eg: int function(int *p) { char *p; } - Use one space around (on each side of) most binary and ternary operators, such as any of these: = + - < > * / % | & ^ <= >= == != ? : but no space after unary operators: & * + - ~ ! sizeof typeof alignof __attribute__ defined no space before the postfix increment & decrement unary operators: ++ -- and no space around the '.' and "->" structure member operators. - 0 and NULL; use 0 for integers, 0.0 for doubles, NULL for pointers, and '\0' for chars. - Test pointers against NULL. E.g, use: if (p == NULL) not: !(p) - Do not use ! for tests unless it is a boolean. E.g. use: if (*p == '\0') not: if (!*p) - Don't use assignments inside if or while-conditions. E.g, use: struct foo *foo; foo = malloc(sizeof(*foo)); if (foo == NULL) { return -1 } not: struct foo *foo; if ((foo = malloc(sizeof(*foo))) == NULL) { return -1; } - Don't ever use typedef for structure types. Typedefs are problematic because they do not properly hide their underlying type; for example you need to know if the typedef is the structure itself or a pointer to the structure. In addition they must be declared exactly once, whereas an incomplete structure type can be mentioned as many times as necessary. Typedefs are difficult to use in stand-alone header files: the header that defines the typedef must be included before the header that uses it, or by the header that uses it (which causes namespace pollution), or there must be a back-door mechanism for obtaining the typedef. - The only exception for using a typedef is when you are defining a type for a function pointer or a type for an enum. Eg: typedef void (*foo_handler_t)(int, void *); or: typedef enum types { TYPE_1, TYPE_2 } types_t; - Use just one variable declaration per line when variables are part of a struct. This leaves you room for a small comment on each item, explaining its use. Declarations should also be aligned. Eg, use: struct foo { int *foo_a; /* comment for foo_a */ int foo_b; /* comment for foo_b */ unsigned foo_c:1; /* comment for foo_c */ }; and not: struct foo { int *foo_a, foo_b; unsigned foo_c:1; }; - For variable declaration outside a struct, either collect all the declarations of the same type on a single line, or use one variable per line if the variables purpose needs to be commented. Eg: char *a, *b, c; or: char *a, *b; char c; /* comments for c */ - Avoid magic numbers because no-one has a clue (including the author) of what it means after a month. - Function definitions should start the name of the function in column one. This is useful because it makes searching for function definitions fairly trivial. Eg: static char * concat(char *s1, char *s2) { body of the function } - Function and variables local to a file should be static. - Separate two successive functions with one blank line. - Include parameter names with their datypes in function declaration. Eg: void function(int param); - Functions should be short and sweet, and do just one thing. They should fit on one or two screenfuls of text (80 x 24 screen size), and do one thing and do that well. The maximum length of a function is inversely proportional to the complexity and indentation level of that function. So, if you have a conceptually simple function that is just one long (but simple) case-statement, where you have to do lots of small things for a lot of different cases, it's OK to have a longer function. Another measure of the function is the number of local variables. They shouldn't exceed 5-10, or you're doing something wrong. Re-think the function, and split it into smaller pieces. A human brain can generally easily keep track of about 7 different things, anything more and it gets confused. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. - Use const for function parameters passed by reference, if the passed pointer has no side effect. - C style comments only. Don't use // for single line comments. Instead use /* ... */ style. - For multi-line comments use the following style /* * This is the preferred style for multi-line * comments in the Linux kernel source code. * Please use it consistently. * * Description: A column of asterisks on the left side, * with beginning and ending almost-blank lines. */ - To comment out block of code spanning several lines use preprocessor directive "#ifdef 0 ... #endif" - Please write a brief comment at the start of each source file, with the file name and a line or two about the overall purpose of the file. - All major functions should have comments describing what they do at the head of the function. Avoid putting comments in the function body unless absolutely needed. If possible, add a comment on what sorts of arguments the function gets, and what the possible values of arguments mean and what they are used for and the significance of return value if there is one. It is not necessary to duplicate in words the meaning of the C argument declarations, if a C type is being used in its customary fashion. If there is anything nonstandard about its use (such as an argument of type char * which is really the address of the second character of a string, not the first), or any possible values that would not work the way one would expect (such as, that strings containing newlines are not guaranteed to work), be sure to say so. Eg: /* * Try to acquire a physical address lock while a pmap is locked. If we * fail to trylock we unlock and lock the pmap directly and cache the * locked pa in *locked. The caller should then restart their loop in case * the virtual to physical mapping has changed. * * Returns 0 on success and -1 on failure. */ int vm_page_pa_tryrelock(pmap_t pmap, vm_paddr_t pa, vm_paddr_t *locked) { ... - The comment on a function is much clearer if you use the argument names to speak about the argument values. The variable name itself should be lower case, but write it in upper case when you are speaking about the value rather than the variable itself. Thus, “the inode number NODE_NUM” rather than “an inode”. - Every struct definition should have an accompanying comment that describes what it is for and how it should be used. - Finally, while comments are absolutely important to keep the code readable, remember that the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments. - Recommend using UPPERCASE for macro names. However, sometimes using lowercase for macro names makes sense when macros masquerade as well-known function calls. Eg, it makes sense to write the wrapper for the standard free() function in lowercase to keep the readability consistent: #define my_free(_p) do { \ free(_p); \ (_p) = NULL; \ } while (0) - Use enums when defining more than one related constants. All enumeration values are in UPPERCASE. - Avoid macros as much as possible and use inline functions, enums and const variables wherever you can. - For macros encapsulating compound statements, right justify the backslashes and enclose it in do { ... } while (0) - For parameterized macros, all the parameters used in the macro body must be surrounded by parentheses. Eg: #define ADD_1(_x) ((_x) + 1) - Use sizeof(varname) instead of sizeof(type) whenever possible. Eg: char *p; p = malloc(sizeof(*p)); /* good example */ p = malloc(sizeof(char)); /* bad example */ - All variables should be declared at the beginning of a scope block {..}. It is even preferred to declare all variables at the beginning of the function so that all the local variable declarations is in one place and we can see the comprehensive list in one glance. - Global structs should be declared at the top of the file in which they are used, or in separate header files if they are used in multiple source files. - Declarations of external functions and functions to appear later in the source file should all go in one place near the beginning of the file, somewhere before the first function definition in the file or else should go in a header file. - Use of extern should be considered as evil, if it is used in header files to reference global variables. - Don’t put extern declarations inside functions. - Usually every *.c file should have an associated *.h file. There are some exceptions to this rule, such as unit tests and small *.c files containing just the main() function. - Every header file in the source code must have preprocessor conditional to prevent the header file from being scanned multiple times and avoiding mutual dependency cycles. Alternatively you can use #pragma once directive, as it avoids name clashes and increases the compile speed. Eg, for a header file named foo.h, the entire contents of the header file must be between the guard macros as follows: #ifndef _FOO_H_ #define _FOO_H_ ... #endif /* _FOO_H_ */ Or, #pragma once #ifndef _FOO_H_ #define _FOO_H_ ... #endif /* _FOO_H_ */ - Don't use #include when a forward declaration would suffice. - Functions defined in header files should be static inline. - Don’t make the program ugly just to placate GCC when extra warnings options such as ‘-Wconversion’ or ‘-Wundef’ are used. These options can help in finding bugs, but they can also generate so many false alarms that that it hurts readability to silence them with unnecessary casts, wrappers, and other complications. - Conditional compilation: when supporting configuration options already known when building your program we prefer using if (... ) over conditional compilation, as in the former case the compiler is able to perform more extensive checking of all possible code paths. Eg, use: if (HAS_FOO) ... else ... instead of: #ifdef HAS_FOO ... #else ... #endif A modern compiler such as GCC will generate exactly the same code in both cases and of course, the former method assumes that HAS_FOO is defined as either 0 or 1. - Finally, rules are rules. Sometimes they are sensible and sometimes not and regardless of your preference, we would like you to follow them. A project is easier to follow if all project contributors follow the style rules so that they can all read and understand everyone's code easily. But remember, like all good rules, they are exceptions where it makes sense not to be too rigid on the grounds of common sense and consistency! nutcracker-0.4.0+dfsg/notes/debug.txt000066400000000000000000000142131242132376000175530ustar00rootroot00000000000000- strace strace -o strace.txt -ttT -s 1024 -p `pgrep nutcracker` - libyaml (yaml-0.1.4) - yaml tokens: 0 YAML_NO_TOKEN, 1 YAML_STREAM_START_TOKEN, 2 YAML_STREAM_END_TOKEN, 3 YAML_VERSION_DIRECTIVE_TOKEN, 4 YAML_TAG_DIRECTIVE_TOKEN, 5 YAML_DOCUMENT_START_TOKEN, 6 YAML_DOCUMENT_END_TOKEN, 7 YAML_BLOCK_SEQUENCE_START_TOKEN, 8 YAML_BLOCK_MAPPING_START_TOKEN, 9 YAML_BLOCK_END_TOKEN, 10 YAML_FLOW_SEQUENCE_START_TOKEN, 11 YAML_FLOW_SEQUENCE_END_TOKEN, 12 YAML_FLOW_MAPPING_START_TOKEN, 13 YAML_FLOW_MAPPING_END_TOKEN, 14 YAML_BLOCK_ENTRY_TOKEN, 15 YAML_FLOW_ENTRY_TOKEN, 16 YAML_KEY_TOKEN, 17 YAML_VALUE_TOKEN, 18 YAML_ALIAS_TOKEN, 19 YAML_ANCHOR_TOKEN, 20 YAML_TAG_TOKEN, 21 YAML_SCALAR_TOKEN - yaml events 0 YAML_NO_EVENT, 1 YAML_STREAM_START_EVENT, 2 YAML_STREAM_END_EVENT, 3 YAML_DOCUMENT_START_EVENT, 4 YAML_DOCUMENT_END_EVENT, 5 YAML_ALIAS_EVENT, 6 YAML_SCALAR_EVENT, 7 YAML_SEQUENCE_START_EVENT, 8 YAML_SEQUENCE_END_EVENT, 9 YAML_MAPPING_START_EVENT, 10 YAML_MAPPING_END_EVENT - sys/queue.h queue.h is a generic linked list library adapted from BSD. It has three macro knobs that are useful for debugging: - QUEUE_MACRO_SCRUB nullifies links (next and prev pointers) of deleted elements and catches cases where we are attempting to do operations on an element that has already been unlinked. - QUEUE_MACRO_TRACE keeps track of __FILE__ and __LINE__ of last two updates to the list data structure. - QUEUE_MACRO_ASSERT verifies the sanity of list data structure on every operation. - valgrind valgrind --tool=memcheck --leak-check=yes - Core dump ulimit -c unlimited - Generate ENOMEM to test "Out of Memory" ulimit -m # limit maximum memory size ulimit -v # limit virtual memory - get nutcracker stats printf "" | socat - TCP:localhost:22222 | tee stats.txt printf "" | nc localhost 22222 | python -mjson.tool - Signalling and Logging SIGTTIN - To up the log level SIGTTOU - To down the log level SIGHUP - To reopen log file - Error codes: http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_2.html /usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h - epoll (linux) union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }; struct epoll_event { uint32_t events; /* epoll events */ struct epoll_data data; /* user data variable */ }; /* events */ EPOLLIN = 0x001, EPOLLPRI = 0x002, EPOLLOUT = 0x004, EPOLLERR = 0x008, EPOLLHUP = 0x010, EPOLLRDNORM = 0x040, EPOLLRDBAND = 0x080, EPOLLWRNORM = 0x100, EPOLLWRBAND = 0x200, EPOLLMSG = 0x400, EPOLLRDHUP = 0x2000, EPOLLONESHOT = (1 << 30), EPOLLET = (1 << 31) /* opcodes */ EPOLL_CTL_ADD = 1 /* add a file decriptor to the interface */ EPOLL_CTL_DEL = 2 /* remove a file decriptor from the interface */ EPOLL_CTL_MOD = 3 /* change file decriptor epoll_event structure */ - kqueue (bsd) struct kevent { uintptr_t ident; /* identifier for this event */ int16_t filter; /* filter for event */ uint16_t flags; /* general flags */ uint32_t fflags; /* filter-specific flags */ intptr_t data; /* filter-specific data */ void *udata; /* opaque user data identifier */ }; /* flags / events */ EV_ADD = 0x0001 /* action - add event to kq (implies enable) */ EV_DELETE = 0x0002 /* action - delete event from kq */ EV_ENABLE = 0x0004 /* action - enable event */ EV_DISABLE = 0x0008 /* action - disable event (not reported) */ EV_RECEIPT = 0x0040 /* action - force EV_ERROR on success, data == 0 */ EV_ONESHOT = 0x0010 /* flags - only report one occurrence */ EV_CLEAR = 0x0020 /* flags - clear event state after reporting */ EV_DISPATCH = 0x0080 /* flags - disable event after reporting */ EV_SYSFLAGS = 0xF000 /* flags - reserved by system */ EV_FLAG0 = 0x1000 /* flags - filter-specific flag */ EV_FLAG1 = 0x2000 /* flags - filter-specific flag */ EV_EOF = 0x8000 /* returned values - EOF detected */ EV_ERROR = 0x4000 /* returned values - error, data contains errno */ /* filters */ EVFILT_READ (-1) /* readable */ EVFILT_WRITE (-2) /* writable */ EVFILT_AIO (-3) /* attached to aio requests */ EVFILT_VNODE (-4) /* attached to vnodes */ EVFILT_PROC (-5) /* attached to struct proc */ EVFILT_SIGNAL (-6) /* attached to struct proc */ EVFILT_TIMER (-7) /* timers */ EVFILT_MACHPORT (-8) /* mach portsets */ EVFILT_FS (-9) /* filesystem events */ EVFILT_USER (-10) /* user events */ EVFILT_VM (-12) /* virtual memory events */ EV_CLEAR behaves like EPOLLET because it resets the event after it is returned; without this flag, the event would be repeatedly returned. - poll (unix) POLLIN 0x001 /* there is data to read */ POLLPRI 0x002 /* there is urgent data to read */ POLLOUT 0x004 /* writing now will not block */ POLLRDNORM 0x040 /* normal data may be read */ POLLRDBAND 0x080 /* priority data may be read */ POLLWRNORM 0x100 /* writing now will not block */ POLLWRBAND 0x200 /* priority data may be written */ POLLMSG 0x400 POLLREMOVE 0x1000 POLLRDHUP 0x2000 POLLERR 0x008 /* error condition */ POLLHUP 0x010 /* hung up */ POLLNVAL 0x020 /* invalid polling request */ - event ports (solaris) typedef struct port_event { int portev_events; /* event data is source specific */ ushort_t portev_source; /* event source */ ushort_t portev_pad; /* port internal use */ uintptr_t portev_object; /* source specific object */ void *portev_user; /* user cookie */ } port_event_t; /* port sources */ PORT_SOURCE_AIO 1 PORT_SOURCE_TIMER 2 PORT_SOURCE_USER 3 PORT_SOURCE_FD 4 PORT_SOURCE_ALERT 5 PORT_SOURCE_MQ 6 PORT_SOURCE_FILE 7 nutcracker-0.4.0+dfsg/notes/kqueue.pdf000066400000000000000000003001311242132376000177130ustar00rootroot00000000000000%PDF-1.2 %쏢 4 0 obj <> stream x[Yoq^8D>8Hl8l?P3屲}6@ 8:`D7G?x5po4H<|on$ қ</~?)FYUVE&nɈ73WdZ9lS=4du: "DV"ڼ$*I0ʕw\Qbزm35^:ǡwZ<>*wܗ̣UxF-@=@7d8M=1ep~9?X%1zkYsI+tN.g%\V}pzd㗣4ػޖҵvlY6n{(ŧfPY '.N: ՚y$wgJ Jb*<ݓ1Sdad|V̡eISu#fIi d C{iޗkۧLX|n[ߒ[GL@YmC2qyzjdO%ླ2wQE4aA𩡝JPGz.qΗ18WVˆ $K}vQSV1 @Za3k(Kf+j* MǦU׃+Pk-˩=>un7 dN$\v ʀ3gڍdf"q8{.p夣'DG ^Ӱ2NeF`lfH.5MFBI9aF)+qd{z/>YB f03 t>ѽ;Y*^#{"[tng$XY_*v-tr7%#I&0a{\ņ9e(SZe?qeunL_JحyHw\Ũum*řN2OFdM~=fb&R) 2 (g"ɾ$>v[*~i;EfTM͎IrkFD7RSӎ4`LgzELj43Tn$ iF0&SmpHqz9wq1 ?,G:812ņ>I.b fmaj>.g=v'c@Jr7s L\ * i8I`ߟ漾Qf9o3D:C.* * dE W`SKcNQy"*1VW̲bM*A%̩P]cc3 A\XoeR"v1/R 8 +bD!e}Ro"BQ`b,وAJ#/X]YAp)DbRT.e8kW8W4> 0䦐DbŢrnɯﺒYhML18GV*WPoPXQ!O{ҽA`%V2:.AзIJ1t"~!m"$Oд t5EOB7Н'GG,Un8UHgLv/lj0a zڗPBh&&[ DHRrqi?5VY%]5~a-Z /]Sytb~i9sޏ=ѿ-f"c/$loϷ1 9,l/fK_[XCtӗwv9A\UE2)F'`]*r/7yc%<,WE| n'ڌܑcȸe |sUi-[AhBb% G]*چ^4J`B@b[8{wW.q*{kTe3<"BªV" ŜPytukͦtcC~1C&az,=E; &#,]3KP/Oc}:]TĂ1Zn R˩x-(}iH"pb uЛ;a& +X0_6e~Mgz)=!4D0DyE ,> }Ērud7M=X;OT4MD<#kh4+!*PX;0AnDaA}m+\* !l jSO)4(]]%yT7[F FBVUFTk*od:-!ffj.A*`_j 3(l$T:/'.*`L_oPg)K֕=-m~aw΄kCx+n_2MVn@ ~(ĈAt [%)hg\ w>Y^QsoTZtd2BssM-_n 0êN*^2~LG #ADv)}lv?C:. ZdY@}Vo \8e( ^ڣ+*=?WOm`m@]oƕWQ6\њOd-G8m}ReC4P uV-j'3\ rWK 4X+IVw(تv*H򗽌[ "(pGp揔*zч_W!~[BЂr*U۩ܽǰN Hv)6%R4| }g\?KOMOR>Fb6 ?Xz7D07%4 ߗ["󱙗6ֳ_:-͡R8?^w޶{ZEDoͿ~endstream endobj 5 0 obj 4481 endobj 3 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /Text] /Font << /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 4 0 R >> endobj 12 0 obj <> stream x]ےFr|~1Mw $H^8It7IH]Sgc!}4O {Icg>)6y4}7tY]^n$yHVgOMe?馡;OS>c;Me֬%I[٤YAxn+$5ޝ{|]F2 O;Eټ> !_>~/Mil3s7ubSVzɦv~3JڤNS7_gyZEmBDZQf"tw@60ޓfER[rZ8=}g֘O[lOMF$In~}b^xEʴ=.lސˮ}(k6jAK`/;4F_MRkaڽ,1mF՚xۉ#7ٖGmrؓC(tD[ۺW8j:qS$?훵ћn8ٗ(iUZ)Udͥ1b]u SNo<^HS;dڂw25itk9xbɡ#RhXاPgO=i ? #f`ܻ,v4 D{(˖׮/'yCY$Ip[@m?yEbF>XQ*[dc`/c7Tȫ9 :(@S.#z3햵gBz^@zJM KTŋ!&| !؟/[4lYflH;K@}6}ϧvDZ4ѿݺ'JBNjͰʑ<!HZ!5KaڴmJg2m&gu xly`ݽ,bRG_1ɭK[E Vbv%FB{ppLlۥ3Tَ䮻0>ujT*-M&xEL\U>Z0p)6ǪIK7 XC  {k*Y!M^ IG_|tX/;Kƥ`x%>?vJ6!Y̡ S7c [#fVoՀn!ʘ{VH.bty(&8GLƳmM*֫%b)TXZY1v92Ml*8=| iERBw uzC8崧2D!K,`0(-Mktb{S(f2!&,e,*d,N%).3a'V[W~~De%xy롏ˉV BW]hiu6IϲTД_ ӆ07RL؛]waԬ, gDRL`KtQF$/`,A^ yULY(8`ϕuMKELL[5`e ͢C`Lw0u!y?=!N nyG.S[ŧxԉVx/#-#z\mm#T?x~3vb Cj bbI=? Lmlg:J;%HI 㮝;)/;,M4RF. T@vg-f&5/dJ?4ŋG&M0;W]S`S+W8N"Z: &pt9eL̥󝺝Z6Yf`bKIxZ-@X6P`~DsLv]J;GP +H%H#yhM;@J%'itȍAU.Àʓ&NrEi0Y$@&&mUWv^Oٷ#-~&pwqds0 Ƹv`Oy3/脹 >8;p,)$3hMfDphţpwgjezX9xxEpj]ç+*_+H.bh Ĵu m|;QK"3.t{Tw  1r^-?e)xTOKiwzWQEǞn+HͭK,Zp8 ϱf1Ju[ZczB!d^܈L3#AGB"aĜU6d8Dzb=!YB p1%@㸁O; /##糛WH %Ck3 TBn7oKOnog3g, Qx鵒? k zO{ Oݶ94%?"OL-w.y/mse؍0ti0۩'k0:[H *+(㴊XnjѾ}ld#rZf(ulw*Ǚ Jcaa RQ}xICp[;pS&e(tny #x*O <:Vk j7&5P$\=k4Ng&ņ\7qk KUƽMes,FˎЯuG8'h0x=Ȍ g!ϫMhdz=؄f{ ¡hQn]7m{Ԕ;\yg0bⴿb;Utju4Z wJÙpDž)W&z 3"OLIxt p*@1{ )`g͔账ݵZ2B?ЌZu\w+Jp^n<{-߰ixEgxqztxhFNÃ~l\@wD*Ǔs U/x{n{тp<52$HagOǣJ26I]NCݾޘQo5Yȱɴ8 L]St1d"w#cSa*e>a'+ =7$+ݎ$We)<%JDK0i,p{roǎcws.K=@%ԌwZҡA~V[x):8eX$e>?@ 9+Ɲcmxb!YO $g ?~cx'N*SO7zendstream endobj 13 0 obj 6483 endobj 11 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 12 0 R >> endobj 26 0 obj <> stream x\[uN^+GIj0zUy+Nʒ7VCbf% .@h7kt;GRƀ|;dwxwngvԳ|Yfe1{}saOgU6|wniO^EjT^??ܵYZ̫?W޼9aݎn/E2/0uO{|.}^Gy*Ԝ̯J=l`Ffj1E8q_Ϯ4grvf~ӭĈy.k~*'n%v釭yUFOyp蚍~U46vYKW8= uяy~UG7@w.f+dpr5UXfY ,׵î?tFrfB5:/M)i;Tц[/wI_m{YjwbE&+X}ON>ի9XEF+gӭU? wkE%Z$Z+rwڭݭ 6C2*H'ȝ?y##I;(nd5C;t4DBtTNiC7]s;jͳD3͚yfk <,Ҽ%הdlBĮݲq9-Mtw EJ9@AZh>:~8fݍ mM/qZfKf`x2.) 5JXT_Q)jƖ4S͌#XWƒޒ&,.UE JDđVfb}gdcc&d.dp$x8_ݶvh Gl,X\mN-}K`ZW h@ΪUK&V!ebzI-+.C(b$~ڹowk"Vb`3X::.3hdetljN{mB6BEfm<7 ) s :.|y插* a$^ }-kW.YD><՞u'_&(&)I>CsGR 5a #X#ЯTuIX$U *k<y`#;!#)da"OdwtNWc6SmFdALs`'a!n~87d[#Fd)fsZ^w5 ?@. 5r;8ہ&؋z_?Jji{eT"MDrpMm`g<1j0aA#zXX\JsUqfHdAQUyL0ᇑ!(D+AQ鍃|¡@R1Q"z#_u׭B S'˂)Y.}S9:a!2}k7A@Ȏ#D@L8 A`No5 qsF.Dn%3=G\ cu'c&[e, ]i.kv"2&+"s/C8cex( +iJ:cΪ M*JalKyHUw{´e:'{/,lGh`VCAPm؝$)dWk)?hbed$e%ɼb)2R*)1S.T# _Sy`(J r:%e֨zcW|ɼ+^0(yr1O5m7jle$RKfw6U›ZZ$yQ(~EA(ID9D2rN2HJn۫K]"'TE$uP jPP4j:+r]Jei.80f\ JQmތw6FV5Ks.^a8//~W_ҋW2.>yDĔJ 2#V2n7]JËCYnZBe+ӓex{2޹4~!o%>cyre;l=ʥXAnS§T(j$:S}F e%ӪqFvYkb} }g嘧6!!9.EU{$eb͇85BUsֵ`#!-&`T/2Z}H:yuWc+.!דRMO (|]Q- >=~xywYҸ4ElBplIc( .Եcί_~/{!&28/KtھºLf%<@ZTzsi)/Qc(W^DyOwu|! ̼]z0‰\΋)KL:9k:828s| ̽J%VPH) sy3&2=&ԕ"-<>Y4QDu|}2(F|(ښ0tL2A7ʒ$sO I*צʦN/eclxbn~dY?`!K}N%XGuUV53+SE W [pgC\oB*h=| `5Bz17Ӄllۃ!Yޅð.$,f&x]|)Edilg`MO~YW׌-1zhgh;$vr50iტ~Ӷ0Oi[dQݫP6h{ i]_>N-2$_V6ܟ,PӽᣢR 1UP zko !>wDʎxK2b[~:4=ҳNbYs^壯c9׷zy4{ۤ02̕A7(Z2LN k~:|8SpCrP!dt)'"Ѣb|I9>YiC Qv+9E4O$"m"XsC" AQ{ I L\Q r`U!-R_;xl{ΊҖd!!~Л8)K9/^$j\uՅ\|<:m#`Ua$P66:1m]GYNDŽŕ|NQ1ղ^o????__o~􃿝}O~g6H ިjF;s_tZveWWTI]nwmwaԝb!<%zi!(6ӹY4go% "s"Ηd˄$o:):I!t7k3iq)#W-%.q9h97"x>n#+ ;]bl`̘ۡ9l )=_>i!:Rg׆u* 2\Ϻl2PV/_Oqgw,j I]Cd%@Ґ ImD^qzZ-*Jƕ8]HS@/; z\P}.rJ0Krm3A[%"VʽxQuxPҚa 2ed|:v[nc}OH,AL `cA=9E>}Zai1n. I05LS0SA/f{ޜJ(2X4'[E0N>ۭ X ԿcEu錰N^=l^'\\0AJbk_baN -LΝG>Lts\o ^0*VX! `+K,v\86-_slZ\G%W -x֓;A@`wk G\rs3|o2'SfYIVd!)}F̜mfFƻP ×w+y]rݍ@'ҥw=,۵|c,}gEC }aAsubDn_DUendstream endobj 27 0 obj 6223 endobj 25 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 26 0 R >> endobj 66 0 obj <> stream x\KsN)7 *ohJXQDf]@,EV }=R%V*2f=$T?H3gIPQ ,=?_~{y<4o?C_9^_;OQ2y Hw'?߼~{]A,9{mV~C$f4*Ky_'uur&o*Mgr#uԍ7'jCqHB"weew&&@7j;ޙe;{YŸfZmx6Z$\)g{֠/)^ǡ94>!GqZʩ4y}AK G)>?\x.!ڌVK++2bXirߴS=Ẇb]j\ʸTW C흺T+.R_ 汽of!_?T4yYKǽB DN6\șSp3 OJmURq beBku-~0HM+KG.fWi_ 7wxګnnF(Stڨ bq|5v:dAr W|ZXIy2mj.nIjnvw+' d)inv`,JZqΞq&L&xm[or۪ͱk\fc<ae:ZjDK4S=yBLɩX0jn\= n4]3R"&ۦ|Ȕ#IѴ" YXT /+ zi^KZ|C*/JP0$zӮMgHs yc'BFm:i#>r΢,NQih Os5Ed1Z" Z+K [ʵ:pAz1Ii^u3 Y@`з3f?3,4Ptۡ7m?M`D3"5C }}G8dj+ V〚Pttk<$ySU`0qXT Πq` z+vn .QE1\RJh w\B'HyRIROC>9&Lж"t0wdƷ8W̐s@j#W;![#.i{UV J0[Z*dD"{R?!c0Pb{zxعFC :1wI "G*oNAoS. !raClx`OOFE\WnbKCyjb\L <Ɇ1.~]w,FFzB̀Mn_RiMVV:ynY..e!|Ci]_[RaA&5.x&PaHdiðBhX&Y<ЍD 7v6Baz>m+='xhIq~tɌ4N#x'_s|HՑ2%IRK]MU9 }%su%Jnȗ ̶gStb#!a?E/#Fm `+k/*rP)=z @)b}i!xQ4 @#cmyUwĉdNK5PdlIΜγO26W 'i!/R2;1р=agp0?;1ĞR%yO,ˑ꘳M74NKA,0_ 9A ~Vv ř;$x߯۶C"cpՒ5|HJdGG'Ds7SigZ.)Sn4Wx(f\üwR 4SP bc."03MY7=c})37j`\:-;^͵.ѧ%3.Yj DYПnک6\\VaYHIv-khb_o f4LC=+gG=ױ&FJH~8`y mӛ@wVYƤFu۵ 4MstE*>D]Q̈́c3ǡ4,[Qf B>y^R/OպzE+|p/=BbW^H3ֈ]y"=2^rEn z:%A sz]m_'d1)!cjKrɷN!?s^|A<8`=~븮H)U* _LWtzіke8<6{Um@ ;Ed9609cX.Rw:xZ`@zGgD@*-OZ=[L,aUm"r(+^j(QPDh9S=lUMmAQϴ&fDPL@9^%,컪n-f:RDXl}7lڃ>P`^L3P#֝Rߍ ЀL`Q4+QI[$MEOfV8r 5Kx/)m ^~+^Z(8idj\7%Vipze3Zw<9K)#ɐؐb)WЄyԪ0dc+ $B}@]$鋩*4"ߑ*B!@C_@m%A~02=N-%b] zޢ%ʞGp+ЍA6s}1#{Bk-D.EȨS.Rg~i@t][e <#` <)mj'/s-rL ?AxkH΄L!Bؔ1c|2@`ܣCΫ+EMƘq^19[,|U^M8%H!S n,@OpCx|ܞ1y;lɭv-Aԑ|vn>r䘧z0C9YK;#HZҳ諄:%Ԝ:gK:%cNOr98#+־ مMe?+ $ŏg1kcuʎGӗ؇>X,bPi Ypʄty][6g@|s=$ϼ~88:-ʬ`E)#5=?o>q8 #RQ06kYǘL#->cttԵJLvS4䈡h Z4G{Y'~üHj':}@ItDFvtN"_'D-r S8b,.ba #_> >> /Contents 66 0 R >> endobj 69 0 obj <> stream x\KsN)7 *o$KteYRXL/.hXX̯<{0(J`~|cGYW EUgeRGG"+uϏW_\z6ogɋ~|,y~o/S>EV?D>-e$9g^!ڟxuuVGu!fܬ>9+KDDp_;yɻnkՕ΅B$Q޾S$YTum~ys.]ޜ 1 )TI.(Q\]‘#Qߏê&u[K#ҤKsnnQH ui]HQR)JRJ*?% 4ʪڵ" &0~N"(# _-\ZL`._D#؂hF j&#jW0xbEݨ+UT5^&cxTZ~,+lց5~oJ56 'rJ/\F})Jwfѭn":|j Lu\G+),[I# tSy5>ޫ1 ikdoV9w̋TdJI(kjQohƶ!4iS3Mv>gy +*pusV Bi?oap,ý|c(ܵDh773좴u=16KS ]F_-Cgn}e\ng4k'v@OCliJI@2 *r= X7A=2Q᫟޼6!''(Aw1ڍmMŝ]QɤeM^Z`fn"L댣s\O3<':OY]+Wbu.YN.W]ͶR+sInMTjEr%0=srҵhd`Kjp ng?:>b"yhI.OGQd (x Vanr`]@z"a&hPh@3Yp[ϘR[3 IɢvӃkL|)2/E1TXrH_}ۥXwҘ0HgWx:#-ȪH* !{؇cRt,TRZS?p/7'},y\!S0qv!Hݴl(%gaVsukH(yK,B س%@I]Eq];.$>hh2E9yrifx7ECTwiW[ሁ]u{2U 3R@q@/C\{ ; pgl!j]hѰ2dɕXϭ4!HVRkQ!dmAe殛QHH 2JBcBI2j$W-c-X f(2]kv¥KKfSAϱFw$oI5]HC;6巟AJWUӬ>Mi|EV ^_4+慁[{* q; v}-|J|[z]W.>3ŋZT]Bp뻹n򃿢 QekB]w.\%`B_QTߢ+%0]˷^Wym␄ Kƫ',i\C>X8,# ֶ LնZ}LЦY_RUT8"ZdN[CdG436݊:א˥#C37B*7}^h %g<aМ&d"S6e@0;p`Ƣ DP=)[7Tqc?aϔXk 4.,a|qUmcPyQTUQȅ)TKKfXh&^I!|1"6{B"(umo6_8t7m(B)YQGS; {H8nf cNݛp127basYF, *Dhu9<,'|A`!YS:yN֟F V}JMHe)/1_ :Owxշ/ڬ`j8uxyMA ?aR KjwAtxLAVv0|;Ƣ -d 999 &֨&]މmQhHOYF2%>S S̩ ki::=PuhK28=Ƶ%,btclj~aF>35~ Af*fЀ40OҮ^J4<(" i c! @o3[ƃNK4ԍ6RiBr-zxw1d#S^HfɲÄ+C={M0|O5i0"YwJJ梲_| :v;~85)a_]c鉛J(i;Ҽ!:4 T0*K1rRUzŲ4[ QjIZlh*?46bA0Dowt:"`tncpd$v7'[iiw[{Af 8єĠwp@4d/BJڋƣnn1Pɼ^ `)AQzPK$ Ҕ(=ExP)OhqVc~ +w4ە/֤~[Yːk gX4-G$(3'1EͷxgY,Z-(ăc S֖32=}B6&’N09ãK⾛1bjg`7?2B -#\ 6a!>-l?gPW uI n#"i`L h"#rSqCݮqinj(:D 9'xXZhf$$M;b͘zγɍUݯZS?_fhJE3*.?8zyG;[ Z_UnA`9#Ԁ:ƄAUYF:_g oL.-BK䰇`#8(>5?(UlzKZH7߃R$PZ?3Q3kL2eG$(lӝfa,q G:h|"X.wNlSJWMBS.Dv7PİeB/gԽH |$E apqL<KjI4U'm~HNYjLYQ>3=~ @-S=4 yQOa"Zmq>p -Xi@ yS.7[ 97ݶ: 86bU98NloQ'Fߠh!#ń&.[Vƫ!֓ Iհg 7cs:Lh0X'6f6-|UEDϸjaOEbc 8^3Ȍ| k+Yd*q? &qW+ +LNbEE ӫwY<2Y!pӮo/n'q6 8>#w |1x#mS\`4w#/[ zJkTYfل L)u0)igv"GUEf/)#ftShΜϟU" ?cMD< iy(s8WmgI3}$VL%8]mM[; ze6SGqdi]9&#}Y첮=@N6L5"X{]8;Ϻ$h7R)(0Rj@YS f>Jz0vc8Ss| r:̓g Jnv"Y@ی#Hz,#Aj㟶 |A1PB`߰ ! IcsN8|ZC,)kvuS!x#Z!m&($5FaPZtVǏtc6툀^c$&pSp9@1L1b8@S/yH4퐥C\vMKsB^!agOx:&:uw;(U[l G;uNTiNU/$x~. b[3֚d3ΧC\N.Q|ַ}Nmysbg!JpN+Haw|M"{TyS_˰v\*[I{:J66-N ^F2i W@4Y_oހ岸aUK{& ]z9lW[M&&mBZH=byvGRVZeɭ+p3t]+Kԡ?㏫_E3?,p(!$oVaN0z@ɇvًe={6vg+E@7% *guc(HTqyހV/LI7 EsB3PaY9Jd-AkOx м!<+[YY[,>Ui<q{lz8K 9n*s6rP:W[)c%J rTI!.kR(=]%;Lk\>l&/iB8S't3GiwvdY+rv>;b#U~Ix7Kgˆ3rYS6S^[,฽kGNl(JH W`1"UMxJ!Z<ONvBKfndV T8J{J&-L>XŎ0:osf3ky;UbaP('\:8:M׋~>YN(s2?Ŀ2$?jji3rluw?#>?Kǩè-6~飆4XoWFSU{52k6|| endstream endobj 70 0 obj 5728 endobj 68 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 69 0 R >> endobj 72 0 obj <> stream xZoF?}IF~8 F:QMlsEcfwȥںpH6+rw ]Ǜ}:4N^-NI8I,q=o\IN/uxj>{Vuӧ,suruX2Mrer:˲,֦r)t*\ S]e^T+X,/_~|.~eY8ʭYʮlw땔"L$Ӫ~ZLn0.Pqms$}²;'M)UZ"XGD uc긴D >Dgϔ~$pf:a hޖ^*F!QN!^Lb'2']uՕ՞)f6.c;^E=\|vXHIm9A2 0y'tqq(sIn˛} 9`ᩴ>å˦)|#=%VFzhW+mC] ɓ6G$D.ABė8>ZnˊIud{[?ٵOHd[u$Z5[TX`G' DO>2k޴ݱ|TX&HU y0ù6Ȅq"u $~KeژxS8B!mYHpRi"󄲾܄5򝌸[wrիנ1xak[w۬ZZf,\apܳe ,cƁ0.q ٶ-  ̾UqPLp,w"8yEk`S؛ؼ@C˿\{~QaSekHI- ֘<&6E ŋƷ{wlkmyr*˴ePaRsّ[YAaZ&yvs Ԅo Nk_|@a"Bޔ-?pX~$ 8`!iaǤGˑ}5,ݾDîH]v! RFTʤ8cH (?%IGCSGEܓCĜ}uh~ 8heBz/|dW7eDČPnB5#_"d[faСvM^QTX-I'`4&C(_j%3+ {8PLX.05$ÛF lժwyg 1/ x닷R|9ySD< {nn:%y=zuqtrkwջwtq>OĮH`^(:?nA3x0/-0V|G`E oc<^ɜ yf),(0??\PZVvF9 X;$^N!>VSC"L0745X[f0u rc|Wj_1X19,cl0Cby 5BvFmW6UUBj{3$xJ O ^|`û4TT६ fo_Ba"Mo`H{4]S8Mٵx;3jJN!$ݦ4{H͵SK)J;;{TT0{X/J6@Pe]$NQVf5(0,A &k}-߯bk= ˤ/l8Y$B_X#R&؅+)/@pZS@pE8&]=J-uIZ]~ 7t(S̋uOϦVEԓԅR1Dy>X2 DŽEvhGE>S!3/2ӘV4A[MLL sHiJxLmXV-yX $5ltBGLb٠7閗E"͹DK**pÆo?{K~f LDm%oTn޽:dYN-Vw S!r lx05L=~F/sCܰkkzl(uL贈ݗzw 5:CX"Ϳ8_,.8hKQMԼ鰜%^ѡ@.?./a2)a8@`/.s[#7K +ntq,~/=BRZB?ǂsr>Q֟vK ?pW&{l6^+H6y0@}I8FzhZ/LU_wUuڷ^j'f^~%/ɷ)uhgǬHPy v5: , X"x{t](kXw!y[.XZ5(<>}qՠZ=L YH٬MRoQpO{gdS19 \::„øƎ!W!pP1\C 9&)sd\&jTl$1Q]/kn-yx:otZZȳ|Hт`LHH w}(%J@ae$h P m]]@%HCלiɲe0obtlVf^_بc@2<6/3:0(H  )t{2fGhc%`|k6߯O |&sgT^0KiOmtNL]6Ӕj ZC<ϰL:cMu=K11:SgmS<7И/WAKΤۧR#w hQOˇvvBB9osb[\.dH"p׊m0m(yg#G mK.v1G l!jɋ;V>`$ܷ`@hr.xm e:fA}z*mBt!MGk !!*V{*U7k0 nCPo yXR0kØطHUK c@i5/hIt_1 ?FU ֩K,?Ԑ9g^BDRۯ%ȽTLI^ҷ!$F+v8 G($4+Qzlmx_V$k( yZ6hUFMvB7` ]9J;([> >> /Contents 72 0 R >> endobj 76 0 obj <> stream x[sGѦo7^ plDZ!Tɱrl\/r|nOw&3=;{Jx߷؛y{FL|Z\\O U&'Y<ɼ"`2IO x2]6Og͑z{QGQYM "?nWI~c۬~SW8ڝ~!P$^BK%ƚϿSeh"֒OoSOkEPnf& #/7n<üZ_>dkN2/2o/ 䧃+R?PEhYU8r6p]-Wg%ElmZ}o+8 e<¨HQe4sWzmYh,'05-|n[%Vw]۽c),y9tTNUfR5G贃]GinVnKIbrI"&6vC-\BKƒÎc/V.:OYYjհ6!ӗ+50e.Pvr!|bz4kZ{wj۴%U3ߔjW`qrɱ.&vXM-+wwƻ 8MR4{_M"G#Bcן?gOR6ܧO8h<;}M$cyd+PViE0,Q܄g_=ksĈz)X{o^%wN q TBV?x.7rQ]8bnr qrB$9ѝɌ;2$:7Zz/-x0s0Mճn;nDų"\Pf&xăcE" :1!Cg }RDI`;m,zi7J̌e_I0twVop~>s(Ϟ?ф,eRbAɝpYc"O^})%P>{>={q!& 89_biTP,7==ce2j7)1R|@pOrQH-( ,-L3]n5Ymb6 پ0 !3бʓ1uHϑBnml`fwPz;(t4bzabNE{޳۲<`#Fk@͒$KBa",$-f$)㛪 \gU'HM*~6I#SyjxvH?k()/u]ARv]x@ #鿥 A^zq/vr=K͘p5u CQ̟v-6|2[aHM; :DEq##S"Jമg.fg1C<>Xj|O*[g?hK)1Ì 2Vo]= P;0 YmӦ RLt\z\bդ#u՜)rWP:zYd?8e 1]C/SRJPl@P1lsF[mHEx?0iK /gb;hKv+@$t֘E% Wװ \l_F,8p='٬53RmW1<&C3L9.gp6N,0 Bgy͙z4sL|-aoSW=HaXSf^f֙F`)DA !pļOjg`W<޾̜A3&dU.4.U<4cS@[[pX~u}!AN ~:,b9$ZmO0y?x-A6rvF!fdY5~E*Wn3ehRb jI@[M;4_rJ e儩B<ܔUPL5Ezvǒ)'|>4CąUBxSd0TXw@b2 P%"YԨb}|C#H(fms{Ul^>ҍҌǦ챓biX@ d6V "9RUD1|(SvKxzYJ/311LuBxu;GF> 2+3pMcFJg-X|.twz 8$A̻ ,z#Q&QenQ0&i x6|qp-<5~.q(һw:x|~prDPf}Ipt?w#U=B|Ce /(ID "mrB/lT=uT8NÍ{^JIJ8^4=mrJ+ԉ!sD c?""Iڒ{g?=z~_O~r7HT"A0r8ͦl:GJ:m{8z1!g|V Vr@.[@$)k9Pi$_M\zr>ب Ma»!ݭg,?".VXo&c- 0ʛ'4F?&yPeO,|/$JI /%Hޒ?,_~ `H_o=EK~qƈl8,ÅUFϻv 8\7Mc>QBg VbG %8۲ s[טXV}vm4­R2۲Y'3Lƫw8T4d^vǹaȋFwazJ(b= nee_.V@/NpK.ac_"c<FvN@Un fȴx@`9 LZഖN>NƝ3*Dȡ ҖU||w+L h2+VvHNV5 va9*#-rʘᔀj:Q v$:bztZbȉ(w^PRU aX:g>k >_/p)@vsNJ΅] y4 Ů~8kkW92?L#)\^;W0ʥ8r^.aJ8T0,NVt^Տi"Lx -. +dzi,B{;R 朁CϤ_)&(S1c:o=+}]8b ~>Hz,E,7P-19Yn:<M33Gw}b?We{UEc疰Gv0v#+uoxѓ@Y~o 0q{ /Ԯ|S-A!֭s$1ɓlXe˫I %s]:ԝTXd|Jn)HM ;R<իF%wqFu s霪^rn>{JH9̙0߽脔0Rܲ]Fw5UN($i5")4S apg+KG uCgqّy֙6>GM:JQ'<&x&s)k2 ƥS$Yq!M[e >Y=zf@Y J1nwgݘQF ᷔsZA b7t9k\&$% ɾW5xEp?"ŽeR˽H)~ώE4jHv2({;LU{i,l/Cum05SaϗM՛~#vь";%pK9]d_?lendstream endobj 77 0 obj 4441 endobj 75 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R74 74 0 R /A 15 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 76 0 R >> endobj 92 0 obj <> stream x]ےFrup+Qr-IZɱXk҄`؍ᴧo ]U )7O7Ⱥd74frcW:>v6]1+zj镆jV cse|K]cצQ@mk;<qbjg >Ӏ xY!zb.> upEۮK7[}zf$vІ$p+JX+N`%N}tHn%>Z%1:G%>:+15;CI }4Vbi$>j/1QkqN;J }Tx};%hkRh`G >__D+aEej;>zt_sCgth S)4T  X X ո!p ո#qK4TcO4TcG4TcK4TcC4TcM4TcE4ThHj ո%'#%!&"qC4TaHC5j     X X ոye %/GI*&ϧ띭?g(sJ2!q|18с}/qi-VbR;Nbh%6 k#J6J8AGk$>VbSCzH }^b裷C}#>:/1iGk%>FbC紓GlK }TZbNbcc%QITRNJ +7a +qCQ3Jz|0>jþ1 `/X3m)H =؊nTc`+F w`+*-+phKKt| 5wQָ{'l%F[Fmiіp#H ^bu+յ V$F[Vbe6hKW(/EYګfӸ06vBw*> bvWqڶk| ʥ_ cII`I 6j II`II`I?R Raq%R#R"Rp))UDJ5vDJ5VDJ5DJ5* )f"{" CJHƖHR{MUcI}2) l3) eRҙjLJLJwDJ;"*p(3K8h=ݫ'ڶsJHNWL[-%nS&0%nS&0%nS&0%nS&0%nSVcC L L L LXS&0%nS&0%nS&0%nS&0%nS&0%n5V L L&qq>xXlW=۾擸~г(Ɇp[K݈n ^ ;^b ܵL#0Ԫb9S`$F[Pqj T'0Pb9\`ݴ`+e-ڲ-#A[Zb$F[ip'q;{mYіmi=RF`klww[SvhJhK lr c] qt?os>e\]cc׶՝>".H 3 qG` @+@V>3]'0w`+?*іm-%1j2S`uFlY/1زN6w@VQf 7YE)02`˴YE)02NmYіmi;$F[@VQf 1؂0#Ͽ/˜#v-Vy?9 zN~::׌t؝>*I^5qm$NkX4;ɣ%nk4M'1W48}Il[FT6^Qw=^`TH0THaNT0M/1) 紕Np s B89սi:RaVbc:Rac:RaӴ8vZMe}ewӤ%'[|g9=AbXhGlIUkQ LƊjܐ* &U&UjI LK`_VbYΪqK:K`Y5Hg L:K`Y5Ig L:P\O:K`YΪ"%0kYtV?^hֱs݁T>og,ē |RyJXW{ʲH`6Lk5k#y+̂ :p.U)$Q}n][J5f|?nK߼ߴ˦(}soC|uia/攌63zgw]6c|s8lGjpBiM`pfIJ807`L#μbKvӒӽ녴9LO[GŁ{]2m>ͨK܁y}k TYY|1.Q8~ +C7(c}>첔(^߁[eR p8C Pכ-u~9^9T9S_oڒ8{uLTMc[ip*AZɤy2,Jr;o-k*yY)%gQ;t0K&-6nXBoGN|1 㘊sHٺ0CXRaݽ!0t+hЉ6;6;^WCeH'tKwT&c9qbb6|Ӱ]~(k=G=nWh3KMYibjl1(GՌJ LU8kCk;o ^O8\07gtŔ/j3/ t8#@߻ѵc@TD+ŷP$v;Oo͉/{z\ʼnڠ--W3>W z‘9OJQx:'gI~i9ze ^l?/F,X&:NSR<Ĩ" 9F剺ϫG&928ΨX=L8ǐ+ mʯ:1G_||8?`s*$X›qn*Ifw_ H)sE=gظ?Uy2#'V6-qZm"*Bq-|H3^ A0g3ѡiȲQ Y="G̤5&қ⎍90DFF,Җ5kҕa2CW^(|_Q nUH6tr&-zfҲ3ل~?pL"a|,8#U_ ]{~sn= [Z戇zQb Ab\Xse~'.VoJ7W}_lz ͇0)?LtNxF8jv?6_ZXe9T\H|9]WD0-?mˇfE6zJWu0}R Igs/lJ£ޜ'E*?,)ɈU}H=|"1CPX5zA'[`N0X%zSyΞT 亵17s1[R7뛩菱W~/?-1&c%*G=ѣ0Jj /@ƺen],]O,{ ZD\ u o7ϩII4n<(:*4ld,|6`*I70x"^w!z^Ƣ} 'Jحbؖg"-[$LL5'Uf% ḾóνY-Չ,$(ۇXvfŲR r,K|@o\$+;n0.W܊%.btJWkP\a%rٛ" z( d(vUjٶ4 i,嵗K<WUr:omٙBg.ŧ?첇(&`Xx d sa:׭8( (?-w6 T(3pLtek&K=]{;hJ"9]-nUaeט=O^/ULW4b:D=̼7e~!btzc_LwlW o{3 ycխ7U&Kp3bW;.B|'ljl'adQ3QgCÊvnYCrw^6(D5YQbo _ϑOx$]eBd>"ܜ텦ؓ<9ວ8ƙ!X/k|ZQO1@ri&/Gf28AŦ6RrtA_gV*."NښۉakK,^}ﴦzܬ=`cT>Ǝ7-Suv(BE0۱:{^ f1xؠu@ Km~b/#eclꥊ/xX"j/`.7+7:Jk|yдM&O"v7߽,BesBݼ~/7?gx ^ӚҤSհ!'48yUCdmN)B+M,b4D]&|l.T01D0Q3E-<6Pyc oQ<yu$WU?v{ef_rىS>撈sH 3y{؏Q"E>nfz +]|r^e>n!1 1SƧC(B[@1]U.w;rW,6JGZVwPv,ͫv~_r摹PTӸ;sg$r1cvf\8(R1.s]HĿt۬̾՚1Gʗ8<,!uzҎz$lؾczm`6ll%c?翦9[_;.N_xN)Ve輶5pTv̬B 8dw,[tiV:H TEX|%'K1f`.Wcf=t4X{gV ؽ IO|ensS택/45!߀ԩ^nzB&{)\].aRg'[bΘ)YϽS2sΤepΉhcɏb'QWFZx=H5,/^+] ̻.GX<9V2úB#J{$|<(* !hx-?MZˑzXozo#ܘ-S[Tաa Wuo`b't5.r1 srӗ;&LOԱiekP`gfvC(Š6t:e 7W%I!*k1 BU" `.{{%EVMS}w )R c%G .?1>&'p<, [=Άs |wzYҞ8&e^rŔC4DKoJEykqtyk\>[XJYs 7B@zUUJVX<"^ ݽ[<@bt=漬:(E '8:n턼wQ{SlT1~SHJ>acDygN3}jNJj{3> as~_`ǧr x`|L_XFo^P@͔=J4PD<۫qC䃖Ch8q vu{%; !endstream endobj 93 0 obj 6944 endobj 91 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 92 0 R >> endobj 95 0 obj <> stream x\ے6о+юPwVI䙎,[WOi֨ZUUu|el// ٗb-%suxU=({D˓Bσf18E(618-R^[m &U#fӔz-MkIDҥl%E"66w]a'Jwsj;tBK9e҅QMlg Sډ֨ňc@աL\ Vw[SKaS?Z0ɠod@~g)U206 I0؟LǪfcze=B^-LdV ;+! H꩑X#nO@w{ba9@ozĊ^.+l4dNZ{+_]Vy|D_St]7I/F @|b /#Tkof'}zRt']Cozlm򧱹QFX)m)]:R3"ʡ,6.4qVub+c5΄GbPJdd>Vxfx:L9)6PT~ 7MTQ`De쀢&;sIm*X}Gu{ 9YZXLiJX1Q܂8/Pff6 @=黶ۏGkT'b֑#E< $s6<!NwȘbfH$*Z *cr1e0l'̖fUz&V*PRE_ZdtcE6z[PW=#:9E3 Nz{qNYG, 1 H`gC#vF뼙jsUߑ` T\&mv+*"ӄx9TkFH}'beh#k{FtϜeg; %3VLc]~F*ccZ1>'Z #Ӑcyf* &l'I݄lЎt_5-sI4^>'5!h?q5KV;'GpSCiF?D[zs:o^$wBdϞ!?IٿL(v8qhO`D:A n Dö J6z .)SaPh]ӚR] ;qhK5U5ba'v[ 88/`aI5pv6lWNNmoe߃ةuƟf^?},Ѳ@v؞PDt}hgjRRngRBť6qZqıo:mt7K9RX$tĐ σH'ۧ!8 z ϭl=28Qv/ab2ul "xۘ%.OgB塻2A3yێʲH,y}bcb%K^<zfO--o=U2R- (|x}93l#8DH8}r ϙ9f!ppiFue3|蕾V~QRZY'i¼x4ڟ׍m[Od]I3)&/M 'MX|`ό{V͗MyL־ vqTY|8#El\nҬļu ƫ jO˫+]dhv< lζE<yhTPLfA8x{*Qy~{I8TQ˷/L"OLE6[8!6uJA[nr:]<2DM= %ǽZ7M`*Dq>R((Pl|j'`2)` l5OMw!-Q0vZ?QΓlehƮBoʔSs=CUtष$c6O xv q1d$aB!-\@fdZR{ &u FCwէfx5+EHD8%SX&-fYưuZ |!#<«M"d^ALi]ͦ@Y/^ZV -fi;*wkMa6 _pA"Coc)Iqt{;G3v,G]}vAt9U**Oo^ \WXL+S[3<}r [I {l,+&%oO{?I(VͽB]5/LآS/nF|}>t MVl__Uzlۦbfow`S637Cv& ; ocMdebnK@I0|6Gx }NLF4uCװ$TEE $CQ,75sil(b S'B q_ }U$_l :M^wn HcL\xT=:$.<IcP8qcQ}a A ȫ7E7^ǞCu36;Y]kVekC24A&r&p&/ܼٸ}˕201[bsk!6[jTX`˞*3CvzoiVW'^ss¨M`k++Ი^cvB7|q#o1[ē8lmԧ`Ύ#򼡢/Z'0:fl?ܭ UCP<\#a*~B^͙~M>ƚl."-%:ŋ88LhUnHV{i1lm?7#BNO%o[oޡp_\~&P$@6mקrendstream endobj 96 0 obj 6419 endobj 94 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R8 8 0 R /R6 6 0 R >> >> /Contents 95 0 R >> endobj 98 0 obj <> stream x]ێǑ]_1UYy*IX-`03EN/{G=oDfܲzi @)y22nynVۇW?rWW~6MjpêsWwW?.eEU~hW} ͫ\}ͫUX޼{UpYy:h <|y ^}ςsH*(NRi CE3Ʌf) Ry0Hy)H~U; ]꓅ISxLoVd 00UoQ9"s\0`0="L SW5# GP ϥ0SZ5>O~մ&Q L#00;u؀_y]35ޭZk=LQ0a*υ0ƭŖkٺ"#8Gp ϥ8]ƹ`?Ƶo*"HChЬ6b4iB.փA((\5`޾(~gq2Eqdv.ضiET0b`2EaJ;DoZ[88bp2EqJ;L3Z~*'S'Q N(NiR=-hpϘ"8bSs!ͨu-I(('Ss)N4ڎ:[$)\ ~ڰޢ{BQD188Kq&Y`qLQ)\!?0!$.s;ήלϵ!g4aˇv9_ +h*RNv<\]ݍ?u<hݗ4~~O_\t rДe.D >/⪷<5]Iz^UJ!U}ucڎ8Pe09з  I\+RT] F\#W `kFs#A 4 s}06bA˨ JF 7FCz0C.H\T".¦##W"gdB$9VyЀP݌~TA4adlΕlGC$*4oƝQ[ЖU-ƶRPYm*c6Fh'Vk,P {¡ ʚ*}afg3tV'L6]7QJZl{kCD4Uu25WY$I'SNd@P10gJzB* v,WۂbM{>Wv_'e)4`mnT:KJgClXZuCD0_묺ܰr0eq,-W2G5QG4%OPJ bjEXB|iJ5\Gv.t`=ɥW͊Le*vͤvS`mL>]2Iza,S4r/-Wnf'ZySMto{j#7*L;Tڮ;f$5ƹV.' 1NU\K{d.eFѸYH;I)]޺ҏXmIzP_JH802}9OvWfJfdÐ 69I1`η(nHaFzZe 8T &I0ȟoq2-j~Tj9a_rFӵNB.˗dImzXSt> `s!gJf'(J Y Heb{(Tz,jֻC36j|Hral{\Ui)*,Xgnw̵ǻɐS8Y6i,Řǧi&vS-iu^V`StWl*^5i\8<:~Tt,HiލyИ iގqLƑD5i a<>mO,k7y5yԧʋ@ ɐߎ{҉<0UxWzY-:+B?q_7k7'bӉ]!c߬I!_}ܜiʃE4!5+PP'D-XQHA5Hk 0=ba;U﨑!҆Yh)Yn,ʄL> J˓hvl"؍H1 ϒVzx~K餁{RfG dY}ɳ3v(AĴݎ3'p ꌸ >$$C>oZ)ڐom{ NSG6Ehz!Wk80?4(gͻڜ Un}{ڐ,LيT& Mhj~w:b"VBAgDW ~M?onvfwTv_+z q܂_z8ׇ-v̺e|#y!z uZgS:דyj oa̓P&,_\,R ,jf~z|`%S\?h~~WDggIOhTZe  Lm]8/\QP|]8ut0h~GF)tqכw-.itupWa(j%ԙEi_cIvt,[ 4:ǚ`bt^Y0O'h-Wk<Iaֶl<(0Dfjfm׍νMy $3Oc70:rW)--syUVs9n8Ā3Vcy7 ax2Ff7roZF4ֺT%#M[]qo InۛV5\qv)V9$fT&Zmpb Hs9jX./ P8IMЀlH,ɠر5ej d#Fݏ*%?yD:lv#k.`G#s U9_ |pu:<_}7ݕ{շ߽˶ ǣ7Fߡj+$Y,% 7O#IlEYJeUֶc˼^q/+A2fv<}]I 2v=J>[7]qIT|q^\O2ni丏^uDC($%ܼ$SFrj؋/5/=s!̯$T?mE*"[lWi*6o?h]uapCŸ~Y4͝Gad76(.8ͱ1HrmU;LX=]\ Fix|x$'LN~86Y9zҾ1ʗm9(Kz(MvwzMmC !n[Vͪ4viMjRcݑ%I>BOX7hhd栊, ψk 'WڙYH4Dۧ-ZĬEt%1ai;4nRrFevp l.2bKXLLTaEȔ!Dc'gh+_qǼ` v/EU:֤X(6 KMLj'Q65kY g x;`y?ݨ/Wwd Kf9KY‡* Ό3 .8:BZ/VM={w,OnZT ٨ieZrfiv>>[gV?/vZ /6P$oy=nq|a&Nj} ›O5>ח-o,I(Jbu];yg(>@TLQ(Q*e(f]~~ (Ls!L*/O&Q L#0B%J%M#0G` υ0;pMY2k< Sy\sV͐o,\` ytiަw 0$< \ QIs@_~oT((bP2EQJ; x5^y;HPV(('Ss)-JLQD188KqU38M]))\3^WӺa9^$,۸"s/Obd$A)\36*opxd$)\U8p$,۸}]ek]H\3_Di/ea,})TɳtmRilX .+65]ߥrDpHWe"=7/CU}q ,ب"C:aS!8|]^hӐPR^H=1>_3l\ (H\x r ZR<|+\/z w^H̅EDT)ލɛ! 2FW>FCpF/ʥ_{TmӗxӸw%+DWuϩk˪RӅP+2z=5lJf-Y[UnR9 'LfW>;1 M rF­42,S(dHL '30gF>C'~,A)=A]x._FDjQU|UU+nņ8u7*ƩF/'z\GܣTuZ<(j~|/kC,l.ߢ6TOReiK˥AK{e ^)=WIza R40,S&펙;:ݠ4_fP EPŠ.Ew]RgKLEFEF_]K7(~B V JJBՅ(.dP`ER((P|u)ʀW;2)4UA)AIR(Re"BaY$A)A)_]`Q]oQ*Q2u0JBS L ч)RmtWya T k`XN,Nlqï'w{r):ݹ.tJ^\ߕ!l-9[tM7bB0PIy"Z;amIdez<,u!J)=%ְ{`?LojhjR|y Uœ1 2ҍ"3;kp㏅\ h{c20G!۩sc4@pG{mc"W~uJo̤TU(,i %w}l vqW9#Y]g/k Ѡ&Ψ,O3X=Q\;K2S{M_D4UL@Z/蔫,W2t%L0*4U TaLf$Q$ТnUh|gLsneO\\|gLAdLvgĴ%>,Q8܌9 :!2Ocx.0Pq? Ԗq"ʳ~!Cx*_)?w#]zeqLܐK B/ Wȇⅰvgi] S/$#/T(5/$ %P*%F!UMX|YaդG2=&oK:gh*_Zzͥ=2:6IC͖`yw)7]3Iocz§vJnI 3@r/-Wi(PXr_b&C<3˓y/Ѿĵr}D(H$ 2I\*_Zzͥ=29i|K (r/-Wi)P-_5#Jx\BU = }iVq[#N}?/8rQr/-WiK{|پa)ת}}*דj TZdTYzK/Ԑ_%]3a\xnE$yTO;>䃩f;*]NdZۦi-ϵR4]g8kak'Wr\emW~cl[R)J+Z~E^$FP:WSG Nj4T#'oܗ1UĢ!.s)&̎^Bɪʕ:;uJKҠB SZ/OOΥ%E4fK:^HWckȍ渦1꺓Zr>b$^Kغ |L.5lR=r뻻x*u.чPָ)dOKYc'Qk 6f-2*[\5G^ RزJ9`X֛b o0pV!hZ{Z-,.Rxt_똦1[~h5ުhJf2_#q]s]c*mM-կ"Wo0mT~g.j~a|KY}՟ƽ)%O$S KIWK:Nע^z{q:m*RKg\b|m[-zUBr3;i*9T(h`GfeDo~':ۙamM^qNZ(8lD~D ZKOCdjuf-T7uٟ8+dəI*hJ>^^+`U h]k^IžXO8g\­MKޒ)[U^WR7]hCTA ΰPQ-a.|7rUqwu/UG κU״rRޯ%m~Dj>㵺d?knn)ZՑVO ** L[RJNwVƼmWendstream endobj 99 0 obj 8616 endobj 97 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 98 0 R >> endobj 101 0 obj <> stream x\7rq|4n|DG:8}0F3݉汚NSE֋ݽkk%T?EjY/*\oԋ[6U.>:.zv_n.. ew OǸlf7޼_ԩoc_|o]KWۛ^aAaCx 2TM[) Ry0HTYGh<A'08 (Ls-~ 530} ޢ9#sDA{Dfv/"Q F#(Z]m@ ?w$)<Sx|";l]vGu55 N(Na8Jۤ_D!LGB˪S!Wݴ͗~uo E 2E1 E@j;W+`PF;)('Ss-ΦǶ L;u`P&Q L(LiZt'*lXLQD188kqZֱרmkq2Eqdv#l տ- N\A3jjӆ?X$I)Sڹ'QiGʼn~ Eqdv vTB?mlPO(NLQε8,3Zӄ)s08kqBVyG uO_v^py_on{o}]r?? b3קx:#<&@oכ7` .u*C(> 4nٴEyՍCz"xm@zm5mwݲNPbt>s5hX40ELA.ErT 3\ ~ͭku0 V m^ WBQ#n x \TD\ȅME#[#Bh? Q)4&B[NLДk+.FXIBfG2uFUQ ps3ˠ*=Iy/}U/nb5ښ)k+ȵ0K]h0UiW5yt 2ǬVZ+-\۪gī>l5aĕi+W#8yFLfW:ؓ:D4˕3 fQ`Zh5 ϵ(@EVf_RܣU~QG Rнⰺ\K˕/?1$/$mWOΑ50˖l;oom`l EO,WrK.푹Lu0ire &4mUw)]0iw̤I$ Hƶ peWɮBl0,Sn`IDnw*0Vf{{ڵ (~'S;0)>yL+*WfN S埈4dR?S\/}ɘVaPGMAg؏E@`ޟll,lIRiq4ٹI#fmA@ZfDFԺHCh~>` f{xPLqc&X_VCB+H+Q` ix= e)1 aO}W15 LJᴺlL1Y]@[LQ[QOVj-ϧo{X@ґљUeaLQ(Q*ϕ(!aIaS ,b(bP A)<7߃Ct歆)LuJ&Q L#0Zl4PZPf9"s\KX=)Bvb0 A)<ĭ.N3#.$)<Sx\ 3Vˬn*̶-j)fNxZx\Tm9P (Ls-Ms LօPZG)_b2^ۥPwr'2Of@>L)ý7A& W1R -svl9M@\F9`U VrA@AiTE ֻJu>B.0JZSSC[Kwt745>v>";&Ic:EPtlJlBydčh T|&qtQ29fNoنUˠ6#*uf<2vhc%AENu0@BT߯-ÿڔ\*GV'97I0{c%|yhyF3wZ)E2ʥQY)cY'0N89/a_sHRjz3GHx/cvm_n+ϲ~4GL C>u ^`Eco;N+<&%(#^:IW>U Sp0Las%YqT '0q, (Ls-L,ʄ)̴s[D10G` kaF5]dʷ> q D# GP ϵ0, ||s֢$A)x[d)<Sx|2LMlJ5L$V Q䶺(xL*+%1(1v: 1 D"(BnZTc񌂭r=(F(HLQε8;XVgQ髾)P(PPmJ )Eݦw.U(N"(L0( k1624[Eɦoc$BQD108kXW0sAGGNzz:δ\Pm'ȒCO$iY.\Ԁ|Օo_Ū->r 9)fOJG^sJ딦' 5eMÎvLm%zT\WՋ\+#)=VԗL,a UIwr=#ہJxgtXf)j=;;Sqh,T':,ͲxըLXh$ڗՌA=[$Ϲ8L4e#c?yX;s߈ ΘܐtjxЃfg-oק᰾φAg2.U^sOk26Y`E56rb@å3LeZռd}<WhD*ڧ%M"ܶ^MP͇-}֧D[X"iʯv`%ӿgZrd*ejF4z=V!{9(;['+k4Tff΁}*>͟( jGxgNٟOwM ng,BHM= s|LzINNxZg/[\K.2Lz rE1s-U_|l>U"Ĕ՞3ET 2vODB쒰zz=>o=L`wYZ3 L6[h8&}̭>8ba.acjif22-Uto8Ļ_u,4uZ9n>LR]wTW^$Nsh+ntT0ބ>r&B̥0\y04kL5cxwrAbFj-ݤ2]+mёin>g2"&M 9=]Ti_nh6L`80hq.?W&Ջ]J\kh0N4.|ߩZH򥾧^}3`l j3 K b:iZo݋z"se?SL)-oR̝ FpsMOEct~WƕitZ}$T~3 @$J%~l~Ol,<8uPgRc1r*{RMu/cJ~}yUpq.-~X2P t4e*Ψkg*6c] Zg.mpy;uFͲOSO)UDBjx|Aڬ՝8r2i`׻iޯ*|)4w&O(n~ u,93w:>MsiutVo/"N='.9<, 8c.c\ ユ<~H-`+8 zSb_!w/8 Ww;n:.N|1wA9* eGpZޔ~1&3;+(7MȾe ֙kohU:3V O~JYz.H헕\} |LGˬ+:Ş\U>WooġMi~ް$;Tbx7\q?Sڬ&V'e΅cFn 6YV-g"^fzm)j^@{ ߜv`RWP4dbKb"YW}PaXԵY =ɿ\Mo/˲ttM ?]Hendstream endobj 102 0 obj 7964 endobj 100 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 101 0 R >> endobj 105 0 obj <> stream x\[sƑҾWK*u^D ]QQe +WUTbOZ"Yee!\%Osؽ|u,Q^ڽz{Kvee*!W_ŷv`***%ACT-JKeTy'QY*5*4FI+L&A2y [JJ(Ֆt̬"+L[<~˓ɄĒmd !2Od22҅%3 ACd-LdbM )LZa2y[Ld2AkRgBj4BACd-LdꤊrgBz6iLȒ $%$bO)Yu̢FBf+ <B)cDa'QU$@JZT₠VJӨLS-L*s^ҊTcFeFeRI*J>TO%.*ihxIc ?_SP5d(ÌHX1=i=+ 1zmJE|mvw_meҴ$Ͽ: }D/$Kܨh-*vJQ/iirJ \fnkR%Ж!E*UҝDFU-.@0pT% R)pD)@RجZA@jVLTE4*x%pVn!*swgܮr-9K+W.ϵTI>GO]* oP/XQ ܝOVebv%$!) >1dB`.^fBF8͌ZJ9]=I̮gոa 仰qC{ 2:u#^D*)"NIyfO "hQD^d YBhXB":]pYHHa:ʴa1X TQӳě l,(>JPJʜ`$*%M[܍94WXqrN Je])a7ZGS,) FVdfSF~#d:𝐬xXz00Ͱ_CT ([+1j2Q$ &K;zNAH _{n-kHy@ _ѽ|HllV$|^ ޹,e:Vsqf`}܀\i%@H:fDUZ}AݪDۥS,+b[S`)u^S.&wT\aT6%=&\F'!Y'E'Ѧ՞7$tTx,/ܚeV,覜Jb[sCҹ;R.\9i53IM>w5=?H/1¬cADJ`1AQF[ P[|;AmQtc&8Q\"9p{p8~OOݕQvlK< W?w{miopx׎62faԎ0x<;?dmBnRO,o:C)twwݭ*/d+yn~V?ѳ7ϭ0iW :@w9u۵S Rd+L,6B^ܑ:3d8~pTdkTS;~J08}x /oQlR+$J{YsWywX^ 5TO=yS4׳e :2d~|< #2`1Ժ=O  g<ٮPŵV곒);0x^`QcS+Xê&IfNxTh%T?'jvZ(U 5<Zc)s*5YN[w? *v ?rvpU>[T +.pFBD o!Q\it `@Rgph6Rh]6lrX A͑MT vQG D\ #a!ie*v$؀[b0BM {~+橻;=zR6n6YP^>8f|p z\畦8)`cJ67+#^糦 }]*:_55g ,:t\8~g@ P[}LS jxa3m߭@r|B=KU/HLB\v}wEyU!mQlev~ߙb0=$Np<0g)ֹLsڧ_*x( Ayp`JW46bKF a-F"󙭧p@mxaXy(6e7)R yn'؛;+čozXEwwdwmZx2XtkRJ-14ElqBhqXH,RUo)T55b;04[ӷ;uyO#j|,8u"V&e#~p8hw8}(Iف,*sJd**m61i&e(]3 %;)bq8(,/>SUI2\YmۀC N_S2I:{>oT!BB2s>@`Me #EV+ydn,Yd][xK$O&WV't[9'i:q~rs})+HDj $`yE:; c"H~5説V*5zMFe%m5t(RuHφlE)gnW$2v%uKc-ûwxIkߟ'xbYP `GAz3HBԔ0'N0};c"2#qQ`G7|ܪ]:3+62f񠸥=BzHASLil!`r!8ư@oXC6UOhY(_l>BEd¿V'z$dj/QMX €T{Ձ{H{f|uQ,¤B[:P%NuMQepV@L[WRgR9"%-خ:SEQ Cr j_`Cj/@."-B\[˩jb%!CV'Pj"Џ<<"SE.,e\"T4%Cip(rXݠ̵ 80cCW9!q؞'y\n"<Dfyl]|N<reV őud" dvFc]tA0MDTP͝ZtpmsO.f˾q~ru2?]Ě$Gk41}~q=$>;-,f9QXwV-hL&ps? akt4Vv>Քi~;d@x{_av HSr\9 K}i.{PL&=MΞ|?4ruO~` (d7$ë)g>աgvC7S>豧{j#Evt@b6[{xdwG DddF4X.V"DܨZ W仺;׺Q[%EG&XWQ.O[,.~i,(HoQ4Ut|s"N  %w\*m۵g܅47'?ƌ]GlQe邷[>9z 'srr.Q2-Pk#NV_?@0)Ԁ,@)ԤɆ\Lsd:=H]7 ^?YN/OԘ,Dѧ9eV@VM-Jq=YuB~5"v.@-C6P-Vf9Ż<eS%n+AJ~AV9@j:MKa+?E@4aFl ñY0D0aAnP7?pK5؂kj"zҨTM}b}q uyJ 󅠖;]N?+$2_?\-no7ߢ,sKJp1N!}EN9orVr[ j֝3(5#r*[-8凮"{1aLa \_;WrʈL=V<Z\#l xnYEJVDi*ٜ}z{nTbf!#[rx՟`BfRLC!@5p)DgM/]YSUKZzi1,ryKۙuIBHR]3PֆQ9֧ glس15!5d(Rί 繧:APS7DŹ>KJ|_`$u8_8wҰz@Y³(I/FhYJotl_ոa&?q696^YVa"ml 3H2O .z'2yx9@)kKH8{t A?jk2bc1ǒz|qM-1}366Qzْ emgnn)r4\ xȅ^EZDgXwi_sC~.NV5*ȭm|α*w\VIendstream endobj 106 0 obj 6622 endobj 104 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /A 15 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 105 0 R >> endobj 110 0 obj <> stream x\rFvN'S$š(y^%YN490FRM_N9(%J\,sw.0C}8pkū <7WG踈( "~qƉozDAU{q% u(.Ƒ5ݵ| ʤ*O^x\a_yL\ Յ~W2iSOel\ nRFeDYu|njZ 1kض ŏ䮣|j8Hi2'nbz8F6Jl./oIqT"K/,,OvmqW}&> z*K7T7ZeRIt*3 -1`[\ZFe>l;2S&(/UvUl5n 78,r7U_v}р4F4,t +J[ʏ*b{SuE.Q[S}+w r<R0Mݭ[E+kTtR1WC9sw0DansIW zY9ms]q&Sa])LnjXT@U7&ncVcO187R )3'Y8.QK& dž[ Fho6 P ҮQpF-xKJY;ZiM WS24xn Q"f ^(HHlKfiQ:ܗD^Ϫi7u%X8D!)`Etuz >6VC,KhC;oGf)[ݵlk,cRdTxOT( <;8UX7>K[LFBfR0V0kR12 b]PwTpU6iw%{0 ZhJ 6閮04iVp&:g$fBbQk~[A#Yw1y2̬b)#rd|xV:A!'X ,>%بbVL.ћS-C*IAVBZl"tY/yF] @|B0\sQ([5چĔP bUێ_XcnЌjcfǑY)m-`%(Ok]˾Wa߂`(V',48u 6J جu8GΩEL 0<ƅ_ 4G*{t'GK-KJ몍!B>GrYR79BaM e9,1uКFi1q6d];P8#ht9(NѽG?˛8| #.&*gTI+S9ʼMB-16R/1pQ0 :;A\mUd_خ8`L!hU2ԙd{}V)~[VͿ|䩳^h ^ 0#!R!GbrZzE&d BށbǦ7^@X^qkg<dC{kSFT;PTXSy(%)$_$m*Ru+ 4"X3B 5#Pn6YG8.^jЅiRQ1=5 45!uRbٴoEn $Ģ 5.730.dhR![lO@h{-~[pсbl(QD恮l/l(p8't1 3 ߤdNI1Vht5i&r%F㟎>b]ʣ\q_ÿ=8Z\=UAH8G54tV)*[e͔ˋzX=\Le ޙ1t8ͣtf rjOf~G tsÒ~oj.&ҚaB=@JRڵiQ*I ф:0ShGIiRHa2`\dnnTIw4̏s1"'^7c17<)mzZo)!wTeŔ=#(mmͧ5LW&O RPhUր=Ь4 f-'Ą'KȢ)Xi#uuf<"AOp s',~qΕg&DaNP'Crկ 5NSS<_zSם{#%u!; LapΩa6'U0X6c[LUBiՑ/U5-J@؊?j6} LfGbhY]L 8PPcxu: E [,C0KRY6>Him4 73R|4r6OΡ1ٙ;,[݃L@iPTZ{@bbʝhrZZ୼7XNIIFo6ⵃj pz!`|?険- zAOe7./V)͎LOL$%ڬYAxuրY?GfiBt/Œ b $~x)ɻo+q-3e7e 7 4[3[*R4Lbc0^7.䗳Ji B[LvVmJևnqͦu Ͽ7h.޷0߭8Q] G8ȏy3qQEP[U`Ē*^zѤ{KoKnI+= xg_Ζm؍,ijEݼTKabN&HhYhJ`͍@.ƾ-/xR$6#o}|7p pθ@,M ~cwA?\SlMXRy\]2?yq(0R҃1Ug)¹70x[Ix߼$gߔBik癲 Aԃ$p,쩞;T}uᵏXQ"ǥ;@ +OBYh8@KQ-)/y|1qe?LT_0} _A,uWcc4H<+99s)9T2neE*$tEJ^z|-TT>XQQĉ+4)B1<43A(Oԉ"v&)aE{*CI 4-)_M7"E~c> >> /Contents 110 0 R >> endobj 74 0 obj <> endobj 31 0 obj <> endobj 33 0 obj <> endobj 15 0 obj <>/FontBBox[0 -41 84 176]/FontMatrix[1 0 0 1 0 0]/Widths[ 0 0 0 111 0 0 119 0 78 0 0 41 0 0 0 106 0 54 0 53 0 38 0 36 0 52 0 45 0 60 42 0 58 0 31 37 0 46 0 80 100 35 40 110 51 32 91 0 0 0 0 44 62 39 61 0 0 0 0 0 0 48 0 47]>> endobj 9 0 obj <> endobj 64 0 obj <> endobj 8 0 obj <> endobj 10 0 obj <> endobj 6 0 obj <> endobj 7 0 obj <> endobj 2 0 obj << /Type /Pages /Kids [ 3 0 R 11 0 R 25 0 R 65 0 R 68 0 R 71 0 R 75 0 R 91 0 R 94 0 R 97 0 R 100 0 R 104 0 R 109 0 R ] /Count 13 >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 117 0 obj << /CreationDate (D:20010430210556) /Producer (Aladdin Ghostscript 5.50) >> endobj 14 0 obj <> endobj 16 0 obj <> stream 0 0 0 0 84 69 d1 84 0 0 69 0 0 cm BI /IM true/W 84/H 69/BPC 1/F/CCF/DP<> ID *ejD&{߮ :}{ ?:i׸W?|?W}? _߂\ ?A  EI endstream endobj 17 0 obj <> stream 0 0 0 -69 72 3 d1 72 0 0 72 0 -69 cm BI /IM true/W 72/H 72/BPC 1/F/CCF/DP<> ID &ph$  a<+zO& Ň봽/ P !}a}߷p:^ ]Kl  EI endstream endobj 18 0 obj <> stream 0 0 0 -55 61 5 d1 61 0 0 60 0 -55 cm BI /IM true/W 61/H 60/BPC 1/F/CCF/DP<> ID &N,X@Aap`a .,X\ XAp Tx|0x0<0a  >  EI endstream endobj 19 0 obj <> stream 111 0 0 0 0 0 d1 endstream endobj 20 0 obj <> stream 0 0 0 -75 24 24 d1 24 0 0 99 0 -75 cm BI /IM true/W 24/H 99/BPC 1/F/CCF/DP<> ID &XZZ -p]~z/Z_ /Mw|= ߀ EI endstream endobj 21 0 obj <> stream 0 0 0 41 61 47 d1 61 0 0 6 0 41 cm BI /IM true/W 61/H 6/BPC 1/F/CCF/DP<> ID &|^MR EI endstream endobj 22 0 obj <> stream 119 0 0 0 0 0 d1 endstream endobj 23 0 obj <> stream 0 0 0 -75 24 24 d1 24 0 0 99 0 -75 cm BI /IM true/W 24/H 99/BPC 1/F/CCF/DP<> ID &h{{{>{ZX_^Akֽa.Z]iu EI endstream endobj 24 0 obj <> stream 78 0 0 0 0 0 d1 endstream endobj 28 0 obj <> stream 0 0 0 -67 32 -34 d1 32 0 0 33 0 -67 cm BI /IM true/W 32/H 33/BPC 1/F/CCF/DP<> ID &l}o!n0v߹ lV> ( EI endstream endobj 29 0 obj <> stream 0 0 0 -84 36 -34 d1 36 0 0 50 0 -84 cm BI /IM true/W 36/H 50/BPC 1/F/CCF/DP<> ID &<z8A[)p߿ÿ6~k? ?3|@ EI endstream endobj 30 0 obj <> stream 41 0 0 0 0 0 d1 endstream endobj 32 0 obj <> stream 0 0 0 -60 30 21 d1 30 0 0 81 0 -60 cm BI /IM true/W 30/H 81/BPC 1/F/CCF/DP<> ID &_! ^K\=ȴo<~ EI endstream endobj 34 0 obj <> stream 0 0 0 0 30 81 d1 30 0 0 81 0 0 cm BI /IM true/W 30/H 81/BPC 1/F/CCF/DP<> ID &6`%L=dZ4,-zׅX]ipP EI endstream endobj 35 0 obj <> stream 0 0 0 -70 47 2 d1 47 0 0 72 0 -70 cm BI /IM true/W 47/H 72/BPC 1/F/CCF/DP<> ID &n|/A{ ;ׯ\.H i~ݲ i n~߰8 B"Ubp EI endstream endobj 36 0 obj <> stream 106 0 0 0 0 0 d1 endstream endobj 37 0 obj <> stream 0 0 0 -45 42 20 d1 42 0 0 65 0 -45 cm BI /IM true/W 42/H 65/BPC 1/F/CCF/DP<> ID &JL0!&c}~wÿ^oxa]q;i<` EI endstream endobj 38 0 obj <> stream 54 0 0 0 0 0 d1 endstream endobj 39 0 obj <> stream 0 0 0 -11 13 20 d1 13 0 0 31 0 -11 cm BI /IM true/W 13/H 31/BPC 1/F/CCF/DP<> ID &~˾c  EI endstream endobj 40 0 obj <> stream 53 0 0 0 0 0 d1 endstream endobj 41 0 obj <> stream 0 0 0 -67 26 2 d1 26 0 0 69 0 -67 cm BI /IM true/W 26/H 69/BPC 1/F/CCF/DP<> ID & z<޷?|b`^? ( EI endstream endobj 42 0 obj <> stream 38 0 0 0 0 0 d1 endstream endobj 43 0 obj <> stream 0 0 0 -70 49 2 d1 49 0 0 72 0 -70 cm BI /IM true/W 49/H 72/BPC 1/F/CCF/DP<> ID & 0A4zސ0owMwaavGz>Cd~ـ EI endstream endobj 44 0 obj <> stream 36 0 0 0 0 0 d1 endstream endobj 45 0 obj <> stream 0 0 0 -45 39 2 d1 39 0 0 47 0 -45 cm BI /IM true/W 39/H 47/BPC 1/F/CCF/DP<> ID &™ 0}~`\ ~R܆)op}￷]Xzn EI endstream endobj 46 0 obj <> stream 52 0 0 0 0 0 d1 endstream endobj 47 0 obj <> stream 0 0 0 -45 54 2 d1 54 0 0 47 0 -45 cm BI /IM true/W 54/H 47/BPC 1/F/CCF/DP<> ID &l2τa8{ppp߿ }&> stream 45 0 0 0 0 0 d1 endstream endobj 49 0 obj <> stream 0 0 0 -63 31 2 d1 31 0 0 65 0 -63 cm BI /IM true/W 31/H 65/BPC 1/F/CCF/DP<> ID &>0C}߿;"y5_!{ﰾ  EI endstream endobj 50 0 obj <> stream 60 0 0 0 0 0 d1 endstream endobj 51 0 obj <> stream 42 0 0 0 0 0 d1 endstream endobj 52 0 obj <> stream 0 0 0 -71 50 21 d1 50 0 0 92 0 -71 cm BI /IM true/W 50/H 92/BPC 1/F/CCF/DP<> ID &<@}߾} eO%{ǃXa@@ EI endstream endobj 53 0 obj <> stream 58 0 0 0 0 0 d1 endstream endobj 54 0 obj <> stream 0 0 0 -70 23 2 d1 23 0 0 72 0 -70 cm BI /IM true/W 23/H 72/BPC 1/F/CCF/DP<> ID &x/G'8 rk%_C EI endstream endobj 55 0 obj <> stream 31 0 0 0 0 0 d1 endstream endobj 56 0 obj <> stream 37 0 0 0 0 0 d1 endstream endobj 57 0 obj <> stream 0 0 0 -45 40 2 d1 40 0 0 47 0 -45 cm BI /IM true/W 40/H 47/BPC 1/F/CCF/DP<> ID &l>}rjB=s4t oNbb0 EI endstream endobj 58 0 obj <> stream 46 0 0 0 0 0 d1 endstream endobj 59 0 obj <> stream 0 0 0 -55 61 5 d1 61 0 0 60 0 -55 cm BI /IM true/W 61/H 60/BPC 1/F/CCF/DP<> ID &pp > <0x|x`x0߂XApAap   ,, \,X@X MQX EI endstream endobj 60 0 obj <> stream 80 0 0 0 0 0 d1 endstream endobj 61 0 obj <> stream 100 0 0 0 0 0 d1 endstream endobj 62 0 obj <> stream 35 0 0 0 0 0 d1 endstream endobj 63 0 obj <> stream 40 0 0 0 0 0 d1 endstream endobj 78 0 obj <> stream 110 0 0 0 0 0 d1 endstream endobj 79 0 obj <> stream 51 0 0 0 0 0 d1 endstream endobj 80 0 obj <> stream 32 0 0 0 0 0 d1 endstream endobj 81 0 obj <> stream 91 0 0 0 0 0 d1 endstream endobj 82 0 obj <> stream 0 0 0 -75 35 26 d1 35 0 0 101 0 -75 cm BI /IM true/W 35/H 101/BPC 1/F/CCF/DP<> ID &th-.t,xQ4fFF ﷿Pf EI endstream endobj 83 0 obj <> stream 0 0 0 -45 47 2 d1 47 0 0 47 0 -45 cm BI /IM true/W 47/H 47/BPC 1/F/CCF/DP<> ID & `0h a ;A?ɯ>pOo.Cd  EI endstream endobj 84 0 obj <> stream 0 0 0 -45 39 2 d1 39 0 0 47 0 -45 cm BI /IM true/W 39/H 47/BPC 1/F/CCF/DP<> ID &>!AH~ 7m~k!w|0>o %aY EI endstream endobj 85 0 obj <> stream 0 0 0 -70 50 2 d1 50 0 0 72 0 -70 cm BI /IM true/W 50/H 72/BPC 1/F/CCF/DP<> ID &>'A,={wÿ{~^4^ WhWA@ EI endstream endobj 86 0 obj <> stream 44 0 0 0 0 0 d1 endstream endobj 87 0 obj <> stream 62 0 0 0 0 0 d1 endstream endobj 88 0 obj <> stream 39 0 0 0 0 0 d1 endstream endobj 89 0 obj <> stream 61 0 0 0 0 0 d1 endstream endobj 90 0 obj <> stream 0 0 0 -75 35 26 d1 35 0 0 101 0 -75 cm BI /IM true/W 35/H 101/BPC 1/F/CCF/DP<> ID 3 ɩ~o npK.׭pK.B^  EI endstream endobj 103 0 obj <> stream 0 0 0 0 34 25 d1 34 0 0 25 0 0 cm BI /IM true/W 34/H 25/BPC 1/F/CCF/DP<> ID )ܚS{Z EUB EI endstream endobj 107 0 obj <> stream 0 0 0 0 25 21 d1 25 0 0 21 0 0 cm BI /IM true/W 25/H 21/BPC 1/F/CCF/DP<> ID &( FAm+uIz]xKczÌ@ EI endstream endobj 108 0 obj <> stream 0 0 0 -67 41 0 d1 41 0 0 67 0 -67 cm BI /IM true/W 41/H 67/BPC 1/F/CCF/DP<> ID &=Qa{7߶>{7.@ vvK0\ !@ EI endstream endobj 112 0 obj <> stream 0 0 0 -67 34 0 d1 34 0 0 67 0 -67 cm BI /IM true/W 34/H 67/BPC 1/F/CCF/DP<> ID &l/MB  EI endstream endobj 113 0 obj <> stream 0 0 0 -75 39 26 d1 39 0 0 101 0 -75 cm BI /IM true/W 39/H 101/BPC 1/F/CCF/DP<> ID &}?߿7~ ߿~߿? EI endstream endobj 114 0 obj <> stream 48 0 0 0 0 0 d1 endstream endobj 115 0 obj <> stream 0 0 0 -69 45 0 d1 45 0 0 69 0 -69 cm BI /IM true/W 45/H 69/BPC 1/F/CCF/DP<> ID & `57A|0a EI endstream endobj 116 0 obj <> stream 47 0 0 0 0 0 d1 endstream endobj xref 0 118 0000000000 65535 f 0000084611 00000 n 0000084464 00000 n 0000004586 00000 n 0000000015 00000 n 0000004566 00000 n 0000084324 00000 n 0000084411 00000 n 0000084129 00000 n 0000083982 00000 n 0000084218 00000 n 0000011330 00000 n 0000004754 00000 n 0000011309 00000 n 0000084753 00000 n 0000082954 00000 n 0000085991 00000 n 0000086267 00000 n 0000086546 00000 n 0000086811 00000 n 0000086876 00000 n 0000087116 00000 n 0000087291 00000 n 0000087356 00000 n 0000087598 00000 n 0000017834 00000 n 0000011518 00000 n 0000017813 00000 n 0000087662 00000 n 0000087878 00000 n 0000088122 00000 n 0000082808 00000 n 0000088186 00000 n 0000082897 00000 n 0000088424 00000 n 0000088657 00000 n 0000088932 00000 n 0000088997 00000 n 0000089249 00000 n 0000089313 00000 n 0000089512 00000 n 0000089576 00000 n 0000089814 00000 n 0000089878 00000 n 0000090148 00000 n 0000090212 00000 n 0000090456 00000 n 0000090520 00000 n 0000090774 00000 n 0000090838 00000 n 0000091070 00000 n 0000091134 00000 n 0000091198 00000 n 0000091456 00000 n 0000091520 00000 n 0000091750 00000 n 0000091814 00000 n 0000091878 00000 n 0000092108 00000 n 0000092172 00000 n 0000092440 00000 n 0000092504 00000 n 0000092569 00000 n 0000092633 00000 n 0000084072 00000 n 0000023784 00000 n 0000018034 00000 n 0000023763 00000 n 0000029783 00000 n 0000023962 00000 n 0000029762 00000 n 0000033746 00000 n 0000029961 00000 n 0000033725 00000 n 0000082737 00000 n 0000038472 00000 n 0000033938 00000 n 0000038451 00000 n 0000092697 00000 n 0000092762 00000 n 0000092826 00000 n 0000092890 00000 n 0000092954 00000 n 0000093214 00000 n 0000093463 00000 n 0000093698 00000 n 0000093961 00000 n 0000094025 00000 n 0000094089 00000 n 0000094153 00000 n 0000094217 00000 n 0000045699 00000 n 0000038662 00000 n 0000045678 00000 n 0000052389 00000 n 0000045877 00000 n 0000052368 00000 n 0000061266 00000 n 0000052557 00000 n 0000061245 00000 n 0000069506 00000 n 0000061446 00000 n 0000069484 00000 n 0000094471 00000 n 0000076426 00000 n 0000069708 00000 n 0000076404 00000 n 0000094661 00000 n 0000094865 00000 n 0000082535 00000 n 0000076618 00000 n 0000082513 00000 n 0000095117 00000 n 0000095331 00000 n 0000095579 00000 n 0000095644 00000 n 0000095885 00000 n 0000084660 00000 n trailer << /Size 118 /Root 1 0 R /Info 117 0 R >> startxref 95950 %%EOF nutcracker-0.4.0+dfsg/notes/memcache.txt000066400000000000000000000070211242132376000202260ustar00rootroot00000000000000- ascii: - Storage Commands (set, add, replace, append, prepend, cas): set [noreply]\r\n\r\n add [noreply]\r\n\r\n replace [noreply]\r\n\r\n append [noreply]\r\n\r\n prepend [noreply]\r\n\r\n cas [noreply]\r\n\r\n where, - uint32_t : data specific client side flags - uint32_t : expiration time (in seconds) - uint32_t : size of the data (in bytes) - uint8_t[]: data block - uint64_t - Retrival Commands (get, gets): get \r\n get []+\r\n gets \r\n gets []+\r\n - Delete Command (delete): delete [noreply]\r\n - Arithmetic Commands (incr, decr): incr [noreply]\r\n decr [noreply]\r\n where, - uint64_t - Misc Commands (quit) quit\r\n flush_all [] [noreply]\r\n version\r\n verbosity [noreply]\r\n - Statistics Commands stats\r\n stats \r\n - Error Responses: ERROR\r\n CLIENT_ERROR [error]\r\n SERVER_ERROR [error]\r\n where, ERROR means client sent a non-existent command name CLIENT_ERROR means that command sent by the client does not conform to the protocol SERVER_ERROR means that there was an error on the server side that made processing of the command impossible - Storage Command Responses: STORED\r\n NOT_STORED\r\n EXISTS\r\n NOT_FOUND\r\n where, STORED indicates success. NOT_STORED indicates the data was not stored because condition for an add or replace wasn't met. EXISTS indicates that the item you are trying to store with a cas has been modified since you last fetched it. NOT_FOUND indicates that the item you are trying to store with a cas does not exist. - Delete Command Response: NOT_FOUND\r\n DELETED\r\n - Retrival Responses: END\r\n VALUE []\r\n\r\nEND\r\n VALUE []\r\n\r\n[VALUE []\r\n]+\r\nEND\r\n - Arithmetic Responses: NOT_FOUND\r\n \r\n where, - uint64_t : new key value after incr or decr operation - Statistics Response [STAT \r\n]+END\r\n - Misc Response OK\r\n VERSION \r\n - Notes: - set always creates mapping irrespective of whether it is present on not. - add, adds only if the mapping is not present - replace, only replaces if the mapping is present - append and prepend command ignore flags and expiry values - noreply instructs the server to not send the reply even if there is an error. - decr of 0 is 0, while incr of UINT64_MAX is 0 - maximum length of the key is 250 characters - expiry of 0 means that item never expires, though it could be evicted from the cache - non-zero expiry is either unix time (# seconds since 01/01/1970) or, offset in seconds from the current time (< 60 x 60 x 24 x 30 seconds = 30 days) - expiry time is with respect to the server (not client) - can be zero and when it is, the block is empty. - Thoughts: - ascii protocol is easier to debug - think using strace or tcpdump to see protocol on the wire, Or using telnet or netcat or socat to build memcache requests and responses http://stackoverflow.com/questions/2525188/are-binary-protocols-dead - http://news.ycombinator.com/item?id=1712788 nutcracker-0.4.0+dfsg/notes/recommendation.md000066400000000000000000000302571242132376000212600ustar00rootroot00000000000000If you are deploying nutcracker in your production environment, here are a few recommendations that might be worth considering. ## Log Level By default debug logging is disabled in nutcracker. However, it is worthwhile running nutcracker with debug logging enabled and verbosity level set to LOG_INFO (-v 6 or --verbosity=6). This in reality does not add much overhead as you only pay the cost of checking an if condition for every log line encountered during the run time. At LOG_INFO level, nutcracker logs the life cycle of every client and server connection and important events like the server being ejected from the hash ring and so on. Eg. [Thu Aug 2 00:03:09 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54009' [Thu Aug 2 00:03:09 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11211:1' [Thu Aug 2 00:03:09 2012] nc_core.c:270 req 1 on s 8 timedout [Thu Aug 2 00:03:09 2012] nc_core.c:207 close s 8 '127.0.0.1:11211' on event 0004 eof 0 done 0 rb 0 sb 20: Connection timed out [Thu Aug 2 00:03:09 2012] nc_server.c:406 close s 8 schedule error for req 1 len 20 type 5 from c 7: Connection timed out [Thu Aug 2 00:03:09 2012] nc_server.c:281 update pool 0 'alpha' to delete server '127.0.0.1:11211:1' for next 2 secs [Thu Aug 2 00:03:10 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 35 [Thu Aug 2 00:03:10 2012] nc_request.c:334 c 7 is done [Thu Aug 2 00:03:10 2012] nc_core.c:207 close c 7 '127.0.0.1:54009' on event 0001 eof 1 done 1 rb 20 sb 35 [Thu Aug 2 00:03:11 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54011' [Thu Aug 2 00:03:11 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11212:1' [Thu Aug 2 00:03:12 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 8 [Thu Aug 2 00:03:12 2012] nc_request.c:334 c 7 is done [Thu Aug 2 00:03:12 2012] nc_core.c:207 close c 7 '127.0.0.1:54011' on event 0001 eof 1 done 1 rb 20 sb 8 To enable debug logging, you have to compile nutcracker with logging enabled using --enable-debug=log configure option. ## Liveness Failures are a fact of life, especially when things are distributed. To be resilient against failures, it is recommended that you configure the following keys for every server pool. Eg: resilient_pool: auto_eject_hosts: true server_retry_timeout: 30000 server_failure_limit: 3 Enabling `auto_eject_hosts:` ensures that a dead server can be ejected out of the hash ring after `server_failure_limit:` consecutive failures have been encountered on that said server. A non-zero `server_retry_timeout:` ensures that we don't incorrectly mark a server as dead forever especially when the failures were really transient. The combination of `server_retry_timeout:` and `server_failure_limit:` controls the tradeoff between resiliency to permanent and transient failures. Note that an ejected server will not be included in the hash ring for any requests until the retry timeout passes. This will lead to data partitioning as keys originally on the ejected server will now be written to a server still in the pool. To ensure that requests always succeed in the face of server ejections (`auto_eject_hosts:` is enabled), some form of retry must be implemented at the client layer since nutcracker itself does not retry a request. This client-side retry count must be greater than `server_failure_limit:` value, which ensures that the original request has a chance to make it to a live server. ## Timeout It is always a good idea to configure nutcracker `timeout:` for every server pool, rather than purely relying on client-side timeouts. Eg: resilient_pool_with_timeout: auto_eject_hosts: true server_retry_timeout: 30000 server_failure_limit: 3 timeout: 400 Relying only on client-side timeouts has the adverse effect of the original request having timedout on the client to proxy connection, but still pending and outstanding on the proxy to server connection. This further gets exacerbated when client retries the original request. By default, nutcracker waits indefinitely for any request sent to the server. However, when `timeout:` key is configured, a requests for which no response is received from the server in `timeout:` msec is timedout and an error response `SERVER_ERROR Connection timed out\r\n` is sent back to the client. ## Error Response Whenever a request encounters failure on a server we usually send to the client a response with the general form - `SERVER_ERROR \r\n` (memcached) or `-ERR ` (redis). For example, when a memcache server is down, this error response is usually: + `SERVER_ERROR Connection refused\r\n` or, + `SERVER_ERROR Connection reset by peer\r\n` When the request timedout, the response is usually: + `SERVER_ERROR Connection timed out\r\n` Seeing a `SERVER_ERROR` or `-ERR` response should be considered as a transient failure by a client which makes the original request an ideal candidate for a retry. ## read, writev and mbuf All memory for incoming requests and outgoing responses is allocated in mbuf. Mbuf enables zero copy for requests and responses flowing through the proxy. By default an mbuf is 16K bytes in size and this value can be tuned between 512 and 16M bytes using -m or --mbuf-size=N argument. Every connection has at least one mbuf allocated to it. This means that the number of concurrent connections nutcracker can support is dependent on the mbuf size. A small mbuf allows us to handle more connections, while a large mbuf allows us to read and write more data to and from kernel socket buffers. If nutcracker is meant to handle a large number of concurrent client connections, you should set the mbuf size to 512 or 1K bytes. ## How to interpret mbuf-size=N argument? Every client connection consumes at least one mbuf. To service a request we need two connections (one from client to proxy and another from proxy to server). So we would need two mbufs. A fragmentable request like 'get foo bar\r\n', which btw gets fragmented to 'get foo\r\n' and 'get bar\r\n' would consume two mbuf for request and two mbuf for response. So a fragmentable request with N fragments needs N * 2 mbufs. The good thing about mbuf is that the memory comes from a reuse pool. Once a mbuf is allocated, it is never freed but just put back into the reuse pool. The bad thing is that once mbuf is allocated it is never freed, since a freed mbuf always goes back to the [reuse pool](https://github.com/twitter/twemproxy/blob/master/src/nc_mbuf.c#L23-L24). This can however be easily fixed if needed by putting a threshold parameter on the reuse pool. So, if nutcracker is handling say 1K client connections and 100 server connections, it would consume (max(1000, 100) * 2 * mbuf-size) memory for mbuf. If we assume that clients are sending non-pipelined request, then with default mbuf-size of 16K this would in total consume 32M. Furthermore, if on average every requests has 10 fragments, then the memory consumption would be 320M. Instead of handling 1K client connections, lets say you were handling 10K, then the memory consumption would be 3.2G. Now instead of using a default mbuf-size of 16K, you used 512 bytes, then memory consumption for the same scenario would drop to 1000 * 2 * 512 * 10 = 10M This is the reason why for 'large number' of connections or for wide multi-get like requests, you want to choose a small value for mbuf-size like 512 ## Maximum Key Length The memcache ascii protocol [specification](notes/memcache.txt) limits the maximum length of the key to 250 characters. The key should not include whitespace, or '\r' or '\n' character. For redis, we have no such limitation. However, nutcracker requires the key to be stored in a contiguous memory region. Since all requests and responses in nutcracker are stored in mbuf, the maximum length of the redis key is limited by the size of the maximum available space for data in mbuf (mbuf_data_size()). This means that if you want your redis instances to handle large keys, you might want to choose large mbuf size set using -m or --mbuf-size=N command-line argument. ## Node Names for Consistent Hashing The server cluster in twemproxy can either be specified as list strings in format 'host:port:weight' or 'host:port:weight name'. servers: - 127.0.0.1:6379:1 - 127.0.0.1:6380:1 - 127.0.0.1:6381:1 - 127.0.0.1:6382:1 Or, servers: - 127.0.0.1:6379:1 server1 - 127.0.0.1:6380:1 server2 - 127.0.0.1:6381:1 server3 - 127.0.0.1:6382:1 server4 In the former configuration, keys are mapped **directly** to **'host:port:weight'** triplet and in the latter they are mapped to **node names** which are then mapped to nodes i.e. host:port pair. The latter configuration gives us the freedom to relocate nodes to a different server without disturbing the hash ring and hence makes this configuration ideal when auto_eject_hosts is set to false. See [issue 25](https://github.com/twitter/twemproxy/issues/25) for details. Note that when using node names for consistent hashing, twemproxy ignores the weight value in the 'host:port:weight name' format string. ## Hash Tags [Hash Tags](http://antirez.com/post/redis-presharding.html) enables you to use part of the key for calculating the hash. When the hash tag is present, we use part of the key within the tag as the key to be used for consistent hashing. Otherwise, we use the full key as is. Hash tags enable you to map different keys to the same server as long as the part of the key within the tag is the same. For example, the configuration of server pool _beta_, aslo shown below, specifies a two character hash_tag string - "{}". This means that keys "user:{user1}:ids" and "user:{user1}:tweets" map to the same server because we compute the hash on "user1". For a key like "user:user1:ids", we use the entire string "user:user1:ids" to compute the hash and it may map to a different server. beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 ## Graphing Cache-pool State When running nutcracker in production, you often would like to know the list of live and ejected servers at any given time. You can easily answer this question, by generating a time series graph of live and/or dead servers that are part of any cache pool. To do this your graphing client must collect the following stats exposed by nutcracker: - **server_eof** which is incremented when server closes the connection normally which should not happen because we use persistent connections. - **server_timedout** is incremented when the connection / request to server timedout. - **server_err** is incremented for any other kinds of errors. So, on a given server, the cumulative number of times a server is ejected can be computed as: ```c (server_err + server_timedout + server_eof) / server_failure_limit ``` A diff of the above value between two successive time intervals would generate a nice timeseries graph for ejected servers. You can also graph the timestamp at which any given server was ejected by graphing `server_ejected_at` stat. ## server_connections: > 1 By design, twemproxy multiplexes several client connections over few server connections. It is important to note that **"read my last write"** constraint doesn't necessarily hold true when twemproxy is configured with `server_connections: > 1`. To illustrate this, consider a scenario where twemproxy is configured with `server_connections: 2`. If a client makes pipelined requests with the first request in pipeline being `set foo 0 0 3\r\nbar\r\n` (write) and the second request being `get foo\r\n` (read), the expectation is that the read of key `foo` would return the value `bar`. However, with configuration of two server connections it is possible that write and read request are sent on different server connections which would mean that their completion could race with one another. In summary, if the client expects "read my last write" constraint, you either configure twemproxy to use `server_connections:1` or use clients that only make synchronous requests to twemproxy. nutcracker-0.4.0+dfsg/notes/redis.md000066400000000000000000001620651242132376000173650ustar00rootroot00000000000000## Redis Command Support ### Keys Command +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DEL | Yes | DEL key [key …] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DUMP | Yes | DUMP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXISTS | Yes | EXISTS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXPIRE | Yes | EXPIRE key seconds | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXPIREAT | Yes | EXPIREAT key timestamp | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | KEYS | No | KEYS pattern | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MIGRATE | No | MIGRATE host port key destination-db timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MOVE | No | MOVE key db | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | OBJECT | No | OBJECT subcommand [arguments [arguments …]] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PERSIST | Yes | PERSIST key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PEXPIRE | Yes | PEXPIRE key milliseconds | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PEXPIREAT | Yes | PEXPIREAT key milliseconds-timestamp | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PTTL | Yes | PTTL key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RANDOMKEY | No | RANDOMKEY | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RENAME | No | RENAME key newkey | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RENAMENX | No | RENAMENX key newkey | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RESTORE | Yes | RESTORE key ttl serialized-value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SORT | Yes* | SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | TTL | Yes | TTL key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | TYPE | Yes | TYPE key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCAN | No | SCAN cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * SORT support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](notes/recommendation.md#hash-tags) for all keys in the command. ### Strings Command +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | APPEND | Yes | APPEND key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BITCOUNT | Yes | BITCOUNT key [start] [end] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BITOP | No | BITOP operation destkey key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DECR | Yes | DECR key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DECRBY | Yes | DECRBY key decrement | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GET | Yes | GET key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GETBIT | Yes | GETBIT key offset | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GETRANGE | Yes | GETRANGE key start end | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GETSET | Yes | GETSET key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INCR | Yes | INCR key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INCRBY | Yes | INCRBY key increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INCRBYFLOAT | Yes | INCRBYFLOAT key increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MGET | Yes | MGET key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MSET | Yes* | MSET key value [key value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MSETNX | No | MSETNX key value [key value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PSETEX | Yes | PSETEX key milliseconds value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SET | Yes | SET key value [EX seconds] [PX milliseconds] [NX|XX] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETBIT | Yes | SETBIT key offset value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETEX | Yes | SETEX key seconds value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETNX | Yes | SETNX key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETRANGE | Yes | SETRANGE key offset value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | STRLEN | Yes | STRLEN key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * MSET support is not Atomic ### Hashes +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HDEL | Yes | HDEL key field [field ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HEXISTS | Yes | HEXISTS key field | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HGET | Yes | HGET key field | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HGETALL | Yes | HGETALL key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HINCRBY | Yes | HINCRBY key field increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HINCRBYFLOAT | Yes | HINCRBYFLOAT key field increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HKEYS | Yes | HKEYS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HLEN | Yes | HLEN key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HMGET | Yes | HMGET key field [field ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HMSET | Yes | HMSET key field value [field value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HSET | Yes | HSET key field value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HSETNX | Yes | HSETNX key field value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HVALS | Yes | HVALS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HSCAN | Yes | HSCAN key cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Lists +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BLPOP | No | BLPOP key [key ...] timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BRPOP | No | BRPOP key [key ...] timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BRPOPLPUSH | No | BRPOPLPUSH source destination timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LINDEX | Yes | LINDEX key index | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LINSERT | Yes | LINSERT key BEFORE|AFTER pivot value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LLEN | Yes | LLEN key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LPOP | Yes | LPOP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LPUSH | Yes | LPUSH key value [value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LPUSHX | Yes | LPUSHX key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LRANGE | Yes | LRANGE key start stop | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LREM | Yes | LREM key count value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LSET | Yes | LSET key index value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LTRIM | Yes | LTRIM key start stop | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPOP | Yes | RPOP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPOPLPUSH | Yes* | RPOPLPUSH source destination | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPUSH | Yes | RPUSH key value [value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPUSHX | Yes | RPUSHX key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * RPOPLPUSH support requires that source and destination keys hash to the same server. You can ensure this by using the same [hashtag](notes/recommendation.md#hash-tags) for source and destination key. Twemproxy does no checking on its end to verify that source and destination key hash to the same server, and the RPOPLPUSH command is forwarded to the server that the source key hashes to ### Sets +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SADD | Yes | SADD key member [member ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCARD | Yes | SCARD key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SDIFF | Yes* | SDIFF key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SDIFFSTORE | Yes* | SDIFFSTORE destination key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SINTER | Yes* | SINTER key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SINTERSTORE | Yes* | SINTERSTORE destination key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SISMEMBER | Yes | SISMEMBER key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SMEMBERS | Yes | SMEMBERS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SMOVE | Yes* | SMOVE source destination member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SPOP | Yes | SPOP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SRANDMEMBER | Yes | SRANDMEMBER key [count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SREM | Yes | SREM key member [member ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SUNION | Yes* | SUNION key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SUNIONSTORE | Yes* | SUNIONSTORE destination key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SSCAN | Yes | SSCAN key cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * SIDFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION and SUNIONSTORE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](notes/recommendation.md#hash-tags) for all keys in the command. Twemproxy does no checking on its end to verify that all the keys hash to the same server, and the given command is forwarded to the server that the first key hashes to. ### Sorted Sets +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZADD | Yes | ZADD key score member [score] [member] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZCARD | Yes | ZCARD key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZCOUNT | Yes | ZCOUNT key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZINCRBY | Yes | ZINCRBY key increment member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZINTERSTORE | Yes* | ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] | +------------------------------------------------------------------------------------------------------------------------------------------------------+ | ZLEXCOUNT | Yes | ZLEXCOUNT key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZRANGE | Yes | ZRANGE key start stop [WITHSCORES] | +------------------------------------------------------------------------------------------------------------------------------------------------------+ | ZRANGEBYLEX | Yes | ZRANGEBYLEX key min max [LIMIT offset count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZRANGEBYSCORE | Yes | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZRANK | Yes | ZRANK key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREM | Yes | ZREM key member [member ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREMRANGEBYLEX | Yes | ZREMRANGEBYLEX key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREMRANGEBYRANK | Yes | ZREMRANGEBYRANK key start stop | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREMRANGEBYSCORE | Yes | ZREMRANGEBYSCORE key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREVRANGE | Yes | ZREVRANGE key start stop [WITHSCORES] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREVRANGEBYSCORE | Yes | ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREVRANK | Yes | ZREVRANK key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZSCORE | Yes | ZSCORE key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZUNIONSTORE | Yes* | ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZSCAN | Yes | ZSCAN key cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * ZINTERSTORE and ZUNIONSTORE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](notes/recommendation.md#hash-tags) for all keys in the command. Twemproxy does no checking on its end to verify that all the keys hash to the same server, and the given command is forwarded to the server that the first key hashes to. ### HyperLogLog +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PFADD | Yes | PFADD key element [element ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PFCOUNT | Yes | PFCOUNT key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PFMERGE | Yes* | PFMERGE destkey sourcekey [sourcekey ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * PFMERGE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](notes/recommendation.md#hash-tags) for all keys in the command. Twemproxy does no checking on its end to verify that all the keys hash to the same server, and the given command is forwarded to the server that the first key hashes to. ### Pub/Sub +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PSUBSCRIBE | No | PSUBSCRIBE pattern [pattern ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PUBLISH | No | PUBLISH channel message | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PUNSUBSCRIBE | No | PUNSUBSCRIBE [pattern [pattern ...]] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SUBSCRIBE | No | SUBSCRIBE channel [channel ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | UNSUBSCRIBE | No | UNSUBSCRIBE [channel [channel ...]] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Transactions +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DISCARD | No | DISCARD | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXEC | No | EXEC | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MULTI | No | MULTI | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | UNWATCH | No | UNWATCH | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | WATCH | No | WATCH key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Scripting +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EVAL | Yes* | EVAL script numkeys key [key ...] arg [arg ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EVALSHA | Yes* | EVALSHA sha1 numkeys key [key ...] arg [arg ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT EXISTS | No | SCRIPT EXISTS script [script ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT FLUSH | No | SCRIPT FLUSH | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT KILL | No | SCRIPT KILL | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT LOAD | No | SCRIPT LOAD script | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * EVAL and EVALSHA support is limited to scripts that take at least 1 key. If multiple keys are used, all keys must hash to the same server. You can ensure this by using the same [hashtag](notes/recommendation.md#hash-tags) for all keys. If you use more than 1 key, the proxy does no checking to verify that all keys hash to the same server, and the entire command is forwarded to the server that the first key hashes to ### Connection +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | AUTH | No | AUTH password | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ECHO | No | ECHO message | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PING | No | PING | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | QUIT | No | QUIT | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SELECT | No | SELECT index | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Server +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BGREWRITEAOF | No | BGREWRITEAOF | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BGSAVE | No | BGSAVE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CLIENT KILL | No | CLIENT KILL ip:port | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CLIENT LIST | No | CLIENT LIST | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CONFIG GET | No | CONFIG GET parameter | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CONFIG SET | No | CONFIG SET parameter value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CONFIG RESETSTAT | No | CONFIG RESETSTAT | +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------+ | DBSIZE | No | DBSIZE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DEBUG OBJECT | No | DEBUG OBJECT key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DEBUG SEGFAULT | No | DEBUG SEGFAULT | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | FLUSHALL | No | FLUSHALL | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | FLUSHDB | No | FLUSHDB | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INFO | No | INFO | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LASTSAVE | No | LASTSAVE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MONITOR | No | MONITOR | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SAVE | No | SAVE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SHUTDOWN | No | SHUTDOWN [NOSAVE] [SAVE] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SLAVEOF | No | SLAVEOF host port | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SLOWLOG | No | SLOWLOG subcommand [argument] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SYNC | No | SYNC | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | TIME | No | TIME | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ## Note - redis commands are not case sensitive - only vectored commands 'MGET key [key ...]' and 'DEL key [key ...]' needs to be fragmented ## Performance ### Setup + redis-server running on machine A. + nutcracker running on machine A as a local proxy to redis-server. + redis-benchmark running on machine B. + machine A != machine B. + nutcracker built with --enable-debug=no + nutcracker running with mbuf-size of 512 (-m 512) + redis-server built from redis 2.6 branch ### redis-benchmark against redis-server $ redis-benchmark -h -q -t set,get,incr,lpush,lpop,sadd,spop,lpush,lrange -c 100 -p 6379 SET: 89285.71 requests per second GET: 92592.59 requests per second INCR: 89285.71 requests per second LPUSH: 90090.09 requests per second LPOP: 90090.09 requests per second SADD: 90090.09 requests per second SPOP: 93457.95 requests per second LPUSH (needed to benchmark LRANGE): 89285.71 requests per second LRANGE_100 (first 100 elements): 36496.35 requests per second LRANGE_300 (first 300 elements): 15748.03 requests per second LRANGE_500 (first 450 elements): 11135.86 requests per second LRANGE_600 (first 600 elements): 8650.52 requests per second ### redis-benchmark against nutcracker proxing redis-server $ redis-benchmark -h -q -t set,get,incr,lpush,lpop,sadd,spop,lpush,lrange -c 100 -p 22121 SET: 85470.09 requests per second GET: 86956.52 requests per second INCR: 85470.09 requests per second LPUSH: 84745.77 requests per second LPOP: 86206.90 requests per second SADD: 84745.77 requests per second SPOP: 86956.52 requests per second LPUSH (needed to benchmark LRANGE): 84745.77 requests per second LRANGE_100 (first 100 elements): 29761.90 requests per second LRANGE_300 (first 300 elements): 12376.24 requests per second LRANGE_500 (first 450 elements): 8605.85 requests per second LRANGE_600 (first 600 elements): 6587.62 requests per second nutcracker-0.4.0+dfsg/notes/socket.txt000066400000000000000000000142131242132376000177550ustar00rootroot00000000000000- int listen(int sockfd, int backlog); Linux: The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds. backlog specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is 128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128. BSD: The backlog argument defines the maximum length the queue of pending connections may grow to. The real maximum queue length will be 1.5 times more than the value specified in the backlog argument. A subsequent listen() system call on the listening socket allows the caller to change the maximum queue length using a new backlog argument. If a connection request arrives with the queue full the client may receive an error with an indication of ECONNREFUSED, or, in the case of TCP, the connection will be silently dropped. The listen() system call appeared in 4.2BSD. The ability to configure the maximum backlog at run-time, and to use a negative backlog to request the maximum allowable value, was introduced in FreeBSD 2.2. - SO_LINGER (linger) socket option This option specifies what should happen when the socket of a type that promises reliable delivery still has untransmitted messages when it is closed struct linger { int l_onoff; /* nonzero to linger on close */ int l_linger; /* time to linger (in secs) */ }; l_onoff = 0 (default), then l_linger value is ignored and close returns immediately. But if there is any data still remaining in the socket send buffer, the system will try to deliver the data to the peer l_onoff = nonzero, then close blocks until data is transmitted or the l_linger timeout period expires a) l_linger = 0, TCP aborts connection, discards any data still remaining in the socket send buffer and sends RST to peer. This avoids the TCP's TIME_WAIT state b) l_linger = nonzero, then kernel will linger when socket is closed. If there is any pending data in the socket send buffer, the kernel waits until all the data is sent and acknowledged by peer TCP, or the linger time expires If a socket is set as nonblocking, it will not wait for close to complete even if linger time is nonzero - TIME_WAIT state The end that performs active close i.e. the end that sends the first FIN goes into TIME_WAIT state. After a FIN packet is sent to the peer and after that peers FIN/ACK arrvies and is ACKed, we go into a TIME_WAIT state. The duration that the end point remains in this state is 2 x MSL (maximum segment lifetime). The reason that the duration of the TIME_WAIT state is 2 x MSL is because the maximum amount of time a packet can wander around a network is assumed to be MSL seconds. The factor of 2 is for the round-trip. The recommended value for MSL is 120 seconds, but Berkeley derived implementations normally use 30 seconds instead. This means a TIME_WAIT delay is between 1 and 4 minutes. For Linux, the TIME_WAIT state duration is 1 minute (net/tcp.h): #define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT * state, about 60 seconds */ TIME_WAIT state on client, combined with limited number of ephermeral ports available for TCP connections severely limits the rate at which new connections to the server can be created. On Linux, by default ephemeral ports are in the range of 32768 to 61000: $ cat /proc/sys/net/ipv4/ip_local_port_range 32768 61000 So with a TIME_WAIT state duration of 1 minute, the maximum sustained rate for any client is ~470 new connections per second - TCP keepalive TCP keepalive packet (TCP packet with no data and the ACK flag turned on) is used to assert that connection is still up and running. This is useful because if the remote peer goes away without closing their connection, the keepalive probe will detect this and notice that the connection is broken even if there is no traffic on it. Imagine, the following scenario: You have a valid TCP connection established between two endpoints A and B. B terminates abnormally (think kernel panic or unplugging of network cable) without sending anything over the network to notify A that connection is broken. A, from its side, is ready to receive data, and has no idea that B has gone away. Now B comes back up again, and while A knows about a connection with B and still thinks that it active, B has no such idea. A tries to send data to B over a dead connection, and B replies with an RST packet, causing A to finally close the connection. So, without a keepalive probe A would never close the connection if it never sent data over it. - There are four socket functions that pass a socket address structure from the process to the kernel - bind, connect, sendmsg and sendto. These function are also responsible for passing the length of the sockaddr that they are passing (socklen_t). There are five socket functions that pass a socket from the kernel to the process - accept, recvfrom, recvmsg, getpeername, getsockname. The kernel is also responsible for returning the length of the sockaddr struct that it returns back to the userspace Different sockaddr structs: 1. sockaddr_in 2. sockaddr_in6 3. sockaddr_un Special types of in_addr_t /* Address to accept any incoming messages */ #define INADDR_ANY ((in_addr_t) 0x00000000) /* Address to send to all hosts */ #define INADDR_BROADCAST ((in_addr_t) 0xffffffff) /* Address indicating an error return */ #define INADDR_NONE ((in_addr_t) 0xffffffff) nutcracker-0.4.0+dfsg/scripts/000077500000000000000000000000001242132376000162625ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/scripts/benchmark-mget.py000066400000000000000000000021661242132376000215250ustar00rootroot00000000000000#!/usr/bin/env python #coding: utf-8 #file : test_mget.py #author : ning #date : 2014-04-01 13:15:48 import os import re import commands ports = [ 4001, # before improve 4000, # after improve 2000 # redis ] def system(cmd): return commands.getoutput(cmd) def extra(regex, text): match = re.search(regex, text, re.DOTALL) if match: return match.group(1) def testit(): for mget_size in [10, 100, 1000, 10000]: for port in ports: cnt = 100*1000 / mget_size clients = 50 if mget_size == 10000: clients = 2 cmd = 'cd /home/ning/xredis/deploy-srcs/redis-2.8.3/src && ./redis-benchmark.%d -n %d -p %d -t mget -r 1000000000 -c %d' % (mget_size, cnt, port, clients) #print cmd rst = system(cmd) #100.00% <= 2 milliseconds #28089.89 requests per second rtime = extra('100.00% <= (\d+) milliseconds', rst) qps = extra('([\.\d]+) requests per second', rst) print 'mget_size=%d on %d: pqs: %s, rtime: %s' % (mget_size, port, qps, rtime) testit() nutcracker-0.4.0+dfsg/scripts/multi_get.sh000077500000000000000000000010161242132376000206100ustar00rootroot00000000000000#!/bin/sh port=22123 socatopt="-t 20 -T 20 -b 8193 -d -d " key="" keys="" get_command="" # build for i in `seq 1 512`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` keys=`printf "%s %s" "${keys}" "${key}"` done get_command="get ${keys}\r\n" printf "%b" "$get_command" # read for i in `seq 1 16`; do printf "%b" "${get_command}" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 & done nutcracker-0.4.0+dfsg/scripts/nutcracker.init000066400000000000000000000022041242132376000213060ustar00rootroot00000000000000#! /bin/sh # # chkconfig: - 55 45 # description: Twitter's twemproxy nutcracker # processname: nutcracker # config: /etc/sysconfig/nutcracker # Source function library. . /etc/rc.d/init.d/functions USER="nobody" OPTIONS="-d -c /etc/nutcracker/nutcracker.yml" if [ -f /etc/sysconfig/nutcracker ];then . /etc/sysconfig/nutcracker fi # Check that networking is up. if [ "$NETWORKING" = "no" ] then exit 0 fi RETVAL=0 prog="nutcracker" start () { echo -n $"Starting $prog: " daemon --user ${USER} ${prog} $OPTIONS RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/${prog} } stop () { echo -n $"Stopping $prog: " killproc ${prog} RETVAL=$? echo if [ $RETVAL -eq 0 ] ; then rm -f /var/lock/subsys/${prog} fi } restart () { stop start } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status ${prog} ;; restart|reload) restart ;; condrestart) [ -f /var/lock/subsys/nutcracker ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}" exit 1 esac exit $? nutcracker-0.4.0+dfsg/scripts/nutcracker.spec000066400000000000000000000066771242132376000213170ustar00rootroot00000000000000Summary: Twitter's nutcracker redis and memcached proxy Name: nutcracker Version: 0.3.0 Release: 1 URL: https://github.com/twitter/twemproxy/ Source0: %{name}-%{version}.tar.gz License: Apache License 2.0 Group: System Environment/Libraries Packager: Tom Parrott BuildRoot: %{_tmppath}/%{name}-root BuildRequires: autoconf BuildRequires: automake BuildRequires: libtool %description twemproxy (pronounced "two-em-proxy"), aka nutcracker is a fast and lightweight proxy for memcached and redis protocol. It was primarily built to reduce the connection count on the backend caching servers. %prep %setup -q %if 0%{?rhel} == 6 sed -i 's/2.64/2.63/g' configure.ac %endif autoreconf -fvi %build %configure %__make %install [ %{buildroot} != "/" ] && rm -rf %{buildroot} %makeinstall PREFIX=%{buildroot} #Install init script %{__install} -p -D -m 0755 scripts/%{name}.init %{buildroot}%{_initrddir}/%{name} #Install example config file %{__install} -p -D -m 0644 conf/%{name}.yml %{buildroot}%{_sysconfdir}/%{name}/%{name}.yml %post /sbin/chkconfig --add %{name} %preun if [ $1 = 0 ]; then /sbin/service %{name} stop > /dev/null 2>&1 /sbin/chkconfig --del %{name} fi %clean [ %{buildroot} != "/" ] && rm -rf %{buildroot} %files %defattr(-,root,root,-) %if 0%{?rhel} == 6 /usr/sbin/nutcracker %else /usr/bin/nutcracker %endif %{_initrddir}/%{name} %{_mandir}/man8/nutcracker.8.gz %config(noreplace)%{_sysconfdir}/%{name}/%{name}.yml %changelog * Fri Dec 20 2013 Manju Rajashekhar - twemproxy: version 0.3.0 release - SRANDMEMBER support for the optional count argument (mkhq) - Handle case where server responds while the request is still being sent (jdi-tagged) - event ports (solaris/smartos) support - add timestamp when the server was ejected - support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat) - kqueue (bsd) support (ferenyx) - fix parsing redis response to accept integer reply (charsyam) * Tue Jul 30 2013 Tait Clarridge - Rebuild SPEC to work with CentOS - Added buildrequires if building with mock/koji * Tue Apr 23 2013 Manju Rajashekhar - twemproxy: version 0.2.4 release - redis keys must be less than mbuf_data_size() in length (fifsky) - Adds support for DUMP/RESTORE commands in Redis (remotezygote) - Use of the weight value in the modula distribution (mezzatto) - Add support to unix socket connections to servers (mezzatto) - only check for duplicate server name and not 'host:port:weight' when 'name' is configured - crc16 hash support added (mezzatto) * Thu Jan 31 2013 Manju Rajashekhar - twemproxy: version 0.2.3 release - RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon) - EVAL and EVALSHA support (ferenyx) - exit 1 if configuration file is invalid (cofyc) - return non-zero exit status when nutcracker cannot start for some reason - use server names in stats (charsyam) - Fix failure to resolve long FQDN name resolve (conmame) - add support for hash tags * Thu Oct 18 2012 Manju Rajashekhar - twemproxy: version 0.2.2 release - fix the off-by-one error when calculating redis key length * Fri Oct 12 2012 Manju Rajashekhar - twemproxy: version 0.2.1 release - don't use buf in conf_add_server - allow an optional instance name for consistent hashing (charsyam) - add --stats-addr=S option - add stats-bind-any -a option (charsyam) nutcracker-0.4.0+dfsg/scripts/pipelined_read.sh000077500000000000000000000007601242132376000215700ustar00rootroot00000000000000#!/bin/sh socatopt="-t 4 -T 4 -b 8193 -d -d " get_commands="" # build for i in `seq 1 128`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` get_command="get ${key}\r\n" get_commands=`printf "%s%s" "${get_commands}" "${get_command}"` done # read for i in `seq 1 64`; do printf "%b" "$get_commands" | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 & done nutcracker-0.4.0+dfsg/scripts/pipelined_write.sh000077500000000000000000000011771242132376000220120ustar00rootroot00000000000000#!/bin/sh socatopt="-t 1 -T 1 -b 16384" val=`echo 6^6^6 | bc` val=`printf "%s" "${val}"` vallen=`printf "%s" "${val}" | wc -c` set_command="" set_commands="" # build for i in `seq 1 64`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` set_command="set ${key} 0 0 ${vallen}\r\n${val}\r\n" set_commands=`printf "%s%s" "${set_commands}" "${set_command}"` done printf "%b" "$set_commands" > /tmp/socat.input # write for i in `seq 1 16`; do cat /tmp/socat.input | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-down,nonblock=1 & done nutcracker-0.4.0+dfsg/scripts/populate_memcached.sh000077500000000000000000000007571242132376000224510ustar00rootroot00000000000000#!/bin/sh port=22123 socatopt="-t 1 -T 1 -b 65537" val=`echo 6^6^6 | bc` val=`printf "%s\r\n" "${val}"` vallen=`printf "%s" "${val}" | wc -c` set_command="" # build for i in `seq 1 512`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` set_command="set ${key} 0 0 ${vallen}\r\n${val}\r\n" printf "%b" "$set_command" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-down,nonblock=1 & done nutcracker-0.4.0+dfsg/scripts/redis-check.py000066400000000000000000000012311242132376000210120ustar00rootroot00000000000000import redis range=100 factor=32 port=22121 r = redis.StrictRedis(host='localhost', port=port, db=0) # lrange print [r.lrange('lfoo', 0, x) for x in xrange(1, range)] print [r.lpush('lfoo', str(x)*factor) for x in xrange(1, range)] print [r.lrange('lfoo', 0, x) for x in xrange(1, range)] print r.delete('lfoo') # del print [r.set('foo' + str(x), str(x)*factor) for x in xrange(1, range)] keys = ['foo' + str(x) for x in xrange(1, range)] print [r.delete(keys) for x in xrange(1, range)] # mget print [r.set('foo' + str(x), str(x)*100) for x in xrange(1, range)] keys = ['foo' + str(x) for x in xrange(1, range)] print [r.mget(keys) for x in xrange(1, range)] nutcracker-0.4.0+dfsg/scripts/redis-check.sh000077500000000000000000001470731242132376000210160ustar00rootroot00000000000000#!/bin/sh port=6379 port=22121 debug="-v -d" debug="-d" timeout="-t 1" timeout="" # keys printf '\ndel\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\ndel\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nbar\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$3\r\ndel\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$6\r\nfoobar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ndump\n' printf '*2\r\n$4\r\ndump\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\ndump\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexists\n' printf '*2\r\n$6\r\nexists\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$6\r\nexists\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexpire\n' printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npersist\n' printf '*2\r\n$7\r\npersist\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$2\r\n10\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npersist\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npersist\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexpireat\n' printf '*3\r\n$8\r\nexpireat\r\n$3\r\nfoo\r\n$10\r\n1282463464\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$8\r\nexpireat\r\n$3\r\nfoo\r\n$10\r\n1282463464\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexpire\n' printf '*3\r\n$7\r\npexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\npexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrestore\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nrestore\r\n$3\r\nfoo\r\n$1\r\n0\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npttl\n' printf '*2\r\n$4\r\npttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\npexpire\r\n$3\r\nfoo\r\n$7\r\n1000000\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\npttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nttl\n' printf '*2\r\n$4\r\npttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$2\r\n10\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ntype\n' printf '*2\r\n$4\r\ntype\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\ntype\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # strings printf '\nappend\n' printf '*3\r\n$6\r\nappend\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nappend\r\n$3\r\n999\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nbitcount\n' printf '*2\r\n$8\r\nbitcount\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\nbitcount\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ndecr\n' printf '*2\r\n$4\r\ndecr\r\n$7\r\ncounter\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ndecrby\n' printf '*3\r\n$6\r\ndecrby\r\n$7\r\ncounter\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nget\n' printf '*2\r\n$3\r\nget\r\n$16\r\nnon-existent-key\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ngetbit\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\ngetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\ngetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ngetrange\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\ngetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\ngetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\ngetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ngetset\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\ngetset\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nincr\n' printf '*2\r\n$3\r\ndel\r\n$7\r\ncounter\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nincr\r\n$7\r\ncounter\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nincrby\n' printf '*3\r\n$6\r\nincrby\r\n$7\r\ncounter\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nincrbyfloat\n' printf '*3\r\n$11\r\nincrbyfloat\r\n$7\r\ncounter\r\n$5\r\n10.10\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nmget\n' printf '*2\r\n$4\r\nmget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nmget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nmget\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*13\r\n$4\r\nmget\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npsetex\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\npsetex\r\n$3\r\nfoo\r\n$4\r\n1000\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nset\n' printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsetbit\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nsetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\n000\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nsetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npsetex\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nsetex\r\n$3\r\nfoo\r\n$4\r\n1000\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsetnx\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nsetnx\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nsetnx\r\n$3\r\nfoo\r\n$3\r\nooo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsetrange\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\nsetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\nsetrange\r\n$3\r\nfoo\r\n$1\r\n4\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # hashes printf '\nhdel\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhexists\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\nhexists\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\nhexists\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhget\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nhget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nhget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhgetall\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhincrby\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nhincrby\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhincrbyfloat\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$12\r\nhincrbyfloat\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$6\r\n100.12\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhkeys\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhkeys\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhkeys\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhkeys\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhlen\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nhlen\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nhlen\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhmget\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nhmget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nhmget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$6\r\n1dleif\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nhmget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$6\r\n1dleif\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhmset\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nhmset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n$6\r\nfield2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhset\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhsetnx\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nhsetnx\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nhsetnx\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhvals\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhvals\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nhmset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n$6\r\nfield2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhvals\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # lists printf '\nlindex\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlindex\r\n$4\r\nlfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlindex\r\n$4\r\nlfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlinsert\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$7\r\nlinsert\r\n$4\r\nlfoo\r\n$6\r\nBEFORE\r\n$3\r\nbar\r\n$3\r\nbaq\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlindex\r\n$4\r\nlfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nllen\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nllen\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nllen\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlpop\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlpush\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlpushx\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlpushx\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlrange\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n$3\r\nbat\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlrem\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlrem\r\n$4\r\nlfoo\r\n$1\r\n2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlrem\r\n$4\r\nlfoo\r\n$1\r\n2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlrem\r\n$4\r\nlfoo\r\n$1\r\n2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlset\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlset\r\n$4\r\nlfoo\r\n$1\r\n1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlset\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$3\r\nbaq\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlset\r\n$4\r\nlfoo\r\n$1\r\n1\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nltrim\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nltrim\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpop\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpoplpush\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{lfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{lfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$5\r\nlpush\r\n$6\r\n{lfoo}\r\n$3\r\nbar\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nrpoplpush\r\n$6\r\n{lfoo}\r\n$7\r\n{lfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nrpoplpush\r\n$6\r\n{lfoo}\r\n$7\r\n{lfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$6\r\n{lfoo}\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$7\r\n{lfoo}2\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpush\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbat\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpushx\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nrpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nrpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nrpushx\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # sets printf '\nsadd\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nscard\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nscard\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nscard\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsdiff\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nsdiff\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsdiffstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$10\r\nsdiffstore\r\n$7\r\n{sfoo}3\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsinter\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nsinter\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsinterstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nsinterstore\r\n$7\r\n{sfoo}3\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsismember\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nsismember\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nsismember\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nsismember\r\n$4\r\nsfoo\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsmembers\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsmove\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nsmove\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nspop\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nspop\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nspop\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsrandmember\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsrem\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsrem\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n$3\r\nbat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsrem\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$4\r\nsrem\r\n$4\r\nsfoo\r\n$3\r\nbas\r\n$3\r\nbat\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsunion\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nsunion\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsunionstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nsunionstore\r\n$7\r\n{sfoo}3\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # sorted sets printf '\nzadd\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzcard\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nzcard\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nzcard\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzcount\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzcount\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzcount\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzcount\r\n$4\r\nzfoo\r\n$4\r\n-inf\r\n$4\r\n+inf\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzincrby\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nzincrby\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nzincrby\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzinterstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{zfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$6\r\n{zfoo}\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$4\r\nzadd\r\n$7\r\n{zfoo}2\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$11\r\nzinterstore\r\n$7\r\n{zfoo}3\r\n$1\r\n2\r\n$6\r\n{zfoo}\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$6\r\nzrange\r\n$7\r\n{zfoo}3\r\n$1\r\n0\r\n$1\r\n3\r\n$10\r\nWITHSCORES\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrange\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$6\r\nzrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n3\r\n$10\r\nWITHSCORES\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrangebyscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$13\r\nzrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$13\r\nzrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrank\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nzrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nzrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrem\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nzrem\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzrem\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzremrangebyrank\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$15\r\nzremrangebyrank\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$15\r\nzremrangebyrank\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzremrangebyscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzremrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzremrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrevrange\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzrevrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzrevrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrevrangebyscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzrevrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n101\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzrevrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n101\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrevrank\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$8\r\nzrevrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$8\r\nzrevrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nzscore\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nzscore\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzunionstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{zfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$6\r\n{zfoo}\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$4\r\nzadd\r\n$7\r\n{zfoo}2\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$11\r\nzunionstore\r\n$7\r\n{zfoo}3\r\n$1\r\n2\r\n$6\r\n{zfoo}\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$6\r\nzrange\r\n$7\r\n{zfoo}3\r\n$1\r\n0\r\n$1\r\n3\r\n$10\r\nWITHSCORES\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzlexcount\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzlexcount\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzlexcount\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$4\r\n[bat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzlexcount\r\n$4\r\nzfoo\r\n$1\r\n-\r\n$1\r\n+\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrangebylex\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nzrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nzrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$4\r\n[bat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nzrangebylex\r\n$4\r\nzfoo\r\n$1\r\n-\r\n$1\r\n+\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzremrangebylex\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$14\r\nzremrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$14\r\nzremrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$14\r\nzremrangebylex\r\n$4\r\nzfoo\r\n$1\r\n-\r\n$1\r\n+\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # hyperloglog printf '\npfadd\n' printf '*2\r\n$3\r\ndel\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\npfadd\r\n$4\r\npfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npfcount\n' printf '*2\r\n$3\r\ndel\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\npfadd\r\n$4\r\npfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npfmerge\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{pfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{pfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{pfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\npfadd\r\n$6\r\n{pfoo}\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\npfadd\r\n$7\r\n{pfoo}2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$7\r\npfmerge\r\n$7\r\n{pfoo}3\r\n$1\r\n2\r\n$6\r\n{pfoo}\r\n$7\r\n{pfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$7\r\n{pfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # scripting printf '\neval\n' printf '*2\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*7\r\n$4\r\neval\r\n$40\r\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\r\n$1\r\n2\r\n$9\r\nkey1{tag}\r\n$4\r\narg1\r\n$9\r\nkey2{tag}\r\n$4\r\narg2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*9\r\n$4\r\neval\r\n$56\r\nreturn {KEYS[1],KEYS[2],KEYS[3],ARGV[1],ARGV[2],ARGV[3]}\r\n$1\r\n3\r\n$9\r\nkey1{tag}\r\n$4\r\narg1\r\n$9\r\nkey2{tag}\r\n$4\r\narg2\r\n$9\r\nkey3{tag}\r\n$4\r\narg3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\neval\r\n$11\r\nreturn {10}\r\n$1\r\n1\r\n$4\r\nTEMP\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close nutcracker-0.4.0+dfsg/src/000077500000000000000000000000001242132376000153625ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/src/Makefile.am000066400000000000000000000027671242132376000174320ustar00rootroot00000000000000MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = if !OS_SOLARIS AM_CPPFLAGS += -D_GNU_SOURCE endif AM_CPPFLAGS += -I $(top_srcdir)/src/hashkit AM_CPPFLAGS += -I $(top_srcdir)/src/proto AM_CPPFLAGS += -I $(top_srcdir)/src/event AM_CPPFLAGS += -I $(top_srcdir)/contrib/yaml-0.1.4/include AM_CFLAGS = AM_CFLAGS += -Wall -Wshadow AM_CFLAGS += -Wpointer-arith AM_CFLAGS += -Winline AM_CFLAGS += -Wunused-function -Wunused-variable -Wunused-value AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value AM_CFLAGS += -Wconversion -Wsign-compare AM_CFLAGS += -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Wmissing-declarations AM_LDFLAGS = AM_LDFLAGS += -lm -lpthread -rdynamic if OS_SOLARIS AM_LDFLAGS += -lnsl -lsocket endif SUBDIRS = hashkit proto event sbin_PROGRAMS = nutcracker nutcracker_SOURCES = \ nc_core.c nc_core.h \ nc_connection.c nc_connection.h \ nc_client.c nc_client.h \ nc_server.c nc_server.h \ nc_proxy.c nc_proxy.h \ nc_message.c nc_message.h \ nc_request.c \ nc_response.c \ nc_mbuf.c nc_mbuf.h \ nc_conf.c nc_conf.h \ nc_stats.c nc_stats.h \ nc_signal.c nc_signal.h \ nc_rbtree.c nc_rbtree.h \ nc_log.c nc_log.h \ nc_string.c nc_string.h \ nc_array.c nc_array.h \ nc_util.c nc_util.h \ nc_queue.h \ nc.c nutcracker_LDADD = $(top_builddir)/src/hashkit/libhashkit.a nutcracker_LDADD += $(top_builddir)/src/proto/libproto.a nutcracker_LDADD += $(top_builddir)/src/event/libevent.a nutcracker_LDADD += $(top_builddir)/contrib/yaml-0.1.4/src/.libs/libyaml.a nutcracker-0.4.0+dfsg/src/event/000077500000000000000000000000001242132376000165035ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/src/event/Makefile.am000066400000000000000000000004271242132376000205420ustar00rootroot00000000000000MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I $(top_srcdir)/src AM_CFLAGS = -Wall -Wshadow AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value noinst_LIBRARIES = libevent.a noinst_HEADERS = nc_event.h libevent_a_SOURCES = \ nc_epoll.c \ nc_kqueue.c \ nc_evport.c nutcracker-0.4.0+dfsg/src/event/nc_epoll.c000066400000000000000000000166541242132376000204560ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #ifdef NC_HAVE_EPOLL #include struct event_base * event_base_create(int nevent, event_cb_t cb) { struct event_base *evb; int status, ep; struct epoll_event *event; ASSERT(nevent > 0); ep = epoll_create(nevent); if (ep < 0) { log_error("epoll create of size %d failed: %s", nevent, strerror(errno)); return NULL; } event = nc_calloc(nevent, sizeof(*event)); if (event == NULL) { status = close(ep); if (status < 0) { log_error("close e %d failed, ignored: %s", ep, strerror(errno)); } return NULL; } evb = nc_alloc(sizeof(*evb)); if (evb == NULL) { nc_free(event); status = close(ep); if (status < 0) { log_error("close e %d failed, ignored: %s", ep, strerror(errno)); } return NULL; } evb->ep = ep; evb->event = event; evb->nevent = nevent; evb->cb = cb; log_debug(LOG_INFO, "e %d with nevent %d", evb->ep, evb->nevent); return evb; } void event_base_destroy(struct event_base *evb) { int status; if (evb == NULL) { return; } ASSERT(evb->ep > 0); nc_free(evb->event); status = close(evb->ep); if (status < 0) { log_error("close e %d failed, ignored: %s", evb->ep, strerror(errno)); } evb->ep = -1; nc_free(evb); } int event_add_in(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); if (c->recv_active) { return 0; } event.events = (uint32_t)(EPOLLIN | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->recv_active = 1; } return status; } int event_del_in(struct event_base *evb, struct conn *c) { return 0; } int event_add_out(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (c->send_active) { return 0; } event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->send_active = 1; } return status; } int event_del_out(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (!c->send_active) { return 0; } event.events = (uint32_t)(EPOLLIN | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->send_active = 0; } return status; } int event_add_conn(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_ADD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->send_active = 1; c->recv_active = 1; } return status; } int event_del_conn(struct event_base *evb, struct conn *c) { int status; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); status = epoll_ctl(ep, EPOLL_CTL_DEL, c->sd, NULL); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->recv_active = 0; c->send_active = 0; } return status; } int event_wait(struct event_base *evb, int timeout) { int ep = evb->ep; struct epoll_event *event = evb->event; int nevent = evb->nevent; ASSERT(ep > 0); ASSERT(event != NULL); ASSERT(nevent > 0); for (;;) { int i, nsd; nsd = epoll_wait(ep, event, nevent, timeout); if (nsd > 0) { for (i = 0; i < nsd; i++) { struct epoll_event *ev = &evb->event[i]; uint32_t events = 0; log_debug(LOG_VVERB, "epoll %04"PRIX32" triggered on conn %p", ev->events, ev->data.ptr); if (ev->events & EPOLLERR) { events |= EVENT_ERR; } if (ev->events & (EPOLLIN | EPOLLHUP)) { events |= EVENT_READ; } if (ev->events & EPOLLOUT) { events |= EVENT_WRITE; } if (evb->cb != NULL) { evb->cb(ev->data.ptr, events); } } return nsd; } if (nsd == 0) { if (timeout == -1) { log_error("epoll wait on e %d with %d events and %d timeout " "returned no events", ep, nevent, timeout); return -1; } return 0; } if (errno == EINTR) { continue; } log_error("epoll wait on e %d with %d events failed: %s", ep, nevent, strerror(errno)); return -1; } NOT_REACHED(); } void event_loop_stats(event_stats_cb_t cb, void *arg) { struct stats *st = arg; int status, ep; struct epoll_event ev; ep = epoll_create(1); if (ep < 0) { log_error("epoll create failed: %s", strerror(errno)); return; } ev.data.fd = st->sd; ev.events = EPOLLIN; status = epoll_ctl(ep, EPOLL_CTL_ADD, st->sd, &ev); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, st->sd, strerror(errno)); goto error; } for (;;) { int n; n = epoll_wait(ep, &ev, 1, st->interval); if (n < 0) { if (errno == EINTR) { continue; } log_error("epoll wait on e %d with m %d failed: %s", ep, st->sd, strerror(errno)); break; } cb(st, &n); } error: status = close(ep); if (status < 0) { log_error("close e %d failed, ignored: %s", ep, strerror(errno)); } ep = -1; } #endif /* NC_HAVE_EPOLL */ nutcracker-0.4.0+dfsg/src/event/nc_event.h000066400000000000000000000052271242132376000204630ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_EVENT_H_ #define _NC_EVENT_H_ #include #define EVENT_SIZE 1024 #define EVENT_READ 0x0000ff #define EVENT_WRITE 0x00ff00 #define EVENT_ERR 0xff0000 typedef int (*event_cb_t)(void *, uint32_t); typedef void (*event_stats_cb_t)(void *, void *); #ifdef NC_HAVE_KQUEUE struct event_base { int kq; /* kernel event queue descriptor */ struct kevent *change; /* change[] - events we want to monitor */ int nchange; /* # change */ struct kevent *event; /* event[] - events that were triggered */ int nevent; /* # event */ int nreturned; /* # event placed in event[] */ int nprocessed; /* # event processed from event[] */ event_cb_t cb; /* event callback */ }; #elif NC_HAVE_EPOLL struct event_base { int ep; /* epoll descriptor */ struct epoll_event *event; /* event[] - events that were triggered */ int nevent; /* # event */ event_cb_t cb; /* event callback */ }; #elif NC_HAVE_EVENT_PORTS #include struct event_base { int evp; /* event port descriptor */ port_event_t *event; /* event[] - events that were triggered */ int nevent; /* # event */ event_cb_t cb; /* event callback */ }; #else # error missing scalable I/O event notification mechanism #endif struct event_base *event_base_create(int size, event_cb_t cb); void event_base_destroy(struct event_base *evb); int event_add_in(struct event_base *evb, struct conn *c); int event_del_in(struct event_base *evb, struct conn *c); int event_add_out(struct event_base *evb, struct conn *c); int event_del_out(struct event_base *evb, struct conn *c); int event_add_conn(struct event_base *evb, struct conn *c); int event_del_conn(struct event_base *evb, struct conn *c); int event_wait(struct event_base *evb, int timeout); void event_loop_stats(event_stats_cb_t cb, void *arg); #endif /* _NC_EVENT_H */ nutcracker-0.4.0+dfsg/src/event/nc_evport.c000066400000000000000000000253121242132376000206510ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #ifdef NC_HAVE_EVENT_PORTS #include #include struct event_base * event_base_create(int nevent, event_cb_t cb) { struct event_base *evb; int status, evp; port_event_t *event; ASSERT(nevent > 0); evp = port_create(); if (evp < 0) { log_error("port create failed: %s", strerror(errno)); return NULL; } event = nc_calloc(nevent, sizeof(*event)); if (event == NULL) { status = close(evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evp, strerror(errno)); } return NULL; } evb = nc_alloc(sizeof(*evb)); if (evb == NULL) { nc_free(event); status = close(evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evp, strerror(errno)); } return NULL; } evb->evp = evp; evb->event = event; evb->nevent = nevent; evb->cb = cb; log_debug(LOG_INFO, "evp %d with nevent %d", evb->evp, evb->nevent); return evb; } void event_base_destroy(struct event_base *evb) { int status; if (evb == NULL) { return; } ASSERT(evb->evp >= 0); nc_free(evb->event); status = close(evb->evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evb->evp, strerror(errno)); } evb->evp = -1; nc_free(evb); } int event_add_in(struct event_base *evb, struct conn *c) { return 0; } int event_del_in(struct event_base *evb, struct conn *c) { return 0; } int event_add_out(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (c->send_active) { return 0; } status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN | POLLOUT, c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } else { c->send_active = 1; } return status; } int event_del_out(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (!c->send_active) { return 0; } status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN, c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } else { c->send_active = 0; } return status; } int event_add_conn(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(!c->recv_active); ASSERT(!c->send_active); status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN | POLLOUT, c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } else { c->send_active = 1; c->recv_active = 1; } return status; } int event_del_conn(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); if (!c->send_active && !c->recv_active) { return 0; } /* * Removes the association of an object with a port. The association * is also removed if the port gets closed. * * On failure, we check for ENOENT errno because it is likely that we * are deleting this connection after it was returned from the event * loop and before we had a chance of reactivating it by calling * port_associate() on it. */ status = port_dissociate(evp, PORT_SOURCE_FD, c->sd); if (status < 0 && errno != ENOENT) { log_error("port dissociate evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); return status; } c->recv_active = 0; c->send_active = 0; return 0; } static int event_reassociate(struct event_base *evb, struct conn *c) { int status, events; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (c->send_active) { events = POLLIN | POLLOUT; } else { events = POLLIN; } status = port_associate(evp, PORT_SOURCE_FD, c->sd, events , c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } return status; } int event_wait(struct event_base *evb, int timeout) { int evp = evb->evp; port_event_t *event = evb->event; int nevent = evb->nevent; struct timespec ts, *tsp; ASSERT(evp > 0); ASSERT(event != NULL); ASSERT(nevent > 0); /* port_getn should block indefinitely if timeout < 0 */ if (timeout < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = timeout / 1000LL; tsp->tv_nsec = (timeout % 1000LL) * 1000000LL; } for (;;) { int i, status; unsigned int nreturned = 1; /* * port_getn() retrieves multiple events from a port. A port_getn() * call will block until at least nreturned events is triggered. On * a successful return event[] is populated with triggered events * up to the maximum sized allowed by nevent. The number of entries * actually placed in event[] is saved in nreturned, which may be * more than what we asked for but less than nevent. */ status = port_getn(evp, event, nevent, &nreturned, tsp); if (status < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } /* * ETIME - The time interval expired before the expected number * of events have been posted to the port or nreturned is updated * with the number of returned port_event_t structures in event[] */ if (errno != ETIME) { log_error("port getn on evp %d with %d events failed: %s", evp, nevent, strerror(errno)); return -1; } } if (nreturned > 0) { for (i = 0; i < nreturned; i++) { port_event_t *ev = &evb->event[i]; uint32_t events = 0; log_debug(LOG_VVERB, "port %04"PRIX32" from source %d " "triggered on conn %p", ev->portev_events, ev->portev_source, ev->portev_user); if (ev->portev_events & POLLERR) { events |= EVENT_ERR; } if (ev->portev_events & POLLIN) { events |= EVENT_READ; } if (ev->portev_events & POLLOUT) { events |= EVENT_WRITE; } if (evb->cb != NULL && events != 0) { status = evb->cb(ev->portev_user, events); if (status < 0) { continue; } /* * When an event for a PORT_SOURCE_FD object is retrieved, * the object no longer has an association with the port. * The event can be processed without the possibility that * another thread can retrieve a subsequent event for the * same object. After processing of the file descriptor * is completed, the port_associate() function can be * called to reassociate the object with the port. * * If the descriptor is still capable of accepting data, * this reassociation is required for the reactivation of * the data detection. */ event_reassociate(evb, ev->portev_user); } } return nreturned; } if (timeout == -1) { log_error("port getn on evp %d with %d events and %d timeout " "returned no events", evp, nevent, timeout); return -1; } return 0; } NOT_REACHED(); } void event_loop_stats(event_stats_cb_t cb, void *arg) { struct stats *st = arg; int status, evp; port_event_t event; struct timespec ts, *tsp; evp = port_create(); if (evp < 0) { log_error("port create failed: %s", strerror(errno)); return; } status = port_associate(evp, PORT_SOURCE_FD, st->sd, POLLIN, NULL); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, st->sd, strerror(errno)); goto error; } /* port_getn should block indefinitely if st->interval < 0 */ if (st->interval < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = st->interval / 1000LL; tsp->tv_nsec = (st->interval % 1000LL) * 1000000LL; } for (;;) { unsigned int nreturned = 1; status = port_getn(evp, &event, 1, &nreturned, tsp); if (status != NC_OK) { if (errno == EINTR || errno == EAGAIN) { continue; } if (errno != ETIME) { log_error("port getn on evp %d with m %d failed: %s", evp, st->sd, strerror(errno)); goto error; } } ASSERT(nreturned <= 1); if (nreturned == 1) { /* re-associate monitoring descriptor with the port */ status = port_associate(evp, PORT_SOURCE_FD, st->sd, POLLIN, NULL); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, st->sd, strerror(errno)); } } cb(st, &nreturned); } error: status = close(evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evp, strerror(errno)); } evp = -1; } #endif /* NC_HAVE_EVENT_PORTS */ nutcracker-0.4.0+dfsg/src/event/nc_kqueue.c000066400000000000000000000245551242132376000206410ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #ifdef NC_HAVE_KQUEUE #include struct event_base * event_base_create(int nevent, event_cb_t cb) { struct event_base *evb; int status, kq; struct kevent *change, *event; ASSERT(nevent > 0); kq = kqueue(); if (kq < 0) { log_error("kqueue failed: %s", strerror(errno)); return NULL; } change = nc_calloc(nevent, sizeof(*change)); if (change == NULL) { status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } return NULL; } event = nc_calloc(nevent, sizeof(*event)); if (event == NULL) { nc_free(change); status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } return NULL; } evb = nc_alloc(sizeof(*evb)); if (evb == NULL) { nc_free(change); nc_free(event); status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } return NULL; } evb->kq = kq; evb->change = change; evb->nchange = 0; evb->event = event; evb->nevent = nevent; evb->nreturned = 0; evb->nprocessed = 0; evb->cb = cb; log_debug(LOG_INFO, "kq %d with nevent %d", evb->kq, evb->nevent); return evb; } void event_base_destroy(struct event_base *evb) { int status; if (evb == NULL) { return; } ASSERT(evb->kq > 0); nc_free(evb->change); nc_free(evb->event); status = close(evb->kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", evb->kq, strerror(errno)); } evb->kq = -1; nc_free(evb); } int event_add_in(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(evb->nchange < evb->nevent); if (c->recv_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, c); c->recv_active = 1; return 0; } int event_del_in(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(evb->nchange < evb->nevent); if (!c->recv_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_READ, EV_DELETE, 0, 0, c); c->recv_active = 0; return 0; } int event_add_out(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); ASSERT(evb->nchange < evb->nevent); if (c->send_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, c); c->send_active = 1; return 0; } int event_del_out(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); ASSERT(evb->nchange < evb->nevent); if (!c->send_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_WRITE, EV_DELETE, 0, 0, c); c->send_active = 0; return 0; } int event_add_conn(struct event_base *evb, struct conn *c) { ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(!c->recv_active); ASSERT(!c->send_active); ASSERT(evb->nchange < evb->nevent); event_add_in(evb, c); event_add_out(evb, c); return 0; } int event_del_conn(struct event_base *evb, struct conn *c) { int i; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(evb->nchange < evb->nevent); event_del_out(evb, c); event_del_in(evb, c); /* * Now, eliminate pending events for c->sd (there should be at most one * other event). This is important because we will close c->sd and free * c when we return. */ for (i = evb->nprocessed + 1; i < evb->nreturned; i++) { struct kevent *ev = &evb->event[i]; if (ev->ident == (uintptr_t)c->sd) { ev->flags = 0; ev->filter = 0; break; } } return 0; } int event_wait(struct event_base *evb, int timeout) { int kq = evb->kq; struct timespec ts, *tsp; ASSERT(kq > 0); /* kevent should block indefinitely if timeout < 0 */ if (timeout < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = timeout / 1000LL; tsp->tv_nsec = (timeout % 1000LL) * 1000000LL; } for (;;) { /* * kevent() is used both to register new events with kqueue, and to * retrieve any pending events. Changes that should be applied to the * kqueue are given in the change[] and any returned events are placed * in event[], up to the maximum sized allowed by nevent. The number * of entries actually placed in event[] is returned by the kevent() * call and saved in nreturned. * * Events are registered with the system by the application via a * struct kevent, and an event is uniquely identified with the system * by a (kq, ident, filter) tuple. This means that there can be only * one (ident, filter) pair for a given kqueue. */ evb->nreturned = kevent(kq, evb->change, evb->nchange, evb->event, evb->nevent, tsp); evb->nchange = 0; if (evb->nreturned > 0) { for (evb->nprocessed = 0; evb->nprocessed < evb->nreturned; evb->nprocessed++) { struct kevent *ev = &evb->event[evb->nprocessed]; uint32_t events = 0; log_debug(LOG_VVERB, "kevent %04"PRIX32" with filter %d " "triggered on sd %d", ev->flags, ev->filter, ev->ident); /* * If an error occurs while processing an element of the * change[] and there is enough room in the event[], then the * event event will be placed in the eventlist with EV_ERROR * set in flags and the system error(errno) in data. */ if (ev->flags & EV_ERROR) { /* * Error messages that can happen, when a delete fails. * EBADF happens when the file descriptor has been closed * ENOENT when the file descriptor was closed and then * reopened. * EINVAL for some reasons not understood; EINVAL * should not be returned ever; but FreeBSD does :-\ * An error is also indicated when a callback deletes an * event we are still processing. In that case the data * field is set to ENOENT. */ if (ev->data == EBADF || ev->data == EINVAL || ev->data == ENOENT || ev->data == EINTR) { continue; } events |= EVENT_ERR; } if (ev->filter == EVFILT_READ) { events |= EVENT_READ; } if (ev->filter == EVFILT_WRITE) { events |= EVENT_WRITE; } if (evb->cb != NULL && events != 0) { evb->cb(ev->udata, events); } } return evb->nreturned; } if (evb->nreturned == 0) { if (timeout == -1) { log_error("kevent on kq %d with %d events and %d timeout " "returned no events", kq, evb->nevent, timeout); return -1; } return 0; } if (errno == EINTR) { continue; } log_error("kevent on kq %d with %d events failed: %s", kq, evb->nevent, strerror(errno)); return -1; } NOT_REACHED(); } void event_loop_stats(event_stats_cb_t cb, void *arg) { struct stats *st = arg; int status, kq; struct kevent change, event; struct timespec ts, *tsp; kq = kqueue(); if (kq < 0) { log_error("kqueue failed: %s", strerror(errno)); return; } EV_SET(&change, st->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, NULL); /* kevent should block indefinitely if st->interval < 0 */ if (st->interval < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = st->interval / 1000LL; tsp->tv_nsec = (st->interval % 1000LL) * 1000000LL; } for (;;) { int nreturned; nreturned = kevent(kq, &change, 1, &event, 1, tsp); if (nreturned < 0) { if (errno == EINTR) { continue; } log_error("kevent on kq %d with m %d failed: %s", kq, st->sd, strerror(errno)); goto error; } ASSERT(nreturned <= 1); if (nreturned == 1) { struct kevent *ev = &event; if (ev->flags & EV_ERROR) { if (ev->data == EINTR) { continue; } log_error("kevent on kq %d with m %d failed: %s", kq, st->sd, strerror(ev->data)); goto error; } } cb(st, &nreturned); } error: status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } kq = -1; } #endif /* NC_HAVE_KQUEUE */ nutcracker-0.4.0+dfsg/src/hashkit/000077500000000000000000000000001242132376000170155ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/src/hashkit/Makefile.am000066400000000000000000000006361242132376000210560ustar00rootroot00000000000000MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I $(top_srcdir)/src AM_CFLAGS = -Wall -Wshadow AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value noinst_LIBRARIES = libhashkit.a noinst_HEADERS = nc_hashkit.h libhashkit_a_SOURCES = \ nc_crc16.c \ nc_crc32.c \ nc_fnv.c \ nc_hsieh.c \ nc_jenkins.c \ nc_ketama.c \ nc_md5.c \ nc_modula.c \ nc_murmur.c \ nc_one_at_a_time.c \ nc_random.c nutcracker-0.4.0+dfsg/src/hashkit/nc_crc16.c000066400000000000000000000060001242132376000205530ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include static const uint16_t crc16tab[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, }; uint32_t hash_crc16(const char *key, size_t key_length) { uint64_t x; uint32_t crc = 0; for (x=0; x < key_length; x++) { crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *key++) & 0x00ff]; } return crc; } nutcracker-0.4.0+dfsg/src/hashkit/nc_crc32.c000066400000000000000000000115161242132376000205610ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * The crc32 functions and data was originally written by Spencer * Garrett and was gleaned from the PostgreSQL source * tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at * src/usr.bin/cksum/crc32.c. */ #include static const uint32_t crc32tab[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, }; /* * CRC-32 implementation compatible with libmemcached library. Unfortunately * this implementation does not return CRC-32 as per spec. */ uint32_t hash_crc32(const char *key, size_t key_length) { uint64_t x; uint32_t crc = UINT32_MAX; for (x = 0; x < key_length; x++) { crc = (crc >> 8) ^ crc32tab[(crc ^ (uint64_t)key[x]) & 0xff]; } return ((~crc) >> 16) & 0x7fff; } uint32_t hash_crc32a(const char *key, size_t key_length) { const uint8_t *p = key; uint32_t crc; crc = ~0U; while (key_length--) { crc = crc32tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); } return crc ^ ~0U; } nutcracker-0.4.0+dfsg/src/hashkit/nc_fnv.c000066400000000000000000000036201242132376000204330ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include static uint64_t FNV_64_INIT = UINT64_C(0xcbf29ce484222325); static uint64_t FNV_64_PRIME = UINT64_C(0x100000001b3); static uint32_t FNV_32_INIT = 2166136261UL; static uint32_t FNV_32_PRIME = 16777619; uint32_t hash_fnv1_64(const char *key, size_t key_length) { uint64_t hash = FNV_64_INIT; size_t x; for (x = 0; x < key_length; x++) { hash *= FNV_64_PRIME; hash ^= (uint64_t)key[x]; } return (uint32_t)hash; } uint32_t hash_fnv1a_64(const char *key, size_t key_length) { uint32_t hash = (uint32_t) FNV_64_INIT; size_t x; for (x = 0; x < key_length; x++) { uint32_t val = (uint32_t)key[x]; hash ^= val; hash *= (uint32_t) FNV_64_PRIME; } return hash; } uint32_t hash_fnv1_32(const char *key, size_t key_length) { uint32_t hash = FNV_32_INIT; size_t x; for (x = 0; x < key_length; x++) { uint32_t val = (uint32_t)key[x]; hash *= FNV_32_PRIME; hash ^= val; } return hash; } uint32_t hash_fnv1a_32(const char *key, size_t key_length) { uint32_t hash = FNV_32_INIT; size_t x; for (x= 0; x < key_length; x++) { uint32_t val = (uint32_t)key[x]; hash ^= val; hash *= FNV_32_PRIME; } return hash; } nutcracker-0.4.0+dfsg/src/hashkit/nc_hashkit.h000066400000000000000000000061051242132376000213030ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_HASHKIT_H_ #define _NC_HASHKIT_H_ #include #include #define HASH_CODEC(ACTION) \ ACTION( HASH_ONE_AT_A_TIME, one_at_a_time ) \ ACTION( HASH_MD5, md5 ) \ ACTION( HASH_CRC16, crc16 ) \ ACTION( HASH_CRC32, crc32 ) \ ACTION( HASH_CRC32A, crc32a ) \ ACTION( HASH_FNV1_64, fnv1_64 ) \ ACTION( HASH_FNV1A_64, fnv1a_64 ) \ ACTION( HASH_FNV1_32, fnv1_32 ) \ ACTION( HASH_FNV1A_32, fnv1a_32 ) \ ACTION( HASH_HSIEH, hsieh ) \ ACTION( HASH_MURMUR, murmur ) \ ACTION( HASH_JENKINS, jenkins ) \ #define DIST_CODEC(ACTION) \ ACTION( DIST_KETAMA, ketama ) \ ACTION( DIST_MODULA, modula ) \ ACTION( DIST_RANDOM, random ) \ #define DEFINE_ACTION(_hash, _name) _hash, typedef enum hash_type { HASH_CODEC( DEFINE_ACTION ) HASH_SENTINEL } hash_type_t; #undef DEFINE_ACTION #define DEFINE_ACTION(_dist, _name) _dist, typedef enum dist_type { DIST_CODEC( DEFINE_ACTION ) DIST_SENTINEL } dist_type_t; #undef DEFINE_ACTION uint32_t hash_one_at_a_time(const char *key, size_t key_length); void md5_signature(const unsigned char *key, unsigned int length, unsigned char *result); uint32_t hash_md5(const char *key, size_t key_length); uint32_t hash_crc16(const char *key, size_t key_length); uint32_t hash_crc32(const char *key, size_t key_length); uint32_t hash_crc32a(const char *key, size_t key_length); uint32_t hash_fnv1_64(const char *key, size_t key_length); uint32_t hash_fnv1a_64(const char *key, size_t key_length); uint32_t hash_fnv1_32(const char *key, size_t key_length); uint32_t hash_fnv1a_32(const char *key, size_t key_length); uint32_t hash_hsieh(const char *key, size_t key_length); uint32_t hash_jenkins(const char *key, size_t length); uint32_t hash_murmur(const char *key, size_t length); rstatus_t ketama_update(struct server_pool *pool); uint32_t ketama_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); rstatus_t modula_update(struct server_pool *pool); uint32_t modula_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); rstatus_t random_update(struct server_pool *pool); uint32_t random_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); #endif nutcracker-0.4.0+dfsg/src/hashkit/nc_hsieh.c000066400000000000000000000044541242132376000207500ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * By Paul Hsieh (C) 2004, 2005. Covered under the Paul Hsieh * derivative license. * See: http://www.azillionmonkeys.com/qed/weblicense.html for license * details. * http://www.azillionmonkeys.com/qed/hash.html */ #include #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif uint32_t hash_hsieh(const char *key, size_t key_length) { uint32_t hash = 0, tmp; int rem; if (key_length <= 0 || key == NULL) { return 0; } rem = key_length & 3; key_length >>= 2; /* Main loop */ for (;key_length > 0; key_length--) { hash += get16bits (key); tmp = (get16bits (key+2) << 11) ^ hash; hash = (hash << 16) ^ tmp; key += 2*sizeof (uint16_t); hash += hash >> 11; } /* Handle end cases */ switch (rem) { case 3: hash += get16bits (key); hash ^= hash << 16; hash ^= (uint32_t)key[sizeof (uint16_t)] << 18; hash += hash >> 11; break; case 2: hash += get16bits (key); hash ^= hash << 11; hash += hash >> 17; break; case 1: hash += (unsigned char)(*key); hash ^= hash << 10; hash += hash >> 1; default: break; } /* Force "avalanching" of final 127 bits */ hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } nutcracker-0.4.0+dfsg/src/hashkit/nc_jenkins.c000066400000000000000000000163461242132376000213140ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this * code any way you wish, private, educational, or commercial. It's free. * Use for hash table lookup, or anything where one collision in 2^^32 is * acceptable. Do NOT use for cryptographic purposes. * http://burtleburtle.net/bob/hash/index.html * * Modified by Brian Pontz for libmemcached * TODO: * Add big endian support */ #include #define hashsize(n) ((uint32_t)1<<(n)) #define hashmask(n) (hashsize(n)-1) #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) #define mix(a,b,c) \ { \ a -= c; a ^= rot(c, 4); c += b; \ b -= a; b ^= rot(a, 6); a += c; \ c -= b; c ^= rot(b, 8); b += a; \ a -= c; a ^= rot(c,16); c += b; \ b -= a; b ^= rot(a,19); a += c; \ c -= b; c ^= rot(b, 4); b += a; \ } #define final(a,b,c) \ { \ c ^= b; c -= rot(b,14); \ a ^= c; a -= rot(c,11); \ b ^= a; b -= rot(a,25); \ c ^= b; c -= rot(b,16); \ a ^= c; a -= rot(c,4); \ b ^= a; b -= rot(a,14); \ c ^= b; c -= rot(b,24); \ } #define JENKINS_INITVAL 13 /* * jenkins_hash() -- hash a variable-length key into a 32-bit value * k : the key (the unaligned variable-length array of bytes) * length : the length of the key, counting by bytes * initval : can be any 4-byte value * Returns a 32-bit value. Every bit of the key affects every bit of * the return value. Two keys differing by one or two bits will have * totally different hash values. * The best hash table sizes are powers of 2. There is no need to do * mod a prime (mod is sooo slow!). If you need less than 32 bits, * use a bitmask. For example, if you need only 10 bits, do * h = (h & hashmask(10)); * In which case, the hash table should have hashsize(10) elements. */ uint32_t hash_jenkins(const char *key, size_t length) { uint32_t a,b,c; /* internal state */ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ /* Set up the internal state */ a = b = c = 0xdeadbeef + ((uint32_t)length) + JENKINS_INITVAL; u.ptr = key; #ifndef WORDS_BIGENDIAN if ((u.i & 0x3) == 0) { const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; b += k[1]; c += k[2]; mix(a,b,c); length -= 12; k += 3; } /*----------------------------- handle the last (probably partial) block */ /* * "k[2]&0xffffff" actually reads beyond the end of the string, but * then masks off the part it's not allowed to read. Because the * string is aligned, the masked-off tail is in the same word as the * rest of the string. Every machine with memory protection I've seen * does it on word boundaries, so is OK with this. But VALGRIND will * still catch it and complain. The masking trick does make the hash * noticably faster for short strings (like English words). */ switch(length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=k[1]&0xffffff; a+=k[0]; break; case 6 : b+=k[1]&0xffff; a+=k[0]; break; case 5 : b+=k[1]&0xff; a+=k[0]; break; case 4 : a+=k[0]; break; case 3 : a+=k[0]&0xffffff; break; case 2 : a+=k[0]&0xffff; break; case 1 : a+=k[0]&0xff; break; case 0 : return c; /* zero length strings require no mixing */ default: return c; } } else if ((u.i & 0x1) == 0) { const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ const uint8_t *k8; /*--------------- all but last block: aligned reads and different mixing */ while (length > 12) { a += k[0] + (((uint32_t)k[1])<<16); b += k[2] + (((uint32_t)k[3])<<16); c += k[4] + (((uint32_t)k[5])<<16); mix(a,b,c); length -= 12; k += 6; } /*----------------------------- handle the last (probably partial) block */ k8 = (const uint8_t *)k; switch(length) { case 12: c+=k[4]+(((uint32_t)k[5])<<16); b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ case 10: c+=k[4]; b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 9 : c+=k8[8]; /* fall through */ case 8 : b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ case 6 : b+=k[2]; a+=k[0]+(((uint32_t)k[1])<<16); break; case 5 : b+=k8[4]; /* fall through */ case 4 : a+=k[0]+(((uint32_t)k[1])<<16); break; case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=k[0]; break; case 1 : a+=k8[0]; break; case 0 : return c; /* zero length requires no mixing */ default: return c; } } else { /* need to read the key one byte at a time */ #endif /* little endian */ const uint8_t *k = (const uint8_t *)key; /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; a += ((uint32_t)k[1])<<8; a += ((uint32_t)k[2])<<16; a += ((uint32_t)k[3])<<24; b += k[4]; b += ((uint32_t)k[5])<<8; b += ((uint32_t)k[6])<<16; b += ((uint32_t)k[7])<<24; c += k[8]; c += ((uint32_t)k[9])<<8; c += ((uint32_t)k[10])<<16; c += ((uint32_t)k[11])<<24; mix(a,b,c); length -= 12; k += 12; } /*-------------------------------- last block: affect all 32 bits of (c) */ switch(length) /* all the case statements fall through */ { case 12: c+=((uint32_t)k[11])<<24; case 11: c+=((uint32_t)k[10])<<16; case 10: c+=((uint32_t)k[9])<<8; case 9 : c+=k[8]; case 8 : b+=((uint32_t)k[7])<<24; case 7 : b+=((uint32_t)k[6])<<16; case 6 : b+=((uint32_t)k[5])<<8; case 5 : b+=k[4]; case 4 : a+=((uint32_t)k[3])<<24; case 3 : a+=((uint32_t)k[2])<<16; case 2 : a+=((uint32_t)k[1])<<8; case 1 : a+=k[0]; break; case 0 : return c; default : return c; } #ifndef WORDS_BIGENDIAN } #endif final(a,b,c); return c; } nutcracker-0.4.0+dfsg/src/hashkit/nc_ketama.c000066400000000000000000000175751242132376000211220ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #define KETAMA_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ #define KETAMA_POINTS_PER_SERVER 160 /* 40 points per hash */ #define KETAMA_MAX_HOSTLEN 86 static uint32_t ketama_hash(const char *key, size_t key_length, uint32_t alignment) { unsigned char results[16]; md5_signature((unsigned char*)key, key_length, results); return ((uint32_t) (results[3 + alignment * 4] & 0xFF) << 24) | ((uint32_t) (results[2 + alignment * 4] & 0xFF) << 16) | ((uint32_t) (results[1 + alignment * 4] & 0xFF) << 8) | (results[0 + alignment * 4] & 0xFF); } static int ketama_item_cmp(const void *t1, const void *t2) { const struct continuum *ct1 = t1, *ct2 = t2; if (ct1->value == ct2->value) { return 0; } else if (ct1->value > ct2->value) { return 1; } else { return -1; } } rstatus_t ketama_update(struct server_pool *pool) { uint32_t nserver; /* # server - live and dead */ uint32_t nlive_server; /* # live server */ uint32_t pointer_per_server; /* pointers per server proportional to weight */ uint32_t pointer_per_hash; /* pointers per hash */ uint32_t pointer_counter; /* # pointers on continuum */ uint32_t pointer_index; /* pointer index */ uint32_t points_per_server; /* points per server */ uint32_t continuum_index; /* continuum index */ uint32_t continuum_addition; /* extra space in the continuum */ uint32_t server_index; /* server index */ uint32_t value; /* continuum value */ uint32_t total_weight; /* total live server weight */ int64_t now; /* current timestamp in usec */ ASSERT(array_n(&pool->server) > 0); now = nc_usec_now(); if (now < 0) { return NC_ERROR; } /* * Count live servers and total weight, and also update the next time to * rebuild the distribution */ nserver = array_n(&pool->server); nlive_server = 0; total_weight = 0; pool->next_rebuild = 0LL; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts) { if (server->next_retry <= now) { server->next_retry = 0LL; nlive_server++; } else if (pool->next_rebuild == 0LL || server->next_retry < pool->next_rebuild) { pool->next_rebuild = server->next_retry; } } else { nlive_server++; } ASSERT(server->weight > 0); /* count weight only for live servers */ if (!pool->auto_eject_hosts || server->next_retry <= now) { total_weight += server->weight; } } pool->nlive_server = nlive_server; if (nlive_server == 0) { log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", pool->idx, pool->name.len, pool->name.data); return NC_OK; } log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, pool->name.len, pool->name.data); continuum_addition = KETAMA_CONTINUUM_ADDITION; points_per_server = KETAMA_POINTS_PER_SERVER; /* * Allocate the continuum for the pool, the first time, and every time we * add a new server to the pool */ if (nlive_server > pool->nserver_continuum) { struct continuum *continuum; uint32_t nserver_continuum = nlive_server + continuum_addition; uint32_t ncontinuum = nserver_continuum * points_per_server; continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); if (continuum == NULL) { return NC_ENOMEM; } pool->continuum = continuum; pool->nserver_continuum = nserver_continuum; /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ } /* * Build a continuum with the servers that are live and points from * these servers that are proportial to their weight */ continuum_index = 0; pointer_counter = 0; for (server_index = 0; server_index < nserver; server_index++) { struct server *server; float pct; server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts && server->next_retry > now) { continue; } pct = (float)server->weight / (float)total_weight; pointer_per_server = (uint32_t) ((floorf((float) (pct * KETAMA_POINTS_PER_SERVER / 4 * (float)nlive_server + 0.0000000001))) * 4); pointer_per_hash = 4; log_debug(LOG_VERB, "%.*s:%"PRIu16" weight %"PRIu32" of %"PRIu32" " "pct %0.5f points per server %"PRIu32"", server->name.len, server->name.data, server->port, server->weight, total_weight, pct, pointer_per_server); for (pointer_index = 1; pointer_index <= pointer_per_server / pointer_per_hash; pointer_index++) { char host[KETAMA_MAX_HOSTLEN]= ""; size_t hostlen; uint32_t x; hostlen = snprintf(host, KETAMA_MAX_HOSTLEN, "%.*s-%u", server->name.len, server->name.data, pointer_index - 1); for (x = 0; x < pointer_per_hash; x++) { value = ketama_hash(host, hostlen, x); pool->continuum[continuum_index].index = server_index; pool->continuum[continuum_index++].value = value; } } pointer_counter += pointer_per_server; } pool->ncontinuum = pointer_counter; qsort(pool->continuum, pool->ncontinuum, sizeof(*pool->continuum), ketama_item_cmp); for (pointer_index = 0; pointer_index < ((nlive_server * KETAMA_POINTS_PER_SERVER) - 1); pointer_index++) { if (pointer_index + 1 >= pointer_counter) { break; } ASSERT(pool->continuum[pointer_index].value <= pool->continuum[pointer_index + 1].value); } log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " "active points in %"PRIu32" slots", pool->idx, pool->name.len, pool->name.data, nlive_server, nserver, pool->nserver_continuum, pool->ncontinuum, (pool->nserver_continuum + continuum_addition) * points_per_server); return NC_OK; } uint32_t ketama_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) { struct continuum *begin, *end, *left, *right, *middle; ASSERT(continuum != NULL); ASSERT(ncontinuum != 0); begin = left = continuum; end = right = continuum + ncontinuum; while (left < right) { middle = left + (right - left) / 2; if (middle->value < hash) { left = middle + 1; } else { right = middle; } } if (right == end) { right = begin; } return right->index; } nutcracker-0.4.0+dfsg/src/hashkit/nc_md5.c000066400000000000000000000224341242132376000203330ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include /* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: Alexander Peslyak, better known as Solar Designer */ #include typedef unsigned int MD5_u32plus; typedef struct { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[64]; MD5_u32plus block[16]; } MD5_CTX; /* * The basic MD5 functions. * * F and G are optimized compared to their RFC 1321 definitions for * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned * memory accesses is just an optimization. Nothing will break if it * doesn't work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define SET(n) \ (*(MD5_u32plus *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update * the bit counters. There are no alignment requirements. */ static void * body(MD5_CTX *ctx, void *data, unsigned long size) { unsigned char *ptr; MD5_u32plus a, b, c, d; MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while (size -= 64); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void MD5_Init(MD5_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size) { MD5_u32plus saved_lo; unsigned long used, free; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { ctx->hi++; } ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used) { free = 64 - used; if (size < free) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, free); data = (unsigned char *)data + free; size -= free; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } void MD5_Final(unsigned char *result, MD5_CTX *ctx) { unsigned long used, free; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; free = 64 - used; if (free < 8) { memset(&ctx->buffer[used], 0, free); body(ctx, ctx->buffer, 64); used = 0; free = 64; } memset(&ctx->buffer[used], 0, free - 8); ctx->lo <<= 3; ctx->buffer[56] = ctx->lo; ctx->buffer[57] = ctx->lo >> 8; ctx->buffer[58] = ctx->lo >> 16; ctx->buffer[59] = ctx->lo >> 24; ctx->buffer[60] = ctx->hi; ctx->buffer[61] = ctx->hi >> 8; ctx->buffer[62] = ctx->hi >> 16; ctx->buffer[63] = ctx->hi >> 24; body(ctx, ctx->buffer, 64); result[0] = ctx->a; result[1] = ctx->a >> 8; result[2] = ctx->a >> 16; result[3] = ctx->a >> 24; result[4] = ctx->b; result[5] = ctx->b >> 8; result[6] = ctx->b >> 16; result[7] = ctx->b >> 24; result[8] = ctx->c; result[9] = ctx->c >> 8; result[10] = ctx->c >> 16; result[11] = ctx->c >> 24; result[12] = ctx->d; result[13] = ctx->d >> 8; result[14] = ctx->d >> 16; result[15] = ctx->d >> 24; memset(ctx, 0, sizeof(*ctx)); } /* * Just a simple method for getting the signature * result must be == 16 */ void md5_signature(unsigned char *key, unsigned long length, unsigned char *result) { MD5_CTX my_md5; MD5_Init(&my_md5); (void)MD5_Update(&my_md5, key, length); MD5_Final(result, &my_md5); } uint32_t hash_md5(const char *key, size_t key_length) { unsigned char results[16]; md5_signature((unsigned char*)key, (unsigned long)key_length, results); return ((uint32_t) (results[3] & 0xFF) << 24) | ((uint32_t) (results[2] & 0xFF) << 16) | ((uint32_t) (results[1] & 0xFF) << 8) | (results[0] & 0xFF); } nutcracker-0.4.0+dfsg/src/hashkit/nc_modula.c000066400000000000000000000122551242132376000211270ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #define MODULA_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ #define MODULA_POINTS_PER_SERVER 1 rstatus_t modula_update(struct server_pool *pool) { uint32_t nserver; /* # server - live and dead */ uint32_t nlive_server; /* # live server */ uint32_t pointer_per_server; /* pointers per server proportional to weight */ uint32_t pointer_counter; /* # pointers on continuum */ uint32_t points_per_server; /* points per server */ uint32_t continuum_index; /* continuum index */ uint32_t continuum_addition; /* extra space in the continuum */ uint32_t server_index; /* server index */ uint32_t weight_index; /* weight index */ uint32_t total_weight; /* total live server weight */ int64_t now; /* current timestamp in usec */ now = nc_usec_now(); if (now < 0) { return NC_ERROR; } nserver = array_n(&pool->server); nlive_server = 0; total_weight = 0; pool->next_rebuild = 0LL; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts) { if (server->next_retry <= now) { server->next_retry = 0LL; nlive_server++; } else if (pool->next_rebuild == 0LL || server->next_retry < pool->next_rebuild) { pool->next_rebuild = server->next_retry; } } else { nlive_server++; } ASSERT(server->weight > 0); /* count weight only for live servers */ if (!pool->auto_eject_hosts || server->next_retry <= now) { total_weight += server->weight; } } pool->nlive_server = nlive_server; if (nlive_server == 0) { ASSERT(pool->continuum != NULL); ASSERT(pool->ncontinuum != 0); log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", pool->idx, pool->name.len, pool->name.data); return NC_OK; } log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, pool->name.len, pool->name.data); continuum_addition = MODULA_CONTINUUM_ADDITION; points_per_server = MODULA_POINTS_PER_SERVER; /* * Allocate the continuum for the pool, the first time, and every time we * add a new server to the pool */ if (total_weight > pool->nserver_continuum) { struct continuum *continuum; uint32_t nserver_continuum = total_weight + MODULA_CONTINUUM_ADDITION; uint32_t ncontinuum = nserver_continuum * MODULA_POINTS_PER_SERVER; continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); if (continuum == NULL) { return NC_ENOMEM; } pool->continuum = continuum; pool->nserver_continuum = nserver_continuum; /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ } /* update the continuum with the servers that are live */ continuum_index = 0; pointer_counter = 0; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts && server->next_retry > now) { continue; } for (weight_index = 0; weight_index < server->weight; weight_index++) { pointer_per_server = 1; pool->continuum[continuum_index].index = server_index; pool->continuum[continuum_index++].value = 0; pointer_counter += pointer_per_server; } } pool->ncontinuum = pointer_counter; log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " "active points in %"PRIu32" slots", pool->idx, pool->name.len, pool->name.data, nlive_server, nserver, pool->nserver_continuum, pool->ncontinuum, (pool->nserver_continuum + continuum_addition) * points_per_server); return NC_OK; } uint32_t modula_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) { struct continuum *c; ASSERT(continuum != NULL); ASSERT(ncontinuum != 0); c = continuum + hash % ncontinuum; return c->index; } nutcracker-0.4.0+dfsg/src/hashkit/nc_murmur.c000066400000000000000000000044461242132376000212000ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * "Murmur" hash provided by Austin, tanjent@gmail.com * http://murmurhash.googlepages.com/ * * Note - This code makes a few assumptions about how your machine behaves - * * 1. We can read a 4-byte value from any address without crashing * 2. sizeof(int) == 4 * * And it has a few limitations - * 1. It will not work incrementally. * 2. It will not produce the same results on little-endian and big-endian * machines. * * Updated to murmur2 hash - BP */ #include uint32_t hash_murmur(const char *key, size_t length) { /* * 'm' and 'r' are mixing constants generated offline. They're not * really 'magic', they just happen to work well. */ const unsigned int m = 0x5bd1e995; const uint32_t seed = (0xdeadbeef * (uint32_t)length); const int r = 24; /* Initialize the hash to a 'random' value */ uint32_t h = seed ^ (uint32_t)length; /* Mix 4 bytes at a time into the hash */ const unsigned char * data = (const unsigned char *)key; while (length >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; length -= 4; } /* Handle the last few bytes of the input array */ switch(length) { case 3: h ^= ((uint32_t)data[2]) << 16; case 2: h ^= ((uint32_t)data[1]) << 8; case 1: h ^= data[0]; h *= m; default: break; }; /* * Do a few final mixes of the hash to ensure the last few bytes are * well-incorporated. */ h ^= h >> 13; h *= m; h ^= h >> 15; return h; } nutcracker-0.4.0+dfsg/src/hashkit/nc_one_at_a_time.c000066400000000000000000000025411242132376000224260ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * HashKit * Copyright (C) 2009 Brian Aker * All rights reserved. * * Use and distribution licensed under the BSD license. See * the COPYING file in the parent directory for full text. */ /* * This has is Jenkin's "One at A time Hash". * http://en.wikipedia.org/wiki/Jenkins_hash_function */ #include uint32_t hash_one_at_a_time(const char *key, size_t key_length) { const char *ptr = key; uint32_t value = 0; while (key_length--) { uint32_t val = (uint32_t) *ptr++; value += val; value += (value << 10); value ^= (value >> 6); } value += (value << 3); value ^= (value >> 11); value += (value << 15); return value; } nutcracker-0.4.0+dfsg/src/hashkit/nc_random.c000066400000000000000000000114221242132376000211210ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #define RANDOM_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ #define RANDOM_POINTS_PER_SERVER 1 rstatus_t random_update(struct server_pool *pool) { uint32_t nserver; /* # server - live and dead */ uint32_t nlive_server; /* # live server */ uint32_t pointer_per_server; /* pointers per server proportional to weight */ uint32_t pointer_counter; /* # pointers on continuum */ uint32_t points_per_server; /* points per server */ uint32_t continuum_index; /* continuum index */ uint32_t continuum_addition; /* extra space in the continuum */ uint32_t server_index; /* server index */ int64_t now; /* current timestamp in usec */ now = nc_usec_now(); if (now < 0) { return NC_ERROR; } nserver = array_n(&pool->server); nlive_server = 0; pool->next_rebuild = 0LL; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts) { if (server->next_retry <= now) { server->next_retry = 0LL; nlive_server++; } else if (pool->next_rebuild == 0LL || server->next_retry < pool->next_rebuild) { pool->next_rebuild = server->next_retry; } } else { nlive_server++; } } pool->nlive_server = nlive_server; if (nlive_server == 0) { ASSERT(pool->continuum != NULL); ASSERT(pool->ncontinuum != 0); log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", pool->idx, pool->name.len, pool->name.data); return NC_OK; } log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, pool->name.len, pool->name.data); continuum_addition = RANDOM_CONTINUUM_ADDITION; points_per_server = RANDOM_POINTS_PER_SERVER; /* * Allocate the continuum for the pool, the first time, and every time we * add a new server to the pool */ if (nlive_server > pool->nserver_continuum) { struct continuum *continuum; uint32_t nserver_continuum = nlive_server + RANDOM_CONTINUUM_ADDITION; uint32_t ncontinuum = nserver_continuum * RANDOM_POINTS_PER_SERVER; continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); if (continuum == NULL) { return NC_ENOMEM; } srandom((uint32_t)time(NULL)); pool->continuum = continuum; pool->nserver_continuum = nserver_continuum; /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ } /* update the continuum with the servers that are live */ continuum_index = 0; pointer_counter = 0; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts && server->next_retry > now) { continue; } pointer_per_server = 1; pool->continuum[continuum_index].index = server_index; pool->continuum[continuum_index++].value = 0; pointer_counter += pointer_per_server; } pool->ncontinuum = pointer_counter; log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " "active points in %"PRIu32" slots", pool->idx, pool->name.len, pool->name.data, nlive_server, nserver, pool->nserver_continuum, pool->ncontinuum, (pool->nserver_continuum + continuum_addition) * points_per_server); return NC_OK; } uint32_t random_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) { struct continuum *c; ASSERT(continuum != NULL); ASSERT(ncontinuum != 0); c = continuum + random() % ncontinuum; return c->index; } nutcracker-0.4.0+dfsg/src/nc.c000066400000000000000000000342621242132376000161350ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #define NC_CONF_PATH "conf/nutcracker.yml" #define NC_LOG_DEFAULT LOG_NOTICE #define NC_LOG_MIN LOG_EMERG #define NC_LOG_MAX LOG_PVERB #define NC_LOG_PATH NULL #define NC_STATS_PORT STATS_PORT #define NC_STATS_ADDR STATS_ADDR #define NC_STATS_INTERVAL STATS_INTERVAL #define NC_PID_FILE NULL #define NC_MBUF_SIZE MBUF_SIZE #define NC_MBUF_MIN_SIZE MBUF_MIN_SIZE #define NC_MBUF_MAX_SIZE MBUF_MAX_SIZE static int show_help; static int show_version; static int test_conf; static int daemonize; static int describe_stats; static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "test-conf", no_argument, NULL, 't' }, { "daemonize", no_argument, NULL, 'd' }, { "describe-stats", no_argument, NULL, 'D' }, { "verbose", required_argument, NULL, 'v' }, { "output", required_argument, NULL, 'o' }, { "conf-file", required_argument, NULL, 'c' }, { "stats-port", required_argument, NULL, 's' }, { "stats-interval", required_argument, NULL, 'i' }, { "stats-addr", required_argument, NULL, 'a' }, { "pid-file", required_argument, NULL, 'p' }, { "mbuf-size", required_argument, NULL, 'm' }, { NULL, 0, NULL, 0 } }; static char short_options[] = "hVtdDv:o:c:s:i:a:p:m:"; static rstatus_t nc_daemonize(int dump_core) { rstatus_t status; pid_t pid, sid; int fd; pid = fork(); switch (pid) { case -1: log_error("fork() failed: %s", strerror(errno)); return NC_ERROR; case 0: break; default: /* parent terminates */ _exit(0); } /* 1st child continues and becomes the session leader */ sid = setsid(); if (sid < 0) { log_error("setsid() failed: %s", strerror(errno)); return NC_ERROR; } if (signal(SIGHUP, SIG_IGN) == SIG_ERR) { log_error("signal(SIGHUP, SIG_IGN) failed: %s", strerror(errno)); return NC_ERROR; } pid = fork(); switch (pid) { case -1: log_error("fork() failed: %s", strerror(errno)); return NC_ERROR; case 0: break; default: /* 1st child terminates */ _exit(0); } /* 2nd child continues */ /* change working directory */ if (dump_core == 0) { status = chdir("/"); if (status < 0) { log_error("chdir(\"/\") failed: %s", strerror(errno)); return NC_ERROR; } } /* clear file mode creation mask */ umask(0); /* redirect stdin, stdout and stderr to "/dev/null" */ fd = open("/dev/null", O_RDWR); if (fd < 0) { log_error("open(\"/dev/null\") failed: %s", strerror(errno)); return NC_ERROR; } status = dup2(fd, STDIN_FILENO); if (status < 0) { log_error("dup2(%d, STDIN) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } status = dup2(fd, STDOUT_FILENO); if (status < 0) { log_error("dup2(%d, STDOUT) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } status = dup2(fd, STDERR_FILENO); if (status < 0) { log_error("dup2(%d, STDERR) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } if (fd > STDERR_FILENO) { status = close(fd); if (status < 0) { log_error("close(%d) failed: %s", fd, strerror(errno)); return NC_ERROR; } } return NC_OK; } static void nc_print_run(struct instance *nci) { int status; struct utsname name; status = uname(&name); if (status < 0) { loga("nutcracker-%s started on pid %d", NC_VERSION_STRING, nci->pid); } else { loga("nutcracker-%s built for %s %s %s started on pid %d", NC_VERSION_STRING, name.sysname, name.release, name.machine, nci->pid); } loga("run, rabbit run / dig that hole, forget the sun / " "and when at last the work is done / don't sit down / " "it's time to dig another one"); } static void nc_print_done(void) { loga("done, rabbit done"); } static void nc_show_usage(void) { log_stderr( "Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file]" CRLF " [-c conf file] [-s stats port] [-a stats addr]" CRLF " [-i stats interval] [-p pid file] [-m mbuf size]" CRLF ""); log_stderr( "Options:" CRLF " -h, --help : this help" CRLF " -V, --version : show version and exit" CRLF " -t, --test-conf : test configuration for syntax errors and exit" CRLF " -d, --daemonize : run as a daemon" CRLF " -D, --describe-stats : print stats description and exit"); log_stderr( " -v, --verbosity=N : set logging level (default: %d, min: %d, max: %d)" CRLF " -o, --output=S : set logging file (default: %s)" CRLF " -c, --conf-file=S : set configuration file (default: %s)" CRLF " -s, --stats-port=N : set stats monitoring port (default: %d)" CRLF " -a, --stats-addr=S : set stats monitoring ip (default: %s)" CRLF " -i, --stats-interval=N : set stats aggregation interval in msec (default: %d msec)" CRLF " -p, --pid-file=S : set pid file (default: %s)" CRLF " -m, --mbuf-size=N : set size of mbuf chunk in bytes (default: %d bytes)" CRLF "", NC_LOG_DEFAULT, NC_LOG_MIN, NC_LOG_MAX, NC_LOG_PATH != NULL ? NC_LOG_PATH : "stderr", NC_CONF_PATH, NC_STATS_PORT, NC_STATS_ADDR, NC_STATS_INTERVAL, NC_PID_FILE != NULL ? NC_PID_FILE : "off", NC_MBUF_SIZE); } static rstatus_t nc_create_pidfile(struct instance *nci) { char pid[NC_UINTMAX_MAXLEN]; int fd, pid_len; ssize_t n; fd = open(nci->pid_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { log_error("opening pid file '%s' failed: %s", nci->pid_filename, strerror(errno)); return NC_ERROR; } nci->pidfile = 1; pid_len = nc_snprintf(pid, NC_UINTMAX_MAXLEN, "%d", nci->pid); n = nc_write(fd, pid, pid_len); if (n < 0) { log_error("write to pid file '%s' failed: %s", nci->pid_filename, strerror(errno)); return NC_ERROR; } close(fd); return NC_OK; } static void nc_remove_pidfile(struct instance *nci) { int status; status = unlink(nci->pid_filename); if (status < 0) { log_error("unlink of pid file '%s' failed, ignored: %s", nci->pid_filename, strerror(errno)); } } static void nc_set_default_options(struct instance *nci) { int status; nci->ctx = NULL; nci->log_level = NC_LOG_DEFAULT; nci->log_filename = NC_LOG_PATH; nci->conf_filename = NC_CONF_PATH; nci->stats_port = NC_STATS_PORT; nci->stats_addr = NC_STATS_ADDR; nci->stats_interval = NC_STATS_INTERVAL; status = nc_gethostname(nci->hostname, NC_MAXHOSTNAMELEN); if (status < 0) { log_warn("gethostname failed, ignored: %s", strerror(errno)); nc_snprintf(nci->hostname, NC_MAXHOSTNAMELEN, "unknown"); } nci->hostname[NC_MAXHOSTNAMELEN - 1] = '\0'; nci->mbuf_chunk_size = NC_MBUF_SIZE; nci->pid = (pid_t)-1; nci->pid_filename = NULL; nci->pidfile = 0; } static rstatus_t nc_get_options(int argc, char **argv, struct instance *nci) { int c, value; opterr = 0; for (;;) { c = getopt_long(argc, argv, short_options, long_options, NULL); if (c == -1) { /* no more options */ break; } switch (c) { case 'h': show_version = 1; show_help = 1; break; case 'V': show_version = 1; break; case 't': test_conf = 1; break; case 'd': daemonize = 1; break; case 'D': describe_stats = 1; show_version = 1; break; case 'v': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -v requires a number"); return NC_ERROR; } nci->log_level = value; break; case 'o': nci->log_filename = optarg; break; case 'c': nci->conf_filename = optarg; break; case 's': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -s requires a number"); return NC_ERROR; } if (!nc_valid_port(value)) { log_stderr("nutcracker: option -s value %d is not a valid " "port", value); return NC_ERROR; } nci->stats_port = (uint16_t)value; break; case 'i': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -i requires a number"); return NC_ERROR; } nci->stats_interval = value; break; case 'a': nci->stats_addr = optarg; break; case 'p': nci->pid_filename = optarg; break; case 'm': value = nc_atoi(optarg, strlen(optarg)); if (value <= 0) { log_stderr("nutcracker: option -m requires a non-zero number"); return NC_ERROR; } if (value < NC_MBUF_MIN_SIZE || value > NC_MBUF_MAX_SIZE) { log_stderr("nutcracker: mbuf chunk size must be between %zu and" " %zu bytes", NC_MBUF_MIN_SIZE, NC_MBUF_MAX_SIZE); return NC_ERROR; } nci->mbuf_chunk_size = (size_t)value; break; case '?': switch (optopt) { case 'o': case 'c': case 'p': log_stderr("nutcracker: option -%c requires a file name", optopt); break; case 'm': case 'v': case 's': case 'i': log_stderr("nutcracker: option -%c requires a number", optopt); break; case 'a': log_stderr("nutcracker: option -%c requires a string", optopt); break; default: log_stderr("nutcracker: invalid option -- '%c'", optopt); break; } return NC_ERROR; default: log_stderr("nutcracker: invalid option -- '%c'", optopt); return NC_ERROR; } } return NC_OK; } /* * Returns true if configuration file has a valid syntax, otherwise * returns false */ static bool nc_test_conf(struct instance *nci) { struct conf *cf; cf = conf_create(nci->conf_filename); if (cf == NULL) { log_stderr("nutcracker: configuration file '%s' syntax is invalid", nci->conf_filename); return false; } conf_destroy(cf); log_stderr("nutcracker: configuration file '%s' syntax is ok", nci->conf_filename); return true; } static rstatus_t nc_pre_run(struct instance *nci) { rstatus_t status; status = log_init(nci->log_level, nci->log_filename); if (status != NC_OK) { return status; } if (daemonize) { status = nc_daemonize(1); if (status != NC_OK) { return status; } } nci->pid = getpid(); status = signal_init(); if (status != NC_OK) { return status; } if (nci->pid_filename) { status = nc_create_pidfile(nci); if (status != NC_OK) { return status; } } nc_print_run(nci); return NC_OK; } static void nc_post_run(struct instance *nci) { if (nci->pidfile) { nc_remove_pidfile(nci); } signal_deinit(); nc_print_done(); log_deinit(); } static void nc_run(struct instance *nci) { rstatus_t status; struct context *ctx; ctx = core_start(nci); if (ctx == NULL) { return; } /* run rabbit run */ for (;;) { status = core_loop(ctx); if (status != NC_OK) { break; } } core_stop(ctx); } int main(int argc, char **argv) { rstatus_t status; struct instance nci; nc_set_default_options(&nci); status = nc_get_options(argc, argv, &nci); if (status != NC_OK) { nc_show_usage(); exit(1); } if (show_version) { log_stderr("This is nutcracker-%s" CRLF, NC_VERSION_STRING); if (show_help) { nc_show_usage(); } if (describe_stats) { stats_describe(); } exit(0); } if (test_conf) { if (!nc_test_conf(&nci)) { exit(1); } exit(0); } status = nc_pre_run(&nci); if (status != NC_OK) { nc_post_run(&nci); exit(1); } nc_run(&nci); nc_post_run(&nci); exit(1); } nutcracker-0.4.0+dfsg/src/nc_array.c000066400000000000000000000071261242132376000173320ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include struct array * array_create(uint32_t n, size_t size) { struct array *a; ASSERT(n != 0 && size != 0); a = nc_alloc(sizeof(*a)); if (a == NULL) { return NULL; } a->elem = nc_alloc(n * size); if (a->elem == NULL) { nc_free(a); return NULL; } a->nelem = 0; a->size = size; a->nalloc = n; return a; } void array_destroy(struct array *a) { array_deinit(a); nc_free(a); } rstatus_t array_init(struct array *a, uint32_t n, size_t size) { ASSERT(n != 0 && size != 0); a->elem = nc_alloc(n * size); if (a->elem == NULL) { return NC_ENOMEM; } a->nelem = 0; a->size = size; a->nalloc = n; return NC_OK; } void array_deinit(struct array *a) { ASSERT(a->nelem == 0); if (a->elem != NULL) { nc_free(a->elem); } } uint32_t array_idx(struct array *a, void *elem) { uint8_t *p, *q; uint32_t off, idx; ASSERT(elem >= a->elem); p = a->elem; q = elem; off = (uint32_t)(q - p); ASSERT(off % (uint32_t)a->size == 0); idx = off / (uint32_t)a->size; return idx; } void * array_push(struct array *a) { void *elem, *new; size_t size; if (a->nelem == a->nalloc) { /* the array is full; allocate new array */ size = a->size * a->nalloc; new = nc_realloc(a->elem, 2 * size); if (new == NULL) { return NULL; } a->elem = new; a->nalloc *= 2; } elem = (uint8_t *)a->elem + a->size * a->nelem; a->nelem++; return elem; } void * array_pop(struct array *a) { void *elem; ASSERT(a->nelem != 0); a->nelem--; elem = (uint8_t *)a->elem + a->size * a->nelem; return elem; } void * array_get(struct array *a, uint32_t idx) { void *elem; ASSERT(a->nelem != 0); ASSERT(idx < a->nelem); elem = (uint8_t *)a->elem + (a->size * idx); return elem; } void * array_top(struct array *a) { ASSERT(a->nelem != 0); return array_get(a, a->nelem - 1); } void array_swap(struct array *a, struct array *b) { struct array tmp; tmp = *a; *a = *b; *b = tmp; } /* * Sort nelem elements of the array in ascending order based on the * compare comparator. */ void array_sort(struct array *a, array_compare_t compare) { ASSERT(a->nelem != 0); qsort(a->elem, a->nelem, a->size, compare); } /* * Calls the func once for each element in the array as long as func returns * success. On failure short-circuits and returns the error status. */ rstatus_t array_each(struct array *a, array_each_t func, void *data) { uint32_t i, nelem; ASSERT(array_n(a) != 0); ASSERT(func != NULL); for (i = 0, nelem = array_n(a); i < nelem; i++) { void *elem = array_get(a, i); rstatus_t status; status = func(elem, data); if (status != NC_OK) { return status; } } return NC_OK; } nutcracker-0.4.0+dfsg/src/nc_array.h000066400000000000000000000037431242132376000173400ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_ARRAY_H_ #define _NC_ARRAY_H_ #include typedef int (*array_compare_t)(const void *, const void *); typedef rstatus_t (*array_each_t)(void *, void *); struct array { uint32_t nelem; /* # element */ void *elem; /* element */ size_t size; /* element size */ uint32_t nalloc; /* # allocated element */ }; #define null_array { 0, NULL, 0, 0 } static inline void array_null(struct array *a) { a->nelem = 0; a->elem = NULL; a->size = 0; a->nalloc = 0; } static inline void array_set(struct array *a, void *elem, size_t size, uint32_t nalloc) { a->nelem = 0; a->elem = elem; a->size = size; a->nalloc = nalloc; } static inline uint32_t array_n(const struct array *a) { return a->nelem; } struct array *array_create(uint32_t n, size_t size); void array_destroy(struct array *a); rstatus_t array_init(struct array *a, uint32_t n, size_t size); void array_deinit(struct array *a); uint32_t array_idx(struct array *a, void *elem); void *array_push(struct array *a); void *array_pop(struct array *a); void *array_get(struct array *a, uint32_t idx); void *array_top(struct array *a); void array_swap(struct array *a, struct array *b); void array_sort(struct array *a, array_compare_t compare); rstatus_t array_each(struct array *a, array_each_t func, void *data); #endif nutcracker-0.4.0+dfsg/src/nc_client.c000066400000000000000000000113661242132376000174730ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include void client_ref(struct conn *conn, void *owner) { struct server_pool *pool = owner; ASSERT(conn->client && !conn->proxy); ASSERT(conn->owner == NULL); /* * We use null pointer as the sockaddr argument in the accept() call as * we are not interested in the address of the peer for the accepted * connection */ conn->family = 0; conn->addrlen = 0; conn->addr = NULL; pool->nc_conn_q++; TAILQ_INSERT_TAIL(&pool->c_conn_q, conn, conn_tqe); /* owner of the client connection is the server pool */ conn->owner = owner; log_debug(LOG_VVERB, "ref conn %p owner %p into pool '%.*s'", conn, pool, pool->name.len, pool->name.data); } void client_unref(struct conn *conn) { struct server_pool *pool; ASSERT(conn->client && !conn->proxy); ASSERT(conn->owner != NULL); pool = conn->owner; conn->owner = NULL; ASSERT(pool->nc_conn_q != 0); pool->nc_conn_q--; TAILQ_REMOVE(&pool->c_conn_q, conn, conn_tqe); log_debug(LOG_VVERB, "unref conn %p owner %p from pool '%.*s'", conn, pool, pool->name.len, pool->name.data); } bool client_active(struct conn *conn) { ASSERT(conn->client && !conn->proxy); ASSERT(TAILQ_EMPTY(&conn->imsg_q)); if (!TAILQ_EMPTY(&conn->omsg_q)) { log_debug(LOG_VVERB, "c %d is active", conn->sd); return true; } if (conn->rmsg != NULL) { log_debug(LOG_VVERB, "c %d is active", conn->sd); return true; } if (conn->smsg != NULL) { log_debug(LOG_VVERB, "c %d is active", conn->sd); return true; } log_debug(LOG_VVERB, "c %d is inactive", conn->sd); return false; } static void client_close_stats(struct context *ctx, struct server_pool *pool, err_t err, unsigned eof) { stats_pool_decr(ctx, pool, client_connections); if (eof) { stats_pool_incr(ctx, pool, client_eof); return; } switch (err) { case EPIPE: case ETIMEDOUT: case ECONNRESET: case ECONNABORTED: case ENOTCONN: case ENETDOWN: case ENETUNREACH: case EHOSTDOWN: case EHOSTUNREACH: default: stats_pool_incr(ctx, pool, client_err); break; } } void client_close(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *nmsg; /* current and next message */ ASSERT(conn->client && !conn->proxy); client_close_stats(ctx, conn->owner, conn->err, conn->eof); if (conn->sd < 0) { conn->unref(conn); conn_put(conn); return; } msg = conn->rmsg; if (msg != NULL) { conn->rmsg = NULL; ASSERT(msg->peer == NULL); ASSERT(msg->request && !msg->done); log_debug(LOG_INFO, "close c %d discarding pending req %"PRIu64" len " "%"PRIu32" type %d", conn->sd, msg->id, msg->mlen, msg->type); req_put(msg); } ASSERT(conn->smsg == NULL); ASSERT(TAILQ_EMPTY(&conn->imsg_q)); for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, c_tqe); /* dequeue the message (request) from client outq */ conn->dequeue_outq(ctx, conn, msg); if (msg->done) { log_debug(LOG_INFO, "close c %d discarding %s req %"PRIu64" len " "%"PRIu32" type %d", conn->sd, msg->error ? "error": "completed", msg->id, msg->mlen, msg->type); req_put(msg); } else { msg->swallow = 1; ASSERT(msg->request); ASSERT(msg->peer == NULL); log_debug(LOG_INFO, "close c %d schedule swallow of req %"PRIu64" " "len %"PRIu32" type %d", conn->sd, msg->id, msg->mlen, msg->type); } } ASSERT(TAILQ_EMPTY(&conn->omsg_q)); conn->unref(conn); status = close(conn->sd); if (status < 0) { log_error("close c %d failed, ignored: %s", conn->sd, strerror(errno)); } conn->sd = -1; conn_put(conn); } nutcracker-0.4.0+dfsg/src/nc_client.h000066400000000000000000000016361242132376000174770ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CLIENT_H_ #define _NC_CLIENT_H_ #include bool client_active(struct conn *conn); void client_ref(struct conn *conn, void *owner); void client_unref(struct conn *conn); void client_close(struct context *ctx, struct conn *conn); #endif nutcracker-0.4.0+dfsg/src/nc_conf.c000066400000000000000000001207631242132376000171440ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #define DEFINE_ACTION(_hash, _name) string(#_name), static struct string hash_strings[] = { HASH_CODEC( DEFINE_ACTION ) null_string }; #undef DEFINE_ACTION #define DEFINE_ACTION(_hash, _name) hash_##_name, static hash_t hash_algos[] = { HASH_CODEC( DEFINE_ACTION ) NULL }; #undef DEFINE_ACTION #define DEFINE_ACTION(_dist, _name) string(#_name), static struct string dist_strings[] = { DIST_CODEC( DEFINE_ACTION ) null_string }; #undef DEFINE_ACTION static struct command conf_commands[] = { { string("listen"), conf_set_listen, offsetof(struct conf_pool, listen) }, { string("hash"), conf_set_hash, offsetof(struct conf_pool, hash) }, { string("hash_tag"), conf_set_hashtag, offsetof(struct conf_pool, hash_tag) }, { string("distribution"), conf_set_distribution, offsetof(struct conf_pool, distribution) }, { string("timeout"), conf_set_num, offsetof(struct conf_pool, timeout) }, { string("backlog"), conf_set_num, offsetof(struct conf_pool, backlog) }, { string("client_connections"), conf_set_num, offsetof(struct conf_pool, client_connections) }, { string("redis"), conf_set_bool, offsetof(struct conf_pool, redis) }, { string("preconnect"), conf_set_bool, offsetof(struct conf_pool, preconnect) }, { string("auto_eject_hosts"), conf_set_bool, offsetof(struct conf_pool, auto_eject_hosts) }, { string("server_connections"), conf_set_num, offsetof(struct conf_pool, server_connections) }, { string("server_retry_timeout"), conf_set_num, offsetof(struct conf_pool, server_retry_timeout) }, { string("server_failure_limit"), conf_set_num, offsetof(struct conf_pool, server_failure_limit) }, { string("servers"), conf_add_server, offsetof(struct conf_pool, server) }, null_command }; static void conf_server_init(struct conf_server *cs) { string_init(&cs->pname); string_init(&cs->name); cs->port = 0; cs->weight = 0; memset(&cs->info, 0, sizeof(cs->info)); cs->valid = 0; log_debug(LOG_VVERB, "init conf server %p", cs); } static void conf_server_deinit(struct conf_server *cs) { string_deinit(&cs->pname); string_deinit(&cs->name); cs->valid = 0; log_debug(LOG_VVERB, "deinit conf server %p", cs); } rstatus_t conf_server_each_transform(void *elem, void *data) { struct conf_server *cs = elem; struct array *server = data; struct server *s; ASSERT(cs->valid); s = array_push(server); ASSERT(s != NULL); s->idx = array_idx(server, s); s->owner = NULL; s->pname = cs->pname; s->name = cs->name; s->port = (uint16_t)cs->port; s->weight = (uint32_t)cs->weight; s->family = cs->info.family; s->addrlen = cs->info.addrlen; s->addr = (struct sockaddr *)&cs->info.addr; s->ns_conn_q = 0; TAILQ_INIT(&s->s_conn_q); s->next_retry = 0LL; s->failure_count = 0; log_debug(LOG_VERB, "transform to server %"PRIu32" '%.*s'", s->idx, s->pname.len, s->pname.data); return NC_OK; } static rstatus_t conf_pool_init(struct conf_pool *cp, struct string *name) { rstatus_t status; string_init(&cp->name); string_init(&cp->listen.pname); string_init(&cp->listen.name); cp->listen.port = 0; memset(&cp->listen.info, 0, sizeof(cp->listen.info)); cp->listen.valid = 0; cp->hash = CONF_UNSET_HASH; string_init(&cp->hash_tag); cp->distribution = CONF_UNSET_DIST; cp->timeout = CONF_UNSET_NUM; cp->backlog = CONF_UNSET_NUM; cp->client_connections = CONF_UNSET_NUM; cp->redis = CONF_UNSET_NUM; cp->preconnect = CONF_UNSET_NUM; cp->auto_eject_hosts = CONF_UNSET_NUM; cp->server_connections = CONF_UNSET_NUM; cp->server_retry_timeout = CONF_UNSET_NUM; cp->server_failure_limit = CONF_UNSET_NUM; array_null(&cp->server); cp->valid = 0; status = string_duplicate(&cp->name, name); if (status != NC_OK) { return status; } status = array_init(&cp->server, CONF_DEFAULT_SERVERS, sizeof(struct conf_server)); if (status != NC_OK) { string_deinit(&cp->name); return status; } log_debug(LOG_VVERB, "init conf pool %p, '%.*s'", cp, name->len, name->data); return NC_OK; } static void conf_pool_deinit(struct conf_pool *cp) { string_deinit(&cp->name); string_deinit(&cp->listen.pname); string_deinit(&cp->listen.name); while (array_n(&cp->server) != 0) { conf_server_deinit(array_pop(&cp->server)); } array_deinit(&cp->server); log_debug(LOG_VVERB, "deinit conf pool %p", cp); } rstatus_t conf_pool_each_transform(void *elem, void *data) { rstatus_t status; struct conf_pool *cp = elem; struct array *server_pool = data; struct server_pool *sp; ASSERT(cp->valid); sp = array_push(server_pool); ASSERT(sp != NULL); sp->idx = array_idx(server_pool, sp); sp->ctx = NULL; sp->p_conn = NULL; sp->nc_conn_q = 0; TAILQ_INIT(&sp->c_conn_q); array_null(&sp->server); sp->ncontinuum = 0; sp->nserver_continuum = 0; sp->continuum = NULL; sp->nlive_server = 0; sp->next_rebuild = 0LL; sp->name = cp->name; sp->addrstr = cp->listen.pname; sp->port = (uint16_t)cp->listen.port; sp->family = cp->listen.info.family; sp->addrlen = cp->listen.info.addrlen; sp->addr = (struct sockaddr *)&cp->listen.info.addr; sp->key_hash_type = cp->hash; sp->key_hash = hash_algos[cp->hash]; sp->dist_type = cp->distribution; sp->hash_tag = cp->hash_tag; sp->redis = cp->redis ? 1 : 0; sp->timeout = cp->timeout; sp->backlog = cp->backlog; sp->client_connections = (uint32_t)cp->client_connections; sp->server_connections = (uint32_t)cp->server_connections; sp->server_retry_timeout = (int64_t)cp->server_retry_timeout * 1000LL; sp->server_failure_limit = (uint32_t)cp->server_failure_limit; sp->auto_eject_hosts = cp->auto_eject_hosts ? 1 : 0; sp->preconnect = cp->preconnect ? 1 : 0; status = server_init(&sp->server, &cp->server, sp); if (status != NC_OK) { return status; } log_debug(LOG_VERB, "transform to pool %"PRIu32" '%.*s'", sp->idx, sp->name.len, sp->name.data); return NC_OK; } static void conf_dump(struct conf *cf) { uint32_t i, j, npool, nserver; struct conf_pool *cp; struct string *s; npool = array_n(&cf->pool); if (npool == 0) { return; } log_debug(LOG_VVERB, "%"PRIu32" pools in configuration file '%s'", npool, cf->fname); for (i = 0; i < npool; i++) { cp = array_get(&cf->pool, i); log_debug(LOG_VVERB, "%.*s", cp->name.len, cp->name.data); log_debug(LOG_VVERB, " listen: %.*s", cp->listen.pname.len, cp->listen.pname.data); log_debug(LOG_VVERB, " timeout: %d", cp->timeout); log_debug(LOG_VVERB, " backlog: %d", cp->backlog); log_debug(LOG_VVERB, " hash: %d", cp->hash); log_debug(LOG_VVERB, " hash_tag: \"%.*s\"", cp->hash_tag.len, cp->hash_tag.data); log_debug(LOG_VVERB, " distribution: %d", cp->distribution); log_debug(LOG_VVERB, " client_connections: %d", cp->client_connections); log_debug(LOG_VVERB, " redis: %d", cp->redis); log_debug(LOG_VVERB, " preconnect: %d", cp->preconnect); log_debug(LOG_VVERB, " auto_eject_hosts: %d", cp->auto_eject_hosts); log_debug(LOG_VVERB, " server_connections: %d", cp->server_connections); log_debug(LOG_VVERB, " server_retry_timeout: %d", cp->server_retry_timeout); log_debug(LOG_VVERB, " server_failure_limit: %d", cp->server_failure_limit); nserver = array_n(&cp->server); log_debug(LOG_VVERB, " servers: %"PRIu32"", nserver); for (j = 0; j < nserver; j++) { s = array_get(&cp->server, j); log_debug(LOG_VVERB, " %.*s", s->len, s->data); } } } static rstatus_t conf_yaml_init(struct conf *cf) { int rv; ASSERT(!cf->valid_parser); rv = fseek(cf->fh, 0L, SEEK_SET); if (rv < 0) { log_error("conf: failed to seek to the beginning of file '%s': %s", cf->fname, strerror(errno)); return NC_ERROR; } rv = yaml_parser_initialize(&cf->parser); if (!rv) { log_error("conf: failed (err %d) to initialize yaml parser", cf->parser.error); return NC_ERROR; } yaml_parser_set_input_file(&cf->parser, cf->fh); cf->valid_parser = 1; return NC_OK; } static void conf_yaml_deinit(struct conf *cf) { if (cf->valid_parser) { yaml_parser_delete(&cf->parser); cf->valid_parser = 0; } } static rstatus_t conf_token_next(struct conf *cf) { int rv; ASSERT(cf->valid_parser && !cf->valid_token); rv = yaml_parser_scan(&cf->parser, &cf->token); if (!rv) { log_error("conf: failed (err %d) to scan next token", cf->parser.error); return NC_ERROR; } cf->valid_token = 1; return NC_OK; } static void conf_token_done(struct conf *cf) { ASSERT(cf->valid_parser); if (cf->valid_token) { yaml_token_delete(&cf->token); cf->valid_token = 0; } } static rstatus_t conf_event_next(struct conf *cf) { int rv; ASSERT(cf->valid_parser && !cf->valid_event); rv = yaml_parser_parse(&cf->parser, &cf->event); if (!rv) { log_error("conf: failed (err %d) to get next event", cf->parser.error); return NC_ERROR; } cf->valid_event = 1; return NC_OK; } static void conf_event_done(struct conf *cf) { if (cf->valid_event) { yaml_event_delete(&cf->event); cf->valid_event = 0; } } static rstatus_t conf_push_scalar(struct conf *cf) { rstatus_t status; struct string *value; uint8_t *scalar; uint32_t scalar_len; scalar = cf->event.data.scalar.value; scalar_len = (uint32_t)cf->event.data.scalar.length; log_debug(LOG_VVERB, "push '%.*s'", scalar_len, scalar); value = array_push(&cf->arg); if (value == NULL) { return NC_ENOMEM; } string_init(value); status = string_copy(value, scalar, scalar_len); if (status != NC_OK) { array_pop(&cf->arg); return status; } return NC_OK; } static void conf_pop_scalar(struct conf *cf) { struct string *value; value = array_pop(&cf->arg); log_debug(LOG_VVERB, "pop '%.*s'", value->len, value->data); string_deinit(value); } static rstatus_t conf_handler(struct conf *cf, void *data) { struct command *cmd; struct string *key, *value; uint32_t narg; if (array_n(&cf->arg) == 1) { value = array_top(&cf->arg); log_debug(LOG_VVERB, "conf handler on '%.*s'", value->len, value->data); return conf_pool_init(data, value); } narg = array_n(&cf->arg); value = array_get(&cf->arg, narg - 1); key = array_get(&cf->arg, narg - 2); log_debug(LOG_VVERB, "conf handler on %.*s: %.*s", key->len, key->data, value->len, value->data); for (cmd = conf_commands; cmd->name.len != 0; cmd++) { char *rv; if (string_compare(key, &cmd->name) != 0) { continue; } rv = cmd->set(cf, cmd, data); if (rv != CONF_OK) { log_error("conf: directive \"%.*s\" %s", key->len, key->data, rv); return NC_ERROR; } return NC_OK; } log_error("conf: directive \"%.*s\" is unknown", key->len, key->data); return NC_ERROR; } static rstatus_t conf_begin_parse(struct conf *cf) { rstatus_t status; bool done; ASSERT(cf->sound && !cf->parsed); ASSERT(cf->depth == 0); status = conf_yaml_init(cf); if (status != NC_OK) { return status; } done = false; do { status = conf_event_next(cf); if (status != NC_OK) { return status; } log_debug(LOG_VVERB, "next begin event %d", cf->event.type); switch (cf->event.type) { case YAML_STREAM_START_EVENT: case YAML_DOCUMENT_START_EVENT: break; case YAML_MAPPING_START_EVENT: ASSERT(cf->depth < CONF_MAX_DEPTH); cf->depth++; done = true; break; default: NOT_REACHED(); } conf_event_done(cf); } while (!done); return NC_OK; } static rstatus_t conf_end_parse(struct conf *cf) { rstatus_t status; bool done; ASSERT(cf->sound && !cf->parsed); ASSERT(cf->depth == 0); done = false; do { status = conf_event_next(cf); if (status != NC_OK) { return status; } log_debug(LOG_VVERB, "next end event %d", cf->event.type); switch (cf->event.type) { case YAML_STREAM_END_EVENT: done = true; break; case YAML_DOCUMENT_END_EVENT: break; default: NOT_REACHED(); } conf_event_done(cf); } while (!done); conf_yaml_deinit(cf); return NC_OK; } static rstatus_t conf_parse_core(struct conf *cf, void *data) { rstatus_t status; bool done, leaf, new_pool; ASSERT(cf->sound); status = conf_event_next(cf); if (status != NC_OK) { return status; } log_debug(LOG_VVERB, "next event %d depth %"PRIu32" seq %d", cf->event.type, cf->depth, cf->seq); done = false; leaf = false; new_pool = false; switch (cf->event.type) { case YAML_MAPPING_END_EVENT: cf->depth--; if (cf->depth == 1) { conf_pop_scalar(cf); } else if (cf->depth == 0) { done = true; } break; case YAML_MAPPING_START_EVENT: cf->depth++; break; case YAML_SEQUENCE_START_EVENT: cf->seq = 1; break; case YAML_SEQUENCE_END_EVENT: conf_pop_scalar(cf); cf->seq = 0; break; case YAML_SCALAR_EVENT: status = conf_push_scalar(cf); if (status != NC_OK) { break; } /* take appropriate action */ if (cf->seq) { /* for a sequence, leaf is at CONF_MAX_DEPTH */ ASSERT(cf->depth == CONF_MAX_DEPTH); leaf = true; } else if (cf->depth == CONF_ROOT_DEPTH) { /* create new conf_pool */ data = array_push(&cf->pool); if (data == NULL) { status = NC_ENOMEM; break; } new_pool = true; } else if (array_n(&cf->arg) == cf->depth + 1) { /* for {key: value}, leaf is at CONF_MAX_DEPTH */ ASSERT(cf->depth == CONF_MAX_DEPTH); leaf = true; } break; default: NOT_REACHED(); break; } conf_event_done(cf); if (status != NC_OK) { return status; } if (done) { /* terminating condition */ return NC_OK; } if (leaf || new_pool) { status = conf_handler(cf, data); if (leaf) { conf_pop_scalar(cf); if (!cf->seq) { conf_pop_scalar(cf); } } if (status != NC_OK) { return status; } } return conf_parse_core(cf, data); } static rstatus_t conf_parse(struct conf *cf) { rstatus_t status; ASSERT(cf->sound && !cf->parsed); ASSERT(array_n(&cf->arg) == 0); status = conf_begin_parse(cf); if (status != NC_OK) { return status; } status = conf_parse_core(cf, NULL); if (status != NC_OK) { return status; } status = conf_end_parse(cf); if (status != NC_OK) { return status; } cf->parsed = 1; return NC_OK; } static struct conf * conf_open(char *filename) { rstatus_t status; struct conf *cf; FILE *fh; fh = fopen(filename, "r"); if (fh == NULL) { log_error("conf: failed to open configuration '%s': %s", filename, strerror(errno)); return NULL; } cf = nc_alloc(sizeof(*cf)); if (cf == NULL) { fclose(fh); return NULL; } status = array_init(&cf->arg, CONF_DEFAULT_ARGS, sizeof(struct string)); if (status != NC_OK) { nc_free(cf); fclose(fh); return NULL; } status = array_init(&cf->pool, CONF_DEFAULT_POOL, sizeof(struct conf_pool)); if (status != NC_OK) { array_deinit(&cf->arg); nc_free(cf); fclose(fh); return NULL; } cf->fname = filename; cf->fh = fh; cf->depth = 0; /* parser, event, and token are initialized later */ cf->seq = 0; cf->valid_parser = 0; cf->valid_event = 0; cf->valid_token = 0; cf->sound = 0; cf->parsed = 0; cf->valid = 0; log_debug(LOG_VVERB, "opened conf '%s'", filename); return cf; } static rstatus_t conf_validate_document(struct conf *cf) { rstatus_t status; uint32_t count; bool done; status = conf_yaml_init(cf); if (status != NC_OK) { return status; } count = 0; done = false; do { yaml_document_t document; yaml_node_t *node; int rv; rv = yaml_parser_load(&cf->parser, &document); if (!rv) { log_error("conf: failed (err %d) to get the next yaml document", cf->parser.error); conf_yaml_deinit(cf); return NC_ERROR; } node = yaml_document_get_root_node(&document); if (node == NULL) { done = true; } else { count++; } yaml_document_delete(&document); } while (!done); conf_yaml_deinit(cf); if (count != 1) { log_error("conf: '%s' must contain only 1 document; found %"PRIu32" " "documents", cf->fname, count); return NC_ERROR; } return NC_OK; } static rstatus_t conf_validate_tokens(struct conf *cf) { rstatus_t status; bool done, error; int type; status = conf_yaml_init(cf); if (status != NC_OK) { return status; } done = false; error = false; do { status = conf_token_next(cf); if (status != NC_OK) { return status; } type = cf->token.type; switch (type) { case YAML_NO_TOKEN: error = true; log_error("conf: no token (%d) is disallowed", type); break; case YAML_VERSION_DIRECTIVE_TOKEN: error = true; log_error("conf: version directive token (%d) is disallowed", type); break; case YAML_TAG_DIRECTIVE_TOKEN: error = true; log_error("conf: tag directive token (%d) is disallowed", type); break; case YAML_DOCUMENT_START_TOKEN: error = true; log_error("conf: document start token (%d) is disallowed", type); break; case YAML_DOCUMENT_END_TOKEN: error = true; log_error("conf: document end token (%d) is disallowed", type); break; case YAML_FLOW_SEQUENCE_START_TOKEN: error = true; log_error("conf: flow sequence start token (%d) is disallowed", type); break; case YAML_FLOW_SEQUENCE_END_TOKEN: error = true; log_error("conf: flow sequence end token (%d) is disallowed", type); break; case YAML_FLOW_MAPPING_START_TOKEN: error = true; log_error("conf: flow mapping start token (%d) is disallowed", type); break; case YAML_FLOW_MAPPING_END_TOKEN: error = true; log_error("conf: flow mapping end token (%d) is disallowed", type); break; case YAML_FLOW_ENTRY_TOKEN: error = true; log_error("conf: flow entry token (%d) is disallowed", type); break; case YAML_ALIAS_TOKEN: error = true; log_error("conf: alias token (%d) is disallowed", type); break; case YAML_ANCHOR_TOKEN: error = true; log_error("conf: anchor token (%d) is disallowed", type); break; case YAML_TAG_TOKEN: error = true; log_error("conf: tag token (%d) is disallowed", type); break; case YAML_BLOCK_SEQUENCE_START_TOKEN: case YAML_BLOCK_MAPPING_START_TOKEN: case YAML_BLOCK_END_TOKEN: case YAML_BLOCK_ENTRY_TOKEN: break; case YAML_KEY_TOKEN: case YAML_VALUE_TOKEN: case YAML_SCALAR_TOKEN: break; case YAML_STREAM_START_TOKEN: break; case YAML_STREAM_END_TOKEN: done = true; log_debug(LOG_VVERB, "conf '%s' has valid tokens", cf->fname); break; default: error = true; log_error("conf: unknown token (%d) is disallowed", type); break; } conf_token_done(cf); } while (!done && !error); conf_yaml_deinit(cf); return !error ? NC_OK : NC_ERROR; } static rstatus_t conf_validate_structure(struct conf *cf) { rstatus_t status; int type, depth; uint32_t i, count[CONF_MAX_DEPTH + 1]; bool done, error, seq; status = conf_yaml_init(cf); if (status != NC_OK) { return status; } done = false; error = false; seq = false; depth = 0; for (i = 0; i < CONF_MAX_DEPTH + 1; i++) { count[i] = 0; } /* * Validate that the configuration conforms roughly to the following * yaml tree structure: * * keyx: * key1: value1 * key2: value2 * seq: * - elem1 * - elem2 * - elem3 * key3: value3 * * keyy: * key1: value1 * key2: value2 * seq: * - elem1 * - elem2 * - elem3 * key3: value3 */ do { status = conf_event_next(cf); if (status != NC_OK) { return status; } type = cf->event.type; log_debug(LOG_VVERB, "next event %d depth %d seq %d", type, depth, seq); switch (type) { case YAML_STREAM_START_EVENT: case YAML_DOCUMENT_START_EVENT: break; case YAML_DOCUMENT_END_EVENT: break; case YAML_STREAM_END_EVENT: done = true; break; case YAML_MAPPING_START_EVENT: if (depth == CONF_ROOT_DEPTH && count[depth] != 1) { error = true; log_error("conf: '%s' has more than one \"key:value\" at depth" " %d", cf->fname, depth); } else if (depth >= CONF_MAX_DEPTH) { error = true; log_error("conf: '%s' has a depth greater than %d", cf->fname, CONF_MAX_DEPTH); } depth++; break; case YAML_MAPPING_END_EVENT: if (depth == CONF_MAX_DEPTH) { if (seq) { seq = false; } else { error = true; log_error("conf: '%s' missing sequence directive at depth " "%d", cf->fname, depth); } } depth--; count[depth] = 0; break; case YAML_SEQUENCE_START_EVENT: if (seq) { error = true; log_error("conf: '%s' has more than one sequence directive", cf->fname); } else if (depth != CONF_MAX_DEPTH) { error = true; log_error("conf: '%s' has sequence at depth %d instead of %d", cf->fname, depth, CONF_MAX_DEPTH); } else if (count[depth] != 1) { error = true; log_error("conf: '%s' has invalid \"key:value\" at depth %d", cf->fname, depth); } seq = true; break; case YAML_SEQUENCE_END_EVENT: ASSERT(depth == CONF_MAX_DEPTH); count[depth] = 0; break; case YAML_SCALAR_EVENT: if (depth == 0) { error = true; log_error("conf: '%s' has invalid empty \"key:\" at depth %d", cf->fname, depth); } else if (depth == CONF_ROOT_DEPTH && count[depth] != 0) { error = true; log_error("conf: '%s' has invalid mapping \"key:\" at depth %d", cf->fname, depth); } else if (depth == CONF_MAX_DEPTH && count[depth] == 2) { /* found a "key: value", resetting! */ count[depth] = 0; } count[depth]++; break; default: NOT_REACHED(); } conf_event_done(cf); } while (!done && !error); conf_yaml_deinit(cf); return !error ? NC_OK : NC_ERROR; } static rstatus_t conf_pre_validate(struct conf *cf) { rstatus_t status; status = conf_validate_document(cf); if (status != NC_OK) { return status; } status = conf_validate_tokens(cf); if (status != NC_OK) { return status; } status = conf_validate_structure(cf); if (status != NC_OK) { return status; } cf->sound = 1; return NC_OK; } static int conf_server_name_cmp(const void *t1, const void *t2) { const struct conf_server *s1 = t1, *s2 = t2; return string_compare(&s1->name, &s2->name); } static int conf_pool_name_cmp(const void *t1, const void *t2) { const struct conf_pool *p1 = t1, *p2 = t2; return string_compare(&p1->name, &p2->name); } static int conf_pool_listen_cmp(const void *t1, const void *t2) { const struct conf_pool *p1 = t1, *p2 = t2; return string_compare(&p1->listen.pname, &p2->listen.pname); } static rstatus_t conf_validate_server(struct conf *cf, struct conf_pool *cp) { uint32_t i, nserver; bool valid; nserver = array_n(&cp->server); if (nserver == 0) { log_error("conf: pool '%.*s' has no servers", cp->name.len, cp->name.data); return NC_ERROR; } /* * Disallow duplicate servers - servers with identical "host:port:weight" * or "name" combination are considered as duplicates. When server name * is configured, we only check for duplicate "name" and not for duplicate * "host:port:weight" */ array_sort(&cp->server, conf_server_name_cmp); for (valid = true, i = 0; i < nserver - 1; i++) { struct conf_server *cs1, *cs2; cs1 = array_get(&cp->server, i); cs2 = array_get(&cp->server, i + 1); if (string_compare(&cs1->name, &cs2->name) == 0) { log_error("conf: pool '%.*s' has servers with same name '%.*s'", cp->name.len, cp->name.data, cs1->name.len, cs1->name.data); valid = false; break; } } if (!valid) { return NC_ERROR; } return NC_OK; } static rstatus_t conf_validate_pool(struct conf *cf, struct conf_pool *cp) { rstatus_t status; ASSERT(!cp->valid); ASSERT(!string_empty(&cp->name)); if (!cp->listen.valid) { log_error("conf: directive \"listen:\" is missing"); return NC_ERROR; } /* set default values for unset directives */ if (cp->distribution == CONF_UNSET_DIST) { cp->distribution = CONF_DEFAULT_DIST; } if (cp->hash == CONF_UNSET_HASH) { cp->hash = CONF_DEFAULT_HASH; } if (cp->timeout == CONF_UNSET_NUM) { cp->timeout = CONF_DEFAULT_TIMEOUT; } if (cp->backlog == CONF_UNSET_NUM) { cp->backlog = CONF_DEFAULT_LISTEN_BACKLOG; } cp->client_connections = CONF_DEFAULT_CLIENT_CONNECTIONS; if (cp->redis == CONF_UNSET_NUM) { cp->redis = CONF_DEFAULT_REDIS; } if (cp->preconnect == CONF_UNSET_NUM) { cp->preconnect = CONF_DEFAULT_PRECONNECT; } if (cp->auto_eject_hosts == CONF_UNSET_NUM) { cp->auto_eject_hosts = CONF_DEFAULT_AUTO_EJECT_HOSTS; } if (cp->server_connections == CONF_UNSET_NUM) { cp->server_connections = CONF_DEFAULT_SERVER_CONNECTIONS; } else if (cp->server_connections == 0) { log_error("conf: directive \"server_connections:\" cannot be 0"); return NC_ERROR; } if (cp->server_retry_timeout == CONF_UNSET_NUM) { cp->server_retry_timeout = CONF_DEFAULT_SERVER_RETRY_TIMEOUT; } if (cp->server_failure_limit == CONF_UNSET_NUM) { cp->server_failure_limit = CONF_DEFAULT_SERVER_FAILURE_LIMIT; } status = conf_validate_server(cf, cp); if (status != NC_OK) { return status; } cp->valid = 1; return NC_OK; } static rstatus_t conf_post_validate(struct conf *cf) { rstatus_t status; uint32_t i, npool; bool valid; ASSERT(cf->sound && cf->parsed); ASSERT(!cf->valid); npool = array_n(&cf->pool); if (npool == 0) { log_error("conf: '%.*s' has no pools", cf->fname); return NC_ERROR; } /* validate pool */ for (i = 0; i < npool; i++) { struct conf_pool *cp = array_get(&cf->pool, i); status = conf_validate_pool(cf, cp); if (status != NC_OK) { return status; } } /* disallow pools with duplicate listen: key values */ array_sort(&cf->pool, conf_pool_listen_cmp); for (valid = true, i = 0; i < npool - 1; i++) { struct conf_pool *p1, *p2; p1 = array_get(&cf->pool, i); p2 = array_get(&cf->pool, i + 1); if (string_compare(&p1->listen.pname, &p2->listen.pname) == 0) { log_error("conf: pools '%.*s' and '%.*s' have the same listen " "address '%.*s'", p1->name.len, p1->name.data, p2->name.len, p2->name.data, p1->listen.pname.len, p1->listen.pname.data); valid = false; break; } } if (!valid) { return NC_ERROR; } /* disallow pools with duplicate names */ array_sort(&cf->pool, conf_pool_name_cmp); for (valid = true, i = 0; i < npool - 1; i++) { struct conf_pool *p1, *p2; p1 = array_get(&cf->pool, i); p2 = array_get(&cf->pool, i + 1); if (string_compare(&p1->name, &p2->name) == 0) { log_error("conf: '%s' has pools with same name %.*s'", cf->fname, p1->name.len, p1->name.data); valid = false; break; } } if (!valid) { return NC_ERROR; } return NC_OK; } struct conf * conf_create(char *filename) { rstatus_t status; struct conf *cf; cf = conf_open(filename); if (cf == NULL) { return NULL; } /* validate configuration file before parsing */ status = conf_pre_validate(cf); if (status != NC_OK) { goto error; } /* parse the configuration file */ status = conf_parse(cf); if (status != NC_OK) { goto error; } /* validate parsed configuration */ status = conf_post_validate(cf); if (status != NC_OK) { goto error; } conf_dump(cf); fclose(cf->fh); cf->fh = NULL; return cf; error: fclose(cf->fh); cf->fh = NULL; conf_destroy(cf); return NULL; } void conf_destroy(struct conf *cf) { while (array_n(&cf->arg) != 0) { conf_pop_scalar(cf); } array_deinit(&cf->arg); while (array_n(&cf->pool) != 0) { conf_pool_deinit(array_pop(&cf->pool)); } array_deinit(&cf->pool); nc_free(cf); } char * conf_set_string(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; uint8_t *p; struct string *field, *value; p = conf; field = (struct string *)(p + cmd->offset); if (field->data != CONF_UNSET_PTR) { return "is a duplicate"; } value = array_top(&cf->arg); status = string_duplicate(field, value); if (status != NC_OK) { return CONF_ERROR; } return CONF_OK; } char * conf_set_listen(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; struct string *value; struct conf_listen *field; uint8_t *p, *name; uint32_t namelen; p = conf; field = (struct conf_listen *)(p + cmd->offset); if (field->valid == 1) { return "is a duplicate"; } value = array_top(&cf->arg); status = string_duplicate(&field->pname, value); if (status != NC_OK) { return CONF_ERROR; } if (value->data[0] == '/') { name = value->data; namelen = value->len; } else { uint8_t *q, *start, *port; uint32_t portlen; /* parse "hostname:port" from the end */ p = value->data + value->len - 1; start = value->data; q = nc_strrchr(p, start, ':'); if (q == NULL) { return "has an invalid \"hostname:port\" format string"; } port = q + 1; portlen = (uint32_t)(p - port + 1); p = q - 1; name = start; namelen = (uint32_t)(p - start + 1); field->port = nc_atoi(port, portlen); if (field->port < 0 || !nc_valid_port(field->port)) { return "has an invalid port in \"hostname:port\" format string"; } } status = string_copy(&field->name, name, namelen); if (status != NC_OK) { return CONF_ERROR; } status = nc_resolve(&field->name, field->port, &field->info); if (status != NC_OK) { return CONF_ERROR; } field->valid = 1; return CONF_OK; } char * conf_add_server(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; struct array *a; struct string *value; struct conf_server *field; uint8_t *p, *q, *start; uint8_t *pname, *addr, *port, *weight, *name; uint32_t k, delimlen, pnamelen, addrlen, portlen, weightlen, namelen; struct string address; char delim[] = " ::"; string_init(&address); p = conf; a = (struct array *)(p + cmd->offset); field = array_push(a); if (field == NULL) { return CONF_ERROR; } conf_server_init(field); value = array_top(&cf->arg); /* parse "hostname:port:weight [name]" or "/path/unix_socket:weight [name]" from the end */ p = value->data + value->len - 1; start = value->data; addr = NULL; addrlen = 0; weight = NULL; weightlen = 0; port = NULL; portlen = 0; name = NULL; namelen = 0; delimlen = value->data[0] == '/' ? 2 : 3; for (k = 0; k < sizeof(delim); k++) { q = nc_strrchr(p, start, delim[k]); if (q == NULL) { if (k == 0) { /* * name in "hostname:port:weight [name]" format string is * optional */ continue; } break; } switch (k) { case 0: name = q + 1; namelen = (uint32_t)(p - name + 1); break; case 1: weight = q + 1; weightlen = (uint32_t)(p - weight + 1); break; case 2: port = q + 1; portlen = (uint32_t)(p - port + 1); break; default: NOT_REACHED(); } p = q - 1; } if (k != delimlen) { return "has an invalid \"hostname:port:weight [name]\"or \"/path/unix_socket:weight [name]\" format string"; } pname = value->data; pnamelen = namelen > 0 ? value->len - (namelen + 1) : value->len; status = string_copy(&field->pname, pname, pnamelen); if (status != NC_OK) { array_pop(a); return CONF_ERROR; } addr = start; addrlen = (uint32_t)(p - start + 1); field->weight = nc_atoi(weight, weightlen); if (field->weight < 0) { return "has an invalid weight in \"hostname:port:weight [name]\" format string"; } else if (field->weight == 0) { return "has a zero weight in \"hostname:port:weight [name]\" format string"; } if (value->data[0] != '/') { field->port = nc_atoi(port, portlen); if (field->port < 0 || !nc_valid_port(field->port)) { return "has an invalid port in \"hostname:port:weight [name]\" format string"; } } if (name == NULL) { /* * To maintain backward compatibility with libmemcached, we don't * include the port as the part of the input string to the consistent * hashing algorithm, when it is equal to 11211. */ if (field->port == CONF_DEFAULT_KETAMA_PORT) { name = addr; namelen = addrlen; } else { name = addr; namelen = addrlen + 1 + portlen; } } status = string_copy(&field->name, name, namelen); if (status != NC_OK) { return CONF_ERROR; } status = string_copy(&address, addr, addrlen); if (status != NC_OK) { return CONF_ERROR; } status = nc_resolve(&address, field->port, &field->info); if (status != NC_OK) { string_deinit(&address); return CONF_ERROR; } string_deinit(&address); field->valid = 1; return CONF_OK; } char * conf_set_num(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; int num, *np; struct string *value; p = conf; np = (int *)(p + cmd->offset); if (*np != CONF_UNSET_NUM) { return "is a duplicate"; } value = array_top(&cf->arg); num = nc_atoi(value->data, value->len); if (num < 0) { return "is not a number"; } *np = num; return CONF_OK; } char * conf_set_bool(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; int *bp; struct string *value, true_str, false_str; p = conf; bp = (int *)(p + cmd->offset); if (*bp != CONF_UNSET_NUM) { return "is a duplicate"; } value = array_top(&cf->arg); string_set_text(&true_str, "true"); string_set_text(&false_str, "false"); if (string_compare(value, &true_str) == 0) { *bp = 1; } else if (string_compare(value, &false_str) == 0) { *bp = 0; } else { return "is not \"true\" or \"false\""; } return CONF_OK; } char * conf_set_hash(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; hash_type_t *hp; struct string *value, *hash; p = conf; hp = (hash_type_t *)(p + cmd->offset); if (*hp != CONF_UNSET_HASH) { return "is a duplicate"; } value = array_top(&cf->arg); for (hash = hash_strings; hash->len != 0; hash++) { if (string_compare(value, hash) != 0) { continue; } *hp = hash - hash_strings; return CONF_OK; } return "is not a valid hash"; } char * conf_set_distribution(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; dist_type_t *dp; struct string *value, *dist; p = conf; dp = (dist_type_t *)(p + cmd->offset); if (*dp != CONF_UNSET_DIST) { return "is a duplicate"; } value = array_top(&cf->arg); for (dist = dist_strings; dist->len != 0; dist++) { if (string_compare(value, dist) != 0) { continue; } *dp = dist - dist_strings; return CONF_OK; } return "is not a valid distribution"; } char * conf_set_hashtag(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; uint8_t *p; struct string *field, *value; p = conf; field = (struct string *)(p + cmd->offset); if (field->data != CONF_UNSET_PTR) { return "is a duplicate"; } value = array_top(&cf->arg); if (value->len != 2) { return "is not a valid hash tag string with two characters"; } status = string_duplicate(field, value); if (status != NC_OK) { return CONF_ERROR; } return CONF_OK; } nutcracker-0.4.0+dfsg/src/nc_conf.h000066400000000000000000000125011242132376000171370ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CONF_H_ #define _NC_CONF_H_ #include #include #include #include #include #include #define CONF_OK (void *) NULL #define CONF_ERROR (void *) "has an invalid value" #define CONF_ROOT_DEPTH 1 #define CONF_MAX_DEPTH CONF_ROOT_DEPTH + 1 #define CONF_DEFAULT_ARGS 3 #define CONF_DEFAULT_POOL 8 #define CONF_DEFAULT_SERVERS 8 #define CONF_UNSET_NUM -1 #define CONF_UNSET_PTR NULL #define CONF_UNSET_HASH (hash_type_t) -1 #define CONF_UNSET_DIST (dist_type_t) -1 #define CONF_DEFAULT_HASH HASH_FNV1A_64 #define CONF_DEFAULT_DIST DIST_KETAMA #define CONF_DEFAULT_TIMEOUT -1 #define CONF_DEFAULT_LISTEN_BACKLOG 512 #define CONF_DEFAULT_CLIENT_CONNECTIONS 0 #define CONF_DEFAULT_REDIS false #define CONF_DEFAULT_PRECONNECT false #define CONF_DEFAULT_AUTO_EJECT_HOSTS false #define CONF_DEFAULT_SERVER_RETRY_TIMEOUT 30 * 1000 /* in msec */ #define CONF_DEFAULT_SERVER_FAILURE_LIMIT 2 #define CONF_DEFAULT_SERVER_CONNECTIONS 1 #define CONF_DEFAULT_KETAMA_PORT 11211 struct conf_listen { struct string pname; /* listen: as "name:port" */ struct string name; /* name */ int port; /* port */ struct sockinfo info; /* listen socket info */ unsigned valid:1; /* valid? */ }; struct conf_server { struct string pname; /* server: as "name:port:weight" */ struct string name; /* name */ int port; /* port */ int weight; /* weight */ struct sockinfo info; /* connect socket info */ unsigned valid:1; /* valid? */ }; struct conf_pool { struct string name; /* pool name (root node) */ struct conf_listen listen; /* listen: */ hash_type_t hash; /* hash: */ struct string hash_tag; /* hash_tag: */ dist_type_t distribution; /* distribution: */ int timeout; /* timeout: */ int backlog; /* backlog: */ int client_connections; /* client_connections: */ int redis; /* redis: */ int preconnect; /* preconnect: */ int auto_eject_hosts; /* auto_eject_hosts: */ int server_connections; /* server_connections: */ int server_retry_timeout; /* server_retry_timeout: in msec */ int server_failure_limit; /* server_failure_limit: */ struct array server; /* servers: conf_server[] */ unsigned valid:1; /* valid? */ }; struct conf { char *fname; /* file name (ref in argv[]) */ FILE *fh; /* file handle */ struct array arg; /* string[] (parsed {key, value} pairs) */ struct array pool; /* conf_pool[] (parsed pools) */ uint32_t depth; /* parsed tree depth */ yaml_parser_t parser; /* yaml parser */ yaml_event_t event; /* yaml event */ yaml_token_t token; /* yaml token */ unsigned seq:1; /* sequence? */ unsigned valid_parser:1; /* valid parser? */ unsigned valid_event:1; /* valid event? */ unsigned valid_token:1; /* valid token? */ unsigned sound:1; /* sound? */ unsigned parsed:1; /* parsed? */ unsigned valid:1; /* valid? */ }; struct command { struct string name; char *(*set)(struct conf *cf, struct command *cmd, void *data); int offset; }; #define null_command { null_string, NULL, 0 } char *conf_set_string(struct conf *cf, struct command *cmd, void *conf); char *conf_set_listen(struct conf *cf, struct command *cmd, void *conf); char *conf_add_server(struct conf *cf, struct command *cmd, void *conf); char *conf_set_num(struct conf *cf, struct command *cmd, void *conf); char *conf_set_bool(struct conf *cf, struct command *cmd, void *conf); char *conf_set_hash(struct conf *cf, struct command *cmd, void *conf); char *conf_set_distribution(struct conf *cf, struct command *cmd, void *conf); char *conf_set_hashtag(struct conf *cf, struct command *cmd, void *conf); rstatus_t conf_server_each_transform(void *elem, void *data); rstatus_t conf_pool_each_transform(void *elem, void *data); struct conf *conf_create(char *filename); void conf_destroy(struct conf *cf); #endif nutcracker-0.4.0+dfsg/src/nc_connection.c000066400000000000000000000270001242132376000203440ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include /* * nc_connection.[ch] * Connection (struct conn) * + + + * | | | * | Proxy | * | nc_proxy.[ch] | * / \ * Client Server * nc_client.[ch] nc_server.[ch] * * Nutcracker essentially multiplexes m client connections over n server * connections. Usually m >> n, so that nutcracker can pipeline requests * from several clients over a server connection and hence use the connection * bandwidth to the server efficiently * * Client and server connection maintain two fifo queues for requests: * * 1). in_q (imsg_q): queue of incoming requests * 2). out_q (omsg_q): queue of outstanding (outgoing) requests * * Request received over the client connection are forwarded to the server by * enqueuing the request in the chosen server's in_q. From the client's * perspective once the request is forwarded, it is outstanding and is tracked * in the client's out_q (unless the request was tagged as noreply). The server * in turn picks up requests from its own in_q in fifo order and puts them on * the wire. Once the request is outstanding on the wire, and a response is * expected for it, the server keeps track of outstanding requests it in its * own out_q. * * The server's out_q enables us to pair a request with a response while the * client's out_q enables us to pair request and response in the order in * which they are received from the client. * * * Clients Servers * . * in_q: . * out_q: req11 -> req12 . in_q: req22 * (client1) . out_q: req11 -> req21 -> req12 * . (server1) * in_q: . * out_q: req21 -> req22 -> req23 . * (client2) . * . in_q: req23 * . out_q: * . (server2) * * In the above example, client1 has two pipelined requests req11 and req12 * both of which are outstanding on the server connection server1. On the * other hand, client2 has three requests req21, req22 and req23, of which * only req21 is outstanding on the server connection while req22 and * req23 are still waiting to be put on the wire. The fifo of client's * out_q ensures that we always send back the response of request at the head * of the queue, before sending out responses of other completed requests in * the queue. */ static uint32_t nfree_connq; /* # free conn q */ static struct conn_tqh free_connq; /* free conn q */ static uint64_t ntotal_conn; /* total # connections counter from start */ static uint32_t ncurr_conn; /* current # connections */ static uint32_t ncurr_cconn; /* current # client connections */ /* * Return the context associated with this connection. */ struct context * conn_to_ctx(struct conn *conn) { struct server_pool *pool; if (conn->proxy || conn->client) { pool = conn->owner; } else { struct server *server = conn->owner; pool = server->owner; } return pool->ctx; } static struct conn * _conn_get(void) { struct conn *conn; if (!TAILQ_EMPTY(&free_connq)) { ASSERT(nfree_connq > 0); conn = TAILQ_FIRST(&free_connq); nfree_connq--; TAILQ_REMOVE(&free_connq, conn, conn_tqe); } else { conn = nc_alloc(sizeof(*conn)); if (conn == NULL) { return NULL; } } conn->owner = NULL; conn->sd = -1; /* {family, addrlen, addr} are initialized in enqueue handler */ TAILQ_INIT(&conn->imsg_q); TAILQ_INIT(&conn->omsg_q); conn->rmsg = NULL; conn->smsg = NULL; /* * Callbacks {recv, recv_next, recv_done}, {send, send_next, send_done}, * {close, active}, parse, {ref, unref}, {enqueue_inq, dequeue_inq} and * {enqueue_outq, dequeue_outq} are initialized by the wrapper. */ conn->send_bytes = 0; conn->recv_bytes = 0; conn->events = 0; conn->err = 0; conn->recv_active = 0; conn->recv_ready = 0; conn->send_active = 0; conn->send_ready = 0; conn->client = 0; conn->proxy = 0; conn->connecting = 0; conn->connected = 0; conn->eof = 0; conn->done = 0; conn->redis = 0; ntotal_conn++; ncurr_conn++; return conn; } struct conn * conn_get(void *owner, bool client, bool redis) { struct conn *conn; conn = _conn_get(); if (conn == NULL) { return NULL; } /* connection either handles redis or memcache messages */ conn->redis = redis ? 1 : 0; conn->client = client ? 1 : 0; if (conn->client) { /* * client receives a request, possibly parsing it, and sends a * response downstream. */ conn->recv = msg_recv; conn->recv_next = req_recv_next; conn->recv_done = req_recv_done; conn->send = msg_send; conn->send_next = rsp_send_next; conn->send_done = rsp_send_done; conn->close = client_close; conn->active = client_active; conn->ref = client_ref; conn->unref = client_unref; conn->enqueue_inq = NULL; conn->dequeue_inq = NULL; conn->enqueue_outq = req_client_enqueue_omsgq; conn->dequeue_outq = req_client_dequeue_omsgq; ncurr_cconn++; } else { /* * server receives a response, possibly parsing it, and sends a * request upstream. */ conn->recv = msg_recv; conn->recv_next = rsp_recv_next; conn->recv_done = rsp_recv_done; conn->send = msg_send; conn->send_next = req_send_next; conn->send_done = req_send_done; conn->close = server_close; conn->active = server_active; conn->ref = server_ref; conn->unref = server_unref; conn->enqueue_inq = req_server_enqueue_imsgq; conn->dequeue_inq = req_server_dequeue_imsgq; conn->enqueue_outq = req_server_enqueue_omsgq; conn->dequeue_outq = req_server_dequeue_omsgq; } conn->ref(conn, owner); log_debug(LOG_VVERB, "get conn %p client %d", conn, conn->client); return conn; } struct conn * conn_get_proxy(void *owner) { struct server_pool *pool = owner; struct conn *conn; conn = _conn_get(); if (conn == NULL) { return NULL; } conn->redis = pool->redis; conn->proxy = 1; conn->recv = proxy_recv; conn->recv_next = NULL; conn->recv_done = NULL; conn->send = NULL; conn->send_next = NULL; conn->send_done = NULL; conn->close = proxy_close; conn->active = NULL; conn->ref = proxy_ref; conn->unref = proxy_unref; conn->enqueue_inq = NULL; conn->dequeue_inq = NULL; conn->enqueue_outq = NULL; conn->dequeue_outq = NULL; conn->ref(conn, owner); log_debug(LOG_VVERB, "get conn %p proxy %d", conn, conn->proxy); return conn; } static void conn_free(struct conn *conn) { log_debug(LOG_VVERB, "free conn %p", conn); nc_free(conn); } void conn_put(struct conn *conn) { ASSERT(conn->sd < 0); ASSERT(conn->owner == NULL); log_debug(LOG_VVERB, "put conn %p", conn); nfree_connq++; TAILQ_INSERT_HEAD(&free_connq, conn, conn_tqe); if (conn->client) { ncurr_cconn--; } ncurr_conn--; } void conn_init(void) { log_debug(LOG_DEBUG, "conn size %d", sizeof(struct conn)); nfree_connq = 0; TAILQ_INIT(&free_connq); } void conn_deinit(void) { struct conn *conn, *nconn; /* current and next connection */ for (conn = TAILQ_FIRST(&free_connq); conn != NULL; conn = nconn, nfree_connq--) { ASSERT(nfree_connq > 0); nconn = TAILQ_NEXT(conn, conn_tqe); conn_free(conn); } ASSERT(nfree_connq == 0); } ssize_t conn_recv(struct conn *conn, void *buf, size_t size) { ssize_t n; ASSERT(buf != NULL); ASSERT(size > 0); ASSERT(conn->recv_ready); for (;;) { n = nc_read(conn->sd, buf, size); log_debug(LOG_VERB, "recv on sd %d %zd of %zu", conn->sd, n, size); if (n > 0) { if (n < (ssize_t) size) { conn->recv_ready = 0; } conn->recv_bytes += (size_t)n; return n; } if (n == 0) { conn->recv_ready = 0; conn->eof = 1; log_debug(LOG_INFO, "recv on sd %d eof rb %zu sb %zu", conn->sd, conn->recv_bytes, conn->send_bytes); return n; } if (errno == EINTR) { log_debug(LOG_VERB, "recv on sd %d not ready - eintr", conn->sd); continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { conn->recv_ready = 0; log_debug(LOG_VERB, "recv on sd %d not ready - eagain", conn->sd); return NC_EAGAIN; } else { conn->recv_ready = 0; conn->err = errno; log_error("recv on sd %d failed: %s", conn->sd, strerror(errno)); return NC_ERROR; } } NOT_REACHED(); return NC_ERROR; } ssize_t conn_sendv(struct conn *conn, struct array *sendv, size_t nsend) { ssize_t n; ASSERT(array_n(sendv) > 0); ASSERT(nsend != 0); ASSERT(conn->send_ready); for (;;) { n = nc_writev(conn->sd, sendv->elem, sendv->nelem); log_debug(LOG_VERB, "sendv on sd %d %zd of %zu in %"PRIu32" buffers", conn->sd, n, nsend, sendv->nelem); if (n > 0) { if (n < (ssize_t) nsend) { conn->send_ready = 0; } conn->send_bytes += (size_t)n; return n; } if (n == 0) { log_warn("sendv on sd %d returned zero", conn->sd); conn->send_ready = 0; return 0; } if (errno == EINTR) { log_debug(LOG_VERB, "sendv on sd %d not ready - eintr", conn->sd); continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { conn->send_ready = 0; log_debug(LOG_VERB, "sendv on sd %d not ready - eagain", conn->sd); return NC_EAGAIN; } else { conn->send_ready = 0; conn->err = errno; log_error("sendv on sd %d failed: %s", conn->sd, strerror(errno)); return NC_ERROR; } } NOT_REACHED(); return NC_ERROR; } uint32_t conn_ncurr_conn(void) { return ncurr_conn; } uint64_t conn_ntotal_conn(void) { return ntotal_conn; } uint32_t conn_ncurr_cconn(void) { return ncurr_cconn; } nutcracker-0.4.0+dfsg/src/nc_connection.h000066400000000000000000000107651242132376000203630ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CONNECTION_H_ #define _NC_CONNECTION_H_ #include typedef rstatus_t (*conn_recv_t)(struct context *, struct conn*); typedef struct msg* (*conn_recv_next_t)(struct context *, struct conn *, bool); typedef void (*conn_recv_done_t)(struct context *, struct conn *, struct msg *, struct msg *); typedef rstatus_t (*conn_send_t)(struct context *, struct conn*); typedef struct msg* (*conn_send_next_t)(struct context *, struct conn *); typedef void (*conn_send_done_t)(struct context *, struct conn *, struct msg *); typedef void (*conn_close_t)(struct context *, struct conn *); typedef bool (*conn_active_t)(struct conn *); typedef void (*conn_ref_t)(struct conn *, void *); typedef void (*conn_unref_t)(struct conn *); typedef void (*conn_msgq_t)(struct context *, struct conn *, struct msg *); struct conn { TAILQ_ENTRY(conn) conn_tqe; /* link in server_pool / server / free q */ void *owner; /* connection owner - server_pool / server */ int sd; /* socket descriptor */ int family; /* socket address family */ socklen_t addrlen; /* socket length */ struct sockaddr *addr; /* socket address (ref in server or server_pool) */ struct msg_tqh imsg_q; /* incoming request Q */ struct msg_tqh omsg_q; /* outstanding request Q */ struct msg *rmsg; /* current message being rcvd */ struct msg *smsg; /* current message being sent */ conn_recv_t recv; /* recv (read) handler */ conn_recv_next_t recv_next; /* recv next message handler */ conn_recv_done_t recv_done; /* read done handler */ conn_send_t send; /* send (write) handler */ conn_send_next_t send_next; /* write next message handler */ conn_send_done_t send_done; /* write done handler */ conn_close_t close; /* close handler */ conn_active_t active; /* active? handler */ conn_ref_t ref; /* connection reference handler */ conn_unref_t unref; /* connection unreference handler */ conn_msgq_t enqueue_inq; /* connection inq msg enqueue handler */ conn_msgq_t dequeue_inq; /* connection inq msg dequeue handler */ conn_msgq_t enqueue_outq; /* connection outq msg enqueue handler */ conn_msgq_t dequeue_outq; /* connection outq msg dequeue handler */ size_t recv_bytes; /* received (read) bytes */ size_t send_bytes; /* sent (written) bytes */ uint32_t events; /* connection io events */ err_t err; /* connection errno */ unsigned recv_active:1; /* recv active? */ unsigned recv_ready:1; /* recv ready? */ unsigned send_active:1; /* send active? */ unsigned send_ready:1; /* send ready? */ unsigned client:1; /* client? or server? */ unsigned proxy:1; /* proxy? */ unsigned connecting:1; /* connecting? */ unsigned connected:1; /* connected? */ unsigned eof:1; /* eof? aka passive close? */ unsigned done:1; /* done? aka close? */ unsigned redis:1; /* redis? */ }; TAILQ_HEAD(conn_tqh, conn); struct context *conn_to_ctx(struct conn *conn); struct conn *conn_get(void *owner, bool client, bool redis); struct conn *conn_get_proxy(void *owner); void conn_put(struct conn *conn); ssize_t conn_recv(struct conn *conn, void *buf, size_t size); ssize_t conn_sendv(struct conn *conn, struct array *sendv, size_t nsend); void conn_init(void); void conn_deinit(void); uint32_t conn_ncurr_conn(void); uint64_t conn_ntotal_conn(void); uint32_t conn_ncurr_cconn(void); #endif nutcracker-0.4.0+dfsg/src/nc_core.c000066400000000000000000000213411242132376000171370ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include static uint32_t ctx_id; /* context generation */ static rstatus_t core_calc_connections(struct context *ctx) { int status; struct rlimit limit; status = getrlimit(RLIMIT_NOFILE, &limit); if (status < 0) { log_error("getrlimit failed: %s", strerror(errno)); return NC_ERROR; } ctx->max_nfd = (uint32_t)limit.rlim_cur; ctx->max_ncconn = ctx->max_nfd - ctx->max_nsconn - RESERVED_FDS; log_debug(LOG_NOTICE, "max fds %"PRIu32" max client conns %"PRIu32" " "max server conns %"PRIu32"", ctx->max_nfd, ctx->max_ncconn, ctx->max_nsconn); return NC_OK; } static struct context * core_ctx_create(struct instance *nci) { rstatus_t status; struct context *ctx; ctx = nc_alloc(sizeof(*ctx)); if (ctx == NULL) { return NULL; } ctx->id = ++ctx_id; ctx->cf = NULL; ctx->stats = NULL; ctx->evb = NULL; array_null(&ctx->pool); ctx->max_timeout = nci->stats_interval; ctx->timeout = ctx->max_timeout; ctx->max_nfd = 0; ctx->max_ncconn = 0; ctx->max_nsconn = 0; /* parse and create configuration */ ctx->cf = conf_create(nci->conf_filename); if (ctx->cf == NULL) { nc_free(ctx); return NULL; } /* initialize server pool from configuration */ status = server_pool_init(&ctx->pool, &ctx->cf->pool, ctx); if (status != NC_OK) { conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* * Get rlimit and calculate max client connections after we have * calculated max server connections */ status = core_calc_connections(ctx); if (status != NC_OK) { server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* create stats per server pool */ ctx->stats = stats_create(nci->stats_port, nci->stats_addr, nci->stats_interval, nci->hostname, &ctx->pool); if (ctx->stats == NULL) { server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* initialize event handling for client, proxy and server */ ctx->evb = event_base_create(EVENT_SIZE, &core_core); if (ctx->evb == NULL) { stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* preconnect? servers in server pool */ status = server_pool_preconnect(ctx); if (status != NC_OK) { server_pool_disconnect(ctx); event_base_destroy(ctx->evb); stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* initialize proxy per server pool */ status = proxy_init(ctx); if (status != NC_OK) { server_pool_disconnect(ctx); event_base_destroy(ctx->evb); stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } log_debug(LOG_VVERB, "created ctx %p id %"PRIu32"", ctx, ctx->id); return ctx; } static void core_ctx_destroy(struct context *ctx) { log_debug(LOG_VVERB, "destroy ctx %p id %"PRIu32"", ctx, ctx->id); proxy_deinit(ctx); server_pool_disconnect(ctx); event_base_destroy(ctx->evb); stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); } struct context * core_start(struct instance *nci) { struct context *ctx; mbuf_init(nci); msg_init(); conn_init(); ctx = core_ctx_create(nci); if (ctx != NULL) { nci->ctx = ctx; return ctx; } conn_deinit(); msg_deinit(); mbuf_deinit(); return NULL; } void core_stop(struct context *ctx) { conn_deinit(); msg_deinit(); mbuf_deinit(); core_ctx_destroy(ctx); } static rstatus_t core_recv(struct context *ctx, struct conn *conn) { rstatus_t status; status = conn->recv(ctx, conn); if (status != NC_OK) { log_debug(LOG_INFO, "recv on %c %d failed: %s", conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd, strerror(errno)); } return status; } static rstatus_t core_send(struct context *ctx, struct conn *conn) { rstatus_t status; status = conn->send(ctx, conn); if (status != NC_OK) { log_debug(LOG_INFO, "send on %c %d failed: status: %d errno: %d %s", conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd, status, errno, strerror(errno)); } return status; } static void core_close(struct context *ctx, struct conn *conn) { rstatus_t status; char type, *addrstr; ASSERT(conn->sd > 0); if (conn->client) { type = 'c'; addrstr = nc_unresolve_peer_desc(conn->sd); } else { type = conn->proxy ? 'p' : 's'; addrstr = nc_unresolve_addr(conn->addr, conn->addrlen); } log_debug(LOG_NOTICE, "close %c %d '%s' on event %04"PRIX32" eof %d done " "%d rb %zu sb %zu%c %s", type, conn->sd, addrstr, conn->events, conn->eof, conn->done, conn->recv_bytes, conn->send_bytes, conn->err ? ':' : ' ', conn->err ? strerror(conn->err) : ""); status = event_del_conn(ctx->evb, conn); if (status < 0) { log_warn("event del conn %c %d failed, ignored: %s", type, conn->sd, strerror(errno)); } conn->close(ctx, conn); } static void core_error(struct context *ctx, struct conn *conn) { rstatus_t status; char type = conn->client ? 'c' : (conn->proxy ? 'p' : 's'); status = nc_get_soerror(conn->sd); if (status < 0) { log_warn("get soerr on %c %d failed, ignored: %s", type, conn->sd, strerror(errno)); } conn->err = errno; core_close(ctx, conn); } static void core_timeout(struct context *ctx) { for (;;) { struct msg *msg; struct conn *conn; int64_t now, then; msg = msg_tmo_min(); if (msg == NULL) { ctx->timeout = ctx->max_timeout; return; } /* skip over req that are in-error or done */ if (msg->error || msg->done) { msg_tmo_delete(msg); continue; } /* * timeout expired req and all the outstanding req on the timing * out server */ conn = msg->tmo_rbe.data; then = msg->tmo_rbe.key; now = nc_msec_now(); if (now < then) { int delta = (int)(then - now); ctx->timeout = MIN(delta, ctx->max_timeout); return; } log_debug(LOG_INFO, "req %"PRIu64" on s %d timedout", msg->id, conn->sd); msg_tmo_delete(msg); conn->err = ETIMEDOUT; core_close(ctx, conn); } } rstatus_t core_core(void *arg, uint32_t events) { rstatus_t status; struct conn *conn = arg; struct context *ctx = conn_to_ctx(conn); log_debug(LOG_VVERB, "event %04"PRIX32" on %c %d", events, conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd); conn->events = events; /* error takes precedence over read | write */ if (events & EVENT_ERR) { core_error(ctx, conn); return NC_ERROR; } /* read takes precedence over write */ if (events & EVENT_READ) { status = core_recv(ctx, conn); if (status != NC_OK || conn->done || conn->err) { core_close(ctx, conn); return NC_ERROR; } } if (events & EVENT_WRITE) { status = core_send(ctx, conn); if (status != NC_OK || conn->done || conn->err) { core_close(ctx, conn); return NC_ERROR; } } return NC_OK; } rstatus_t core_loop(struct context *ctx) { int nsd; nsd = event_wait(ctx->evb, ctx->timeout); if (nsd < 0) { return nsd; } core_timeout(ctx); stats_swap(ctx->stats); return NC_OK; } nutcracker-0.4.0+dfsg/src/nc_core.h000066400000000000000000000100701242132376000171410ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CORE_H_ #define _NC_CORE_H_ #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_DEBUG_LOG # define NC_DEBUG_LOG 1 #endif #ifdef HAVE_ASSERT_PANIC # define NC_ASSERT_PANIC 1 #endif #ifdef HAVE_ASSERT_LOG # define NC_ASSERT_LOG 1 #endif #ifdef HAVE_STATS # define NC_STATS 1 #else # define NC_STATS 0 #endif #ifdef HAVE_EPOLL # define NC_HAVE_EPOLL 1 #elif HAVE_KQUEUE # define NC_HAVE_KQUEUE 1 #elif HAVE_EVENT_PORTS # define NC_HAVE_EVENT_PORTS 1 #else # error missing scalable I/O event notification mechanism #endif #ifdef HAVE_LITTLE_ENDIAN # define NC_LITTLE_ENDIAN 1 #endif #ifdef HAVE_BACKTRACE # define NC_HAVE_BACKTRACE 1 #endif #define NC_OK 0 #define NC_ERROR -1 #define NC_EAGAIN -2 #define NC_ENOMEM -3 /* reserved fds for std streams, log, stats fd, epoll etc. */ #define RESERVED_FDS 32 typedef int rstatus_t; /* return type */ typedef int err_t; /* error type */ struct array; struct string; struct context; struct conn; struct conn_tqh; struct msg; struct msg_tqh; struct server; struct server_pool; struct mbuf; struct mhdr; struct conf; struct stats; struct instance; struct event_base; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct context { uint32_t id; /* unique context id */ struct conf *cf; /* configuration */ struct stats *stats; /* stats */ struct array pool; /* server_pool[] */ struct event_base *evb; /* event base */ int max_timeout; /* max timeout in msec */ int timeout; /* timeout in msec */ uint32_t max_nfd; /* max # files */ uint32_t max_ncconn; /* max # client connections */ uint32_t max_nsconn; /* max # server connections */ }; struct instance { struct context *ctx; /* active context */ int log_level; /* log level */ char *log_filename; /* log filename */ char *conf_filename; /* configuration filename */ uint16_t stats_port; /* stats monitoring port */ int stats_interval; /* stats aggregation interval */ char *stats_addr; /* stats monitoring addr */ char hostname[NC_MAXHOSTNAMELEN]; /* hostname */ size_t mbuf_chunk_size; /* mbuf chunk size */ pid_t pid; /* process id */ char *pid_filename; /* pid filename */ unsigned pidfile:1; /* pid file created? */ }; struct context *core_start(struct instance *nci); void core_stop(struct context *ctx); rstatus_t core_core(void *arg, uint32_t events); rstatus_t core_loop(struct context *ctx); #endif nutcracker-0.4.0+dfsg/src/nc_log.c000066400000000000000000000160731242132376000167760ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include static struct logger logger; int log_init(int level, char *name) { struct logger *l = &logger; l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB)); l->name = name; if (name == NULL || !strlen(name)) { l->fd = STDERR_FILENO; } else { l->fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644); if (l->fd < 0) { log_stderr("opening log file '%s' failed: %s", name, strerror(errno)); return -1; } } return 0; } void log_deinit(void) { struct logger *l = &logger; if (l->fd < 0 || l->fd == STDERR_FILENO) { return; } close(l->fd); } void log_reopen(void) { struct logger *l = &logger; if (l->fd != STDERR_FILENO) { close(l->fd); l->fd = open(l->name, O_WRONLY | O_APPEND | O_CREAT, 0644); if (l->fd < 0) { log_stderr_safe("reopening log file '%s' failed, ignored: %s", l->name, strerror(errno)); } } } void log_level_up(void) { struct logger *l = &logger; if (l->level < LOG_PVERB) { l->level++; log_safe("up log level to %d", l->level); } } void log_level_down(void) { struct logger *l = &logger; if (l->level > LOG_EMERG) { l->level--; log_safe("down log level to %d", l->level); } } void log_level_set(int level) { struct logger *l = &logger; l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB)); loga("set log level to %d", l->level); } void log_stacktrace(void) { struct logger *l = &logger; if (l->fd < 0) { return; } nc_stacktrace_fd(l->fd); } int log_loggable(int level) { struct logger *l = &logger; if (level > l->level) { return 0; } return 1; } void _log(const char *file, int line, int panic, const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[LOG_MAX_LEN]; va_list args; ssize_t n; struct timeval tv; if (l->fd < 0) { return; } errno_save = errno; len = 0; /* length of output buffer */ size = LOG_MAX_LEN; /* size of output buffer */ gettimeofday(&tv, NULL); buf[len++] = '['; len += nc_strftime(buf + len, size - len, "%Y-%m-%d %H:%M:%S.", localtime(&tv.tv_sec)); len += nc_scnprintf(buf + len, size - len, "%03ld", tv.tv_usec/1000); len += nc_scnprintf(buf + len, size - len, "] %s:%d ", file, line); va_start(args, fmt); len += nc_vscnprintf(buf + len, size - len, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(l->fd, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; if (panic) { abort(); } } void _log_stderr(const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[4 * LOG_MAX_LEN]; va_list args; ssize_t n; errno_save = errno; len = 0; /* length of output buffer */ size = 4 * LOG_MAX_LEN; /* size of output buffer */ va_start(args, fmt); len += nc_vscnprintf(buf, size, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(STDERR_FILENO, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; } /* * Hexadecimal dump in the canonical hex + ascii display * See -C option in man hexdump */ void _log_hexdump(const char *file, int line, char *data, int datalen, const char *fmt, ...) { struct logger *l = &logger; char buf[8 * LOG_MAX_LEN]; int i, off, len, size, errno_save; ssize_t n; if (l->fd < 0) { return; } /* log hexdump */ errno_save = errno; off = 0; /* data offset */ len = 0; /* length of output buffer */ size = 8 * LOG_MAX_LEN; /* size of output buffer */ while (datalen != 0 && (len < size - 1)) { char *save, *str; unsigned char c; int savelen; len += nc_scnprintf(buf + len, size - len, "%08x ", off); save = data; savelen = datalen; for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) { c = (unsigned char)(*data); str = (i == 7) ? " " : " "; len += nc_scnprintf(buf + len, size - len, "%02x%s", c, str); } for ( ; i < 16; i++) { str = (i == 7) ? " " : " "; len += nc_scnprintf(buf + len, size - len, " %s", str); } data = save; datalen = savelen; len += nc_scnprintf(buf + len, size - len, " |"); for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) { c = (unsigned char)(isprint(*data) ? *data : '.'); len += nc_scnprintf(buf + len, size - len, "%c", c); } len += nc_scnprintf(buf + len, size - len, "|\n"); off += 16; } n = nc_write(l->fd, buf, len); if (n < 0) { l->nerror++; } if (len >= size - 1) { n = nc_write(l->fd, "\n", 1); if (n < 0) { l->nerror++; } } errno = errno_save; } void _log_safe(const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[LOG_MAX_LEN]; va_list args; ssize_t n; if (l->fd < 0) { return; } errno_save = errno; len = 0; /* length of output buffer */ size = LOG_MAX_LEN; /* size of output buffer */ len += nc_safe_snprintf(buf + len, size - len, "[.......................] "); va_start(args, fmt); len += nc_safe_vsnprintf(buf + len, size - len, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(l->fd, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; } void _log_stderr_safe(const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[LOG_MAX_LEN]; va_list args; ssize_t n; errno_save = errno; len = 0; /* length of output buffer */ size = LOG_MAX_LEN; /* size of output buffer */ len += nc_safe_snprintf(buf + len, size - len, "[.......................] "); va_start(args, fmt); len += nc_safe_vsnprintf(buf + len, size - len, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(STDERR_FILENO, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; } nutcracker-0.4.0+dfsg/src/nc_log.h000066400000000000000000000124371242132376000170030ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_LOG_H_ #define _NC_LOG_H_ struct logger { char *name; /* log file name */ int level; /* log level */ int fd; /* log file descriptor */ int nerror; /* # log error */ }; #define LOG_EMERG 0 /* system in unusable */ #define LOG_ALERT 1 /* action must be taken immediately */ #define LOG_CRIT 2 /* critical conditions */ #define LOG_ERR 3 /* error conditions */ #define LOG_WARN 4 /* warning conditions */ #define LOG_NOTICE 5 /* normal but significant condition (default) */ #define LOG_INFO 6 /* informational */ #define LOG_DEBUG 7 /* debug messages */ #define LOG_VERB 8 /* verbose messages */ #define LOG_VVERB 9 /* verbose messages on crack */ #define LOG_VVVERB 10 /* verbose messages on ganga */ #define LOG_PVERB 11 /* periodic verbose messages on crack */ #define LOG_MAX_LEN 256 /* max length of log message */ /* * log_stderr - log to stderr * loga - log always * loga_hexdump - log hexdump always * log_error - error log messages * log_warn - warning log messages * log_panic - log messages followed by a panic * ... * log_debug - debug log messages based on a log level * log_hexdump - hexadump -C of a log buffer */ #ifdef NC_DEBUG_LOG #define log_debug(_level, ...) do { \ if (log_loggable(_level) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } \ } while (0) #define log_hexdump(_level, _data, _datalen, ...) do { \ if (log_loggable(_level) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ __VA_ARGS__); \ } \ } while (0) #else #define log_debug(_level, ...) #define log_hexdump(_level, _data, _datalen, ...) #endif #define log_stderr(...) do { \ _log_stderr(__VA_ARGS__); \ } while (0) #define log_safe(...) do { \ _log_safe(__VA_ARGS__); \ } while (0) #define log_stderr_safe(...) do { \ _log_stderr_safe(__VA_ARGS__); \ } while (0) #define loga(...) do { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } while (0) #define loga_hexdump(_data, _datalen, ...) do { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ __VA_ARGS__); \ } while (0) \ #define log_error(...) do { \ if (log_loggable(LOG_ALERT) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } \ } while (0) #define log_warn(...) do { \ if (log_loggable(LOG_WARN) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } \ } while (0) #define log_panic(...) do { \ if (log_loggable(LOG_EMERG) != 0) { \ _log(__FILE__, __LINE__, 1, __VA_ARGS__); \ } \ } while (0) int log_init(int level, char *filename); void log_deinit(void); void log_level_up(void); void log_level_down(void); void log_level_set(int level); void log_stacktrace(void); void log_reopen(void); int log_loggable(int level); void _log(const char *file, int line, int panic, const char *fmt, ...); void _log_stderr(const char *fmt, ...); void _log_safe(const char *fmt, ...); void _log_stderr_safe(const char *fmt, ...); void _log_hexdump(const char *file, int line, char *data, int datalen, const char *fmt, ...); #endif nutcracker-0.4.0+dfsg/src/nc_mbuf.c000066400000000000000000000152041242132376000171410ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include static uint32_t nfree_mbufq; /* # free mbuf */ static struct mhdr free_mbufq; /* free mbuf q */ static size_t mbuf_chunk_size; /* mbuf chunk size - header + data (const) */ static size_t mbuf_offset; /* mbuf offset in chunk (const) */ static struct mbuf * _mbuf_get(void) { struct mbuf *mbuf; uint8_t *buf; if (!STAILQ_EMPTY(&free_mbufq)) { ASSERT(nfree_mbufq > 0); mbuf = STAILQ_FIRST(&free_mbufq); nfree_mbufq--; STAILQ_REMOVE_HEAD(&free_mbufq, next); ASSERT(mbuf->magic == MBUF_MAGIC); goto done; } buf = nc_alloc(mbuf_chunk_size); if (buf == NULL) { return NULL; } /* * mbuf header is at the tail end of the mbuf. This enables us to catch * buffer overrun early by asserting on the magic value during get or * put operations * * <------------- mbuf_chunk_size -------------> * +-------------------------------------------+ * | mbuf data | mbuf header | * | (mbuf_offset) | (struct mbuf) | * +-------------------------------------------+ * ^ ^ ^ ^^ * | | | || * \ | | |\ * mbuf->start \ | | mbuf->end (one byte past valid bound) * mbuf->pos \ * \ mbuf * mbuf->last (one byte past valid byte) * */ mbuf = (struct mbuf *)(buf + mbuf_offset); mbuf->magic = MBUF_MAGIC; done: STAILQ_NEXT(mbuf, next) = NULL; return mbuf; } struct mbuf * mbuf_get(void) { struct mbuf *mbuf; uint8_t *buf; mbuf = _mbuf_get(); if (mbuf == NULL) { return NULL; } buf = (uint8_t *)mbuf - mbuf_offset; mbuf->start = buf; mbuf->end = buf + mbuf_offset; ASSERT(mbuf->end - mbuf->start == (int)mbuf_offset); ASSERT(mbuf->start < mbuf->end); mbuf->pos = mbuf->start; mbuf->last = mbuf->start; log_debug(LOG_VVERB, "get mbuf %p", mbuf); return mbuf; } static void mbuf_free(struct mbuf *mbuf) { uint8_t *buf; log_debug(LOG_VVERB, "put mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); ASSERT(STAILQ_NEXT(mbuf, next) == NULL); ASSERT(mbuf->magic == MBUF_MAGIC); buf = (uint8_t *)mbuf - mbuf_offset; nc_free(buf); } void mbuf_put(struct mbuf *mbuf) { log_debug(LOG_VVERB, "put mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); ASSERT(STAILQ_NEXT(mbuf, next) == NULL); ASSERT(mbuf->magic == MBUF_MAGIC); nfree_mbufq++; STAILQ_INSERT_HEAD(&free_mbufq, mbuf, next); } /* * Rewind the mbuf by discarding any of the read or unread data that it * might hold. */ void mbuf_rewind(struct mbuf *mbuf) { mbuf->pos = mbuf->start; mbuf->last = mbuf->start; } /* * Return the length of data in mbuf. Mbuf cannot contain more than * 2^32 bytes (4G). */ uint32_t mbuf_length(struct mbuf *mbuf) { ASSERT(mbuf->last >= mbuf->pos); return (uint32_t)(mbuf->last - mbuf->pos); } /* * Return the remaining space size for any new data in mbuf. Mbuf cannot * contain more than 2^32 bytes (4G). */ uint32_t mbuf_size(struct mbuf *mbuf) { ASSERT(mbuf->end >= mbuf->last); return (uint32_t)(mbuf->end - mbuf->last); } /* * Return the maximum available space size for data in any mbuf. Mbuf cannot * contain more than 2^32 bytes (4G). */ size_t mbuf_data_size(void) { return mbuf_offset; } /* * Insert mbuf at the tail of the mhdr Q */ void mbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf) { STAILQ_INSERT_TAIL(mhdr, mbuf, next); log_debug(LOG_VVERB, "insert mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); } /* * Remove mbuf from the mhdr Q */ void mbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf) { log_debug(LOG_VVERB, "remove mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); STAILQ_REMOVE(mhdr, mbuf, mbuf, next); STAILQ_NEXT(mbuf, next) = NULL; } /* * Copy n bytes from memory area pos to mbuf. * * The memory areas should not overlap and the mbuf should have * enough space for n bytes. */ void mbuf_copy(struct mbuf *mbuf, uint8_t *pos, size_t n) { if (n == 0) { return; } /* mbuf has space for n bytes */ ASSERT(!mbuf_full(mbuf) && n <= mbuf_size(mbuf)); /* no overlapping copy */ ASSERT(pos < mbuf->start || pos >= mbuf->end); nc_memcpy(mbuf->last, pos, n); mbuf->last += n; } /* * Split mbuf h into h and t by copying data from h to t. Before * the copy, we invoke a precopy handler cb that will copy a predefined * string to the head of t. * * Return new mbuf t, if the split was successful. */ struct mbuf * mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg) { struct mbuf *mbuf, *nbuf; size_t size; ASSERT(!STAILQ_EMPTY(h)); mbuf = STAILQ_LAST(h, mbuf, next); ASSERT(pos >= mbuf->pos && pos <= mbuf->last); nbuf = mbuf_get(); if (nbuf == NULL) { return NULL; } if (cb != NULL) { /* precopy nbuf */ cb(nbuf, cbarg); } /* copy data from mbuf to nbuf */ size = (size_t)(mbuf->last - pos); mbuf_copy(nbuf, pos, size); /* adjust mbuf */ mbuf->last = pos; log_debug(LOG_VVERB, "split into mbuf %p len %"PRIu32" and nbuf %p len " "%"PRIu32" copied %zu bytes", mbuf, mbuf_length(mbuf), nbuf, mbuf_length(nbuf), size); return nbuf; } void mbuf_init(struct instance *nci) { nfree_mbufq = 0; STAILQ_INIT(&free_mbufq); mbuf_chunk_size = nci->mbuf_chunk_size; mbuf_offset = mbuf_chunk_size - MBUF_HSIZE; log_debug(LOG_DEBUG, "mbuf hsize %d chunk size %zu offset %zu length %zu", MBUF_HSIZE, mbuf_chunk_size, mbuf_offset, mbuf_offset); } void mbuf_deinit(void) { while (!STAILQ_EMPTY(&free_mbufq)) { struct mbuf *mbuf = STAILQ_FIRST(&free_mbufq); mbuf_remove(&free_mbufq, mbuf); mbuf_free(mbuf); nfree_mbufq--; } ASSERT(nfree_mbufq == 0); } nutcracker-0.4.0+dfsg/src/nc_mbuf.h000066400000000000000000000040241242132376000171440ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_MBUF_H_ #define _NC_MBUF_H_ #include typedef void (*mbuf_copy_t)(struct mbuf *, void *); struct mbuf { uint32_t magic; /* mbuf magic (const) */ STAILQ_ENTRY(mbuf) next; /* next mbuf */ uint8_t *pos; /* read marker */ uint8_t *last; /* write marker */ uint8_t *start; /* start of buffer (const) */ uint8_t *end; /* end of buffer (const) */ }; STAILQ_HEAD(mhdr, mbuf); #define MBUF_MAGIC 0xdeadbeef #define MBUF_MIN_SIZE 512 #define MBUF_MAX_SIZE 16777216 #define MBUF_SIZE 16384 #define MBUF_HSIZE sizeof(struct mbuf) static inline bool mbuf_empty(struct mbuf *mbuf) { return mbuf->pos == mbuf->last ? true : false; } static inline bool mbuf_full(struct mbuf *mbuf) { return mbuf->last == mbuf->end ? true : false; } void mbuf_init(struct instance *nci); void mbuf_deinit(void); struct mbuf *mbuf_get(void); void mbuf_put(struct mbuf *mbuf); void mbuf_rewind(struct mbuf *mbuf); uint32_t mbuf_length(struct mbuf *mbuf); uint32_t mbuf_size(struct mbuf *mbuf); size_t mbuf_data_size(void); void mbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf); void mbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf); void mbuf_copy(struct mbuf *mbuf, uint8_t *pos, size_t n); struct mbuf *mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg); #endif nutcracker-0.4.0+dfsg/src/nc_message.c000066400000000000000000000524211242132376000176360ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #if (IOV_MAX > 128) #define NC_IOV_MAX 128 #else #define NC_IOV_MAX IOV_MAX #endif /* * nc_message.[ch] * message (struct msg) * + + . * | | . * / \ . * Request Response .../ nc_mbuf.[ch] (mesage buffers) * nc_request.c nc_response.c .../ nc_memcache.c; nc_redis.c (message parser) * * Messages in nutcracker are manipulated by a chain of processing handlers, * where each handler is responsible for taking the input and producing an * output for the next handler in the chain. This mechanism of processing * loosely conforms to the standard chain-of-responsibility design pattern * * At the high level, each handler takes in a message: request or response * and produces the message for the next handler in the chain. The input * for a handler is either a request or response, but never both and * similarly the output of an handler is either a request or response or * nothing. * * Each handler itself is composed of two processing units: * * 1). filter: manipulates output produced by the handler, usually based * on a policy. If needed, multiple filters can be hooked into each * location. * 2). forwarder: chooses one of the backend servers to send the request * to, usually based on the configured distribution and key hasher. * * Handlers are registered either with Client or Server or Proxy * connections. A Proxy connection only has a read handler as it is only * responsible for accepting new connections from client. Read handler * (conn_recv_t) registered with client is responsible for reading requests, * while that registered with server is responsible for reading responses. * Write handler (conn_send_t) registered with client is responsible for * writing response, while that registered with server is responsible for * writing requests. * * Note that in the above discussion, the terminology send is used * synonymously with write or OUT event. Similarly recv is used synonymously * with read or IN event * * Client+ Proxy Server+ * (nutcracker) * . * msg_recv {read event} . msg_recv {read event} * + . + * | . | * \ . / * req_recv_next . rsp_recv_next * + . + * | . | Rsp * req_recv_done . rsp_recv_done <=== * + . + * | . | * Req \ . / * ===> req_filter* . *rsp_filter * + . + * | . | * \ . / * req_forward-// (a) . (c) \\-rsp_forward * . * . * msg_send {write event} . msg_send {write event} * + . + * | . | * Rsp' \ . / Req' * <=== rsp_send_next . req_send_next ===> * + . + * | . | * \ . / * rsp_send_done-// (d) . (b) //-req_send_done * * * (a) -> (b) -> (c) -> (d) is the normal flow of transaction consisting * of a single request response, where (a) and (b) handle request from * client, while (c) and (d) handle the corresponding response from the * server. */ static uint64_t msg_id; /* message id counter */ static uint64_t frag_id; /* fragment id counter */ static uint32_t nfree_msgq; /* # free msg q */ static struct msg_tqh free_msgq; /* free msg q */ static struct rbtree tmo_rbt; /* timeout rbtree */ static struct rbnode tmo_rbs; /* timeout rbtree sentinel */ #define DEFINE_ACTION(_name) string(#_name), static struct string msg_type_strings[] = { MSG_TYPE_CODEC( DEFINE_ACTION ) null_string }; #undef DEFINE_ACTION static struct msg * msg_from_rbe(struct rbnode *node) { struct msg *msg; int offset; offset = offsetof(struct msg, tmo_rbe); msg = (struct msg *)((char *)node - offset); return msg; } struct msg * msg_tmo_min(void) { struct rbnode *node; node = rbtree_min(&tmo_rbt); if (node == NULL) { return NULL; } return msg_from_rbe(node); } void msg_tmo_insert(struct msg *msg, struct conn *conn) { struct rbnode *node; int timeout; ASSERT(msg->request); ASSERT(!msg->quit && !msg->noreply); timeout = server_timeout(conn); if (timeout <= 0) { return; } node = &msg->tmo_rbe; node->key = nc_msec_now() + timeout; node->data = conn; rbtree_insert(&tmo_rbt, node); log_debug(LOG_VERB, "insert msg %"PRIu64" into tmo rbt with expiry of " "%d msec", msg->id, timeout); } void msg_tmo_delete(struct msg *msg) { struct rbnode *node; node = &msg->tmo_rbe; /* already deleted */ if (node->data == NULL) { return; } rbtree_delete(&tmo_rbt, node); log_debug(LOG_VERB, "delete msg %"PRIu64" from tmo rbt", msg->id); } static struct msg * _msg_get(void) { struct msg *msg; if (!TAILQ_EMPTY(&free_msgq)) { ASSERT(nfree_msgq > 0); msg = TAILQ_FIRST(&free_msgq); nfree_msgq--; TAILQ_REMOVE(&free_msgq, msg, m_tqe); goto done; } msg = nc_alloc(sizeof(*msg)); if (msg == NULL) { return NULL; } done: /* c_tqe, s_tqe, and m_tqe are left uninitialized */ msg->id = ++msg_id; msg->peer = NULL; msg->owner = NULL; rbtree_node_init(&msg->tmo_rbe); STAILQ_INIT(&msg->mhdr); msg->mlen = 0; msg->start_ts = 0; msg->state = 0; msg->pos = NULL; msg->token = NULL; msg->parser = NULL; msg->result = MSG_PARSE_OK; msg->fragment = NULL; msg->reply = NULL; msg->pre_coalesce = NULL; msg->post_coalesce = NULL; msg->type = MSG_UNKNOWN; msg->keys = array_create(1, sizeof(struct keypos)); if (msg->keys == NULL) { nc_free(msg); return NULL; } msg->vlen = 0; msg->end = NULL; msg->frag_owner = NULL; msg->frag_seq = NULL; msg->nfrag = 0; msg->nfrag_done = 0; msg->frag_id = 0; msg->narg_start = NULL; msg->narg_end = NULL; msg->narg = 0; msg->rnarg = 0; msg->rlen = 0; msg->integer = 0; msg->err = 0; msg->error = 0; msg->ferror = 0; msg->request = 0; msg->quit = 0; msg->noreply = 0; msg->noforward = 0; msg->done = 0; msg->fdone = 0; msg->swallow = 0; msg->redis = 0; return msg; } struct msg * msg_get(struct conn *conn, bool request, bool redis) { struct msg *msg; msg = _msg_get(); if (msg == NULL) { return NULL; } msg->owner = conn; msg->request = request ? 1 : 0; msg->redis = redis ? 1 : 0; if (redis) { if (request) { msg->parser = redis_parse_req; } else { msg->parser = redis_parse_rsp; } msg->fragment = redis_fragment; msg->reply = redis_reply; msg->pre_coalesce = redis_pre_coalesce; msg->post_coalesce = redis_post_coalesce; } else { if (request) { msg->parser = memcache_parse_req; } else { msg->parser = memcache_parse_rsp; } msg->fragment = memcache_fragment; msg->pre_coalesce = memcache_pre_coalesce; msg->post_coalesce = memcache_post_coalesce; } if (log_loggable(LOG_NOTICE) != 0) { msg->start_ts = nc_usec_now(); } log_debug(LOG_VVERB, "get msg %p id %"PRIu64" request %d owner sd %d", msg, msg->id, msg->request, conn->sd); return msg; } struct msg * msg_get_error(bool redis, err_t err) { struct msg *msg; struct mbuf *mbuf; int n; char *errstr = err ? strerror(err) : "unknown"; char *protstr = redis ? "-ERR" : "SERVER_ERROR"; msg = _msg_get(); if (msg == NULL) { return NULL; } msg->state = 0; msg->type = MSG_RSP_MC_SERVER_ERROR; mbuf = mbuf_get(); if (mbuf == NULL) { msg_put(msg); return NULL; } mbuf_insert(&msg->mhdr, mbuf); n = nc_scnprintf(mbuf->last, mbuf_size(mbuf), "%s %s"CRLF, protstr, errstr); mbuf->last += n; msg->mlen = (uint32_t)n; log_debug(LOG_VVERB, "get msg %p id %"PRIu64" len %"PRIu32" error '%s'", msg, msg->id, msg->mlen, errstr); return msg; } static void msg_free(struct msg *msg) { ASSERT(STAILQ_EMPTY(&msg->mhdr)); log_debug(LOG_VVERB, "free msg %p id %"PRIu64"", msg, msg->id); nc_free(msg); } void msg_put(struct msg *msg) { log_debug(LOG_VVERB, "put msg %p id %"PRIu64"", msg, msg->id); while (!STAILQ_EMPTY(&msg->mhdr)) { struct mbuf *mbuf = STAILQ_FIRST(&msg->mhdr); mbuf_remove(&msg->mhdr, mbuf); mbuf_put(mbuf); } if (msg->frag_seq) { nc_free(msg->frag_seq); msg->frag_seq = NULL; } if (msg->keys) { msg->keys->nelem = 0; /* a hack here */ array_destroy(msg->keys); msg->keys = NULL; } nfree_msgq++; TAILQ_INSERT_HEAD(&free_msgq, msg, m_tqe); } void msg_dump(struct msg *msg, int level) { struct mbuf *mbuf; if (log_loggable(level) == 0) { return; } loga("msg dump id %"PRIu64" request %d len %"PRIu32" type %d done %d " "error %d (err %d)", msg->id, msg->request, msg->mlen, msg->type, msg->done, msg->error, msg->err); STAILQ_FOREACH(mbuf, &msg->mhdr, next) { uint8_t *p, *q; long int len; p = mbuf->start; q = mbuf->last; len = q - p; loga_hexdump(p, len, "mbuf [%p] with %ld bytes of data", p, len); } } void msg_init(void) { log_debug(LOG_DEBUG, "msg size %d", sizeof(struct msg)); msg_id = 0; frag_id = 0; nfree_msgq = 0; TAILQ_INIT(&free_msgq); rbtree_init(&tmo_rbt, &tmo_rbs); } void msg_deinit(void) { struct msg *msg, *nmsg; for (msg = TAILQ_FIRST(&free_msgq); msg != NULL; msg = nmsg, nfree_msgq--) { ASSERT(nfree_msgq > 0); nmsg = TAILQ_NEXT(msg, m_tqe); msg_free(msg); } ASSERT(nfree_msgq == 0); } struct string * msg_type_string(msg_type_t type) { return &msg_type_strings[type]; } bool msg_empty(struct msg *msg) { return msg->mlen == 0 ? true : false; } uint32_t msg_backend_idx(struct msg *msg, uint8_t *key, uint32_t keylen) { struct conn *conn = msg->owner; struct server_pool *pool = conn->owner; return server_pool_idx(pool, key, keylen); } struct mbuf * msg_ensure_mbuf(struct msg *msg, size_t len) { struct mbuf *mbuf; if (STAILQ_EMPTY(&msg->mhdr) || mbuf_size(STAILQ_LAST(&msg->mhdr, mbuf, next)) < len) { mbuf = mbuf_get(); if (mbuf == NULL) { return NULL; } mbuf_insert(&msg->mhdr, mbuf); } else { mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next); } return mbuf; } /* * append small(small than a mbuf) content into msg */ rstatus_t msg_append(struct msg *msg, uint8_t *pos, size_t n) { struct mbuf *mbuf; ASSERT(n <= mbuf_data_size()); mbuf = msg_ensure_mbuf(msg, n); if (mbuf == NULL) { return NC_ENOMEM; } ASSERT(n <= mbuf_size(mbuf)); mbuf_copy(mbuf, pos, n); msg->mlen += (uint32_t)n; return NC_OK; } /* * prepend small(small than a mbuf) content into msg */ rstatus_t msg_prepend(struct msg *msg, uint8_t *pos, size_t n) { struct mbuf *mbuf; mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } ASSERT(n <= mbuf_size(mbuf)); mbuf_copy(mbuf, pos, n); msg->mlen += (uint32_t)n; STAILQ_INSERT_HEAD(&msg->mhdr, mbuf, next); return NC_OK; } /* * prepend small(small than a mbuf) content into msg */ rstatus_t msg_prepend_format(struct msg *msg, const char *fmt, ...) { struct mbuf *mbuf; int32_t n; va_list args; mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } va_start(args, fmt); n = nc_vscnprintf(mbuf->last, mbuf_size(mbuf), fmt, args); va_end(args); mbuf->last += n; msg->mlen += (uint32_t)n; ASSERT(mbuf_size(mbuf) >= 0); STAILQ_INSERT_HEAD(&msg->mhdr, mbuf, next); return NC_OK; } inline uint64_t msg_gen_frag_id(void) { return ++frag_id; } static rstatus_t msg_parsed(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *nmsg; struct mbuf *mbuf, *nbuf; mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next); if (msg->pos == mbuf->last) { /* no more data to parse */ conn->recv_done(ctx, conn, msg, NULL); return NC_OK; } /* * Input mbuf has un-parsed data. Split mbuf of the current message msg * into (mbuf, nbuf), where mbuf is the portion of the message that has * been parsed and nbuf is the portion of the message that is un-parsed. * Parse nbuf as a new message nmsg in the next iteration. */ nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL); if (nbuf == NULL) { return NC_ENOMEM; } nmsg = msg_get(msg->owner, msg->request, conn->redis); if (nmsg == NULL) { mbuf_put(nbuf); return NC_ENOMEM; } mbuf_insert(&nmsg->mhdr, nbuf); nmsg->pos = nbuf->pos; /* update length of current (msg) and new message (nmsg) */ nmsg->mlen = mbuf_length(nbuf); msg->mlen -= nmsg->mlen; conn->recv_done(ctx, conn, msg, nmsg); return NC_OK; } static rstatus_t msg_repair(struct context *ctx, struct conn *conn, struct msg *msg) { struct mbuf *nbuf; nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL); if (nbuf == NULL) { return NC_ENOMEM; } mbuf_insert(&msg->mhdr, nbuf); msg->pos = nbuf->pos; return NC_OK; } static rstatus_t msg_parse(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; if (msg_empty(msg)) { /* no data to parse */ conn->recv_done(ctx, conn, msg, NULL); return NC_OK; } msg->parser(msg); switch (msg->result) { case MSG_PARSE_OK: status = msg_parsed(ctx, conn, msg); break; case MSG_PARSE_REPAIR: status = msg_repair(ctx, conn, msg); break; case MSG_PARSE_AGAIN: status = NC_OK; break; default: status = NC_ERROR; conn->err = errno; break; } return conn->err != 0 ? NC_ERROR : status; } static rstatus_t msg_recv_chain(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; struct msg *nmsg; struct mbuf *mbuf; size_t msize; ssize_t n; mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next); if (mbuf == NULL || mbuf_full(mbuf)) { mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_insert(&msg->mhdr, mbuf); msg->pos = mbuf->pos; } ASSERT(mbuf->end - mbuf->last > 0); msize = mbuf_size(mbuf); n = conn_recv(conn, mbuf->last, msize); if (n < 0) { if (n == NC_EAGAIN) { return NC_OK; } return NC_ERROR; } ASSERT((mbuf->last + n) <= mbuf->end); mbuf->last += n; msg->mlen += (uint32_t)n; for (;;) { status = msg_parse(ctx, conn, msg); if (status != NC_OK) { return status; } /* get next message to parse */ nmsg = conn->recv_next(ctx, conn, false); if (nmsg == NULL || nmsg == msg) { /* no more data to parse */ break; } msg = nmsg; } return NC_OK; } rstatus_t msg_recv(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg; ASSERT(conn->recv_active); conn->recv_ready = 1; do { msg = conn->recv_next(ctx, conn, true); if (msg == NULL) { return NC_OK; } status = msg_recv_chain(ctx, conn, msg); if (status != NC_OK) { return status; } } while (conn->recv_ready); return NC_OK; } static rstatus_t msg_send_chain(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg_tqh send_msgq; /* send msg q */ struct msg *nmsg; /* next msg */ struct mbuf *mbuf, *nbuf; /* current and next mbuf */ size_t mlen; /* current mbuf data length */ struct iovec *ciov, iov[NC_IOV_MAX]; /* current iovec */ struct array sendv; /* send iovec */ size_t nsend, nsent; /* bytes to send; bytes sent */ size_t limit; /* bytes to send limit */ ssize_t n; /* bytes sent by sendv */ TAILQ_INIT(&send_msgq); array_set(&sendv, iov, sizeof(iov[0]), NC_IOV_MAX); /* preprocess - build iovec */ nsend = 0; /* * readv() and writev() returns EINVAL if the sum of the iov_len values * overflows an ssize_t value Or, the vector count iovcnt is less than * zero or greater than the permitted maximum. */ limit = SSIZE_MAX; for (;;) { ASSERT(conn->smsg == msg); TAILQ_INSERT_TAIL(&send_msgq, msg, m_tqe); for (mbuf = STAILQ_FIRST(&msg->mhdr); mbuf != NULL && array_n(&sendv) < NC_IOV_MAX && nsend < limit; mbuf = nbuf) { nbuf = STAILQ_NEXT(mbuf, next); if (mbuf_empty(mbuf)) { continue; } mlen = mbuf_length(mbuf); if ((nsend + mlen) > limit) { mlen = limit - nsend; } ciov = array_push(&sendv); ciov->iov_base = mbuf->pos; ciov->iov_len = mlen; nsend += mlen; } if (array_n(&sendv) >= NC_IOV_MAX || nsend >= limit) { break; } msg = conn->send_next(ctx, conn); if (msg == NULL) { break; } } /* * (nsend == 0) is possible in redis multi-del * see PR: https://github.com/twitter/twemproxy/pull/225 */ conn->smsg = NULL; if (!TAILQ_EMPTY(&send_msgq) && nsend != 0) { n = conn_sendv(conn, &sendv, nsend); } else { n = 0; } nsent = n > 0 ? (size_t)n : 0; /* postprocess - process sent messages in send_msgq */ for (msg = TAILQ_FIRST(&send_msgq); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, m_tqe); TAILQ_REMOVE(&send_msgq, msg, m_tqe); if (nsent == 0) { if (msg->mlen == 0) { conn->send_done(ctx, conn, msg); } continue; } /* adjust mbufs of the sent message */ for (mbuf = STAILQ_FIRST(&msg->mhdr); mbuf != NULL; mbuf = nbuf) { nbuf = STAILQ_NEXT(mbuf, next); if (mbuf_empty(mbuf)) { continue; } mlen = mbuf_length(mbuf); if (nsent < mlen) { /* mbuf was sent partially; process remaining bytes later */ mbuf->pos += nsent; ASSERT(mbuf->pos < mbuf->last); nsent = 0; break; } /* mbuf was sent completely; mark it empty */ mbuf->pos = mbuf->last; nsent -= mlen; } /* message has been sent completely, finalize it */ if (mbuf == NULL) { conn->send_done(ctx, conn, msg); } } ASSERT(TAILQ_EMPTY(&send_msgq)); if (n >= 0) { return NC_OK; } return (n == NC_EAGAIN) ? NC_OK : NC_ERROR; } rstatus_t msg_send(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg; ASSERT(conn->send_active); conn->send_ready = 1; do { msg = conn->send_next(ctx, conn); if (msg == NULL) { /* nothing to send */ return NC_OK; } status = msg_send_chain(ctx, conn, msg); if (status != NC_OK) { return status; } } while (conn->send_ready); return NC_OK; } nutcracker-0.4.0+dfsg/src/nc_message.h000066400000000000000000000504361242132376000176470ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_MESSAGE_H_ #define _NC_MESSAGE_H_ #include typedef void (*msg_parse_t)(struct msg *); typedef rstatus_t (*msg_fragment_t)(struct msg *, uint32_t, struct msg_tqh *); typedef void (*msg_coalesce_t)(struct msg *r); typedef rstatus_t (*msg_reply_t)(struct msg *r); typedef enum msg_parse_result { MSG_PARSE_OK, /* parsing ok */ MSG_PARSE_ERROR, /* parsing error */ MSG_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ MSG_PARSE_AGAIN, /* incomplete -> parse again */ } msg_parse_result_t; #define MSG_TYPE_CODEC(ACTION) \ ACTION( UNKNOWN ) \ ACTION( REQ_MC_GET ) /* memcache retrieval requests */ \ ACTION( REQ_MC_GETS ) \ ACTION( REQ_MC_DELETE ) /* memcache delete request */ \ ACTION( REQ_MC_CAS ) /* memcache cas request and storage request */ \ ACTION( REQ_MC_SET ) /* memcache storage request */ \ ACTION( REQ_MC_ADD ) \ ACTION( REQ_MC_REPLACE ) \ ACTION( REQ_MC_APPEND ) \ ACTION( REQ_MC_PREPEND ) \ ACTION( REQ_MC_INCR ) /* memcache arithmetic request */ \ ACTION( REQ_MC_DECR ) \ ACTION( REQ_MC_QUIT ) /* memcache quit request */ \ ACTION( RSP_MC_NUM ) /* memcache arithmetic response */ \ ACTION( RSP_MC_STORED ) /* memcache cas and storage response */ \ ACTION( RSP_MC_NOT_STORED ) \ ACTION( RSP_MC_EXISTS ) \ ACTION( RSP_MC_NOT_FOUND ) \ ACTION( RSP_MC_END ) \ ACTION( RSP_MC_VALUE ) \ ACTION( RSP_MC_DELETED ) /* memcache delete response */ \ ACTION( RSP_MC_ERROR ) /* memcache error responses */ \ ACTION( RSP_MC_CLIENT_ERROR ) \ ACTION( RSP_MC_SERVER_ERROR ) \ ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \ ACTION( REQ_REDIS_EXISTS ) \ ACTION( REQ_REDIS_EXPIRE ) \ ACTION( REQ_REDIS_EXPIREAT ) \ ACTION( REQ_REDIS_PEXPIRE ) \ ACTION( REQ_REDIS_PEXPIREAT ) \ ACTION( REQ_REDIS_PERSIST ) \ ACTION( REQ_REDIS_PTTL ) \ ACTION( REQ_REDIS_SORT ) \ ACTION( REQ_REDIS_TTL ) \ ACTION( REQ_REDIS_TYPE ) \ ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \ ACTION( REQ_REDIS_BITCOUNT ) \ ACTION( REQ_REDIS_DECR ) \ ACTION( REQ_REDIS_DECRBY ) \ ACTION( REQ_REDIS_DUMP ) \ ACTION( REQ_REDIS_GET ) \ ACTION( REQ_REDIS_GETBIT ) \ ACTION( REQ_REDIS_GETRANGE ) \ ACTION( REQ_REDIS_GETSET ) \ ACTION( REQ_REDIS_INCR ) \ ACTION( REQ_REDIS_INCRBY ) \ ACTION( REQ_REDIS_INCRBYFLOAT ) \ ACTION( REQ_REDIS_MGET ) \ ACTION( REQ_REDIS_MSET ) \ ACTION( REQ_REDIS_PSETEX ) \ ACTION( REQ_REDIS_RESTORE ) \ ACTION( REQ_REDIS_SET ) \ ACTION( REQ_REDIS_SETBIT ) \ ACTION( REQ_REDIS_SETEX ) \ ACTION( REQ_REDIS_SETNX ) \ ACTION( REQ_REDIS_SETRANGE ) \ ACTION( REQ_REDIS_STRLEN ) \ ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \ ACTION( REQ_REDIS_HEXISTS ) \ ACTION( REQ_REDIS_HGET ) \ ACTION( REQ_REDIS_HGETALL ) \ ACTION( REQ_REDIS_HINCRBY ) \ ACTION( REQ_REDIS_HINCRBYFLOAT ) \ ACTION( REQ_REDIS_HKEYS ) \ ACTION( REQ_REDIS_HLEN ) \ ACTION( REQ_REDIS_HMGET ) \ ACTION( REQ_REDIS_HMSET ) \ ACTION( REQ_REDIS_HSET ) \ ACTION( REQ_REDIS_HSETNX ) \ ACTION( REQ_REDIS_HSCAN) \ ACTION( REQ_REDIS_HVALS ) \ ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \ ACTION( REQ_REDIS_LINSERT ) \ ACTION( REQ_REDIS_LLEN ) \ ACTION( REQ_REDIS_LPOP ) \ ACTION( REQ_REDIS_LPUSH ) \ ACTION( REQ_REDIS_LPUSHX ) \ ACTION( REQ_REDIS_LRANGE ) \ ACTION( REQ_REDIS_LREM ) \ ACTION( REQ_REDIS_LSET ) \ ACTION( REQ_REDIS_LTRIM ) \ ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \ ACTION( REQ_REDIS_PFCOUNT ) \ ACTION( REQ_REDIS_PFMERGE ) \ ACTION( REQ_REDIS_RPOP ) \ ACTION( REQ_REDIS_RPOPLPUSH ) \ ACTION( REQ_REDIS_RPUSH ) \ ACTION( REQ_REDIS_RPUSHX ) \ ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \ ACTION( REQ_REDIS_SCARD ) \ ACTION( REQ_REDIS_SDIFF ) \ ACTION( REQ_REDIS_SDIFFSTORE ) \ ACTION( REQ_REDIS_SINTER ) \ ACTION( REQ_REDIS_SINTERSTORE ) \ ACTION( REQ_REDIS_SISMEMBER ) \ ACTION( REQ_REDIS_SMEMBERS ) \ ACTION( REQ_REDIS_SMOVE ) \ ACTION( REQ_REDIS_SPOP ) \ ACTION( REQ_REDIS_SRANDMEMBER ) \ ACTION( REQ_REDIS_SREM ) \ ACTION( REQ_REDIS_SUNION ) \ ACTION( REQ_REDIS_SUNIONSTORE ) \ ACTION( REQ_REDIS_SSCAN) \ ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \ ACTION( REQ_REDIS_ZCARD ) \ ACTION( REQ_REDIS_ZCOUNT ) \ ACTION( REQ_REDIS_ZINCRBY ) \ ACTION( REQ_REDIS_ZINTERSTORE ) \ ACTION( REQ_REDIS_ZLEXCOUNT ) \ ACTION( REQ_REDIS_ZRANGE ) \ ACTION( REQ_REDIS_ZRANGEBYLEX ) \ ACTION( REQ_REDIS_ZRANGEBYSCORE ) \ ACTION( REQ_REDIS_ZRANK ) \ ACTION( REQ_REDIS_ZREM ) \ ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \ ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \ ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \ ACTION( REQ_REDIS_ZREVRANGE ) \ ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \ ACTION( REQ_REDIS_ZREVRANK ) \ ACTION( REQ_REDIS_ZSCORE ) \ ACTION( REQ_REDIS_ZUNIONSTORE ) \ ACTION( REQ_REDIS_ZSCAN) \ ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \ ACTION( REQ_REDIS_EVALSHA ) \ ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \ ACTION( REQ_REDIS_QUIT) \ ACTION( RSP_REDIS_STATUS ) /* redis response */ \ ACTION( RSP_REDIS_ERROR ) \ ACTION( RSP_REDIS_INTEGER ) \ ACTION( RSP_REDIS_BULK ) \ ACTION( RSP_REDIS_MULTIBULK ) \ ACTION( SENTINEL ) \ #define DEFINE_ACTION(_name) MSG_##_name, typedef enum msg_type { MSG_TYPE_CODEC(DEFINE_ACTION) } msg_type_t; #undef DEFINE_ACTION struct keypos { uint8_t *start; /* key start pos */ uint8_t *end; /* key end pos */ }; struct msg { TAILQ_ENTRY(msg) c_tqe; /* link in client q */ TAILQ_ENTRY(msg) s_tqe; /* link in server q */ TAILQ_ENTRY(msg) m_tqe; /* link in send q / free q */ uint64_t id; /* message id */ struct msg *peer; /* message peer */ struct conn *owner; /* message owner - client | server */ struct rbnode tmo_rbe; /* entry in rbtree */ struct mhdr mhdr; /* message mbuf header */ uint32_t mlen; /* message length */ int64_t start_ts; /* request start timestamp in usec */ int state; /* current parser state */ uint8_t *pos; /* parser position marker */ uint8_t *token; /* token marker */ msg_parse_t parser; /* message parser */ msg_parse_result_t result; /* message parsing result */ msg_fragment_t fragment; /* message fragment */ msg_reply_t reply; /* gen message reply (example: ping) */ msg_coalesce_t pre_coalesce; /* message pre-coalesce */ msg_coalesce_t post_coalesce; /* message post-coalesce */ msg_type_t type; /* message type */ struct array *keys; /* array of keypos, for req */ uint32_t vlen; /* value length (memcache) */ uint8_t *end; /* end marker (memcache) */ uint8_t *narg_start; /* narg start (redis) */ uint8_t *narg_end; /* narg end (redis) */ uint32_t narg; /* # arguments (redis) */ uint32_t rnarg; /* running # arg used by parsing fsa (redis) */ uint32_t rlen; /* running length in parsing fsa (redis) */ uint32_t integer; /* integer reply value (redis) */ struct msg *frag_owner; /* owner of fragment message */ uint32_t nfrag; /* # fragment */ uint32_t nfrag_done; /* # fragment done */ uint64_t frag_id; /* id of fragmented message */ struct msg **frag_seq; /* sequence of fragment message, map from keys to fragments*/ err_t err; /* errno on error? */ unsigned error:1; /* error? */ unsigned ferror:1; /* one or more fragments are in error? */ unsigned request:1; /* request? or response? */ unsigned quit:1; /* quit request? */ unsigned noreply:1; /* noreply? */ unsigned noforward:1; /* not need forward (example: ping) */ unsigned done:1; /* done? */ unsigned fdone:1; /* all fragments are done? */ unsigned swallow:1; /* swallow response? */ unsigned redis:1; /* redis? */ }; TAILQ_HEAD(msg_tqh, msg); struct msg *msg_tmo_min(void); void msg_tmo_insert(struct msg *msg, struct conn *conn); void msg_tmo_delete(struct msg *msg); void msg_init(void); void msg_deinit(void); struct string *msg_type_string(msg_type_t type); struct msg *msg_get(struct conn *conn, bool request, bool redis); void msg_put(struct msg *msg); struct msg *msg_get_error(bool redis, err_t err); void msg_dump(struct msg *msg, int level); bool msg_empty(struct msg *msg); rstatus_t msg_recv(struct context *ctx, struct conn *conn); rstatus_t msg_send(struct context *ctx, struct conn *conn); uint64_t msg_gen_frag_id(void); uint32_t msg_backend_idx(struct msg *msg, uint8_t *key, uint32_t keylen); struct mbuf *msg_ensure_mbuf(struct msg *msg, size_t len); rstatus_t msg_append(struct msg *msg, uint8_t *pos, size_t n); rstatus_t msg_prepend(struct msg *msg, uint8_t *pos, size_t n); rstatus_t msg_prepend_format(struct msg *msg, const char *fmt, ...); struct msg *req_get(struct conn *conn); void req_put(struct msg *msg); bool req_done(struct conn *conn, struct msg *msg); bool req_error(struct conn *conn, struct msg *msg); void req_server_enqueue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_dequeue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_client_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_client_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); struct msg *req_recv_next(struct context *ctx, struct conn *conn, bool alloc); void req_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg); struct msg *req_send_next(struct context *ctx, struct conn *conn); void req_send_done(struct context *ctx, struct conn *conn, struct msg *msg); struct msg *rsp_get(struct conn *conn); void rsp_put(struct msg *msg); struct msg *rsp_recv_next(struct context *ctx, struct conn *conn, bool alloc); void rsp_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg); struct msg *rsp_send_next(struct context *ctx, struct conn *conn); void rsp_send_done(struct context *ctx, struct conn *conn, struct msg *msg); #endif nutcracker-0.4.0+dfsg/src/nc_proxy.c000066400000000000000000000240311242132376000173670ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include void proxy_ref(struct conn *conn, void *owner) { struct server_pool *pool = owner; ASSERT(!conn->client && conn->proxy); ASSERT(conn->owner == NULL); conn->family = pool->family; conn->addrlen = pool->addrlen; conn->addr = pool->addr; pool->p_conn = conn; /* owner of the proxy connection is the server pool */ conn->owner = owner; log_debug(LOG_VVERB, "ref conn %p owner %p into pool %"PRIu32"", conn, pool, pool->idx); } void proxy_unref(struct conn *conn) { struct server_pool *pool; ASSERT(!conn->client && conn->proxy); ASSERT(conn->owner != NULL); pool = conn->owner; conn->owner = NULL; pool->p_conn = NULL; log_debug(LOG_VVERB, "unref conn %p owner %p from pool %"PRIu32"", conn, pool, pool->idx); } void proxy_close(struct context *ctx, struct conn *conn) { rstatus_t status; ASSERT(!conn->client && conn->proxy); if (conn->sd < 0) { conn->unref(conn); conn_put(conn); return; } ASSERT(conn->rmsg == NULL); ASSERT(conn->smsg == NULL); ASSERT(TAILQ_EMPTY(&conn->imsg_q)); ASSERT(TAILQ_EMPTY(&conn->omsg_q)); conn->unref(conn); status = close(conn->sd); if (status < 0) { log_error("close p %d failed, ignored: %s", conn->sd, strerror(errno)); } conn->sd = -1; conn_put(conn); } static rstatus_t proxy_reuse(struct conn *p) { rstatus_t status; struct sockaddr_un *un; switch (p->family) { case AF_INET: case AF_INET6: status = nc_set_reuseaddr(p->sd); break; case AF_UNIX: /* * bind() will fail if the pathname already exist. So, we call unlink() * to delete the pathname, in case it already exists. If it does not * exist, unlink() returns error, which we ignore */ un = (struct sockaddr_un *) p->addr; unlink(un->sun_path); status = NC_OK; break; default: NOT_REACHED(); status = NC_ERROR; } return status; } static rstatus_t proxy_listen(struct context *ctx, struct conn *p) { rstatus_t status; struct server_pool *pool = p->owner; ASSERT(p->proxy); p->sd = socket(p->family, SOCK_STREAM, 0); if (p->sd < 0) { log_error("socket failed: %s", strerror(errno)); return NC_ERROR; } status = proxy_reuse(p); if (status < 0) { log_error("reuse of addr '%.*s' for listening on p %d failed: %s", pool->addrstr.len, pool->addrstr.data, p->sd, strerror(errno)); return NC_ERROR; } status = bind(p->sd, p->addr, p->addrlen); if (status < 0) { log_error("bind on p %d to addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = listen(p->sd, pool->backlog); if (status < 0) { log_error("listen on p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = nc_set_nonblocking(p->sd); if (status < 0) { log_error("set nonblock on p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = event_add_conn(ctx->evb, p); if (status < 0) { log_error("event add conn p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = event_del_out(ctx->evb, p); if (status < 0) { log_error("event del out p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } return NC_OK; } rstatus_t proxy_each_init(void *elem, void *data) { rstatus_t status; struct server_pool *pool = elem; struct conn *p; p = conn_get_proxy(pool); if (p == NULL) { return NC_ENOMEM; } status = proxy_listen(pool->ctx, p); if (status != NC_OK) { p->close(pool->ctx, p); return status; } log_debug(LOG_NOTICE, "p %d listening on '%.*s' in %s pool %"PRIu32" '%.*s'" " with %"PRIu32" servers", p->sd, pool->addrstr.len, pool->addrstr.data, pool->redis ? "redis" : "memcache", pool->idx, pool->name.len, pool->name.data, array_n(&pool->server)); return NC_OK; } rstatus_t proxy_init(struct context *ctx) { rstatus_t status; ASSERT(array_n(&ctx->pool) != 0); status = array_each(&ctx->pool, proxy_each_init, NULL); if (status != NC_OK) { proxy_deinit(ctx); return status; } log_debug(LOG_VVERB, "init proxy with %"PRIu32" pools", array_n(&ctx->pool)); return NC_OK; } rstatus_t proxy_each_deinit(void *elem, void *data) { struct server_pool *pool = elem; struct conn *p; p = pool->p_conn; if (p != NULL) { p->close(pool->ctx, p); } return NC_OK; } void proxy_deinit(struct context *ctx) { rstatus_t status; ASSERT(array_n(&ctx->pool) != 0); status = array_each(&ctx->pool, proxy_each_deinit, NULL); if (status != NC_OK) { return; } log_debug(LOG_VVERB, "deinit proxy with %"PRIu32" pools", array_n(&ctx->pool)); } static rstatus_t proxy_accept(struct context *ctx, struct conn *p) { rstatus_t status; struct conn *c; int sd; ASSERT(p->proxy && !p->client); ASSERT(p->sd > 0); ASSERT(p->recv_active && p->recv_ready); for (;;) { sd = accept(p->sd, NULL, NULL); if (sd < 0) { if (errno == EINTR) { log_debug(LOG_VERB, "accept on p %d not ready - eintr", p->sd); continue; } if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED) { log_debug(LOG_VERB, "accept on p %d not ready - eagain", p->sd); p->recv_ready = 0; return NC_OK; } /* * Workaround of https://github.com/twitter/twemproxy/issues/97 * * We should never reach here because the check for conn_ncurr_cconn() * against ctx->max_ncconn should catch this earlier in the cycle. * If we reach here ignore EMFILE/ENFILE, return NC_OK will enable * the server continue to run instead of close the server socket * * The right solution however, is on EMFILE/ENFILE to mask out IN * event on the proxy and mask it back in when some existing * connections gets closed */ if (errno == EMFILE || errno == ENFILE) { log_debug(LOG_CRIT, "accept on p %d with max fds %"PRIu32" " "used connections %"PRIu32" max client connections %"PRIu32" " "curr client connections %"PRIu32" failed: %s", p->sd, ctx->max_nfd, conn_ncurr_conn(), ctx->max_ncconn, conn_ncurr_cconn(), strerror(errno)); p->recv_ready = 0; return NC_OK; } log_error("accept on p %d failed: %s", p->sd, strerror(errno)); return NC_ERROR; } break; } if (conn_ncurr_cconn() >= ctx->max_ncconn) { log_debug(LOG_CRIT, "client connections %"PRIu32" exceed limit %"PRIu32, conn_ncurr_cconn(), ctx->max_ncconn); status = close(sd); if (status < 0) { log_error("close c %d failed, ignored: %s", sd, strerror(errno)); } return NC_OK; } c = conn_get(p->owner, true, p->redis); if (c == NULL) { log_error("get conn for c %d from p %d failed: %s", sd, p->sd, strerror(errno)); status = close(sd); if (status < 0) { log_error("close c %d failed, ignored: %s", sd, strerror(errno)); } return NC_ENOMEM; } c->sd = sd; stats_pool_incr(ctx, c->owner, client_connections); status = nc_set_nonblocking(c->sd); if (status < 0) { log_error("set nonblock on c %d from p %d failed: %s", c->sd, p->sd, strerror(errno)); c->close(ctx, c); return status; } if (p->family == AF_INET || p->family == AF_INET6) { status = nc_set_tcpnodelay(c->sd); if (status < 0) { log_warn("set tcpnodelay on c %d from p %d failed, ignored: %s", c->sd, p->sd, strerror(errno)); } } status = event_add_conn(ctx->evb, c); if (status < 0) { log_error("event add conn from p %d failed: %s", p->sd, strerror(errno)); c->close(ctx, c); return status; } log_debug(LOG_NOTICE, "accepted c %d on p %d from '%s'", c->sd, p->sd, nc_unresolve_peer_desc(c->sd)); return NC_OK; } rstatus_t proxy_recv(struct context *ctx, struct conn *conn) { rstatus_t status; ASSERT(conn->proxy && !conn->client); ASSERT(conn->recv_active); conn->recv_ready = 1; do { status = proxy_accept(ctx, conn); if (status != NC_OK) { return status; } } while (conn->recv_ready); return NC_OK; } nutcracker-0.4.0+dfsg/src/nc_proxy.h000066400000000000000000000021551242132376000173770ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_PROXY_H_ #define _NC_PROXY_H_ #include void proxy_ref(struct conn *conn, void *owner); void proxy_unref(struct conn *conn); void proxy_close(struct context *ctx, struct conn *conn); rstatus_t proxy_each_init(void *elem, void *data); rstatus_t proxy_each_deinit(void *elem, void *data); rstatus_t proxy_init(struct context *ctx); void proxy_deinit(struct context *ctx); rstatus_t proxy_recv(struct context *ctx, struct conn *conn); #endif nutcracker-0.4.0+dfsg/src/nc_queue.h000066400000000000000000001136261242132376000173500ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 * $FreeBSD: src/sys/sys/queue.h,v 1.73 2010/02/20 01:05:30 emaste Exp $ */ #ifndef _NC_QUEUE_H_ #define _NC_QUEUE_H_ #include #ifndef __offsetof #define __offsetof(type, field) ((size_t)(&((type *)NULL)->field)) #endif /* * This file defines five types of data structures: singly-linked lists, * singly-linked tail queues, lists, tail queues, and circular queues. * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A singly-linked tail queue is headed by a pair of pointers, one to the * head of the list and the other to the tail of the list. The elements are * singly linked for minimum space and pointer manipulation overhead at the * expense of O(n) removal for arbitrary elements. New elements can be added * to the list after an existing element, at the head of the list, or at the * end of the list. Elements being removed from the head of the tail queue * should use the explicit macro for this purpose for optimum efficiency. * A singly-linked tail queue may only be traversed in the forward direction. * Singly-linked tail queues are ideal for applications with large datasets * and few or no removals or for implementing a FIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. * * * SLIST LIST STAILQ TAILQ CIRCLEQ * _HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - - - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_REVERSE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _REMOVE_HEAD + - + - - * _REMOVE + + + + + * */ #define QUEUE_MACRO_SCRUB 1 #ifdef NC_ASSERT_PANIC # define QUEUE_MACRO_TRACE 1 # define QUEUE_MACRO_ASSERT 1 #endif #ifdef QUEUE_MACRO_SCRUB #define QMD_SAVELINK(name, link) void **name = (void *)&(link) #define TRASHIT(x) do { \ (x) = (void *) NULL; \ } while (0) #else #define QMD_SAVELINK(name, link) #define TRASHIT(x) #endif /* QUEUE_MACRO_SCRUB */ #ifdef QUEUE_MACRO_TRACE /* Store the last 2 places the queue element or head was altered */ struct qm_trace { char *lastfile; int lastline; char *prevfile; int prevline; }; #define TRACEBUF struct qm_trace trace; #define QMD_TRACE_HEAD(head) do { \ (head)->trace.prevline = (head)->trace.lastline; \ (head)->trace.prevfile = (head)->trace.lastfile; \ (head)->trace.lastline = __LINE__; \ (head)->trace.lastfile = __FILE__; \ } while (0) #define QMD_TRACE_ELEM(elem) do { \ (elem)->trace.prevline = (elem)->trace.lastline; \ (elem)->trace.prevfile = (elem)->trace.lastfile; \ (elem)->trace.lastline = __LINE__; \ (elem)->trace.lastfile = __FILE__; \ } while (0) #else #define QMD_TRACE_ELEM(elem) #define QMD_TRACE_HEAD(head) #define TRACEBUF #endif /* QUEUE_MACRO_TRACE */ /* * Singly-linked List declarations. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_FOREACH(var, head, field) \ for ((var) = SLIST_FIRST((head)); \ (var); \ (var) = SLIST_NEXT((var), field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST((head)); \ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ (var) = (tvar)) #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ for ((varp) = &SLIST_FIRST((head)); \ ((var) = *(varp)) != NULL; \ (varp) = &SLIST_NEXT((var), field)) #define SLIST_INIT(head) do { \ SLIST_FIRST((head)) = NULL; \ } while (0) #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ SLIST_NEXT((slistelm), field) = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ SLIST_FIRST((head)) = (elm); \ } while (0) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_REMOVE(head, elm, type, field) do { \ if (SLIST_FIRST((head)) == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = SLIST_FIRST((head)); \ while (SLIST_NEXT(curelm, field) != (elm)) { \ curelm = SLIST_NEXT(curelm, field); \ } \ SLIST_REMOVE_AFTER(curelm, field); \ } \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) do { \ QMD_SAVELINK(oldnext, SLIST_NEXT(SLIST_NEXT(elm, field), field)); \ SLIST_NEXT(elm, field) = SLIST_NEXT(SLIST_NEXT(elm, field), field); \ TRASHIT(*oldnext); \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ QMD_SAVELINK(oldnext, SLIST_NEXT(SLIST_FIRST((head)), field)); \ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ TRASHIT(*oldnext); \ } while (0) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first; /* first element */ \ struct type **stqh_last; /* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_CONCAT(head1, head2) do { \ if (!STAILQ_EMPTY((head2))) { \ *(head1)->stqh_last = (head2)->stqh_first; \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_INIT((head2)); \ } \ } while (0) #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_FOREACH(var, head, field) \ for ((var) = STAILQ_FIRST((head)); \ (var); \ (var) = STAILQ_NEXT((var), field)) #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = STAILQ_FIRST((head)); \ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define STAILQ_INIT(head) do { \ STAILQ_FIRST((head)) = NULL; \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_NEXT((tqelm), field) = (elm); \ } while (0) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_FIRST((head)) = (elm); \ } while (0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ STAILQ_NEXT((elm), field) = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_LAST(head, type, field) \ (STAILQ_EMPTY((head)) ? \ NULL : \ ((struct type *)(void *) \ ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) #define STAILQ_REMOVE(head, elm, type, field) do { \ if (STAILQ_FIRST((head)) == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = STAILQ_FIRST((head)); \ while (STAILQ_NEXT(curelm, field) != (elm)) \ curelm = STAILQ_NEXT(curelm, field); \ STAILQ_REMOVE_AFTER(head, curelm, field); \ } \ } while (0) #define STAILQ_REMOVE_HEAD(head, field) do { \ QMD_SAVELINK(oldnext, STAILQ_NEXT(STAILQ_FIRST((head)), field)); \ if ((STAILQ_FIRST((head)) = \ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) { \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } \ TRASHIT(*oldnext); \ } while (0) #define STAILQ_REMOVE_AFTER(head, elm, field) do { \ QMD_SAVELINK(oldnext, STAILQ_NEXT(STAILQ_NEXT(elm, field), field)); \ if ((STAILQ_NEXT(elm, field) = \ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) { \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } \ TRASHIT(*oldnext); \ } while (0) #define STAILQ_SWAP(head1, head2, type) do { \ struct type *swap_first = STAILQ_FIRST(head1); \ struct type **swap_last = (head1)->stqh_last; \ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_FIRST(head2) = swap_first; \ (head2)->stqh_last = swap_last; \ if (STAILQ_EMPTY(head1)) \ (head1)->stqh_last = &STAILQ_FIRST(head1); \ if (STAILQ_EMPTY(head2)) \ (head2)->stqh_last = &STAILQ_FIRST(head2); \ } while (0) /* * List declarations. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #ifdef QUEUE_MACRO_ASSERT #define QMD_LIST_CHECK_HEAD(head, field) do { \ if (LIST_FIRST((head)) != NULL && \ LIST_FIRST((head))->field.le_prev != &LIST_FIRST((head))) { \ log_panic("Bad list head %p first->prev != head", (void *)(head)); \ } \ } while (0) #define QMD_LIST_CHECK_NEXT(elm, field) do { \ if (LIST_NEXT((elm), field) != NULL && \ LIST_NEXT((elm), field)->field.le_prev != &((elm)->field.le_next)) {\ log_panic("Bad link elm %p next->prev != elm",(void *)(elm)); \ } \ } while (0) #define QMD_LIST_CHECK_PREV(elm, field) do { \ if (*(elm)->field.le_prev != (elm)) { \ log_panic("Bad link elm %p prev->next != elm",(void *)(elm)); \ } \ } while (0) #else #define QMD_LIST_CHECK_HEAD(head, field) #define QMD_LIST_CHECK_NEXT(elm, field) #define QMD_LIST_CHECK_PREV(elm, field) #endif /* QUEUE_MACRO_ASSERT */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_FOREACH(var, head, field) \ for ((var) = LIST_FIRST((head)); \ (var); \ (var) = LIST_NEXT((var), field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) #define LIST_INIT(head) do { \ LIST_FIRST((head)) = NULL; \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ QMD_LIST_CHECK_NEXT(listelm, field); \ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ LIST_NEXT((listelm), field)->field.le_prev = \ &LIST_NEXT((elm), field); \ LIST_NEXT((listelm), field) = (elm); \ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ QMD_LIST_CHECK_PREV(listelm, field); \ (elm)->field.le_prev = (listelm)->field.le_prev; \ LIST_NEXT((elm), field) = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ QMD_LIST_CHECK_HEAD((head), field); \ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \ LIST_FIRST((head)) = (elm); \ (elm)->field.le_prev = &LIST_FIRST((head)); \ } while (0) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_REMOVE(elm, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.le_next); \ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ QMD_LIST_CHECK_NEXT(elm, field); \ QMD_LIST_CHECK_PREV(elm, field); \ if (LIST_NEXT((elm), field) != NULL) \ LIST_NEXT((elm), field)->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = LIST_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ } while (0) #define LIST_SWAP(head1, head2, type, field) do { \ struct type *swap_tmp = LIST_FIRST((head1)); \ LIST_FIRST((head1)) = LIST_FIRST((head2)); \ LIST_FIRST((head2)) = swap_tmp; \ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ } while (0) /* * Tail queue declarations. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ TRACEBUF \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ TRACEBUF \ } /* * Tail queue functions. */ #ifdef QUEUE_MACRO_ASSERT #define QMD_TAILQ_CHECK_HEAD(head, field) do { \ if (!TAILQ_EMPTY(head) && \ TAILQ_FIRST((head))->field.tqe_prev != &TAILQ_FIRST((head))) { \ log_panic("Bad tailq head %p first->prev != head", (void *)(head)); \ } \ } while (0) #define QMD_TAILQ_CHECK_TAIL(head, field) do { \ if (*(head)->tqh_last != NULL) { \ log_panic("Bad tailq NEXT(%p->tqh_last) != NULL",(void *)(head)); \ } \ } while (0) #define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ if (TAILQ_NEXT((elm), field) != NULL && \ TAILQ_NEXT((elm), field)->field.tqe_prev != &((elm)->field.tqe_next)) {\ log_panic("Bad link elm %p next->prev != elm",(void *)(elm)); \ } \ } while (0) #define QMD_TAILQ_CHECK_PREV(elm, field) do { \ if (*(elm)->field.tqe_prev != (elm)) { \ log_panic("Bad link elm %p prev->next != elm",(void *)(elm)); \ } \ } while (0) #else #define QMD_TAILQ_CHECK_HEAD(head, field) #define QMD_TAILQ_CHECK_TAIL(head, headname) #define QMD_TAILQ_CHECK_NEXT(elm, field) #define QMD_TAILQ_CHECK_PREV(elm, field) #endif /* QUEUE_MACRO_ASSERT */ #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ QMD_TRACE_HEAD(head1); \ QMD_TRACE_HEAD(head2); \ } \ } while (0) #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST((head)); \ (var); \ (var) = TAILQ_NEXT((var), field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST((head)); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST((head), headname); \ (var); \ (var) = TAILQ_PREV((var), headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST((head), headname); \ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ (var) = (tvar)) #define TAILQ_INIT(head) do { \ TAILQ_FIRST((head)) = NULL; \ (head)->tqh_last = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ QMD_TAILQ_CHECK_NEXT(listelm, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) { \ TAILQ_NEXT((elm), field)->field.tqe_prev = &TAILQ_NEXT((elm), field);\ } else { \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ } \ TAILQ_NEXT((listelm), field) = (elm); \ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ QMD_TAILQ_CHECK_PREV(listelm, field); \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ TAILQ_NEXT((elm), field) = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ QMD_TAILQ_CHECK_HEAD(head, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ TAILQ_FIRST((head))->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ TAILQ_FIRST((head)) = (elm); \ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ QMD_TAILQ_CHECK_TAIL(head, field); \ TAILQ_NEXT((elm), field) = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_REMOVE(head, elm, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ QMD_TAILQ_CHECK_NEXT(elm, field); \ QMD_TAILQ_CHECK_PREV(elm, field); \ if ((TAILQ_NEXT((elm), field)) != NULL) { \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ (elm)->field.tqe_prev; \ } else { \ (head)->tqh_last = (elm)->field.tqe_prev; \ QMD_TRACE_HEAD(head); \ } \ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_SWAP(head1, head2, type, field) do { \ struct type *swap_first = (head1)->tqh_first; \ struct type **swap_last = (head1)->tqh_last; \ (head1)->tqh_first = (head2)->tqh_first; \ (head1)->tqh_last = (head2)->tqh_last; \ (head2)->tqh_first = swap_first; \ (head2)->tqh_last = swap_last; \ if ((swap_first = (head1)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head1)->tqh_first; \ else \ (head1)->tqh_last = &(head1)->tqh_first; \ if ((swap_first = (head2)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head2)->tqh_first; \ else \ (head2)->tqh_last = &(head2)->tqh_first; \ } while (0) /* * Circular queue declarations. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ { (void *)&(head), (void *)&(head) } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue functions. */ #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_FOREACH(var, head, field) \ for ((var) = CIRCLEQ_FIRST((head)); \ (var) != (void *)(head) || ((var) = NULL); \ (var) = CIRCLEQ_NEXT((var), field)) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for ((var) = CIRCLEQ_LAST((head)); \ (var) != (void *)(head) || ((var) = NULL); \ (var) = CIRCLEQ_PREV((var), field)) #define CIRCLEQ_INIT(head) do { \ CIRCLEQ_FIRST((head)) = (void *)(head); \ CIRCLEQ_LAST((head)) = (void *)(head); \ } while (0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field); \ CIRCLEQ_PREV((elm), field) = (listelm); \ if (CIRCLEQ_NEXT((listelm), field) == (void *)(head)) \ CIRCLEQ_LAST((head)) = (elm); \ else \ CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm); \ CIRCLEQ_NEXT((listelm), field) = (elm); \ } while (0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = (listelm); \ CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field); \ if (CIRCLEQ_PREV((listelm), field) == (void *)(head)) \ CIRCLEQ_FIRST((head)) = (elm); \ else \ CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm); \ CIRCLEQ_PREV((listelm), field) = (elm); \ } while (0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head)); \ CIRCLEQ_PREV((elm), field) = (void *)(head); \ if (CIRCLEQ_LAST((head)) == (void *)(head)) \ CIRCLEQ_LAST((head)) = (elm); \ else \ CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm); \ CIRCLEQ_FIRST((head)) = (elm); \ } while (0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = (void *)(head); \ CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head)); \ if (CIRCLEQ_FIRST((head)) == (void *)(head)) \ CIRCLEQ_FIRST((head)) = (elm); \ else \ CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm); \ CIRCLEQ_LAST((head)) = (elm); \ } while (0) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_NEXT(elm,field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm,field) ((elm)->field.cqe_prev) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if (CIRCLEQ_NEXT((elm), field) == (void *)(head)) \ CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field); \ else \ CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) = \ CIRCLEQ_PREV((elm), field); \ if (CIRCLEQ_PREV((elm), field) == (void *)(head)) \ CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field); \ else \ CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) = \ CIRCLEQ_NEXT((elm), field); \ } while (0) #endif nutcracker-0.4.0+dfsg/src/nc_rbtree.c000066400000000000000000000207401242132376000174740ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include void rbtree_node_init(struct rbnode *node) { node->left = NULL; node->right = NULL; node->parent = NULL; node->key = 0ULL; node->data = NULL; /* color is left uninitialized */ } void rbtree_init(struct rbtree *tree, struct rbnode *node) { rbtree_node_init(node); rbtree_black(node); tree->root = node; tree->sentinel = node; } static struct rbnode * rbtree_node_min(struct rbnode *node, struct rbnode *sentinel) { /* traverse left links */ while (node->left != sentinel) { node = node->left; } return node; } struct rbnode * rbtree_min(struct rbtree *tree) { struct rbnode *node = tree->root; struct rbnode *sentinel = tree->sentinel; /* empty tree */ if (node == sentinel) { return NULL; } return rbtree_node_min(node, sentinel); } static void rbtree_left_rotate(struct rbnode **root, struct rbnode *sentinel, struct rbnode *node) { struct rbnode *temp; temp = node->right; node->right = temp->left; if (temp->left != sentinel) { temp->left->parent = node; } temp->parent = node->parent; if (node == *root) { *root = temp; } else if (node == node->parent->left) { node->parent->left = temp; } else { node->parent->right = temp; } temp->left = node; node->parent = temp; } static void rbtree_right_rotate(struct rbnode **root, struct rbnode *sentinel, struct rbnode *node) { struct rbnode *temp; temp = node->left; node->left = temp->right; if (temp->right != sentinel) { temp->right->parent = node; } temp->parent = node->parent; if (node == *root) { *root = temp; } else if (node == node->parent->right) { node->parent->right = temp; } else { node->parent->left = temp; } temp->right = node; node->parent = temp; } void rbtree_insert(struct rbtree *tree, struct rbnode *node) { struct rbnode **root = &tree->root; struct rbnode *sentinel = tree->sentinel; struct rbnode *temp, **p; /* empty tree */ if (*root == sentinel) { node->parent = NULL; node->left = sentinel; node->right = sentinel; rbtree_black(node); *root = node; return; } /* a binary tree insert */ temp = *root; for (;;) { p = (node->key < temp->key) ? &temp->left : &temp->right; if (*p == sentinel) { break; } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; rbtree_red(node); /* re-balance tree */ while (node != *root && rbtree_is_red(node->parent)) { if (node->parent == node->parent->parent->left) { temp = node->parent->parent->right; if (rbtree_is_red(temp)) { rbtree_black(node->parent); rbtree_black(temp); rbtree_red(node->parent->parent); node = node->parent->parent; } else { if (node == node->parent->right) { node = node->parent; rbtree_left_rotate(root, sentinel, node); } rbtree_black(node->parent); rbtree_red(node->parent->parent); rbtree_right_rotate(root, sentinel, node->parent->parent); } } else { temp = node->parent->parent->left; if (rbtree_is_red(temp)) { rbtree_black(node->parent); rbtree_black(temp); rbtree_red(node->parent->parent); node = node->parent->parent; } else { if (node == node->parent->left) { node = node->parent; rbtree_right_rotate(root, sentinel, node); } rbtree_black(node->parent); rbtree_red(node->parent->parent); rbtree_left_rotate(root, sentinel, node->parent->parent); } } } rbtree_black(*root); } void rbtree_delete(struct rbtree *tree, struct rbnode *node) { struct rbnode **root = &tree->root; struct rbnode *sentinel = tree->sentinel; struct rbnode *subst, *temp, *w; uint8_t red; /* a binary tree delete */ if (node->left == sentinel) { temp = node->right; subst = node; } else if (node->right == sentinel) { temp = node->left; subst = node; } else { subst = rbtree_node_min(node->right, sentinel); if (subst->left != sentinel) { temp = subst->left; } else { temp = subst->right; } } if (subst == *root) { *root = temp; rbtree_black(temp); rbtree_node_init(node); return; } red = rbtree_is_red(subst); if (subst == subst->parent->left) { subst->parent->left = temp; } else { subst->parent->right = temp; } if (subst == node) { temp->parent = subst->parent; } else { if (subst->parent == node) { temp->parent = subst; } else { temp->parent = subst->parent; } subst->left = node->left; subst->right = node->right; subst->parent = node->parent; rbtree_copy_color(subst, node); if (node == *root) { *root = subst; } else { if (node == node->parent->left) { node->parent->left = subst; } else { node->parent->right = subst; } } if (subst->left != sentinel) { subst->left->parent = subst; } if (subst->right != sentinel) { subst->right->parent = subst; } } rbtree_node_init(node); if (red) { return; } /* a delete fixup */ while (temp != *root && rbtree_is_black(temp)) { if (temp == temp->parent->left) { w = temp->parent->right; if (rbtree_is_red(w)) { rbtree_black(w); rbtree_red(temp->parent); rbtree_left_rotate(root, sentinel, temp->parent); w = temp->parent->right; } if (rbtree_is_black(w->left) && rbtree_is_black(w->right)) { rbtree_red(w); temp = temp->parent; } else { if (rbtree_is_black(w->right)) { rbtree_black(w->left); rbtree_red(w); rbtree_right_rotate(root, sentinel, w); w = temp->parent->right; } rbtree_copy_color(w, temp->parent); rbtree_black(temp->parent); rbtree_black(w->right); rbtree_left_rotate(root, sentinel, temp->parent); temp = *root; } } else { w = temp->parent->left; if (rbtree_is_red(w)) { rbtree_black(w); rbtree_red(temp->parent); rbtree_right_rotate(root, sentinel, temp->parent); w = temp->parent->left; } if (rbtree_is_black(w->left) && rbtree_is_black(w->right)) { rbtree_red(w); temp = temp->parent; } else { if (rbtree_is_black(w->left)) { rbtree_black(w->right); rbtree_red(w); rbtree_left_rotate(root, sentinel, w); w = temp->parent->left; } rbtree_copy_color(w, temp->parent); rbtree_black(temp->parent); rbtree_black(w->left); rbtree_right_rotate(root, sentinel, temp->parent); temp = *root; } } } rbtree_black(temp); } nutcracker-0.4.0+dfsg/src/nc_rbtree.h000066400000000000000000000032421242132376000174770ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_RBTREE_ #define _NC_RBTREE_ #define rbtree_red(_node) ((_node)->color = 1) #define rbtree_black(_node) ((_node)->color = 0) #define rbtree_is_red(_node) ((_node)->color) #define rbtree_is_black(_node) (!rbtree_is_red(_node)) #define rbtree_copy_color(_n1, _n2) ((_n1)->color = (_n2)->color) struct rbnode { struct rbnode *left; /* left link */ struct rbnode *right; /* right link */ struct rbnode *parent; /* parent link */ int64_t key; /* key for ordering */ void *data; /* opaque data */ uint8_t color; /* red | black */ }; struct rbtree { struct rbnode *root; /* root node */ struct rbnode *sentinel; /* nil node */ }; void rbtree_node_init(struct rbnode *node); void rbtree_init(struct rbtree *tree, struct rbnode *node); struct rbnode *rbtree_min(struct rbtree *tree); void rbtree_insert(struct rbtree *tree, struct rbnode *node); void rbtree_delete(struct rbtree *tree, struct rbnode *node); #endif nutcracker-0.4.0+dfsg/src/nc_request.c000066400000000000000000000433141242132376000177030ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include struct msg * req_get(struct conn *conn) { struct msg *msg; ASSERT(conn->client && !conn->proxy); msg = msg_get(conn, true, conn->redis); if (msg == NULL) { conn->err = errno; } return msg; } static void req_log(struct msg *req) { struct msg *rsp; /* peer message (response) */ int64_t req_time; /* time cost for this request */ char *peer_str; /* peer client ip:port */ uint32_t req_len, rsp_len; /* request and response length */ struct string *req_type; /* request type string */ struct keypos *kpos; if (log_loggable(LOG_NOTICE) == 0) { return; } /* a fragment? */ if (req->frag_id != 0 && req->frag_owner != req) { return; } /* conn close normally? */ if (req->mlen == 0) { return; } /* * there is a race scenario where a requests comes in, the log level is not LOG_NOTICE, * and before the response arrives you modify the log level to LOG_NOTICE * using SIGTTIN OR SIGTTOU, then req_log() wouldn't have msg->start_ts set */ if (req->start_ts == 0) { return; } req_time = nc_usec_now() - req->start_ts; rsp = req->peer; req_len = req->mlen; rsp_len = (rsp != NULL) ? rsp->mlen : 0; if (array_n(req->keys) < 1) { return; } kpos = array_get(req->keys, 0); if (kpos->end != NULL) { *(kpos->end) = '\0'; } /* * FIXME: add backend addr here * Maybe we can store addrstr just like server_pool in conn struct * when connections are resolved */ peer_str = nc_unresolve_peer_desc(req->owner->sd); req_type = msg_type_string(req->type); log_debug(LOG_NOTICE, "req %"PRIu64" done on c %d req_time %"PRIi64".%03"PRIi64 " msec type %.*s narg %"PRIu32" req_len %"PRIu32" rsp_len %"PRIu32 " key0 '%s' peer '%s' done %d error %d", req->id, req->owner->sd, req_time / 1000, req_time % 1000, req_type->len, req_type->data, req->narg, req_len, rsp_len, kpos->start, peer_str, req->done, req->error); } void req_put(struct msg *msg) { struct msg *pmsg; /* peer message (response) */ ASSERT(msg->request); req_log(msg); pmsg = msg->peer; if (pmsg != NULL) { ASSERT(!pmsg->request && pmsg->peer == msg); msg->peer = NULL; pmsg->peer = NULL; rsp_put(pmsg); } msg_tmo_delete(msg); msg_put(msg); } /* * Return true if request is done, false otherwise * * A request is done, if we received response for the given request. * A request vector is done if we received responses for all its * fragments. */ bool req_done(struct conn *conn, struct msg *msg) { struct msg *cmsg, *pmsg; /* current and previous message */ uint64_t id; /* fragment id */ uint32_t nfragment; /* # fragment */ ASSERT(conn->client && !conn->proxy); ASSERT(msg->request); if (!msg->done) { return false; } id = msg->frag_id; if (id == 0) { return true; } if (msg->fdone) { /* request has already been marked as done */ return true; } if (msg->nfrag_done < msg->nfrag) { return false; } /* check all fragments of the given request vector are done */ for (pmsg = msg, cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { if (!cmsg->done) { return false; } } for (pmsg = msg, cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_NEXT(cmsg, c_tqe)) { if (!cmsg->done) { return false; } } /* * At this point, all the fragments including the last fragment have * been received. * * Mark all fragments of the given request vector to be done to speed up * future req_done calls for any of fragments of this request */ msg->fdone = 1; nfragment = 0; for (pmsg = msg, cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { cmsg->fdone = 1; nfragment++; } for (pmsg = msg, cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_NEXT(cmsg, c_tqe)) { cmsg->fdone = 1; nfragment++; } ASSERT(msg->frag_owner->nfrag == nfragment); msg->post_coalesce(msg->frag_owner); log_debug(LOG_DEBUG, "req from c %d with fid %"PRIu64" and %"PRIu32" " "fragments is done", conn->sd, id, nfragment); return true; } /* * Return true if request is in error, false otherwise * * A request is in error, if there was an error in receiving response for the * given request. A multiget request is in error if there was an error in * receiving response for any its fragments. */ bool req_error(struct conn *conn, struct msg *msg) { struct msg *cmsg; /* current message */ uint64_t id; uint32_t nfragment; ASSERT(msg->request && req_done(conn, msg)); if (msg->error) { return true; } id = msg->frag_id; if (id == 0) { return false; } if (msg->ferror) { /* request has already been marked to be in error */ return true; } /* check if any of the fragments of the given request are in error */ for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { if (cmsg->error) { goto ferror; } } for (cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_NEXT(cmsg, c_tqe)) { if (cmsg->error) { goto ferror; } } return false; ferror: /* * Mark all fragments of the given request to be in error to speed up * future req_error calls for any of fragments of this request */ msg->ferror = 1; nfragment = 1; for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { cmsg->ferror = 1; nfragment++; } for (cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_NEXT(cmsg, c_tqe)) { cmsg->ferror = 1; nfragment++; } log_debug(LOG_DEBUG, "req from c %d with fid %"PRIu64" and %"PRIu32" " "fragments is in error", conn->sd, id, nfragment); return true; } void req_server_enqueue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); /* * timeout clock starts ticking the instant the message is enqueued into * the server in_q; the clock continues to tick until it either expires * or the message is dequeued from the server out_q * * noreply request are free from timeouts because client is not intrested * in the reponse anyway! */ if (!msg->noreply) { msg_tmo_insert(msg, conn); } TAILQ_INSERT_TAIL(&conn->imsg_q, msg, s_tqe); stats_server_incr(ctx, conn->owner, in_queue); stats_server_incr_by(ctx, conn->owner, in_queue_bytes, msg->mlen); } void req_server_dequeue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); TAILQ_REMOVE(&conn->imsg_q, msg, s_tqe); stats_server_decr(ctx, conn->owner, in_queue); stats_server_decr_by(ctx, conn->owner, in_queue_bytes, msg->mlen); } void req_client_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(conn->client && !conn->proxy); TAILQ_INSERT_TAIL(&conn->omsg_q, msg, c_tqe); } void req_server_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); TAILQ_INSERT_TAIL(&conn->omsg_q, msg, s_tqe); stats_server_incr(ctx, conn->owner, out_queue); stats_server_incr_by(ctx, conn->owner, out_queue_bytes, msg->mlen); } void req_client_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(conn->client && !conn->proxy); TAILQ_REMOVE(&conn->omsg_q, msg, c_tqe); } void req_server_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); msg_tmo_delete(msg); TAILQ_REMOVE(&conn->omsg_q, msg, s_tqe); stats_server_decr(ctx, conn->owner, out_queue); stats_server_decr_by(ctx, conn->owner, out_queue_bytes, msg->mlen); } struct msg * req_recv_next(struct context *ctx, struct conn *conn, bool alloc) { struct msg *msg; ASSERT(conn->client && !conn->proxy); if (conn->eof) { msg = conn->rmsg; /* client sent eof before sending the entire request */ if (msg != NULL) { conn->rmsg = NULL; ASSERT(msg->peer == NULL); ASSERT(msg->request && !msg->done); log_error("eof c %d discarding incomplete req %"PRIu64" len " "%"PRIu32"", conn->sd, msg->id, msg->mlen); req_put(msg); } /* * TCP half-close enables the client to terminate its half of the * connection (i.e. the client no longer sends data), but it still * is able to receive data from the proxy. The proxy closes its * half (by sending the second FIN) when the client has no * outstanding requests */ if (!conn->active(conn)) { conn->done = 1; log_debug(LOG_INFO, "c %d is done", conn->sd); } return NULL; } msg = conn->rmsg; if (msg != NULL) { ASSERT(msg->request); return msg; } if (!alloc) { return NULL; } msg = req_get(conn); if (msg != NULL) { conn->rmsg = msg; } return msg; } static rstatus_t req_make_reply(struct context *ctx, struct conn *conn, struct msg *req) { struct msg *msg; msg = msg_get(conn, true, conn->redis); /* replay */ if (msg == NULL) { conn->err = errno; return NC_ENOMEM; } req->peer = msg; msg->peer = req; msg->request = 0; req->done = 1; conn->enqueue_outq(ctx, conn, req); return NC_OK; } static bool req_filter(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(conn->client && !conn->proxy); if (msg_empty(msg)) { ASSERT(conn->rmsg == NULL); log_debug(LOG_VERB, "filter empty req %"PRIu64" from c %d", msg->id, conn->sd); req_put(msg); return true; } /* * Handle "quit\r\n", which is the protocol way of doing a * passive close */ if (msg->quit) { ASSERT(conn->rmsg == NULL); log_debug(LOG_INFO, "filter quit req %"PRIu64" from c %d", msg->id, conn->sd); conn->eof = 1; conn->recv_ready = 0; req_put(msg); return true; } return false; } static void req_forward_error(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; ASSERT(conn->client && !conn->proxy); log_debug(LOG_INFO, "forward req %"PRIu64" len %"PRIu32" type %d from " "c %d failed: %s", msg->id, msg->mlen, msg->type, conn->sd, strerror(errno)); msg->done = 1; msg->error = 1; msg->err = errno; /* noreply request don't expect any response */ if (msg->noreply) { req_put(msg); return; } if (req_done(conn, TAILQ_FIRST(&conn->omsg_q))) { status = event_add_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } } } static void req_forward_stats(struct context *ctx, struct server *server, struct msg *msg) { ASSERT(msg->request); stats_server_incr(ctx, server, requests); stats_server_incr_by(ctx, server, request_bytes, msg->mlen); } static void req_forward(struct context *ctx, struct conn *c_conn, struct msg *msg) { rstatus_t status; struct conn *s_conn; struct server_pool *pool; uint8_t *key; uint32_t keylen; struct keypos *kpos; ASSERT(c_conn->client && !c_conn->proxy); /* enqueue message (request) into client outq, if response is expected */ if (!msg->noreply) { c_conn->enqueue_outq(ctx, c_conn, msg); } pool = c_conn->owner; ASSERT(array_n(msg->keys) > 0); kpos = array_get(msg->keys, 0); key = kpos->start; keylen = (uint32_t)(kpos->end - kpos->start); s_conn = server_pool_conn(ctx, c_conn->owner, key, keylen); if (s_conn == NULL) { req_forward_error(ctx, c_conn, msg); return; } ASSERT(!s_conn->client && !s_conn->proxy); /* enqueue the message (request) into server inq */ if (TAILQ_EMPTY(&s_conn->imsg_q)) { status = event_add_out(ctx->evb, s_conn); if (status != NC_OK) { req_forward_error(ctx, c_conn, msg); s_conn->err = errno; return; } } s_conn->enqueue_inq(ctx, s_conn, msg); req_forward_stats(ctx, s_conn->owner, msg); log_debug(LOG_VERB, "forward from c %d to s %d req %"PRIu64" len %"PRIu32 " type %d with key '%.*s'", c_conn->sd, s_conn->sd, msg->id, msg->mlen, msg->type, keylen, key); } void req_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg) { rstatus_t status; struct server_pool *pool; struct msg_tqh frag_msgq; struct msg *sub_msg; struct msg *tmsg; /* tmp next message */ ASSERT(conn->client && !conn->proxy); ASSERT(msg->request); ASSERT(msg->owner == conn); ASSERT(conn->rmsg == msg); ASSERT(nmsg == NULL || nmsg->request); /* enqueue next message (request), if any */ conn->rmsg = nmsg; if (req_filter(ctx, conn, msg)) { return; } if (msg->noforward) { status = req_make_reply(ctx, conn, msg); if (status != NC_OK) { conn->err = errno; return; } status = msg->reply(msg); if (status != NC_OK) { conn->err = errno; return; } status = event_add_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } return; } /* do fragment */ pool = conn->owner; TAILQ_INIT(&frag_msgq); status = msg->fragment(msg, pool->ncontinuum, &frag_msgq); if (status != NC_OK) { if (!msg->noreply) { conn->enqueue_outq(ctx, conn, msg); } req_forward_error(ctx, conn, msg); } /* if no fragment happened */ if (TAILQ_EMPTY(&frag_msgq)) { req_forward(ctx, conn, msg); return; } status = req_make_reply(ctx, conn, msg); if (status != NC_OK) { if (!msg->noreply) { conn->enqueue_outq(ctx, conn, msg); } req_forward_error(ctx, conn, msg); } for (sub_msg = TAILQ_FIRST(&frag_msgq); sub_msg != NULL; sub_msg = tmsg) { tmsg = TAILQ_NEXT(sub_msg, m_tqe); TAILQ_REMOVE(&frag_msgq, sub_msg, m_tqe); req_forward(ctx, conn, sub_msg); } ASSERT(TAILQ_EMPTY(&frag_msgq)); return; } struct msg * req_send_next(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *nmsg; /* current and next message */ ASSERT(!conn->client && !conn->proxy); if (conn->connecting) { server_connected(ctx, conn); } nmsg = TAILQ_FIRST(&conn->imsg_q); if (nmsg == NULL) { /* nothing to send as the server inq is empty */ status = event_del_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } return NULL; } msg = conn->smsg; if (msg != NULL) { ASSERT(msg->request && !msg->done); nmsg = TAILQ_NEXT(msg, s_tqe); } conn->smsg = nmsg; if (nmsg == NULL) { return NULL; } ASSERT(nmsg->request && !nmsg->done); log_debug(LOG_VVERB, "send next req %"PRIu64" len %"PRIu32" type %d on " "s %d", nmsg->id, nmsg->mlen, nmsg->type, conn->sd); return nmsg; } void req_send_done(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(!conn->client && !conn->proxy); ASSERT(msg != NULL && conn->smsg == NULL); ASSERT(msg->request && !msg->done); ASSERT(msg->owner != conn); log_debug(LOG_VVERB, "send done req %"PRIu64" len %"PRIu32" type %d on " "s %d", msg->id, msg->mlen, msg->type, conn->sd); /* dequeue the message (request) from server inq */ conn->dequeue_inq(ctx, conn, msg); /* * noreply request instructs the server not to send any response. So, * enqueue message (request) in server outq, if response is expected. * Otherwise, free the noreply request */ if (!msg->noreply) { conn->enqueue_outq(ctx, conn, msg); } else { req_put(msg); } } nutcracker-0.4.0+dfsg/src/nc_response.c000066400000000000000000000222371242132376000200520ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include struct msg * rsp_get(struct conn *conn) { struct msg *msg; ASSERT(!conn->client && !conn->proxy); msg = msg_get(conn, false, conn->redis); if (msg == NULL) { conn->err = errno; } return msg; } void rsp_put(struct msg *msg) { ASSERT(!msg->request); ASSERT(msg->peer == NULL); msg_put(msg); } static struct msg * rsp_make_error(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *pmsg; /* peer message (response) */ struct msg *cmsg, *nmsg; /* current and next message (request) */ uint64_t id; err_t err; ASSERT(conn->client && !conn->proxy); ASSERT(msg->request && req_error(conn, msg)); ASSERT(msg->owner == conn); id = msg->frag_id; if (id != 0) { for (err = 0, cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = nmsg) { nmsg = TAILQ_NEXT(cmsg, c_tqe); /* dequeue request (error fragment) from client outq */ conn->dequeue_outq(ctx, conn, cmsg); if (err == 0 && cmsg->err != 0) { err = cmsg->err; } req_put(cmsg); } } else { err = msg->err; } pmsg = msg->peer; if (pmsg != NULL) { ASSERT(!pmsg->request && pmsg->peer == msg); msg->peer = NULL; pmsg->peer = NULL; rsp_put(pmsg); } return msg_get_error(conn->redis, err); } struct msg * rsp_recv_next(struct context *ctx, struct conn *conn, bool alloc) { struct msg *msg; ASSERT(!conn->client && !conn->proxy); if (conn->eof) { msg = conn->rmsg; /* server sent eof before sending the entire request */ if (msg != NULL) { conn->rmsg = NULL; ASSERT(msg->peer == NULL); ASSERT(!msg->request); log_error("eof s %d discarding incomplete rsp %"PRIu64" len " "%"PRIu32"", conn->sd, msg->id, msg->mlen); rsp_put(msg); } /* * We treat TCP half-close from a server different from how we treat * those from a client. On a FIN from a server, we close the connection * immediately by sending the second FIN even if there were outstanding * or pending requests. This is actually a tricky part in the FA, as * we don't expect this to happen unless the server is misbehaving or * it crashes */ conn->done = 1; log_error("s %d active %d is done", conn->sd, conn->active(conn)); return NULL; } msg = conn->rmsg; if (msg != NULL) { ASSERT(!msg->request); return msg; } if (!alloc) { return NULL; } msg = rsp_get(conn); if (msg != NULL) { conn->rmsg = msg; } return msg; } static bool rsp_filter(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *pmsg; ASSERT(!conn->client && !conn->proxy); if (msg_empty(msg)) { ASSERT(conn->rmsg == NULL); log_debug(LOG_VERB, "filter empty rsp %"PRIu64" on s %d", msg->id, conn->sd); rsp_put(msg); return true; } pmsg = TAILQ_FIRST(&conn->omsg_q); if (pmsg == NULL) { log_debug(LOG_ERR, "filter stray rsp %"PRIu64" len %"PRIu32" on s %d", msg->id, msg->mlen, conn->sd); rsp_put(msg); /* * Memcached server can respond with an error response before it has * received the entire request. This is most commonly seen for set * requests that exceed item_size_max. IMO, this behavior of memcached * is incorrect. The right behavior for update requests that are over * item_size_max would be to either: * - close the connection Or, * - read the entire item_size_max data and then send CLIENT_ERROR * * We handle this stray packet scenario in nutcracker by closing the * server connection which would end up sending SERVER_ERROR to all * clients that have requests pending on this server connection. The * fix is aggresive, but not doing so would lead to clients getting * out of sync with the server and as a result clients end up getting * responses that don't correspond to the right request. * * See: https://github.com/twitter/twemproxy/issues/149 */ conn->err = EINVAL; conn->done = 1; return true; } ASSERT(pmsg->peer == NULL); ASSERT(pmsg->request && !pmsg->done); if (pmsg->swallow) { conn->dequeue_outq(ctx, conn, pmsg); pmsg->done = 1; log_debug(LOG_INFO, "swallow rsp %"PRIu64" len %"PRIu32" of req " "%"PRIu64" on s %d", msg->id, msg->mlen, pmsg->id, conn->sd); rsp_put(msg); req_put(pmsg); return true; } return false; } static void rsp_forward_stats(struct context *ctx, struct server *server, struct msg *msg) { ASSERT(!msg->request); stats_server_incr(ctx, server, responses); stats_server_incr_by(ctx, server, response_bytes, msg->mlen); } static void rsp_forward(struct context *ctx, struct conn *s_conn, struct msg *msg) { rstatus_t status; struct msg *pmsg; struct conn *c_conn; ASSERT(!s_conn->client && !s_conn->proxy); /* response from server implies that server is ok and heartbeating */ server_ok(ctx, s_conn); /* dequeue peer message (request) from server */ pmsg = TAILQ_FIRST(&s_conn->omsg_q); ASSERT(pmsg != NULL && pmsg->peer == NULL); ASSERT(pmsg->request && !pmsg->done); s_conn->dequeue_outq(ctx, s_conn, pmsg); pmsg->done = 1; /* establish msg <-> pmsg (response <-> request) link */ pmsg->peer = msg; msg->peer = pmsg; msg->pre_coalesce(msg); c_conn = pmsg->owner; ASSERT(c_conn->client && !c_conn->proxy); if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) { status = event_add_out(ctx->evb, c_conn); if (status != NC_OK) { c_conn->err = errno; } } rsp_forward_stats(ctx, s_conn->owner, msg); } void rsp_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg) { ASSERT(!conn->client && !conn->proxy); ASSERT(msg != NULL && conn->rmsg == msg); ASSERT(!msg->request); ASSERT(msg->owner == conn); ASSERT(nmsg == NULL || !nmsg->request); /* enqueue next message (response), if any */ conn->rmsg = nmsg; if (rsp_filter(ctx, conn, msg)) { return; } rsp_forward(ctx, conn, msg); } struct msg * rsp_send_next(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *pmsg; /* response and it's peer request */ ASSERT(conn->client && !conn->proxy); pmsg = TAILQ_FIRST(&conn->omsg_q); if (pmsg == NULL || !req_done(conn, pmsg)) { /* nothing is outstanding, initiate close? */ if (pmsg == NULL && conn->eof) { conn->done = 1; log_debug(LOG_INFO, "c %d is done", conn->sd); } status = event_del_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } return NULL; } msg = conn->smsg; if (msg != NULL) { ASSERT(!msg->request && msg->peer != NULL); ASSERT(req_done(conn, msg->peer)); pmsg = TAILQ_NEXT(msg->peer, c_tqe); } if (pmsg == NULL || !req_done(conn, pmsg)) { conn->smsg = NULL; return NULL; } ASSERT(pmsg->request && !pmsg->swallow); if (req_error(conn, pmsg)) { msg = rsp_make_error(ctx, conn, pmsg); if (msg == NULL) { conn->err = errno; return NULL; } msg->peer = pmsg; pmsg->peer = msg; stats_pool_incr(ctx, conn->owner, forward_error); } else { msg = pmsg->peer; } ASSERT(!msg->request); conn->smsg = msg; log_debug(LOG_VVERB, "send next rsp %"PRIu64" on c %d", msg->id, conn->sd); return msg; } void rsp_send_done(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *pmsg; /* peer message (request) */ ASSERT(conn->client && !conn->proxy); ASSERT(conn->smsg == NULL); log_debug(LOG_VVERB, "send done rsp %"PRIu64" on c %d", msg->id, conn->sd); pmsg = msg->peer; ASSERT(!msg->request && pmsg->request); ASSERT(pmsg->peer == msg); ASSERT(pmsg->done && !pmsg->swallow); /* dequeue request from client outq */ conn->dequeue_outq(ctx, conn, pmsg); req_put(pmsg); } nutcracker-0.4.0+dfsg/src/nc_server.c000066400000000000000000000544021242132376000175210ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include void server_ref(struct conn *conn, void *owner) { struct server *server = owner; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->owner == NULL); conn->family = server->family; conn->addrlen = server->addrlen; conn->addr = server->addr; server->ns_conn_q++; TAILQ_INSERT_TAIL(&server->s_conn_q, conn, conn_tqe); conn->owner = owner; log_debug(LOG_VVERB, "ref conn %p owner %p into '%.*s", conn, server, server->pname.len, server->pname.data); } void server_unref(struct conn *conn) { struct server *server; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->owner != NULL); server = conn->owner; conn->owner = NULL; ASSERT(server->ns_conn_q != 0); server->ns_conn_q--; TAILQ_REMOVE(&server->s_conn_q, conn, conn_tqe); log_debug(LOG_VVERB, "unref conn %p owner %p from '%.*s'", conn, server, server->pname.len, server->pname.data); } int server_timeout(struct conn *conn) { struct server *server; struct server_pool *pool; ASSERT(!conn->client && !conn->proxy); server = conn->owner; pool = server->owner; return pool->timeout; } bool server_active(struct conn *conn) { ASSERT(!conn->client && !conn->proxy); if (!TAILQ_EMPTY(&conn->imsg_q)) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } if (!TAILQ_EMPTY(&conn->omsg_q)) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } if (conn->rmsg != NULL) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } if (conn->smsg != NULL) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } log_debug(LOG_VVERB, "s %d is inactive", conn->sd); return false; } static rstatus_t server_each_set_owner(void *elem, void *data) { struct server *s = elem; struct server_pool *sp = data; s->owner = sp; return NC_OK; } rstatus_t server_init(struct array *server, struct array *conf_server, struct server_pool *sp) { rstatus_t status; uint32_t nserver; nserver = array_n(conf_server); ASSERT(nserver != 0); ASSERT(array_n(server) == 0); status = array_init(server, nserver, sizeof(struct server)); if (status != NC_OK) { return status; } /* transform conf server to server */ status = array_each(conf_server, conf_server_each_transform, server); if (status != NC_OK) { server_deinit(server); return status; } ASSERT(array_n(server) == nserver); /* set server owner */ status = array_each(server, server_each_set_owner, sp); if (status != NC_OK) { server_deinit(server); return status; } log_debug(LOG_DEBUG, "init %"PRIu32" servers in pool %"PRIu32" '%.*s'", nserver, sp->idx, sp->name.len, sp->name.data); return NC_OK; } void server_deinit(struct array *server) { uint32_t i, nserver; for (i = 0, nserver = array_n(server); i < nserver; i++) { struct server *s; s = array_pop(server); ASSERT(TAILQ_EMPTY(&s->s_conn_q) && s->ns_conn_q == 0); } array_deinit(server); } struct conn * server_conn(struct server *server) { struct server_pool *pool; struct conn *conn; pool = server->owner; /* * FIXME: handle multiple server connections per server and do load * balancing on it. Support multiple algorithms for * 'server_connections:' > 0 key */ if (server->ns_conn_q < pool->server_connections) { return conn_get(server, false, pool->redis); } ASSERT(server->ns_conn_q == pool->server_connections); /* * Pick a server connection from the head of the queue and insert * it back into the tail of queue to maintain the lru order */ conn = TAILQ_FIRST(&server->s_conn_q); ASSERT(!conn->client && !conn->proxy); TAILQ_REMOVE(&server->s_conn_q, conn, conn_tqe); TAILQ_INSERT_TAIL(&server->s_conn_q, conn, conn_tqe); return conn; } static rstatus_t server_each_preconnect(void *elem, void *data) { rstatus_t status; struct server *server; struct server_pool *pool; struct conn *conn; server = elem; pool = server->owner; conn = server_conn(server); if (conn == NULL) { return NC_ENOMEM; } status = server_connect(pool->ctx, server, conn); if (status != NC_OK) { log_warn("connect to server '%.*s' failed, ignored: %s", server->pname.len, server->pname.data, strerror(errno)); server_close(pool->ctx, conn); } return NC_OK; } static rstatus_t server_each_disconnect(void *elem, void *data) { struct server *server; struct server_pool *pool; server = elem; pool = server->owner; while (!TAILQ_EMPTY(&server->s_conn_q)) { struct conn *conn; ASSERT(server->ns_conn_q > 0); conn = TAILQ_FIRST(&server->s_conn_q); conn->close(pool->ctx, conn); } return NC_OK; } static void server_failure(struct context *ctx, struct server *server) { struct server_pool *pool = server->owner; int64_t now, next; rstatus_t status; if (!pool->auto_eject_hosts) { return; } server->failure_count++; log_debug(LOG_VERB, "server '%.*s' failure count %"PRIu32" limit %"PRIu32, server->pname.len, server->pname.data, server->failure_count, pool->server_failure_limit); if (server->failure_count < pool->server_failure_limit) { return; } now = nc_usec_now(); if (now < 0) { return; } stats_server_set_ts(ctx, server, server_ejected_at, now); next = now + pool->server_retry_timeout; log_debug(LOG_INFO, "update pool %"PRIu32" '%.*s' to delete server '%.*s' " "for next %"PRIu32" secs", pool->idx, pool->name.len, pool->name.data, server->pname.len, server->pname.data, pool->server_retry_timeout / 1000 / 1000); stats_pool_incr(ctx, pool, server_ejects); server->failure_count = 0; server->next_retry = next; status = server_pool_run(pool); if (status != NC_OK) { log_error("updating pool %"PRIu32" '%.*s' failed: %s", pool->idx, pool->name.len, pool->name.data, strerror(errno)); } } static void server_close_stats(struct context *ctx, struct server *server, err_t err, unsigned eof, unsigned connected) { if (connected) { stats_server_decr(ctx, server, server_connections); } if (eof) { stats_server_incr(ctx, server, server_eof); return; } switch (err) { case ETIMEDOUT: stats_server_incr(ctx, server, server_timedout); break; case EPIPE: case ECONNRESET: case ECONNABORTED: case ECONNREFUSED: case ENOTCONN: case ENETDOWN: case ENETUNREACH: case EHOSTDOWN: case EHOSTUNREACH: default: stats_server_incr(ctx, server, server_err); break; } } void server_close(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *nmsg; /* current and next message */ struct conn *c_conn; /* peer client connection */ ASSERT(!conn->client && !conn->proxy); server_close_stats(ctx, conn->owner, conn->err, conn->eof, conn->connected); if (conn->sd < 0) { server_failure(ctx, conn->owner); conn->unref(conn); conn_put(conn); return; } for (msg = TAILQ_FIRST(&conn->imsg_q); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, s_tqe); /* dequeue the message (request) from server inq */ conn->dequeue_inq(ctx, conn, msg); /* * Don't send any error response, if * 1. request is tagged as noreply or, * 2. client has already closed its connection */ if (msg->swallow || msg->noreply) { log_debug(LOG_INFO, "close s %d swallow req %"PRIu64" len %"PRIu32 " type %d", conn->sd, msg->id, msg->mlen, msg->type); req_put(msg); } else { c_conn = msg->owner; ASSERT(c_conn->client && !c_conn->proxy); msg->done = 1; msg->error = 1; msg->err = conn->err; if (msg->frag_owner != NULL) { msg->frag_owner->nfrag_done++; } if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) { event_add_out(ctx->evb, msg->owner); } log_debug(LOG_INFO, "close s %d schedule error for req %"PRIu64" " "len %"PRIu32" type %d from c %d%c %s", conn->sd, msg->id, msg->mlen, msg->type, c_conn->sd, conn->err ? ':' : ' ', conn->err ? strerror(conn->err): " "); } } ASSERT(TAILQ_EMPTY(&conn->imsg_q)); for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, s_tqe); /* dequeue the message (request) from server outq */ conn->dequeue_outq(ctx, conn, msg); if (msg->swallow) { log_debug(LOG_INFO, "close s %d swallow req %"PRIu64" len %"PRIu32 " type %d", conn->sd, msg->id, msg->mlen, msg->type); req_put(msg); } else { c_conn = msg->owner; ASSERT(c_conn->client && !c_conn->proxy); msg->done = 1; msg->error = 1; msg->err = conn->err; if (msg->frag_owner != NULL) { msg->frag_owner->nfrag_done++; } if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) { event_add_out(ctx->evb, msg->owner); } log_debug(LOG_INFO, "close s %d schedule error for req %"PRIu64" " "len %"PRIu32" type %d from c %d%c %s", conn->sd, msg->id, msg->mlen, msg->type, c_conn->sd, conn->err ? ':' : ' ', conn->err ? strerror(conn->err): " "); } } ASSERT(TAILQ_EMPTY(&conn->omsg_q)); msg = conn->rmsg; if (msg != NULL) { conn->rmsg = NULL; ASSERT(!msg->request); ASSERT(msg->peer == NULL); rsp_put(msg); log_debug(LOG_INFO, "close s %d discarding rsp %"PRIu64" len %"PRIu32" " "in error", conn->sd, msg->id, msg->mlen); } ASSERT(conn->smsg == NULL); server_failure(ctx, conn->owner); conn->unref(conn); status = close(conn->sd); if (status < 0) { log_error("close s %d failed, ignored: %s", conn->sd, strerror(errno)); } conn->sd = -1; conn_put(conn); } rstatus_t server_connect(struct context *ctx, struct server *server, struct conn *conn) { rstatus_t status; ASSERT(!conn->client && !conn->proxy); if (conn->sd > 0) { /* already connected on server connection */ return NC_OK; } log_debug(LOG_VVERB, "connect to server '%.*s'", server->pname.len, server->pname.data); conn->sd = socket(conn->family, SOCK_STREAM, 0); if (conn->sd < 0) { log_error("socket for server '%.*s' failed: %s", server->pname.len, server->pname.data, strerror(errno)); status = NC_ERROR; goto error; } status = nc_set_nonblocking(conn->sd); if (status != NC_OK) { log_error("set nonblock on s %d for server '%.*s' failed: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); goto error; } if (server->pname.data[0] != '/') { status = nc_set_tcpnodelay(conn->sd); if (status != NC_OK) { log_warn("set tcpnodelay on s %d for server '%.*s' failed, ignored: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); } } status = event_add_conn(ctx->evb, conn); if (status != NC_OK) { log_error("event add conn s %d for server '%.*s' failed: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); goto error; } ASSERT(!conn->connecting && !conn->connected); status = connect(conn->sd, conn->addr, conn->addrlen); if (status != NC_OK) { if (errno == EINPROGRESS) { conn->connecting = 1; log_debug(LOG_DEBUG, "connecting on s %d to server '%.*s'", conn->sd, server->pname.len, server->pname.data); return NC_OK; } log_error("connect on s %d to server '%.*s' failed: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); goto error; } ASSERT(!conn->connecting); conn->connected = 1; log_debug(LOG_INFO, "connected on s %d to server '%.*s'", conn->sd, server->pname.len, server->pname.data); return NC_OK; error: conn->err = errno; return status; } void server_connected(struct context *ctx, struct conn *conn) { struct server *server = conn->owner; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->connecting && !conn->connected); stats_server_incr(ctx, server, server_connections); conn->connecting = 0; conn->connected = 1; log_debug(LOG_INFO, "connected on s %d to server '%.*s'", conn->sd, server->pname.len, server->pname.data); } void server_ok(struct context *ctx, struct conn *conn) { struct server *server = conn->owner; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->connected); if (server->failure_count != 0) { log_debug(LOG_VERB, "reset server '%.*s' failure count from %"PRIu32 " to 0", server->pname.len, server->pname.data, server->failure_count); server->failure_count = 0; server->next_retry = 0LL; } } static rstatus_t server_pool_update(struct server_pool *pool) { rstatus_t status; int64_t now; uint32_t pnlive_server; /* prev # live server */ if (!pool->auto_eject_hosts) { return NC_OK; } if (pool->next_rebuild == 0LL) { return NC_OK; } now = nc_usec_now(); if (now < 0) { return NC_ERROR; } if (now <= pool->next_rebuild) { if (pool->nlive_server == 0) { errno = ECONNREFUSED; return NC_ERROR; } return NC_OK; } pnlive_server = pool->nlive_server; status = server_pool_run(pool); if (status != NC_OK) { log_error("updating pool %"PRIu32" with dist %d failed: %s", pool->idx, pool->dist_type, strerror(errno)); return status; } log_debug(LOG_INFO, "update pool %"PRIu32" '%.*s' to add %"PRIu32" servers", pool->idx, pool->name.len, pool->name.data, pool->nlive_server - pnlive_server); return NC_OK; } static uint32_t server_pool_hash(struct server_pool *pool, uint8_t *key, uint32_t keylen) { ASSERT(array_n(&pool->server) != 0); if (array_n(&pool->server) == 1) { return 0; } ASSERT(key != NULL && keylen != 0); return pool->key_hash((char *)key, keylen); } uint32_t server_pool_idx(struct server_pool *pool, uint8_t *key, uint32_t keylen) { uint32_t hash, idx; ASSERT(array_n(&pool->server) != 0); ASSERT(key != NULL && keylen != 0); /* * If hash_tag: is configured for this server pool, we use the part of * the key within the hash tag as an input to the distributor. Otherwise * we use the full key */ if (!string_empty(&pool->hash_tag)) { struct string *tag = &pool->hash_tag; uint8_t *tag_start, *tag_end; tag_start = nc_strchr(key, key + keylen, tag->data[0]); if (tag_start != NULL) { tag_end = nc_strchr(tag_start + 1, key + keylen, tag->data[1]); if ((tag_end != NULL) && (tag_end - tag_start > 1)) { key = tag_start + 1; keylen = (uint32_t)(tag_end - key); } } } switch (pool->dist_type) { case DIST_KETAMA: hash = server_pool_hash(pool, key, keylen); idx = ketama_dispatch(pool->continuum, pool->ncontinuum, hash); break; case DIST_MODULA: hash = server_pool_hash(pool, key, keylen); idx = modula_dispatch(pool->continuum, pool->ncontinuum, hash); break; case DIST_RANDOM: idx = random_dispatch(pool->continuum, pool->ncontinuum, 0); break; default: NOT_REACHED(); return 0; } ASSERT(idx < array_n(&pool->server)); return idx; } static struct server * server_pool_server(struct server_pool *pool, uint8_t *key, uint32_t keylen) { struct server *server; uint32_t idx; idx = server_pool_idx(pool, key, keylen); server = array_get(&pool->server, idx); log_debug(LOG_VERB, "key '%.*s' on dist %d maps to server '%.*s'", keylen, key, pool->dist_type, server->pname.len, server->pname.data); return server; } struct conn * server_pool_conn(struct context *ctx, struct server_pool *pool, uint8_t *key, uint32_t keylen) { rstatus_t status; struct server *server; struct conn *conn; status = server_pool_update(pool); if (status != NC_OK) { return NULL; } /* from a given {key, keylen} pick a server from pool */ server = server_pool_server(pool, key, keylen); if (server == NULL) { return NULL; } /* pick a connection to a given server */ conn = server_conn(server); if (conn == NULL) { return NULL; } status = server_connect(ctx, server, conn); if (status != NC_OK) { server_close(ctx, conn); return NULL; } return conn; } static rstatus_t server_pool_each_preconnect(void *elem, void *data) { rstatus_t status; struct server_pool *sp = elem; if (!sp->preconnect) { return NC_OK; } status = array_each(&sp->server, server_each_preconnect, NULL); if (status != NC_OK) { return status; } return NC_OK; } rstatus_t server_pool_preconnect(struct context *ctx) { rstatus_t status; status = array_each(&ctx->pool, server_pool_each_preconnect, NULL); if (status != NC_OK) { return status; } return NC_OK; } static rstatus_t server_pool_each_disconnect(void *elem, void *data) { rstatus_t status; struct server_pool *sp = elem; status = array_each(&sp->server, server_each_disconnect, NULL); if (status != NC_OK) { return status; } return NC_OK; } void server_pool_disconnect(struct context *ctx) { array_each(&ctx->pool, server_pool_each_disconnect, NULL); } static rstatus_t server_pool_each_set_owner(void *elem, void *data) { struct server_pool *sp = elem; struct context *ctx = data; sp->ctx = ctx; return NC_OK; } static rstatus_t server_pool_each_calc_connections(void *elem, void *data) { struct server_pool *sp = elem; struct context *ctx = data; ctx->max_nsconn += sp->server_connections * array_n(&sp->server); ctx->max_nsconn += 1; /* pool listening socket */ return NC_OK; } rstatus_t server_pool_run(struct server_pool *pool) { ASSERT(array_n(&pool->server) != 0); switch (pool->dist_type) { case DIST_KETAMA: return ketama_update(pool); case DIST_MODULA: return modula_update(pool); case DIST_RANDOM: return random_update(pool); default: NOT_REACHED(); return NC_ERROR; } return NC_OK; } static rstatus_t server_pool_each_run(void *elem, void *data) { return server_pool_run(elem); } rstatus_t server_pool_init(struct array *server_pool, struct array *conf_pool, struct context *ctx) { rstatus_t status; uint32_t npool; npool = array_n(conf_pool); ASSERT(npool != 0); ASSERT(array_n(server_pool) == 0); status = array_init(server_pool, npool, sizeof(struct server_pool)); if (status != NC_OK) { return status; } /* transform conf pool to server pool */ status = array_each(conf_pool, conf_pool_each_transform, server_pool); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } ASSERT(array_n(server_pool) == npool); /* set ctx as the server pool owner */ status = array_each(server_pool, server_pool_each_set_owner, ctx); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } /* compute max server connections */ ctx->max_nsconn = 0; status = array_each(server_pool, server_pool_each_calc_connections, ctx); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } /* update server pool continuum */ status = array_each(server_pool, server_pool_each_run, NULL); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } log_debug(LOG_DEBUG, "init %"PRIu32" pools", npool); return NC_OK; } void server_pool_deinit(struct array *server_pool) { uint32_t i, npool; for (i = 0, npool = array_n(server_pool); i < npool; i++) { struct server_pool *sp; sp = array_pop(server_pool); ASSERT(sp->p_conn == NULL); ASSERT(TAILQ_EMPTY(&sp->c_conn_q) && sp->nc_conn_q == 0); if (sp->continuum != NULL) { nc_free(sp->continuum); sp->ncontinuum = 0; sp->nserver_continuum = 0; sp->nlive_server = 0; } server_deinit(&sp->server); log_debug(LOG_DEBUG, "deinit pool %"PRIu32" '%.*s'", sp->idx, sp->name.len, sp->name.data); } array_deinit(server_pool); log_debug(LOG_DEBUG, "deinit %"PRIu32" pools", npool); } nutcracker-0.4.0+dfsg/src/nc_server.h000066400000000000000000000141651242132376000175300ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_SERVER_H_ #define _NC_SERVER_H_ #include /* * server_pool is a collection of servers and their continuum. Each * server_pool is the owner of a single proxy connection and one or * more client connections. server_pool itself is owned by the current * context. * * Each server is the owner of one or more server connections. server * itself is owned by the server_pool. * * +-------------+ * | |<---------------------+ * | |<------------+ | * | | +-------+--+-----+----+--------------+ * | pool 0 |+--->| | | | * | | | server 0 | server 1 | ... ... | * | | | | | |--+ * | | +----------+----------+--------------+ | * +-------------+ // * | | * | | * | | * | pool 1 | * | | * | | * | | * +-------------+ * | | * | | * . . * . ... . * . . * | | * | | * +-------------+ * | * | * // */ typedef uint32_t (*hash_t)(const char *, size_t); struct continuum { uint32_t index; /* server index */ uint32_t value; /* hash value */ }; struct server { uint32_t idx; /* server index */ struct server_pool *owner; /* owner pool */ struct string pname; /* name:port:weight (ref in conf_server) */ struct string name; /* name (ref in conf_server) */ uint16_t port; /* port */ uint32_t weight; /* weight */ int family; /* socket family */ socklen_t addrlen; /* socket length */ struct sockaddr *addr; /* socket address (ref in conf_server) */ uint32_t ns_conn_q; /* # server connection */ struct conn_tqh s_conn_q; /* server connection q */ int64_t next_retry; /* next retry time in usec */ uint32_t failure_count; /* # consecutive failures */ }; struct server_pool { uint32_t idx; /* pool index */ struct context *ctx; /* owner context */ struct conn *p_conn; /* proxy connection (listener) */ uint32_t nc_conn_q; /* # client connection */ struct conn_tqh c_conn_q; /* client connection q */ struct array server; /* server[] */ uint32_t ncontinuum; /* # continuum points */ uint32_t nserver_continuum; /* # servers - live and dead on continuum (const) */ struct continuum *continuum; /* continuum */ uint32_t nlive_server; /* # live server */ int64_t next_rebuild; /* next distribution rebuild time in usec */ struct string name; /* pool name (ref in conf_pool) */ struct string addrstr; /* pool address (ref in conf_pool) */ uint16_t port; /* port */ int family; /* socket family */ socklen_t addrlen; /* socket length */ struct sockaddr *addr; /* socket address (ref in conf_pool) */ int dist_type; /* distribution type (dist_type_t) */ int key_hash_type; /* key hash type (hash_type_t) */ hash_t key_hash; /* key hasher */ struct string hash_tag; /* key hash tag (ref in conf_pool) */ int timeout; /* timeout in msec */ int backlog; /* listen backlog */ uint32_t client_connections; /* maximum # client connection */ uint32_t server_connections; /* maximum # server connection */ int64_t server_retry_timeout; /* server retry timeout in usec */ uint32_t server_failure_limit; /* server failure limit */ unsigned auto_eject_hosts:1; /* auto_eject_hosts? */ unsigned preconnect:1; /* preconnect? */ unsigned redis:1; /* redis? */ }; void server_ref(struct conn *conn, void *owner); void server_unref(struct conn *conn); int server_timeout(struct conn *conn); bool server_active(struct conn *conn); rstatus_t server_init(struct array *server, struct array *conf_server, struct server_pool *sp); void server_deinit(struct array *server); struct conn *server_conn(struct server *server); rstatus_t server_connect(struct context *ctx, struct server *server, struct conn *conn); void server_close(struct context *ctx, struct conn *conn); void server_connected(struct context *ctx, struct conn *conn); void server_ok(struct context *ctx, struct conn *conn); uint32_t server_pool_idx(struct server_pool *pool, uint8_t *key, uint32_t keylen); struct conn *server_pool_conn(struct context *ctx, struct server_pool *pool, uint8_t *key, uint32_t keylen); rstatus_t server_pool_run(struct server_pool *pool); rstatus_t server_pool_preconnect(struct context *ctx); void server_pool_disconnect(struct context *ctx); rstatus_t server_pool_init(struct array *server_pool, struct array *conf_pool, struct context *ctx); void server_pool_deinit(struct array *server_pool); #endif nutcracker-0.4.0+dfsg/src/nc_signal.c000066400000000000000000000060131242132376000174630ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include static struct signal signals[] = { { SIGUSR1, "SIGUSR1", 0, signal_handler }, { SIGUSR2, "SIGUSR2", 0, signal_handler }, { SIGTTIN, "SIGTTIN", 0, signal_handler }, { SIGTTOU, "SIGTTOU", 0, signal_handler }, { SIGHUP, "SIGHUP", 0, signal_handler }, { SIGINT, "SIGINT", 0, signal_handler }, { SIGSEGV, "SIGSEGV", (int)SA_RESETHAND, signal_handler }, { SIGPIPE, "SIGPIPE", 0, SIG_IGN }, { 0, NULL, 0, NULL } }; rstatus_t signal_init(void) { struct signal *sig; for (sig = signals; sig->signo != 0; sig++) { rstatus_t status; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig->handler; sa.sa_flags = sig->flags; sigemptyset(&sa.sa_mask); status = sigaction(sig->signo, &sa, NULL); if (status < 0) { log_error("sigaction(%s) failed: %s", sig->signame, strerror(errno)); return NC_ERROR; } } return NC_OK; } void signal_deinit(void) { } void signal_handler(int signo) { struct signal *sig; void (*action)(void); char *actionstr; bool done; for (sig = signals; sig->signo != 0; sig++) { if (sig->signo == signo) { break; } } ASSERT(sig->signo != 0); actionstr = ""; action = NULL; done = false; switch (signo) { case SIGUSR1: break; case SIGUSR2: break; case SIGTTIN: actionstr = ", up logging level"; action = log_level_up; break; case SIGTTOU: actionstr = ", down logging level"; action = log_level_down; break; case SIGHUP: actionstr = ", reopening log file"; action = log_reopen; break; case SIGINT: done = true; actionstr = ", exiting"; break; case SIGSEGV: log_stacktrace(); actionstr = ", core dumping"; raise(SIGSEGV); break; default: NOT_REACHED(); } log_safe("signal %d (%s) received%s", signo, sig->signame, actionstr); if (action != NULL) { action(); } if (done) { exit(1); } } nutcracker-0.4.0+dfsg/src/nc_signal.h000066400000000000000000000016431242132376000174740ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_SIGNAL_H_ #define _NC_SIGNAL_H_ #include struct signal { int signo; char *signame; int flags; void (*handler)(int signo); }; rstatus_t signal_init(void); void signal_deinit(void); void signal_handler(int signo); #endif nutcracker-0.4.0+dfsg/src/nc_stats.c000066400000000000000000000675551242132376000173660ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include struct stats_desc { char *name; /* stats name */ char *desc; /* stats description */ }; #define DEFINE_ACTION(_name, _type, _desc) { .type = _type, .name = string(#_name) }, static struct stats_metric stats_pool_codec[] = { STATS_POOL_CODEC( DEFINE_ACTION ) }; static struct stats_metric stats_server_codec[] = { STATS_SERVER_CODEC( DEFINE_ACTION ) }; #undef DEFINE_ACTION #define DEFINE_ACTION(_name, _type, _desc) { .name = #_name, .desc = _desc }, static struct stats_desc stats_pool_desc[] = { STATS_POOL_CODEC( DEFINE_ACTION ) }; static struct stats_desc stats_server_desc[] = { STATS_SERVER_CODEC( DEFINE_ACTION ) }; #undef DEFINE_ACTION void stats_describe(void) { uint32_t i; log_stderr("pool stats:"); for (i = 0; i < NELEMS(stats_pool_desc); i++) { log_stderr(" %-20s\"%s\"", stats_pool_desc[i].name, stats_pool_desc[i].desc); } log_stderr(""); log_stderr("server stats:"); for (i = 0; i < NELEMS(stats_server_desc); i++) { log_stderr(" %-20s\"%s\"", stats_server_desc[i].name, stats_server_desc[i].desc); } } static void stats_metric_init(struct stats_metric *stm) { switch (stm->type) { case STATS_COUNTER: stm->value.counter = 0LL; break; case STATS_GAUGE: stm->value.counter = 0LL; break; case STATS_TIMESTAMP: stm->value.timestamp = 0LL; break; default: NOT_REACHED(); } } static void stats_metric_reset(struct array *stats_metric) { uint32_t i, nmetric; nmetric = array_n(stats_metric); ASSERT(nmetric == STATS_POOL_NFIELD || nmetric == STATS_SERVER_NFIELD); for (i = 0; i < nmetric; i++) { struct stats_metric *stm = array_get(stats_metric, i); stats_metric_init(stm); } } static rstatus_t stats_pool_metric_init(struct array *stats_metric) { rstatus_t status; uint32_t i, nfield = STATS_POOL_NFIELD; status = array_init(stats_metric, nfield, sizeof(struct stats_metric)); if (status != NC_OK) { return status; } for (i = 0; i < nfield; i++) { struct stats_metric *stm = array_push(stats_metric); /* initialize from pool codec first */ *stm = stats_pool_codec[i]; /* initialize individual metric */ stats_metric_init(stm); } return NC_OK; } static rstatus_t stats_server_metric_init(struct stats_server *sts) { rstatus_t status; uint32_t i, nfield = STATS_SERVER_NFIELD; status = array_init(&sts->metric, nfield, sizeof(struct stats_metric)); if (status != NC_OK) { return status; } for (i = 0; i < nfield; i++) { struct stats_metric *stm = array_push(&sts->metric); /* initialize from server codec first */ *stm = stats_server_codec[i]; /* initialize individual metric */ stats_metric_init(stm); } return NC_OK; } static void stats_metric_deinit(struct array *metric) { uint32_t i, nmetric; nmetric = array_n(metric); for (i = 0; i < nmetric; i++) { array_pop(metric); } array_deinit(metric); } static rstatus_t stats_server_init(struct stats_server *sts, struct server *s) { rstatus_t status; sts->name = s->name; array_null(&sts->metric); status = stats_server_metric_init(sts); if (status != NC_OK) { return status; } log_debug(LOG_VVVERB, "init stats server '%.*s' with %"PRIu32" metric", sts->name.len, sts->name.data, array_n(&sts->metric)); return NC_OK; } static rstatus_t stats_server_map(struct array *stats_server, struct array *server) { rstatus_t status; uint32_t i, nserver; nserver = array_n(server); ASSERT(nserver != 0); status = array_init(stats_server, nserver, sizeof(struct stats_server)); if (status != NC_OK) { return status; } for (i = 0; i < nserver; i++) { struct server *s = array_get(server, i); struct stats_server *sts = array_push(stats_server); status = stats_server_init(sts, s); if (status != NC_OK) { return status; } } log_debug(LOG_VVVERB, "map %"PRIu32" stats servers", nserver); return NC_OK; } static void stats_server_unmap(struct array *stats_server) { uint32_t i, nserver; nserver = array_n(stats_server); for (i = 0; i < nserver; i++) { struct stats_server *sts = array_pop(stats_server); stats_metric_deinit(&sts->metric); } array_deinit(stats_server); log_debug(LOG_VVVERB, "unmap %"PRIu32" stats servers", nserver); } static rstatus_t stats_pool_init(struct stats_pool *stp, struct server_pool *sp) { rstatus_t status; stp->name = sp->name; array_null(&stp->metric); array_null(&stp->server); status = stats_pool_metric_init(&stp->metric); if (status != NC_OK) { return status; } status = stats_server_map(&stp->server, &sp->server); if (status != NC_OK) { stats_metric_deinit(&stp->metric); return status; } log_debug(LOG_VVVERB, "init stats pool '%.*s' with %"PRIu32" metric and " "%"PRIu32" server", stp->name.len, stp->name.data, array_n(&stp->metric), array_n(&stp->metric)); return NC_OK; } static void stats_pool_reset(struct array *stats_pool) { uint32_t i, npool; npool = array_n(stats_pool); for (i = 0; i < npool; i++) { struct stats_pool *stp = array_get(stats_pool, i); uint32_t j, nserver; stats_metric_reset(&stp->metric); nserver = array_n(&stp->server); for (j = 0; j < nserver; j++) { struct stats_server *sts = array_get(&stp->server, j); stats_metric_reset(&sts->metric); } } } static rstatus_t stats_pool_map(struct array *stats_pool, struct array *server_pool) { rstatus_t status; uint32_t i, npool; npool = array_n(server_pool); ASSERT(npool != 0); status = array_init(stats_pool, npool, sizeof(struct stats_pool)); if (status != NC_OK) { return status; } for (i = 0; i < npool; i++) { struct server_pool *sp = array_get(server_pool, i); struct stats_pool *stp = array_push(stats_pool); status = stats_pool_init(stp, sp); if (status != NC_OK) { return status; } } log_debug(LOG_VVVERB, "map %"PRIu32" stats pools", npool); return NC_OK; } static void stats_pool_unmap(struct array *stats_pool) { uint32_t i, npool; npool = array_n(stats_pool); for (i = 0; i < npool; i++) { struct stats_pool *stp = array_pop(stats_pool); stats_metric_deinit(&stp->metric); stats_server_unmap(&stp->server); } array_deinit(stats_pool); log_debug(LOG_VVVERB, "unmap %"PRIu32" stats pool", npool); } static rstatus_t stats_create_buf(struct stats *st) { uint32_t int64_max_digits = 20; /* INT64_MAX = 9223372036854775807 */ uint32_t key_value_extra = 8; /* "key": "value", */ uint32_t pool_extra = 8; /* '"pool_name": { ' + ' }' */ uint32_t server_extra = 8; /* '"server_name": { ' + ' }' */ size_t size = 0; uint32_t i; ASSERT(st->buf.data == NULL && st->buf.size == 0); /* header */ size += 1; size += st->service_str.len; size += st->service.len; size += key_value_extra; size += st->source_str.len; size += st->source.len; size += key_value_extra; size += st->version_str.len; size += st->version.len; size += key_value_extra; size += st->uptime_str.len; size += int64_max_digits; size += key_value_extra; size += st->timestamp_str.len; size += int64_max_digits; size += key_value_extra; size += st->ntotal_conn_str.len; size += int64_max_digits; size += key_value_extra; size += st->ncurr_conn_str.len; size += int64_max_digits; size += key_value_extra; /* server pools */ for (i = 0; i < array_n(&st->sum); i++) { struct stats_pool *stp = array_get(&st->sum, i); uint32_t j; size += stp->name.len; size += pool_extra; for (j = 0; j < array_n(&stp->metric); j++) { struct stats_metric *stm = array_get(&stp->metric, j); size += stm->name.len; size += int64_max_digits; size += key_value_extra; } /* servers per pool */ for (j = 0; j < array_n(&stp->server); j++) { struct stats_server *sts = array_get(&stp->server, j); uint32_t k; size += sts->name.len; size += server_extra; for (k = 0; k < array_n(&sts->metric); k++) { struct stats_metric *stm = array_get(&sts->metric, k); size += stm->name.len; size += int64_max_digits; size += key_value_extra; } } } /* footer */ size += 2; size = NC_ALIGN(size, NC_ALIGNMENT); st->buf.data = nc_alloc(size); if (st->buf.data == NULL) { log_error("create stats buffer of size %zu failed: %s", size, strerror(errno)); return NC_ENOMEM; } st->buf.size = size; log_debug(LOG_DEBUG, "stats buffer size %zu", size); return NC_OK; } static void stats_destroy_buf(struct stats *st) { if (st->buf.size != 0) { ASSERT(st->buf.data != NULL); nc_free(st->buf.data); st->buf.size = 0; } } static rstatus_t stats_add_string(struct stats *st, struct string *key, struct string *val) { struct stats_buffer *buf; uint8_t *pos; size_t room; int n; buf = &st->buf; pos = buf->data + buf->len; room = buf->size - buf->len - 1; n = nc_snprintf(pos, room, "\"%.*s\":\"%.*s\", ", key->len, key->data, val->len, val->data); if (n < 0 || n >= (int)room) { return NC_ERROR; } buf->len += (size_t)n; return NC_OK; } static rstatus_t stats_add_num(struct stats *st, struct string *key, int64_t val) { struct stats_buffer *buf; uint8_t *pos; size_t room; int n; buf = &st->buf; pos = buf->data + buf->len; room = buf->size - buf->len - 1; n = nc_snprintf(pos, room, "\"%.*s\":%"PRId64", ", key->len, key->data, val); if (n < 0 || n >= (int)room) { return NC_ERROR; } buf->len += (size_t)n; return NC_OK; } static rstatus_t stats_add_header(struct stats *st) { rstatus_t status; struct stats_buffer *buf; int64_t cur_ts, uptime; buf = &st->buf; buf->data[0] = '{'; buf->len = 1; cur_ts = (int64_t)time(NULL); uptime = cur_ts - st->start_ts; status = stats_add_string(st, &st->service_str, &st->service); if (status != NC_OK) { return status; } status = stats_add_string(st, &st->source_str, &st->source); if (status != NC_OK) { return status; } status = stats_add_string(st, &st->version_str, &st->version); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->uptime_str, uptime); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->timestamp_str, cur_ts); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->ntotal_conn_str, conn_ntotal_conn()); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->ncurr_conn_str, conn_ncurr_conn()); if (status != NC_OK) { return status; } return NC_OK; } static rstatus_t stats_add_footer(struct stats *st) { struct stats_buffer *buf; uint8_t *pos; buf = &st->buf; if (buf->len == buf->size) { return NC_ERROR; } /* overwrite the last byte and add a new byte */ pos = buf->data + buf->len - 1; pos[0] = '}'; pos[1] = '\n'; buf->len += 1; return NC_OK; } static rstatus_t stats_begin_nesting(struct stats *st, struct string *key) { struct stats_buffer *buf; uint8_t *pos; size_t room; int n; buf = &st->buf; pos = buf->data + buf->len; room = buf->size - buf->len - 1; n = nc_snprintf(pos, room, "\"%.*s\": {", key->len, key->data); if (n < 0 || n >= (int)room) { return NC_ERROR; } buf->len += (size_t)n; return NC_OK; } static rstatus_t stats_end_nesting(struct stats *st) { struct stats_buffer *buf; uint8_t *pos; buf = &st->buf; pos = buf->data + buf->len; pos -= 2; /* go back by 2 bytes */ switch (pos[0]) { case ',': /* overwrite last two bytes; len remains unchanged */ ASSERT(pos[1] == ' '); pos[0] = '}'; pos[1] = ','; break; case '}': if (buf->len == buf->size) { return NC_ERROR; } /* overwrite the last byte and add a new byte */ ASSERT(pos[1] == ','); pos[1] = '}'; pos[2] = ','; buf->len += 1; break; default: NOT_REACHED(); } return NC_OK; } static rstatus_t stats_copy_metric(struct stats *st, struct array *metric) { rstatus_t status; uint32_t i; for (i = 0; i < array_n(metric); i++) { struct stats_metric *stm = array_get(metric, i); status = stats_add_num(st, &stm->name, stm->value.counter); if (status != NC_OK) { return status; } } return NC_OK; } static void stats_aggregate_metric(struct array *dst, struct array *src) { uint32_t i; for (i = 0; i < array_n(src); i++) { struct stats_metric *stm1, *stm2; stm1 = array_get(src, i); stm2 = array_get(dst, i); ASSERT(stm1->type == stm2->type); switch (stm1->type) { case STATS_COUNTER: stm2->value.counter += stm1->value.counter; break; case STATS_GAUGE: stm2->value.counter += stm1->value.counter; break; case STATS_TIMESTAMP: if (stm1->value.timestamp) { stm2->value.timestamp = stm1->value.timestamp; } break; default: NOT_REACHED(); } } } static void stats_aggregate(struct stats *st) { uint32_t i; if (st->aggregate == 0) { log_debug(LOG_PVERB, "skip aggregate of shadow %p to sum %p as " "generator is slow", st->shadow.elem, st->sum.elem); return; } log_debug(LOG_PVERB, "aggregate stats shadow %p to sum %p", st->shadow.elem, st->sum.elem); for (i = 0; i < array_n(&st->shadow); i++) { struct stats_pool *stp1, *stp2; uint32_t j; stp1 = array_get(&st->shadow, i); stp2 = array_get(&st->sum, i); stats_aggregate_metric(&stp2->metric, &stp1->metric); for (j = 0; j < array_n(&stp1->server); j++) { struct stats_server *sts1, *sts2; sts1 = array_get(&stp1->server, j); sts2 = array_get(&stp2->server, j); stats_aggregate_metric(&sts2->metric, &sts1->metric); } } st->aggregate = 0; } static rstatus_t stats_make_rsp(struct stats *st) { rstatus_t status; uint32_t i; status = stats_add_header(st); if (status != NC_OK) { return status; } for (i = 0; i < array_n(&st->sum); i++) { struct stats_pool *stp = array_get(&st->sum, i); uint32_t j; status = stats_begin_nesting(st, &stp->name); if (status != NC_OK) { return status; } /* copy pool metric from sum(c) to buffer */ status = stats_copy_metric(st, &stp->metric); if (status != NC_OK) { return status; } for (j = 0; j < array_n(&stp->server); j++) { struct stats_server *sts = array_get(&stp->server, j); status = stats_begin_nesting(st, &sts->name); if (status != NC_OK) { return status; } /* copy server metric from sum(c) to buffer */ status = stats_copy_metric(st, &sts->metric); if (status != NC_OK) { return status; } status = stats_end_nesting(st); if (status != NC_OK) { return status; } } status = stats_end_nesting(st); if (status != NC_OK) { return status; } } status = stats_add_footer(st); if (status != NC_OK) { return status; } return NC_OK; } static rstatus_t stats_send_rsp(struct stats *st) { rstatus_t status; ssize_t n; int sd; status = stats_make_rsp(st); if (status != NC_OK) { return status; } sd = accept(st->sd, NULL, NULL); if (sd < 0) { log_error("accept on m %d failed: %s", st->sd, strerror(errno)); return NC_ERROR; } log_debug(LOG_VERB, "send stats on sd %d %d bytes", sd, st->buf.len); n = nc_sendn(sd, st->buf.data, st->buf.len); if (n < 0) { log_error("send stats on sd %d failed: %s", sd, strerror(errno)); close(sd); return NC_ERROR; } close(sd); return NC_OK; } static void stats_loop_callback(void *arg1, void *arg2) { struct stats *st = arg1; int n = *((int *)arg2); /* aggregate stats from shadow (b) -> sum (c) */ stats_aggregate(st); if (n == 0) { return; } /* send aggregate stats sum (c) to collector */ stats_send_rsp(st); } static void * stats_loop(void *arg) { event_loop_stats(stats_loop_callback, arg); return NULL; } static rstatus_t stats_listen(struct stats *st) { rstatus_t status; struct sockinfo si; status = nc_resolve(&st->addr, st->port, &si); if (status < 0) { return status; } st->sd = socket(si.family, SOCK_STREAM, 0); if (st->sd < 0) { log_error("socket failed: %s", strerror(errno)); return NC_ERROR; } status = nc_set_reuseaddr(st->sd); if (status < 0) { log_error("set reuseaddr on m %d failed: %s", st->sd, strerror(errno)); return NC_ERROR; } status = bind(st->sd, (struct sockaddr *)&si.addr, si.addrlen); if (status < 0) { log_error("bind on m %d to addr '%.*s:%u' failed: %s", st->sd, st->addr.len, st->addr.data, st->port, strerror(errno)); return NC_ERROR; } status = listen(st->sd, SOMAXCONN); if (status < 0) { log_error("listen on m %d failed: %s", st->sd, strerror(errno)); return NC_ERROR; } log_debug(LOG_NOTICE, "m %d listening on '%.*s:%u'", st->sd, st->addr.len, st->addr.data, st->port); return NC_OK; } static rstatus_t stats_start_aggregator(struct stats *st) { rstatus_t status; if (!stats_enabled) { return NC_OK; } status = stats_listen(st); if (status != NC_OK) { return status; } status = pthread_create(&st->tid, NULL, stats_loop, st); if (status < 0) { log_error("stats aggregator create failed: %s", strerror(status)); return NC_ERROR; } return NC_OK; } static void stats_stop_aggregator(struct stats *st) { if (!stats_enabled) { return; } close(st->sd); } struct stats * stats_create(uint16_t stats_port, char *stats_ip, int stats_interval, char *source, struct array *server_pool) { rstatus_t status; struct stats *st; st = nc_alloc(sizeof(*st)); if (st == NULL) { return NULL; } st->port = stats_port; st->interval = stats_interval; string_set_raw(&st->addr, stats_ip); st->start_ts = (int64_t)time(NULL); st->buf.len = 0; st->buf.data = NULL; st->buf.size = 0; array_null(&st->current); array_null(&st->shadow); array_null(&st->sum); st->tid = (pthread_t) -1; st->sd = -1; string_set_text(&st->service_str, "service"); string_set_text(&st->service, "nutcracker"); string_set_text(&st->source_str, "source"); string_set_raw(&st->source, source); string_set_text(&st->version_str, "version"); string_set_text(&st->version, NC_VERSION_STRING); string_set_text(&st->uptime_str, "uptime"); string_set_text(&st->timestamp_str, "timestamp"); string_set_text(&st->ntotal_conn_str, "total_connections"); string_set_text(&st->ncurr_conn_str, "curr_connections"); st->updated = 0; st->aggregate = 0; /* map server pool to current (a), shadow (b) and sum (c) */ status = stats_pool_map(&st->current, server_pool); if (status != NC_OK) { goto error; } status = stats_pool_map(&st->shadow, server_pool); if (status != NC_OK) { goto error; } status = stats_pool_map(&st->sum, server_pool); if (status != NC_OK) { goto error; } status = stats_create_buf(st); if (status != NC_OK) { goto error; } status = stats_start_aggregator(st); if (status != NC_OK) { goto error; } return st; error: stats_destroy(st); return NULL; } void stats_destroy(struct stats *st) { stats_stop_aggregator(st); stats_pool_unmap(&st->sum); stats_pool_unmap(&st->shadow); stats_pool_unmap(&st->current); stats_destroy_buf(st); nc_free(st); } void stats_swap(struct stats *st) { if (!stats_enabled) { return; } if (st->aggregate == 1) { log_debug(LOG_PVERB, "skip swap of current %p shadow %p as aggregator " "is busy", st->current.elem, st->shadow.elem); return; } if (st->updated == 0) { log_debug(LOG_PVERB, "skip swap of current %p shadow %p as there is " "nothing new", st->current.elem, st->shadow.elem); return; } log_debug(LOG_PVERB, "swap stats current %p shadow %p", st->current.elem, st->shadow.elem); array_swap(&st->current, &st->shadow); /* * Reset current (a) stats before giving it back to generator to keep * stats addition idempotent */ stats_pool_reset(&st->current); st->updated = 0; st->aggregate = 1; } static struct stats_metric * stats_pool_to_metric(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx) { struct stats *st; struct stats_pool *stp; struct stats_metric *stm; uint32_t pidx; pidx = pool->idx; st = ctx->stats; stp = array_get(&st->current, pidx); stm = array_get(&stp->metric, fidx); st->updated = 1; log_debug(LOG_VVVERB, "metric '%.*s' in pool %"PRIu32"", stm->name.len, stm->name.data, pidx); return stm; } void _stats_pool_incr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter++; log_debug(LOG_VVVERB, "incr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_decr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter--; log_debug(LOG_VVVERB, "decr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_incr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter += val; log_debug(LOG_VVVERB, "incr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_decr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter -= val; log_debug(LOG_VVVERB, "decr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_set_ts(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_TIMESTAMP); stm->value.timestamp = val; log_debug(LOG_VVVERB, "set ts field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.timestamp); } static struct stats_metric * stats_server_to_metric(struct context *ctx, struct server *server, stats_server_field_t fidx) { struct stats *st; struct stats_pool *stp; struct stats_server *sts; struct stats_metric *stm; uint32_t pidx, sidx; sidx = server->idx; pidx = server->owner->idx; st = ctx->stats; stp = array_get(&st->current, pidx); sts = array_get(&stp->server, sidx); stm = array_get(&sts->metric, fidx); st->updated = 1; log_debug(LOG_VVVERB, "metric '%.*s' in pool %"PRIu32" server %"PRIu32"", stm->name.len, stm->name.data, pidx, sidx); return stm; } void _stats_server_incr(struct context *ctx, struct server *server, stats_server_field_t fidx) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter++; log_debug(LOG_VVVERB, "incr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_decr(struct context *ctx, struct server *server, stats_server_field_t fidx) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter--; log_debug(LOG_VVVERB, "decr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_incr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter += val; log_debug(LOG_VVVERB, "incr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_decr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter -= val; log_debug(LOG_VVVERB, "decr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_set_ts(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_TIMESTAMP); stm->value.timestamp = val; log_debug(LOG_VVVERB, "set ts field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.timestamp); } nutcracker-0.4.0+dfsg/src/nc_stats.h000066400000000000000000000236601242132376000173600ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_STATS_H_ #define _NC_STATS_H_ #include #define STATS_POOL_CODEC(ACTION) \ /* client behavior */ \ ACTION( client_eof, STATS_COUNTER, "# eof on client connections") \ ACTION( client_err, STATS_COUNTER, "# errors on client connections") \ ACTION( client_connections, STATS_GAUGE, "# active client connections") \ /* pool behavior */ \ ACTION( server_ejects, STATS_COUNTER, "# times backend server was ejected") \ /* forwarder behavior */ \ ACTION( forward_error, STATS_COUNTER, "# times we encountered a forwarding error") \ ACTION( fragments, STATS_COUNTER, "# fragments created from a multi-vector request") \ #define STATS_SERVER_CODEC(ACTION) \ /* server behavior */ \ ACTION( server_eof, STATS_COUNTER, "# eof on server connections") \ ACTION( server_err, STATS_COUNTER, "# errors on server connections") \ ACTION( server_timedout, STATS_COUNTER, "# timeouts on server connections") \ ACTION( server_connections, STATS_GAUGE, "# active server connections") \ ACTION( server_ejected_at, STATS_TIMESTAMP, "timestamp when server was ejected in usec since epoch") \ /* data behavior */ \ ACTION( requests, STATS_COUNTER, "# requests") \ ACTION( request_bytes, STATS_COUNTER, "total request bytes") \ ACTION( responses, STATS_COUNTER, "# responses") \ ACTION( response_bytes, STATS_COUNTER, "total response bytes") \ ACTION( in_queue, STATS_GAUGE, "# requests in incoming queue") \ ACTION( in_queue_bytes, STATS_GAUGE, "current request bytes in incoming queue") \ ACTION( out_queue, STATS_GAUGE, "# requests in outgoing queue") \ ACTION( out_queue_bytes, STATS_GAUGE, "current request bytes in outgoing queue") \ #define STATS_ADDR "0.0.0.0" #define STATS_PORT 22222 #define STATS_INTERVAL (30 * 1000) /* in msec */ typedef enum stats_type { STATS_INVALID, STATS_COUNTER, /* monotonic accumulator */ STATS_GAUGE, /* non-monotonic accumulator */ STATS_TIMESTAMP, /* monotonic timestamp (in nsec) */ STATS_SENTINEL } stats_type_t; struct stats_metric { stats_type_t type; /* type */ struct string name; /* name (ref) */ union { int64_t counter; /* accumulating counter */ int64_t timestamp; /* monotonic timestamp */ } value; }; struct stats_server { struct string name; /* server name (ref) */ struct array metric; /* stats_metric[] for server codec */ }; struct stats_pool { struct string name; /* pool name (ref) */ struct array metric; /* stats_metric[] for pool codec */ struct array server; /* stats_server[] */ }; struct stats_buffer { size_t len; /* buffer length */ uint8_t *data; /* buffer data */ size_t size; /* buffer alloc size */ }; struct stats { uint16_t port; /* stats monitoring port */ int interval; /* stats aggregation interval */ struct string addr; /* stats monitoring address */ int64_t start_ts; /* start timestamp of nutcracker */ struct stats_buffer buf; /* output buffer */ struct array current; /* stats_pool[] (a) */ struct array shadow; /* stats_pool[] (b) */ struct array sum; /* stats_pool[] (c = a + b) */ pthread_t tid; /* stats aggregator thread */ int sd; /* stats descriptor */ struct string service_str; /* service string */ struct string service; /* service */ struct string source_str; /* source string */ struct string source; /* source */ struct string version_str; /* version string */ struct string version; /* version */ struct string uptime_str; /* uptime string */ struct string timestamp_str; /* timestamp string */ struct string ntotal_conn_str; /* total connections string */ struct string ncurr_conn_str; /* curr connections string */ volatile int aggregate; /* shadow (b) aggregate? */ volatile int updated; /* current (a) updated? */ }; #define DEFINE_ACTION(_name, _type, _desc) STATS_POOL_##_name, typedef enum stats_pool_field { STATS_POOL_CODEC(DEFINE_ACTION) STATS_POOL_NFIELD } stats_pool_field_t; #undef DEFINE_ACTION #define DEFINE_ACTION(_name, _type, _desc) STATS_SERVER_##_name, typedef enum stats_server_field { STATS_SERVER_CODEC(DEFINE_ACTION) STATS_SERVER_NFIELD } stats_server_field_t; #undef DEFINE_ACTION #if defined NC_STATS && NC_STATS == 1 #define stats_pool_incr(_ctx, _pool, _name) do { \ _stats_pool_incr(_ctx, _pool, STATS_POOL_##_name); \ } while (0) #define stats_pool_decr(_ctx, _pool, _name) do { \ _stats_pool_decr(_ctx, _pool, STATS_POOL_##_name); \ } while (0) #define stats_pool_incr_by(_ctx, _pool, _name, _val) do { \ _stats_pool_incr_by(_ctx, _pool, STATS_POOL_##_name, _val); \ } while (0) #define stats_pool_decr_by(_ctx, _pool, _name, _val) do { \ _stats_pool_decr_by(_ctx, _pool, STATS_POOL_##_name, _val); \ } while (0) #define stats_pool_set_ts(_ctx, _pool, _name, _val) do { \ _stats_pool_set_ts(_ctx, _pool, STATS_POOL_##_name, _val); \ } while (0) #define stats_server_incr(_ctx, _server, _name) do { \ _stats_server_incr(_ctx, _server, STATS_SERVER_##_name); \ } while (0) #define stats_server_decr(_ctx, _server, _name) do { \ _stats_server_decr(_ctx, _server, STATS_SERVER_##_name); \ } while (0) #define stats_server_incr_by(_ctx, _server, _name, _val) do { \ _stats_server_incr_by(_ctx, _server, STATS_SERVER_##_name, _val); \ } while (0) #define stats_server_decr_by(_ctx, _server, _name, _val) do { \ _stats_server_decr_by(_ctx, _server, STATS_SERVER_##_name, _val); \ } while (0) #define stats_server_set_ts(_ctx, _server, _name, _val) do { \ _stats_server_set_ts(_ctx, _server, STATS_SERVER_##_name, _val); \ } while (0) #else #define stats_pool_incr(_ctx, _pool, _name) #define stats_pool_decr(_ctx, _pool, _name) #define stats_pool_incr_by(_ctx, _pool, _name, _val) #define stats_pool_decr_by(_ctx, _pool, _name, _val) #define stats_server_incr(_ctx, _server, _name) #define stats_server_decr(_ctx, _server, _name) #define stats_server_incr_by(_ctx, _server, _name, _val) #define stats_server_decr_by(_ctx, _server, _name, _val) #endif #define stats_enabled NC_STATS void stats_describe(void); void _stats_pool_incr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx); void _stats_pool_decr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx); void _stats_pool_incr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val); void _stats_pool_decr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val); void _stats_pool_set_ts(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val); void _stats_server_incr(struct context *ctx, struct server *server, stats_server_field_t fidx); void _stats_server_decr(struct context *ctx, struct server *server, stats_server_field_t fidx); void _stats_server_incr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val); void _stats_server_decr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val); void _stats_server_set_ts(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val); struct stats *stats_create(uint16_t stats_port, char *stats_ip, int stats_interval, char *source, struct array *server_pool); void stats_destroy(struct stats *stats); void stats_swap(struct stats *stats); #endif nutcracker-0.4.0+dfsg/src/nc_string.c000066400000000000000000000170121242132376000175150ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include /* * String (struct string) is a sequence of unsigned char objects terminated * by the null character '\0'. The length of the string is pre-computed and * made available explicitly as an additional field. This means that we don't * have to walk the entire character sequence until the null terminating * character everytime that the length of the String is requested * * The only way to create a String is to initialize it using, string_init() * and duplicate an existing String - string_duplicate() or copy an existing * raw sequence of character bytes - string_copy(). Such String's must be * freed using string_deinit() * * We can also create String as reference to raw string - string_set_raw() * or to text string - string_set_text() or string(). Such String don't have * to be freed. */ void string_init(struct string *str) { str->len = 0; str->data = NULL; } void string_deinit(struct string *str) { ASSERT((str->len == 0 && str->data == NULL) || (str->len != 0 && str->data != NULL)); if (str->data != NULL) { nc_free(str->data); string_init(str); } } bool string_empty(const struct string *str) { ASSERT((str->len == 0 && str->data == NULL) || (str->len != 0 && str->data != NULL)); return str->len == 0 ? true : false; } rstatus_t string_duplicate(struct string *dst, const struct string *src) { ASSERT(dst->len == 0 && dst->data == NULL); ASSERT(src->len != 0 && src->data != NULL); dst->data = nc_strndup(src->data, src->len + 1); if (dst->data == NULL) { return NC_ENOMEM; } dst->len = src->len; dst->data[dst->len] = '\0'; return NC_OK; } rstatus_t string_copy(struct string *dst, const uint8_t *src, uint32_t srclen) { ASSERT(dst->len == 0 && dst->data == NULL); ASSERT(src != NULL && srclen != 0); dst->data = nc_strndup(src, srclen + 1); if (dst->data == NULL) { return NC_ENOMEM; } dst->len = srclen; dst->data[dst->len] = '\0'; return NC_OK; } int string_compare(const struct string *s1, const struct string *s2) { if (s1->len != s2->len) { return s1->len > s2->len ? 1 : -1; } return nc_strncmp(s1->data, s2->data, s1->len); } static char * _safe_utoa(int _base, uint64_t val, char *buf) { char hex[] = "0123456789abcdef"; uint32_t base = (uint32_t) _base; *buf-- = 0; do { *buf-- = hex[val % base]; } while ((val /= base) != 0); return buf + 1; } static char * _safe_itoa(int base, int64_t val, char *buf) { char hex[] = "0123456789abcdef"; char *orig_buf = buf; const int32_t is_neg = (val < 0); *buf-- = 0; if (is_neg) { val = -val; } if (is_neg && base == 16) { int ix; val -= 1; for (ix = 0; ix < 16; ++ix) buf[-ix] = '0'; } do { *buf-- = hex[val % base]; } while ((val /= base) != 0); if (is_neg && base == 10) { *buf-- = '-'; } if (is_neg && base == 16) { int ix; buf = orig_buf - 1; for (ix = 0; ix < 16; ++ix, --buf) { /* *INDENT-OFF* */ switch (*buf) { case '0': *buf = 'f'; break; case '1': *buf = 'e'; break; case '2': *buf = 'd'; break; case '3': *buf = 'c'; break; case '4': *buf = 'b'; break; case '5': *buf = 'a'; break; case '6': *buf = '9'; break; case '7': *buf = '8'; break; case '8': *buf = '7'; break; case '9': *buf = '6'; break; case 'a': *buf = '5'; break; case 'b': *buf = '4'; break; case 'c': *buf = '3'; break; case 'd': *buf = '2'; break; case 'e': *buf = '1'; break; case 'f': *buf = '0'; break; } /* *INDENT-ON* */ } } return buf + 1; } static const char * _safe_check_longlong(const char *fmt, int32_t * have_longlong) { *have_longlong = false; if (*fmt == 'l') { fmt++; if (*fmt != 'l') { *have_longlong = (sizeof(long) == sizeof(int64_t)); } else { fmt++; *have_longlong = true; } } return fmt; } int _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap) { char *start = to; char *end = start + size - 1; for (; *format; ++format) { int32_t have_longlong = false; if (*format != '%') { if (to == end) { /* end of buffer */ break; } *to++ = *format; /* copy ordinary char */ continue; } ++format; /* skip '%' */ format = _safe_check_longlong(format, &have_longlong); switch (*format) { case 'd': case 'i': case 'u': case 'x': case 'p': { int64_t ival = 0; uint64_t uval = 0; if (*format == 'p') have_longlong = (sizeof(void *) == sizeof(uint64_t)); if (have_longlong) { if (*format == 'u') { uval = va_arg(ap, uint64_t); } else { ival = va_arg(ap, int64_t); } } else { if (*format == 'u') { uval = va_arg(ap, uint32_t); } else { ival = va_arg(ap, int32_t); } } { char buff[22]; const int base = (*format == 'x' || *format == 'p') ? 16 : 10; /* *INDENT-OFF* */ char *val_as_str = (*format == 'u') ? _safe_utoa(base, uval, &buff[sizeof(buff) - 1]) : _safe_itoa(base, ival, &buff[sizeof(buff) - 1]); /* *INDENT-ON* */ /* Strip off "ffffffff" if we have 'x' format without 'll' */ if (*format == 'x' && !have_longlong && ival < 0) { val_as_str += 8; } while (*val_as_str && to < end) { *to++ = *val_as_str++; } continue; } } case 's': { const char *val = va_arg(ap, char *); if (!val) { val = "(null)"; } while (*val && to < end) { *to++ = *val++; } continue; } } } *to = 0; return (int)(to - start); } int _safe_snprintf(char *to, size_t n, const char *fmt, ...) { int result; va_list args; va_start(args, fmt); result = _safe_vsnprintf(to, n, fmt, args); va_end(args); return result; } nutcracker-0.4.0+dfsg/src/nc_string.h000066400000000000000000000077051242132376000175320ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_STRING_H_ #define _NC_STRING_H_ #include #include #include #include struct string { uint32_t len; /* string length */ uint8_t *data; /* string data */ }; #define string(_str) { sizeof(_str) - 1, (uint8_t *)(_str) } #define null_string { 0, NULL } #define string_set_text(_str, _text) do { \ (_str)->len = (uint32_t)(sizeof(_text) - 1);\ (_str)->data = (uint8_t *)(_text); \ } while (0); #define string_set_raw(_str, _raw) do { \ (_str)->len = (uint32_t)(nc_strlen(_raw)); \ (_str)->data = (uint8_t *)(_raw); \ } while (0); void string_init(struct string *str); void string_deinit(struct string *str); bool string_empty(const struct string *str); rstatus_t string_duplicate(struct string *dst, const struct string *src); rstatus_t string_copy(struct string *dst, const uint8_t *src, uint32_t srclen); int string_compare(const struct string *s1, const struct string *s2); /* * Wrapper around common routines for manipulating C character * strings */ #define nc_memcpy(_d, _c, _n) \ memcpy(_d, _c, (size_t)(_n)) #define nc_memmove(_d, _c, _n) \ memmove(_d, _c, (size_t)(_n)) #define nc_memchr(_d, _c, _n) \ memchr(_d, _c, (size_t)(_n)) #define nc_strlen(_s) \ strlen((char *)(_s)) #define nc_strncmp(_s1, _s2, _n) \ strncmp((char *)(_s1), (char *)(_s2), (size_t)(_n)) #define nc_strchr(_p, _l, _c) \ _nc_strchr((uint8_t *)(_p), (uint8_t *)(_l), (uint8_t)(_c)) #define nc_strrchr(_p, _s, _c) \ _nc_strrchr((uint8_t *)(_p),(uint8_t *)(_s), (uint8_t)(_c)) #define nc_strndup(_s, _n) \ (uint8_t *)strndup((char *)(_s), (size_t)(_n)); #define nc_snprintf(_s, _n, ...) \ snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) #define nc_scnprintf(_s, _n, ...) \ _scnprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) #define nc_vscnprintf(_s, _n, _f, _a) \ _vscnprintf((char *)(_s), (size_t)(_n), _f, _a) #define nc_strftime(_s, _n, fmt, tm) \ (int)strftime((char *)(_s), (size_t)(_n), fmt, tm) /* * A (very) limited version of snprintf * @param to Destination buffer * @param n Size of destination buffer * @param fmt printf() style format string * @returns Number of bytes written, including terminating '\0' * Supports 'd' 'i' 'u' 'x' 'p' 's' conversion * Supports 'l' and 'll' modifiers for integral types * Does not support any width/precision * Implemented with simplicity, and async-signal-safety in mind */ int _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap); int _safe_snprintf(char *to, size_t n, const char *fmt, ...); #define nc_safe_snprintf(_s, _n, ...) \ _safe_snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) #define nc_safe_vsnprintf(_s, _n, _f, _a) \ _safe_vsnprintf((char *)(_s), (size_t)(_n), _f, _a) static inline uint8_t * _nc_strchr(uint8_t *p, uint8_t *last, uint8_t c) { while (p < last) { if (*p == c) { return p; } p++; } return NULL; } static inline uint8_t * _nc_strrchr(uint8_t *p, uint8_t *start, uint8_t c) { while (p >= start) { if (*p == c) { return p; } p--; } return NULL; } #endif nutcracker-0.4.0+dfsg/src/nc_util.c000066400000000000000000000310111242132376000171570ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NC_HAVE_BACKTRACE # include #endif int nc_set_blocking(int sd) { int flags; flags = fcntl(sd, F_GETFL, 0); if (flags < 0) { return flags; } return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); } int nc_set_nonblocking(int sd) { int flags; flags = fcntl(sd, F_GETFL, 0); if (flags < 0) { return flags; } return fcntl(sd, F_SETFL, flags | O_NONBLOCK); } int nc_set_reuseaddr(int sd) { int reuse; socklen_t len; reuse = 1; len = sizeof(reuse); return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len); } /* * Disable Nagle algorithm on TCP socket. * * This option helps to minimize transmit latency by disabling coalescing * of data to fill up a TCP segment inside the kernel. Sockets with this * option must use readv() or writev() to do data transfer in bulk and * hence avoid the overhead of small packets. */ int nc_set_tcpnodelay(int sd) { int nodelay; socklen_t len; nodelay = 1; len = sizeof(nodelay); return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); } int nc_set_linger(int sd, int timeout) { struct linger linger; socklen_t len; linger.l_onoff = 1; linger.l_linger = timeout; len = sizeof(linger); return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); } int nc_set_sndbuf(int sd, int size) { socklen_t len; len = sizeof(size); return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); } int nc_set_rcvbuf(int sd, int size) { socklen_t len; len = sizeof(size); return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); } int nc_get_soerror(int sd) { int status, err; socklen_t len; err = 0; len = sizeof(err); status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); if (status == 0) { errno = err; } return status; } int nc_get_sndbuf(int sd) { int status, size; socklen_t len; size = 0; len = sizeof(size); status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); if (status < 0) { return status; } return size; } int nc_get_rcvbuf(int sd) { int status, size; socklen_t len; size = 0; len = sizeof(size); status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); if (status < 0) { return status; } return size; } int _nc_atoi(uint8_t *line, size_t n) { int value; if (n == 0) { return -1; } for (value = 0; n--; line++) { if (*line < '0' || *line > '9') { return -1; } value = value * 10 + (*line - '0'); } if (value < 0) { return -1; } return value; } bool nc_valid_port(int n) { if (n < 1 || n > UINT16_MAX) { return false; } return true; } void * _nc_alloc(size_t size, const char *name, int line) { void *p; ASSERT(size != 0); p = malloc(size); if (p == NULL) { log_error("malloc(%zu) failed @ %s:%d", size, name, line); } else { log_debug(LOG_VVERB, "malloc(%zu) at %p @ %s:%d", size, p, name, line); } return p; } void * _nc_zalloc(size_t size, const char *name, int line) { void *p; p = _nc_alloc(size, name, line); if (p != NULL) { memset(p, 0, size); } return p; } void * _nc_calloc(size_t nmemb, size_t size, const char *name, int line) { return _nc_zalloc(nmemb * size, name, line); } void * _nc_realloc(void *ptr, size_t size, const char *name, int line) { void *p; ASSERT(size != 0); p = realloc(ptr, size); if (p == NULL) { log_error("realloc(%zu) failed @ %s:%d", size, name, line); } else { log_debug(LOG_VVERB, "realloc(%zu) at %p @ %s:%d", size, p, name, line); } return p; } void _nc_free(void *ptr, const char *name, int line) { ASSERT(ptr != NULL); log_debug(LOG_VVERB, "free(%p) @ %s:%d", ptr, name, line); free(ptr); } void nc_stacktrace(int skip_count) { #ifdef NC_HAVE_BACKTRACE void *stack[64]; char **symbols; int size, i, j; size = backtrace(stack, 64); symbols = backtrace_symbols(stack, size); if (symbols == NULL) { return; } skip_count++; /* skip the current frame also */ for (i = skip_count, j = 0; i < size; i++, j++) { loga("[%d] %s", j, symbols[i]); } free(symbols); #endif } void nc_stacktrace_fd(int fd) { #ifdef NC_HAVE_BACKTRACE void *stack[64]; int size; size = backtrace(stack, 64); backtrace_symbols_fd(stack, size, fd); #endif } void nc_assert(const char *cond, const char *file, int line, int panic) { log_error("assert '%s' failed @ (%s, %d)", cond, file, line); if (panic) { nc_stacktrace(1); abort(); } } int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args) { int n; n = vsnprintf(buf, size, fmt, args); /* * The return value is the number of characters which would be written * into buf not including the trailing '\0'. If size is == 0 the * function returns 0. * * On error, the function also returns 0. This is to allow idiom such * as len += _vscnprintf(...) * * See: http://lwn.net/Articles/69419/ */ if (n <= 0) { return 0; } if (n < (int) size) { return n; } return (int)(size - 1); } int _scnprintf(char *buf, size_t size, const char *fmt, ...) { va_list args; int n; va_start(args, fmt); n = _vscnprintf(buf, size, fmt, args); va_end(args); return n; } /* * Send n bytes on a blocking descriptor */ ssize_t _nc_sendn(int sd, const void *vptr, size_t n) { size_t nleft; ssize_t nsend; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { nsend = send(sd, ptr, nleft, 0); if (nsend < 0) { if (errno == EINTR) { continue; } return nsend; } if (nsend == 0) { return -1; } nleft -= (size_t)nsend; ptr += nsend; } return (ssize_t)n; } /* * Recv n bytes from a blocking descriptor */ ssize_t _nc_recvn(int sd, void *vptr, size_t n) { size_t nleft; ssize_t nrecv; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { nrecv = recv(sd, ptr, nleft, 0); if (nrecv < 0) { if (errno == EINTR) { continue; } return nrecv; } if (nrecv == 0) { break; } nleft -= (size_t)nrecv; ptr += nrecv; } return (ssize_t)(n - nleft); } /* * Return the current time in microseconds since Epoch */ int64_t nc_usec_now(void) { struct timeval now; int64_t usec; int status; status = gettimeofday(&now, NULL); if (status < 0) { log_error("gettimeofday failed: %s", strerror(errno)); return -1; } usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; return usec; } /* * Return the current time in milliseconds since Epoch */ int64_t nc_msec_now(void) { return nc_usec_now() / 1000LL; } static int nc_resolve_inet(struct string *name, int port, struct sockinfo *si) { int status; struct addrinfo *ai, *cai; /* head and current addrinfo */ struct addrinfo hints; char *node, service[NC_UINTMAX_MAXLEN]; bool found; ASSERT(nc_valid_port(port)); memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICSERV; hints.ai_family = AF_UNSPEC; /* AF_INET or AF_INET6 */ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = NULL; hints.ai_canonname = NULL; if (name != NULL) { node = (char *)name->data; } else { /* * If AI_PASSIVE flag is specified in hints.ai_flags, and node is * NULL, then the returned socket addresses will be suitable for * bind(2)ing a socket that will accept(2) connections. The returned * socket address will contain the wildcard IP address. */ node = NULL; hints.ai_flags |= AI_PASSIVE; } nc_snprintf(service, NC_UINTMAX_MAXLEN, "%d", port); status = getaddrinfo(node, service, &hints, &ai); if (status < 0) { log_error("address resolution of node '%s' service '%s' failed: %s", node, service, gai_strerror(status)); return -1; } /* * getaddrinfo() can return a linked list of more than one addrinfo, * since we requested for both AF_INET and AF_INET6 addresses and the * host itself can be multi-homed. Since we don't care whether we are * using ipv4 or ipv6, we just use the first address from this collection * in the order in which it was returned. * * The sorting function used within getaddrinfo() is defined in RFC 3484; * the order can be tweaked for a particular system by editing * /etc/gai.conf */ for (cai = ai, found = false; cai != NULL; cai = cai->ai_next) { si->family = cai->ai_family; si->addrlen = cai->ai_addrlen; nc_memcpy(&si->addr, cai->ai_addr, si->addrlen); found = true; break; } freeaddrinfo(ai); return !found ? -1 : 0; } static int nc_resolve_unix(struct string *name, struct sockinfo *si) { struct sockaddr_un *un; if (name->len >= NC_UNIX_ADDRSTRLEN) { return -1; } un = &si->addr.un; un->sun_family = AF_UNIX; nc_memcpy(un->sun_path, name->data, name->len); un->sun_path[name->len] = '\0'; si->family = AF_UNIX; si->addrlen = sizeof(*un); /* si->addr is an alias of un */ return 0; } /* * Resolve a hostname and service by translating it to socket address and * return it in si * * This routine is reentrant */ int nc_resolve(struct string *name, int port, struct sockinfo *si) { if (name != NULL && name->data[0] == '/') { return nc_resolve_unix(name, si); } return nc_resolve_inet(name, port, si); } /* * Unresolve the socket address by translating it to a character string * describing the host and service * * This routine is not reentrant */ char * nc_unresolve_addr(struct sockaddr *addr, socklen_t addrlen) { static char unresolve[NI_MAXHOST + NI_MAXSERV]; static char host[NI_MAXHOST], service[NI_MAXSERV]; int status; status = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV); if (status < 0) { return "unknown"; } nc_snprintf(unresolve, sizeof(unresolve), "%s:%s", host, service); return unresolve; } /* * Unresolve the socket descriptor peer address by translating it to a * character string describing the host and service * * This routine is not reentrant */ char * nc_unresolve_peer_desc(int sd) { static struct sockinfo si; struct sockaddr *addr; socklen_t addrlen; int status; memset(&si, 0, sizeof(si)); addr = (struct sockaddr *)&si.addr; addrlen = sizeof(si.addr); status = getpeername(sd, addr, &addrlen); if (status < 0) { return "unknown"; } return nc_unresolve_addr(addr, addrlen); } /* * Unresolve the socket descriptor address by translating it to a * character string describing the host and service * * This routine is not reentrant */ char * nc_unresolve_desc(int sd) { static struct sockinfo si; struct sockaddr *addr; socklen_t addrlen; int status; memset(&si, 0, sizeof(si)); addr = (struct sockaddr *)&si.addr; addrlen = sizeof(si.addr); status = getsockname(sd, addr, &addrlen); if (status < 0) { return "unknown"; } return nc_unresolve_addr(addr, addrlen); } nutcracker-0.4.0+dfsg/src/nc_util.h000066400000000000000000000146561242132376000172040ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_UTIL_H_ #define _NC_UTIL_H_ #include #define LF (uint8_t) 10 #define CR (uint8_t) 13 #define CRLF "\x0d\x0a" #define CRLF_LEN (sizeof("\x0d\x0a") - 1) #define NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define SQUARE(d) ((d) * (d)) #define VAR(s, s2, n) (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1)) #define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) #define NC_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) #define NC_INET6_ADDRSTRLEN \ (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) #define NC_INET_ADDRSTRLEN MAX(NC_INET4_ADDRSTRLEN, NC_INET6_ADDRSTRLEN) #define NC_UNIX_ADDRSTRLEN \ (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) #define NC_MAXHOSTNAMELEN 256 /* * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral * type (uintmax_t) in ascii, including the null terminator '\0' * * From stdint.h, we have: * # define UINT8_MAX (255) * # define UINT16_MAX (65535) * # define UINT32_MAX (4294967295U) * # define UINT64_MAX (__UINT64_C(18446744073709551615)) */ #define NC_UINT8_MAXLEN (3 + 1) #define NC_UINT16_MAXLEN (5 + 1) #define NC_UINT32_MAXLEN (10 + 1) #define NC_UINT64_MAXLEN (20 + 1) #define NC_UINTMAX_MAXLEN NC_UINT64_MAXLEN /* * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2 * of 2. */ #define NC_ALIGNMENT sizeof(unsigned long) /* platform word */ #define NC_ALIGN(d, n) (((d) + (n - 1)) & ~(n - 1)) #define NC_ALIGN_PTR(p, n) \ (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1)) /* * Wrapper to workaround well known, safe, implicit type conversion when * invoking system calls. */ #define nc_gethostname(_name, _len) \ gethostname((char *)_name, (size_t)_len) #define nc_atoi(_line, _n) \ _nc_atoi((uint8_t *)_line, (size_t)_n) int nc_set_blocking(int sd); int nc_set_nonblocking(int sd); int nc_set_reuseaddr(int sd); int nc_set_tcpnodelay(int sd); int nc_set_linger(int sd, int timeout); int nc_set_sndbuf(int sd, int size); int nc_set_rcvbuf(int sd, int size); int nc_get_soerror(int sd); int nc_get_sndbuf(int sd); int nc_get_rcvbuf(int sd); int _nc_atoi(uint8_t *line, size_t n); bool nc_valid_port(int n); /* * Memory allocation and free wrappers. * * These wrappers enables us to loosely detect double free, dangling * pointer access and zero-byte alloc. */ #define nc_alloc(_s) \ _nc_alloc((size_t)(_s), __FILE__, __LINE__) #define nc_zalloc(_s) \ _nc_zalloc((size_t)(_s), __FILE__, __LINE__) #define nc_calloc(_n, _s) \ _nc_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) #define nc_realloc(_p, _s) \ _nc_realloc(_p, (size_t)(_s), __FILE__, __LINE__) #define nc_free(_p) do { \ _nc_free(_p, __FILE__, __LINE__); \ (_p) = NULL; \ } while (0) void *_nc_alloc(size_t size, const char *name, int line); void *_nc_zalloc(size_t size, const char *name, int line); void *_nc_calloc(size_t nmemb, size_t size, const char *name, int line); void *_nc_realloc(void *ptr, size_t size, const char *name, int line); void _nc_free(void *ptr, const char *name, int line); /* * Wrappers to send or receive n byte message on a blocking * socket descriptor. */ #define nc_sendn(_s, _b, _n) \ _nc_sendn(_s, _b, (size_t)(_n)) #define nc_recvn(_s, _b, _n) \ _nc_recvn(_s, _b, (size_t)(_n)) /* * Wrappers to read or write data to/from (multiple) buffers * to a file or socket descriptor. */ #define nc_read(_d, _b, _n) \ read(_d, _b, (size_t)(_n)) #define nc_readv(_d, _b, _n) \ readv(_d, _b, (int)(_n)) #define nc_write(_d, _b, _n) \ write(_d, _b, (size_t)(_n)) #define nc_writev(_d, _b, _n) \ writev(_d, _b, (int)(_n)) ssize_t _nc_sendn(int sd, const void *vptr, size_t n); ssize_t _nc_recvn(int sd, void *vptr, size_t n); /* * Wrappers for defining custom assert based on whether macro * NC_ASSERT_PANIC or NC_ASSERT_LOG was defined at the moment * ASSERT was called. */ #ifdef NC_ASSERT_PANIC #define ASSERT(_x) do { \ if (!(_x)) { \ nc_assert(#_x, __FILE__, __LINE__, 1); \ } \ } while (0) #define NOT_REACHED() ASSERT(0) #elif NC_ASSERT_LOG #define ASSERT(_x) do { \ if (!(_x)) { \ nc_assert(#_x, __FILE__, __LINE__, 0); \ } \ } while (0) #define NOT_REACHED() ASSERT(0) #else #define ASSERT(_x) #define NOT_REACHED() #endif void nc_assert(const char *cond, const char *file, int line, int panic); void nc_stacktrace(int skip_count); void nc_stacktrace_fd(int fd); int _scnprintf(char *buf, size_t size, const char *fmt, ...); int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); int64_t nc_usec_now(void); int64_t nc_msec_now(void); /* * Address resolution for internet (ipv4 and ipv6) and unix domain * socket address. */ struct sockinfo { int family; /* socket address family */ socklen_t addrlen; /* socket address length */ union { struct sockaddr_in in; /* ipv4 socket address */ struct sockaddr_in6 in6; /* ipv6 socket address */ struct sockaddr_un un; /* unix domain address */ } addr; }; int nc_resolve(struct string *name, int port, struct sockinfo *si); char *nc_unresolve_addr(struct sockaddr *addr, socklen_t addrlen); char *nc_unresolve_peer_desc(int sd); char *nc_unresolve_desc(int sd); #endif nutcracker-0.4.0+dfsg/src/proto/000077500000000000000000000000001242132376000165255ustar00rootroot00000000000000nutcracker-0.4.0+dfsg/src/proto/Makefile.am000066400000000000000000000004151242132376000205610ustar00rootroot00000000000000MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I $(top_srcdir)/src AM_CFLAGS = -Wall -Wshadow AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value noinst_LIBRARIES = libproto.a noinst_HEADERS = nc_proto.h libproto_a_SOURCES = \ nc_memcache.c \ nc_redis.c nutcracker-0.4.0+dfsg/src/proto/nc_memcache.c000066400000000000000000001151101242132376000211120ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include /* * From memcache protocol specification: * * Data stored by memcached is identified with the help of a key. A key * is a text string which should uniquely identify the data for clients * that are interested in storing and retrieving it. Currently the * length limit of a key is set at 250 characters (of course, normally * clients wouldn't need to use such long keys); the key must not include * control characters or whitespace. */ #define MEMCACHE_MAX_KEY_LENGTH 250 /* * Return true, if the memcache command is a storage command, otherwise * return false */ static bool memcache_storage(struct msg *r) { switch (r->type) { case MSG_REQ_MC_SET: case MSG_REQ_MC_CAS: case MSG_REQ_MC_ADD: case MSG_REQ_MC_REPLACE: case MSG_REQ_MC_APPEND: case MSG_REQ_MC_PREPEND: return true; default: break; } return false; } /* * Return true, if the memcache command is a cas command, otherwise * return false */ static bool memcache_cas(struct msg *r) { if (r->type == MSG_REQ_MC_CAS) { return true; } return false; } /* * Return true, if the memcache command is a retrieval command, otherwise * return false */ static bool memcache_retrieval(struct msg *r) { switch (r->type) { case MSG_REQ_MC_GET: case MSG_REQ_MC_GETS: return true; default: break; } return false; } /* * Return true, if the memcache command is a arithmetic command, otherwise * return false */ static bool memcache_arithmetic(struct msg *r) { switch (r->type) { case MSG_REQ_MC_INCR: case MSG_REQ_MC_DECR: return true; default: break; } return false; } /* * Return true, if the memcache command is a delete command, otherwise * return false */ static bool memcache_delete(struct msg *r) { if (r->type == MSG_REQ_MC_DELETE) { return true; } return false; } void memcache_parse_req(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_REQ_TYPE, SW_SPACES_BEFORE_KEY, SW_KEY, SW_SPACES_BEFORE_KEYS, SW_SPACES_BEFORE_FLAGS, SW_FLAGS, SW_SPACES_BEFORE_EXPIRY, SW_EXPIRY, SW_SPACES_BEFORE_VLEN, SW_VLEN, SW_SPACES_BEFORE_CAS, SW_CAS, SW_RUNTO_VAL, SW_VAL, SW_SPACES_BEFORE_NUM, SW_NUM, SW_RUNTO_CRLF, SW_CRLF, SW_NOREPLY, SW_AFTER_NOREPLY, SW_ALMOST_DONE, SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(r->request); ASSERT(!r->redis); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing maker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: if (ch == ' ') { break; } if (!islower(ch)) { goto error; } /* req_start <- p; type_start <- p */ r->token = p; state = SW_REQ_TYPE; break; case SW_REQ_TYPE: if (ch == ' ' || ch == CR) { /* type_end = p - 1 */ m = r->token; r->token = NULL; r->type = MSG_UNKNOWN; r->narg++; switch (p - m) { case 3: if (str4cmp(m, 'g', 'e', 't', ' ')) { r->type = MSG_REQ_MC_GET; break; } if (str4cmp(m, 's', 'e', 't', ' ')) { r->type = MSG_REQ_MC_SET; break; } if (str4cmp(m, 'a', 'd', 'd', ' ')) { r->type = MSG_REQ_MC_ADD; break; } if (str4cmp(m, 'c', 'a', 's', ' ')) { r->type = MSG_REQ_MC_CAS; break; } break; case 4: if (str4cmp(m, 'g', 'e', 't', 's')) { r->type = MSG_REQ_MC_GETS; break; } if (str4cmp(m, 'i', 'n', 'c', 'r')) { r->type = MSG_REQ_MC_INCR; break; } if (str4cmp(m, 'd', 'e', 'c', 'r')) { r->type = MSG_REQ_MC_DECR; break; } if (str4cmp(m, 'q', 'u', 'i', 't')) { r->type = MSG_REQ_MC_QUIT; r->quit = 1; break; } break; case 6: if (str6cmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { r->type = MSG_REQ_MC_APPEND; break; } if (str6cmp(m, 'd', 'e', 'l', 'e', 't', 'e')) { r->type = MSG_REQ_MC_DELETE; break; } break; case 7: if (str7cmp(m, 'p', 'r', 'e', 'p', 'e', 'n', 'd')) { r->type = MSG_REQ_MC_PREPEND; break; } if (str7cmp(m, 'r', 'e', 'p', 'l', 'a', 'c', 'e')) { r->type = MSG_REQ_MC_REPLACE; break; } break; } switch (r->type) { case MSG_REQ_MC_GET: case MSG_REQ_MC_GETS: case MSG_REQ_MC_DELETE: case MSG_REQ_MC_CAS: case MSG_REQ_MC_SET: case MSG_REQ_MC_ADD: case MSG_REQ_MC_REPLACE: case MSG_REQ_MC_APPEND: case MSG_REQ_MC_PREPEND: case MSG_REQ_MC_INCR: case MSG_REQ_MC_DECR: if (ch == CR) { goto error; } state = SW_SPACES_BEFORE_KEY; break; case MSG_REQ_MC_QUIT: p = p - 1; /* go back by 1 byte */ state = SW_CRLF; break; case MSG_UNKNOWN: goto error; default: NOT_REACHED(); } } else if (!islower(ch)) { goto error; } break; case SW_SPACES_BEFORE_KEY: if (ch != ' ') { p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_KEY; } break; case SW_KEY: if (r->token == NULL) { r->token = p; } if (ch == ' ' || ch == CR) { struct keypos *kpos; if ((p - r->token) > MEMCACHE_MAX_KEY_LENGTH) { log_error("parsed bad req %"PRIu64" of type %d with key " "prefix '%.*s...' and length %d that exceeds " "maximum key length", r->id, r->type, 16, r->token, p - r->token); goto error; } kpos = array_push(r->keys); if (kpos == NULL) { goto enomem; } kpos->start = r->token; kpos->end = p; r->narg++; r->token = NULL; /* get next state */ if (memcache_storage(r)) { state = SW_SPACES_BEFORE_FLAGS; } else if (memcache_arithmetic(r)) { state = SW_SPACES_BEFORE_NUM; } else if (memcache_delete(r)) { state = SW_RUNTO_CRLF; } else if (memcache_retrieval(r)) { state = SW_SPACES_BEFORE_KEYS; } else { state = SW_RUNTO_CRLF; } if (ch == CR) { if (memcache_storage(r) || memcache_arithmetic(r)) { goto error; } p = p - 1; /* go back by 1 byte */ } } break; case SW_SPACES_BEFORE_KEYS: ASSERT(memcache_retrieval(r)); switch (ch) { case ' ': break; case CR: state = SW_ALMOST_DONE; break; default: r->token = NULL; p = p - 1; /* go back by 1 byte */ state = SW_KEY; } break; case SW_SPACES_BEFORE_FLAGS: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* flags_start <- p; flags <- ch - '0' */ r->token = p; state = SW_FLAGS; } break; case SW_FLAGS: if (isdigit(ch)) { /* flags <- flags * 10 + (ch - '0') */ ; } else if (ch == ' ') { /* flags_end <- p - 1 */ r->token = NULL; state = SW_SPACES_BEFORE_EXPIRY; } else { goto error; } break; case SW_SPACES_BEFORE_EXPIRY: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* expiry_start <- p; expiry <- ch - '0' */ r->token = p; state = SW_EXPIRY; } break; case SW_EXPIRY: if (isdigit(ch)) { /* expiry <- expiry * 10 + (ch - '0') */ ; } else if (ch == ' ') { /* expiry_end <- p - 1 */ r->token = NULL; state = SW_SPACES_BEFORE_VLEN; } else { goto error; } break; case SW_SPACES_BEFORE_VLEN: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* vlen_start <- p */ r->vlen = (uint32_t)(ch - '0'); state = SW_VLEN; } break; case SW_VLEN: if (isdigit(ch)) { r->vlen = r->vlen * 10 + (uint32_t)(ch - '0'); } else if (memcache_cas(r)) { if (ch != ' ') { goto error; } /* vlen_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_SPACES_BEFORE_CAS; } else if (ch == ' ' || ch == CR) { /* vlen_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_SPACES_BEFORE_CAS: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* cas_start <- p; cas <- ch - '0' */ r->token = p; state = SW_CAS; } break; case SW_CAS: if (isdigit(ch)) { /* cas <- cas * 10 + (ch - '0') */ ; } else if (ch == ' ' || ch == CR) { /* cas_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_RUNTO_VAL: switch (ch) { case LF: /* val_start <- p + 1 */ state = SW_VAL; break; default: goto error; } break; case SW_VAL: m = p + r->vlen; if (m >= b->last) { ASSERT(r->vlen >= (uint32_t)(b->last - p)); r->vlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; /* move forward by vlen bytes */ break; } switch (*m) { case CR: /* val_end <- p - 1 */ p = m; /* move forward by vlen bytes */ state = SW_ALMOST_DONE; break; default: goto error; } break; case SW_SPACES_BEFORE_NUM: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* num_start <- p; num <- ch - '0' */ r->token = p; state = SW_NUM; } break; case SW_NUM: if (isdigit(ch)) { /* num <- num * 10 + (ch - '0') */ ; } else if (ch == ' ' || ch == CR) { r->token = NULL; /* num_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_RUNTO_CRLF: switch (ch) { case ' ': break; case 'n': if (memcache_storage(r) || memcache_arithmetic(r) || memcache_delete(r)) { /* noreply_start <- p */ r->token = p; state = SW_NOREPLY; } else { goto error; } break; case CR: if (memcache_storage(r)) { state = SW_RUNTO_VAL; } else { state = SW_ALMOST_DONE; } break; default: goto error; } break; case SW_NOREPLY: switch (ch) { case ' ': case CR: m = r->token; if (((p - m) == 7) && str7cmp(m, 'n', 'o', 'r', 'e', 'p', 'l', 'y')) { ASSERT(memcache_storage(r) || memcache_arithmetic(r) || memcache_delete(r)); r->token = NULL; /* noreply_end <- p - 1 */ r->noreply = 1; state = SW_AFTER_NOREPLY; p = p - 1; /* go back by 1 byte */ } else { goto error; } } break; case SW_AFTER_NOREPLY: switch (ch) { case ' ': break; case CR: if (memcache_storage(r)) { state = SW_RUNTO_VAL; } else { state = SW_ALMOST_DONE; } break; default: goto error; } break; case SW_CRLF: switch (ch) { case ' ': break; case CR: state = SW_ALMOST_DONE; break; default: goto error; } break; case SW_ALMOST_DONE: switch (ch) { case LF: /* req_end <- p */ goto done; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } /* * At this point, buffer from b->pos to b->last has been parsed completely * but we haven't been able to reach to any conclusion. Normally, this * means that we have to parse again starting from the state we are in * after more data has been read. The newly read data is either read into * a new mbuf, if existing mbuf is full (b->last == b->end) or into the * existing mbuf. * * The only exception to this is when the existing mbuf is full (b->last * is at b->end) and token marker is set, which means that we have to * copy the partial token into a new mbuf and parse again with more data * read into new mbuf. */ ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; enomem: r->result = MSG_PARSE_ERROR; r->state = state; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "out of memory on parse req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } void memcache_parse_rsp(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_RSP_NUM, SW_RSP_STR, SW_SPACES_BEFORE_KEY, SW_KEY, SW_SPACES_BEFORE_FLAGS, /* 5 */ SW_FLAGS, SW_SPACES_BEFORE_VLEN, SW_VLEN, SW_RUNTO_VAL, SW_VAL, /* 10 */ SW_VAL_LF, SW_END, SW_RUNTO_CRLF, SW_CRLF, SW_ALMOST_DONE, /* 15 */ SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(!r->request); ASSERT(!r->redis); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing marker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: if (isdigit(ch)) { state = SW_RSP_NUM; } else { state = SW_RSP_STR; } p = p - 1; /* go back by 1 byte */ break; case SW_RSP_NUM: if (r->token == NULL) { /* rsp_start <- p; type_start <- p */ r->token = p; } if (isdigit(ch)) { /* num <- num * 10 + (ch - '0') */ ; } else if (ch == ' ' || ch == CR) { /* type_end <- p - 1 */ r->token = NULL; r->type = MSG_RSP_MC_NUM; p = p - 1; /* go back by 1 byte */ state = SW_CRLF; } else { goto error; } break; case SW_RSP_STR: if (r->token == NULL) { /* rsp_start <- p; type_start <- p */ r->token = p; } if (ch == ' ' || ch == CR) { /* type_end <- p - 1 */ m = r->token; /* r->token = NULL; */ r->type = MSG_UNKNOWN; switch (p - m) { case 3: if (str4cmp(m, 'E', 'N', 'D', '\r')) { r->type = MSG_RSP_MC_END; /* end_start <- m; end_end <- p - 1 */ r->end = m; break; } break; case 5: if (str5cmp(m, 'V', 'A', 'L', 'U', 'E')) { /* * Encompasses responses for 'get', 'gets' and * 'cas' command. */ r->type = MSG_RSP_MC_VALUE; break; } if (str5cmp(m, 'E', 'R', 'R', 'O', 'R')) { r->type = MSG_RSP_MC_ERROR; break; } break; case 6: if (str6cmp(m, 'S', 'T', 'O', 'R', 'E', 'D')) { r->type = MSG_RSP_MC_STORED; break; } if (str6cmp(m, 'E', 'X', 'I', 'S', 'T', 'S')) { r->type = MSG_RSP_MC_EXISTS; break; } break; case 7: if (str7cmp(m, 'D', 'E', 'L', 'E', 'T', 'E', 'D')) { r->type = MSG_RSP_MC_DELETED; break; } break; case 9: if (str9cmp(m, 'N', 'O', 'T', '_', 'F', 'O', 'U', 'N', 'D')) { r->type = MSG_RSP_MC_NOT_FOUND; break; } break; case 10: if (str10cmp(m, 'N', 'O', 'T', '_', 'S', 'T', 'O', 'R', 'E', 'D')) { r->type = MSG_RSP_MC_NOT_STORED; break; } break; case 12: if (str12cmp(m, 'C', 'L', 'I', 'E', 'N', 'T', '_', 'E', 'R', 'R', 'O', 'R')) { r->type = MSG_RSP_MC_CLIENT_ERROR; break; } if (str12cmp(m, 'S', 'E', 'R', 'V', 'E', 'R', '_', 'E', 'R', 'R', 'O', 'R')) { r->type = MSG_RSP_MC_SERVER_ERROR; break; } break; } switch (r->type) { case MSG_UNKNOWN: goto error; case MSG_RSP_MC_STORED: case MSG_RSP_MC_NOT_STORED: case MSG_RSP_MC_EXISTS: case MSG_RSP_MC_NOT_FOUND: case MSG_RSP_MC_DELETED: state = SW_CRLF; break; case MSG_RSP_MC_END: state = SW_CRLF; break; case MSG_RSP_MC_VALUE: state = SW_SPACES_BEFORE_KEY; break; case MSG_RSP_MC_ERROR: state = SW_CRLF; break; case MSG_RSP_MC_CLIENT_ERROR: case MSG_RSP_MC_SERVER_ERROR: state = SW_RUNTO_CRLF; break; default: NOT_REACHED(); } p = p - 1; /* go back by 1 byte */ } break; case SW_SPACES_BEFORE_KEY: if (ch != ' ') { state = SW_KEY; p = p - 1; /* go back by 1 byte */ } break; case SW_KEY: if (ch == ' ') { /* r->token = NULL; */ state = SW_SPACES_BEFORE_FLAGS; } break; case SW_SPACES_BEFORE_FLAGS: if (ch != ' ') { if (!isdigit(ch)) { goto error; } state = SW_FLAGS; p = p - 1; /* go back by 1 byte */ } break; case SW_FLAGS: if (r->token == NULL) { /* flags_start <- p */ /* r->token = p; */ } if (isdigit(ch)) { /* flags <- flags * 10 + (ch - '0') */ ; } else if (ch == ' ') { /* flags_end <- p - 1 */ /* r->token = NULL; */ state = SW_SPACES_BEFORE_VLEN; } else { goto error; } break; case SW_SPACES_BEFORE_VLEN: if (ch != ' ') { if (!isdigit(ch)) { goto error; } p = p - 1; /* go back by 1 byte */ state = SW_VLEN; r->vlen = 0; } break; case SW_VLEN: if (isdigit(ch)) { r->vlen = r->vlen * 10 + (uint32_t)(ch - '0'); } else if (ch == ' ' || ch == CR) { /* vlen_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ /* r->token = NULL; */ state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_RUNTO_VAL: switch (ch) { case LF: /* val_start <- p + 1 */ state = SW_VAL; r->token = NULL; break; default: goto error; } break; case SW_VAL: m = p + r->vlen; if (m >= b->last) { ASSERT(r->vlen >= (uint32_t)(b->last - p)); r->vlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; /* move forward by vlen bytes */ break; } switch (*m) { case CR: /* val_end <- p - 1 */ p = m; /* move forward by vlen bytes */ state = SW_VAL_LF; break; default: goto error; } break; case SW_VAL_LF: switch (ch) { case LF: /* state = SW_END; */ state = SW_RSP_STR; break; default: goto error; } break; case SW_END: if (r->token == NULL) { if (ch != 'E') { goto error; } /* end_start <- p */ r->token = p; } else if (ch == CR) { /* end_end <- p */ m = r->token; r->token = NULL; switch (p - m) { case 3: if (str4cmp(m, 'E', 'N', 'D', '\r')) { r->end = m; state = SW_ALMOST_DONE; } break; default: goto error; } } break; case SW_RUNTO_CRLF: switch (ch) { case CR: if (r->type == MSG_RSP_MC_VALUE) { state = SW_RUNTO_VAL; } else { state = SW_ALMOST_DONE; } break; default: break; } break; case SW_CRLF: switch (ch) { case ' ': break; case CR: state = SW_ALMOST_DONE; break; default: goto error; } break; case SW_ALMOST_DONE: switch (ch) { case LF: /* rsp_end <- p */ goto done; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { if (state <= SW_RUNTO_VAL || state == SW_CRLF || state == SW_ALMOST_DONE) { r->state = SW_START; } r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->token = NULL; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad rsp %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } static rstatus_t memcache_append_key(struct msg *r, uint8_t *key, uint32_t keylen) { struct mbuf *mbuf; struct keypos *kpos; mbuf = msg_ensure_mbuf(r, keylen + 2); if (mbuf == NULL) { return NC_ENOMEM; } kpos = array_push(r->keys); if (kpos == NULL) { return NC_ENOMEM; } kpos->start = mbuf->last; kpos->end = mbuf->last + keylen; mbuf_copy(mbuf, key, keylen); r->mlen += keylen; mbuf_copy(mbuf, (uint8_t *)" ", 1); r->mlen += 1; return NC_OK; } /* * read the comment in proto/nc_redis.c */ static rstatus_t memcache_fragment_retrieval(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq, uint32_t key_step) { struct mbuf *mbuf; struct msg **sub_msgs; uint32_t i; rstatus_t status; sub_msgs = nc_zalloc(ncontinuum * sizeof(*sub_msgs)); if (sub_msgs == NULL) { return NC_ENOMEM; } ASSERT(r->frag_seq == NULL); r->frag_seq = nc_alloc(array_n(r->keys) * sizeof(*r->frag_seq)); if (r->frag_seq == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } mbuf = STAILQ_FIRST(&r->mhdr); mbuf->pos = mbuf->start; /* * This code is based on the assumption that 'gets ' is located * in a contiguous location. * This is always true because we have capped our MBUF_MIN_SIZE at 512 and * whenever we have multiple messages, we copy the tail message into a new mbuf */ for (; *(mbuf->pos) != ' ';) { /* eat get/gets */ mbuf->pos++; } mbuf->pos++; r->frag_id = msg_gen_frag_id(); r->nfrag = 0; r->frag_owner = r; for (i = 0; i < array_n(r->keys); i++) { /* for each key */ struct msg *sub_msg; struct keypos *kpos = array_get(r->keys, i); uint32_t idx = msg_backend_idx(r, kpos->start, kpos->end - kpos->start); if (sub_msgs[idx] == NULL) { sub_msgs[idx] = msg_get(r->owner, r->request, r->redis); if (sub_msgs[idx] == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } } r->frag_seq[i] = sub_msg = sub_msgs[idx]; sub_msg->narg++; status = memcache_append_key(sub_msg, kpos->start, kpos->end - kpos->start); if (status != NC_OK) { nc_free(sub_msgs); return status; } } for (i = 0; i < ncontinuum; i++) { /* prepend mget header, and forward it */ struct msg *sub_msg = sub_msgs[i]; if (sub_msg == NULL) { continue; } /* prepend get/gets */ if (r->type == MSG_REQ_MC_GET) { status = msg_prepend(sub_msg, (uint8_t *)"get ", 4); } else if (r->type == MSG_REQ_MC_GETS) { status = msg_prepend(sub_msg, (uint8_t *)"gets ", 5); } if (status != NC_OK) { nc_free(sub_msgs); return status; } /* append \r\n */ status = msg_append(sub_msg, (uint8_t *)CRLF, CRLF_LEN); if (status != NC_OK) { nc_free(sub_msgs); return status; } sub_msg->type = r->type; sub_msg->frag_id = r->frag_id; sub_msg->frag_owner = r->frag_owner; TAILQ_INSERT_TAIL(frag_msgq, sub_msg, m_tqe); r->nfrag++; } nc_free(sub_msgs); return NC_OK; } rstatus_t memcache_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq) { if (memcache_retrieval(r)) { return memcache_fragment_retrieval(r, ncontinuum, frag_msgq, 1); } return NC_OK; } /* * Pre-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'get' or 'gets' and all the * responses to the fragmented request vector hasn't been received */ void memcache_pre_coalesce(struct msg *r) { struct msg *pr = r->peer; /* peer request */ struct mbuf *mbuf; ASSERT(!r->request); ASSERT(pr->request); if (pr->frag_id == 0) { /* do nothing, if not a response to a fragmented request */ return; } pr->frag_owner->nfrag_done++; switch (r->type) { case MSG_RSP_MC_VALUE: case MSG_RSP_MC_END: /* * Readjust responses of the fragmented message vector by not * including the end marker for all */ ASSERT(r->end != NULL); for (;;) { mbuf = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(mbuf != NULL); /* * We cannot assert that end marker points to the last mbuf * Consider a scenario where end marker points to the * penultimate mbuf and the last mbuf only contains spaces * and CRLF: mhdr -> [...END] -> [\r\n] */ if (r->end >= mbuf->pos && r->end < mbuf->last) { /* end marker is within this mbuf */ r->mlen -= (uint32_t)(mbuf->last - r->end); mbuf->last = r->end; break; } /* end marker is not in this mbuf */ r->mlen -= mbuf_length(mbuf); mbuf_remove(&r->mhdr, mbuf); mbuf_put(mbuf); } break; default: /* * Valid responses for a fragmented requests are MSG_RSP_MC_VALUE or, * MSG_RSP_MC_END. For an invalid response, we send out SERVER_ERRROR * with EINVAL errno */ mbuf = STAILQ_FIRST(&r->mhdr); log_hexdump(LOG_ERR, mbuf->pos, mbuf_length(mbuf), "rsp fragment " "with unknown type %d", r->type); pr->error = 1; pr->err = EINVAL; break; } } /* * copy one response from src to dst * return bytes copied * */ static rstatus_t memcache_copy_bulk(struct msg *dst, struct msg *src) { struct mbuf *mbuf, *nbuf; uint8_t *p; uint32_t len = 0; uint32_t bytes = 0; uint32_t i = 0; for (mbuf = STAILQ_FIRST(&src->mhdr); mbuf && mbuf_empty(mbuf); mbuf = STAILQ_FIRST(&src->mhdr)) { mbuf_remove(&src->mhdr, mbuf); mbuf_put(mbuf); } mbuf = STAILQ_FIRST(&src->mhdr); if (mbuf == NULL) { return NC_OK; /* key not exists */ } p = mbuf->pos; /* get : VALUE key 0 len\r\nval\r\n */ /* gets: VALUE key 0 len cas\r\nval\r\n */ ASSERT(*p == 'V'); for (i = 0; i < 3; i++) { /* eat 'VALUE key 0 ' */ for (; *p != ' ';) { p++; } p++; } len = 0; for (; p < mbuf->last && isdigit(*p); p++) { len = len * 10 + (uint32_t)(*p - '0'); } for (; p < mbuf->last && ('\r' != *p); p++) { /* eat cas for gets */ ; } len += CRLF_LEN * 2; len += (p - mbuf->pos); bytes = len; /* copy len bytes to dst */ for (; mbuf;) { if (mbuf_length(mbuf) <= len) { /* steal this mbuf from src to dst */ nbuf = STAILQ_NEXT(mbuf, next); mbuf_remove(&src->mhdr, mbuf); mbuf_insert(&dst->mhdr, mbuf); len -= mbuf_length(mbuf); mbuf = nbuf; } else { /* split it */ nbuf = mbuf_get(); if (nbuf == NULL) { return NC_ENOMEM; } mbuf_copy(nbuf, mbuf->pos, len); mbuf_insert(&dst->mhdr, nbuf); mbuf->pos += len; break; } } dst->mlen += bytes; src->mlen -= bytes; log_debug(LOG_VVERB, "memcache_copy_bulk copy bytes: %d", bytes); return NC_OK; } /* * Post-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'get' or 'gets' and all the * responses to the fragmented request vector has been received and * the fragmented request is consider to be done */ void memcache_post_coalesce(struct msg *request) { struct msg *response = request->peer; struct msg *sub_msg; uint32_t i; rstatus_t status; ASSERT(!response->request); ASSERT(request->request && (request->frag_owner == request)); if (request->error || request->ferror) { response->owner->err = 1; return; } for (i = 0; i < array_n(request->keys); i++) { /* for each key */ sub_msg = request->frag_seq[i]->peer; /* get it's peer response */ if (sub_msg == NULL) { response->owner->err = 1; return; } status = memcache_copy_bulk(response, sub_msg); if (status != NC_OK) { response->owner->err = 1; return; } } /* append END\r\n */ status = msg_append(response, (uint8_t *)"END\r\n", 5); if (status != NC_OK) { response->owner->err = 1; return; } } nutcracker-0.4.0+dfsg/src/proto/nc_proto.h000066400000000000000000000170431242132376000205260ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_PROTO_H_ #define _NC_PROTO_H_ #include #ifdef NC_LITTLE_ENDIAN #define str4cmp(m, c0, c1, c2, c3) \ (*(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)) #define str5cmp(m, c0, c1, c2, c3, c4) \ (str4cmp(m, c0, c1, c2, c3) && (m[4] == c4)) #define str6cmp(m, c0, c1, c2, c3, c4, c5) \ (str4cmp(m, c0, c1, c2, c3) && \ (((uint32_t *) m)[1] & 0xffff) == ((c5 << 8) | c4)) #define str7cmp(m, c0, c1, c2, c3, c4, c5, c6) \ (str6cmp(m, c0, c1, c2, c3, c4, c5) && (m[6] == c6)) #define str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ (str4cmp(m, c0, c1, c2, c3) && \ (((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4))) #define str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && m[8] == c8) #define str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ (((uint32_t *) m)[2] & 0xffff) == ((c9 << 8) | c8)) #define str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ (str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && (m[10] == c10)) #define str12cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ (((uint32_t *) m)[2] == ((c11 << 24) | (c10 << 16) | (c9 << 8) | c8))) #else #define str4cmp(m, c0, c1, c2, c3) \ (m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3) #define str5cmp(m, c0, c1, c2, c3, c4) \ (str4cmp(m, c0, c1, c2, c3) && (m[4] == c4)) #define str6cmp(m, c0, c1, c2, c3, c4, c5) \ (str5cmp(m, c0, c1, c2, c3, c4) && m[5] == c5) #define str7cmp(m, c0, c1, c2, c3, c4, c5, c6) \ (str6cmp(m, c0, c1, c2, c3, c4, c5) && m[6] == c6) #define str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ (str7cmp(m, c0, c1, c2, c3, c4, c5, c6) && m[7] == c7) #define str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && m[8] == c8) #define str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ (str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && m[9] == c9) #define str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ (str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && m[10] == c10) #define str12cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ (str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && m[11] == c11) #endif #define str3icmp(m, c0, c1, c2) \ ((m[0] == c0 || m[0] == (c0 ^ 0x20)) && \ (m[1] == c1 || m[1] == (c1 ^ 0x20)) && \ (m[2] == c2 || m[2] == (c2 ^ 0x20))) #define str4icmp(m, c0, c1, c2, c3) \ (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20))) #define str5icmp(m, c0, c1, c2, c3, c4) \ (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20))) #define str6icmp(m, c0, c1, c2, c3, c4, c5) \ (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20))) #define str7icmp(m, c0, c1, c2, c3, c4, c5, c6) \ (str6icmp(m, c0, c1, c2, c3, c4, c5) && \ (m[6] == c6 || m[6] == (c6 ^ 0x20))) #define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) && \ (m[7] == c7 || m[7] == (c7 ^ 0x20))) #define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ (m[8] == c8 || m[8] == (c8 ^ 0x20))) #define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && \ (m[9] == c9 || m[9] == (c9 ^ 0x20))) #define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && \ (m[10] == c10 || m[10] == (c10 ^ 0x20))) #define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && \ (m[11] == c11 || m[11] == (c11 ^ 0x20))) #define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) \ (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) && \ (m[12] == c12 || m[12] == (c12 ^ 0x20))) #define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) \ (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) && \ (m[13] == c13 || m[13] == (c13 ^ 0x20))) #define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) \ (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) && \ (m[14] == c14 || m[14] == (c14 ^ 0x20))) #define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) \ (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) && \ (m[15] == c15 || m[15] == (c15 ^ 0x20))) void memcache_parse_req(struct msg *r); void memcache_parse_rsp(struct msg *r); void memcache_pre_coalesce(struct msg *r); void memcache_post_coalesce(struct msg *r); rstatus_t memcache_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq); void redis_parse_req(struct msg *r); void redis_parse_rsp(struct msg *r); void redis_pre_coalesce(struct msg *r); void redis_post_coalesce(struct msg *r); rstatus_t redis_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq); rstatus_t redis_reply(struct msg *r); #endif nutcracker-0.4.0+dfsg/src/proto/nc_redis.c000066400000000000000000002126511242132376000204660ustar00rootroot00000000000000/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include /* * Return true, if the redis command take no key, otherwise * return false */ static bool redis_argz(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_PING: case MSG_REQ_REDIS_QUIT: return true; default: break; } return false; } /* * Return true, if the redis command accepts no arguments, otherwise * return false */ static bool redis_arg0(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_EXISTS: case MSG_REQ_REDIS_PERSIST: case MSG_REQ_REDIS_PTTL: case MSG_REQ_REDIS_SORT: case MSG_REQ_REDIS_TTL: case MSG_REQ_REDIS_TYPE: case MSG_REQ_REDIS_DUMP: case MSG_REQ_REDIS_DECR: case MSG_REQ_REDIS_GET: case MSG_REQ_REDIS_INCR: case MSG_REQ_REDIS_STRLEN: case MSG_REQ_REDIS_HGETALL: case MSG_REQ_REDIS_HKEYS: case MSG_REQ_REDIS_HLEN: case MSG_REQ_REDIS_HVALS: case MSG_REQ_REDIS_LLEN: case MSG_REQ_REDIS_LPOP: case MSG_REQ_REDIS_RPOP: case MSG_REQ_REDIS_SCARD: case MSG_REQ_REDIS_SMEMBERS: case MSG_REQ_REDIS_SPOP: case MSG_REQ_REDIS_ZCARD: case MSG_REQ_REDIS_PFCOUNT: return true; default: break; } return false; } /* * Return true, if the redis command accepts exactly 1 argument, otherwise * return false */ static bool redis_arg1(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_EXPIRE: case MSG_REQ_REDIS_EXPIREAT: case MSG_REQ_REDIS_PEXPIRE: case MSG_REQ_REDIS_PEXPIREAT: case MSG_REQ_REDIS_APPEND: case MSG_REQ_REDIS_DECRBY: case MSG_REQ_REDIS_GETBIT: case MSG_REQ_REDIS_GETSET: case MSG_REQ_REDIS_INCRBY: case MSG_REQ_REDIS_INCRBYFLOAT: case MSG_REQ_REDIS_SETNX: case MSG_REQ_REDIS_HEXISTS: case MSG_REQ_REDIS_HGET: case MSG_REQ_REDIS_LINDEX: case MSG_REQ_REDIS_LPUSHX: case MSG_REQ_REDIS_RPOPLPUSH: case MSG_REQ_REDIS_RPUSHX: case MSG_REQ_REDIS_SISMEMBER: case MSG_REQ_REDIS_ZRANK: case MSG_REQ_REDIS_ZREVRANK: case MSG_REQ_REDIS_ZSCORE: return true; default: break; } return false; } /* * Return true, if the redis command accepts exactly 2 arguments, otherwise * return false */ static bool redis_arg2(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_GETRANGE: case MSG_REQ_REDIS_PSETEX: case MSG_REQ_REDIS_SETBIT: case MSG_REQ_REDIS_SETEX: case MSG_REQ_REDIS_SETRANGE: case MSG_REQ_REDIS_HINCRBY: case MSG_REQ_REDIS_HINCRBYFLOAT: case MSG_REQ_REDIS_HSET: case MSG_REQ_REDIS_HSETNX: case MSG_REQ_REDIS_LRANGE: case MSG_REQ_REDIS_LREM: case MSG_REQ_REDIS_LSET: case MSG_REQ_REDIS_LTRIM: case MSG_REQ_REDIS_SMOVE: case MSG_REQ_REDIS_ZCOUNT: case MSG_REQ_REDIS_ZLEXCOUNT: case MSG_REQ_REDIS_ZINCRBY: case MSG_REQ_REDIS_ZREMRANGEBYLEX: case MSG_REQ_REDIS_ZREMRANGEBYRANK: case MSG_REQ_REDIS_ZREMRANGEBYSCORE: case MSG_REQ_REDIS_RESTORE: return true; default: break; } return false; } /* * Return true, if the redis command accepts exactly 3 arguments, otherwise * return false */ static bool redis_arg3(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_LINSERT: return true; default: break; } return false; } /* * Return true, if the redis command accepts 0 or more arguments, otherwise * return false */ static bool redis_argn(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_BITCOUNT: case MSG_REQ_REDIS_SET: case MSG_REQ_REDIS_HDEL: case MSG_REQ_REDIS_HMGET: case MSG_REQ_REDIS_HMSET: case MSG_REQ_REDIS_HSCAN: case MSG_REQ_REDIS_LPUSH: case MSG_REQ_REDIS_RPUSH: case MSG_REQ_REDIS_SADD: case MSG_REQ_REDIS_SDIFF: case MSG_REQ_REDIS_SDIFFSTORE: case MSG_REQ_REDIS_SINTER: case MSG_REQ_REDIS_SINTERSTORE: case MSG_REQ_REDIS_SREM: case MSG_REQ_REDIS_SUNION: case MSG_REQ_REDIS_SUNIONSTORE: case MSG_REQ_REDIS_SRANDMEMBER: case MSG_REQ_REDIS_SSCAN: case MSG_REQ_REDIS_PFADD: case MSG_REQ_REDIS_PFMERGE: case MSG_REQ_REDIS_ZADD: case MSG_REQ_REDIS_ZINTERSTORE: case MSG_REQ_REDIS_ZRANGE: case MSG_REQ_REDIS_ZRANGEBYSCORE: case MSG_REQ_REDIS_ZREM: case MSG_REQ_REDIS_ZREVRANGE: case MSG_REQ_REDIS_ZRANGEBYLEX: case MSG_REQ_REDIS_ZREVRANGEBYSCORE: case MSG_REQ_REDIS_ZUNIONSTORE: case MSG_REQ_REDIS_ZSCAN: return true; default: break; } return false; } /* * Return true, if the redis command is a vector command accepting one or * more keys, otherwise return false */ static bool redis_argx(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_MGET: case MSG_REQ_REDIS_DEL: return true; default: break; } return false; } /* * Return true, if the redis command is a vector command accepting one or * more key-value pairs, otherwise return false */ static bool redis_argkvx(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_MSET: return true; default: break; } return false; } /* * Return true, if the redis command is either EVAL or EVALSHA. These commands * have a special format with exactly 2 arguments, followed by one or more keys, * followed by zero or more arguments (the documentation online seems to suggest * that at least one argument is required, but that shouldn't be the case). */ static bool redis_argeval(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_EVAL: case MSG_REQ_REDIS_EVALSHA: return true; default: break; } return false; } /* * Reference: http://redis.io/topics/protocol * * Redis >= 1.2 uses the unified protocol to send requests to the Redis * server. In the unified protocol all the arguments sent to the server * are binary safe and every request has the following general form: * * * CR LF * $ CR LF * CR LF * ... * $ CR LF * CR LF * * Before the unified request protocol, redis protocol for requests supported * the following commands * 1). Inline commands: simple commands where arguments are just space * separated strings. No binary safeness is possible. * 2). Bulk commands: bulk commands are exactly like inline commands, but * the last argument is handled in a special way in order to allow for * a binary-safe last argument. * * Nutcracker only supports the Redis unified protocol for requests. */ void redis_parse_req(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_NARG, SW_NARG_LF, SW_REQ_TYPE_LEN, SW_REQ_TYPE_LEN_LF, SW_REQ_TYPE, SW_REQ_TYPE_LF, SW_KEY_LEN, SW_KEY_LEN_LF, SW_KEY, SW_KEY_LF, SW_ARG1_LEN, SW_ARG1_LEN_LF, SW_ARG1, SW_ARG1_LF, SW_ARG2_LEN, SW_ARG2_LEN_LF, SW_ARG2, SW_ARG2_LF, SW_ARG3_LEN, SW_ARG3_LEN_LF, SW_ARG3, SW_ARG3_LF, SW_ARGN_LEN, SW_ARGN_LEN_LF, SW_ARGN, SW_ARGN_LF, SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(r->request); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing maker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: case SW_NARG: if (r->token == NULL) { if (ch != '*') { goto error; } r->token = p; /* req_start <- p */ r->narg_start = p; r->rnarg = 0; state = SW_NARG; } else if (isdigit(ch)) { r->rnarg = r->rnarg * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if (r->rnarg == 0) { goto error; } r->narg = r->rnarg; r->narg_end = p; r->token = NULL; state = SW_NARG_LF; } else { goto error; } break; case SW_NARG_LF: switch (ch) { case LF: state = SW_REQ_TYPE_LEN; break; default: goto error; } break; case SW_REQ_TYPE_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->token = p; r->rlen = 0; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if (r->rlen == 0 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_REQ_TYPE_LEN_LF; } else { goto error; } break; case SW_REQ_TYPE_LEN_LF: switch (ch) { case LF: state = SW_REQ_TYPE; break; default: goto error; } break; case SW_REQ_TYPE: if (r->token == NULL) { r->token = p; } m = r->token + r->rlen; if (m >= b->last) { m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; m = r->token; r->token = NULL; r->type = MSG_UNKNOWN; switch (p - m) { case 3: if (str3icmp(m, 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_GET; break; } if (str3icmp(m, 's', 'e', 't')) { r->type = MSG_REQ_REDIS_SET; break; } if (str3icmp(m, 't', 't', 'l')) { r->type = MSG_REQ_REDIS_TTL; break; } if (str3icmp(m, 'd', 'e', 'l')) { r->type = MSG_REQ_REDIS_DEL; break; } break; case 4: if (str4icmp(m, 'p', 't', 't', 'l')) { r->type = MSG_REQ_REDIS_PTTL; break; } if (str4icmp(m, 'd', 'e', 'c', 'r')) { r->type = MSG_REQ_REDIS_DECR; break; } if (str4icmp(m, 'd', 'u', 'm', 'p')) { r->type = MSG_REQ_REDIS_DUMP; break; } if (str4icmp(m, 'h', 'd', 'e', 'l')) { r->type = MSG_REQ_REDIS_HDEL; break; } if (str4icmp(m, 'h', 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_HGET; break; } if (str4icmp(m, 'h', 'l', 'e', 'n')) { r->type = MSG_REQ_REDIS_HLEN; break; } if (str4icmp(m, 'h', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_HSET; break; } if (str4icmp(m, 'i', 'n', 'c', 'r')) { r->type = MSG_REQ_REDIS_INCR; break; } if (str4icmp(m, 'l', 'l', 'e', 'n')) { r->type = MSG_REQ_REDIS_LLEN; break; } if (str4icmp(m, 'l', 'p', 'o', 'p')) { r->type = MSG_REQ_REDIS_LPOP; break; } if (str4icmp(m, 'l', 'r', 'e', 'm')) { r->type = MSG_REQ_REDIS_LREM; break; } if (str4icmp(m, 'l', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_LSET; break; } if (str4icmp(m, 'r', 'p', 'o', 'p')) { r->type = MSG_REQ_REDIS_RPOP; break; } if (str4icmp(m, 's', 'a', 'd', 'd')) { r->type = MSG_REQ_REDIS_SADD; break; } if (str4icmp(m, 's', 'p', 'o', 'p')) { r->type = MSG_REQ_REDIS_SPOP; break; } if (str4icmp(m, 's', 'r', 'e', 'm')) { r->type = MSG_REQ_REDIS_SREM; break; } if (str4icmp(m, 't', 'y', 'p', 'e')) { r->type = MSG_REQ_REDIS_TYPE; break; } if (str4icmp(m, 'm', 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_MGET; break; } if (str4icmp(m, 'm', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_MSET; break; } if (str4icmp(m, 'z', 'a', 'd', 'd')) { r->type = MSG_REQ_REDIS_ZADD; break; } if (str4icmp(m, 'z', 'r', 'e', 'm')) { r->type = MSG_REQ_REDIS_ZREM; break; } if (str4icmp(m, 'e', 'v', 'a', 'l')) { r->type = MSG_REQ_REDIS_EVAL; break; } if (str4icmp(m, 's', 'o', 'r', 't')) { r->type = MSG_REQ_REDIS_SORT; break; } if (str4icmp(m, 'p', 'i', 'n', 'g')) { r->type = MSG_REQ_REDIS_PING; r->noforward = 1; break; } if (str4icmp(m, 'q', 'u', 'i', 't')) { r->type = MSG_REQ_REDIS_QUIT; r->quit = 1; break; } break; case 5: if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) { r->type = MSG_REQ_REDIS_HKEYS; break; } if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_HMGET; break; } if (str5icmp(m, 'h', 'm', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_HMSET; break; } if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) { r->type = MSG_REQ_REDIS_HVALS; break; } if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) { r->type = MSG_REQ_REDIS_HSCAN; break; } if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) { r->type = MSG_REQ_REDIS_LPUSH; break; } if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) { r->type = MSG_REQ_REDIS_LTRIM; break; } if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) { r->type = MSG_REQ_REDIS_RPUSH; break; } if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) { r->type = MSG_REQ_REDIS_SCARD; break; } if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) { r->type = MSG_REQ_REDIS_SDIFF; break; } if (str5icmp(m, 's', 'e', 't', 'e', 'x')) { r->type = MSG_REQ_REDIS_SETEX; break; } if (str5icmp(m, 's', 'e', 't', 'n', 'x')) { r->type = MSG_REQ_REDIS_SETNX; break; } if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) { r->type = MSG_REQ_REDIS_SMOVE; break; } if (str5icmp(m, 's', 's', 'c', 'a', 'n')) { r->type = MSG_REQ_REDIS_SSCAN; break; } if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) { r->type = MSG_REQ_REDIS_ZCARD; break; } if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) { r->type = MSG_REQ_REDIS_ZRANK; break; } if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) { r->type = MSG_REQ_REDIS_ZSCAN; break; } if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) { r->type = MSG_REQ_REDIS_PFADD; break; } break; case 6: if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { r->type = MSG_REQ_REDIS_APPEND; break; } if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_DECRBY; break; } if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) { r->type = MSG_REQ_REDIS_EXISTS; break; } if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) { r->type = MSG_REQ_REDIS_EXPIRE; break; } if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) { r->type = MSG_REQ_REDIS_GETBIT; break; } if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_GETSET; break; } if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) { r->type = MSG_REQ_REDIS_PSETEX; break; } if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) { r->type = MSG_REQ_REDIS_HSETNX; break; } if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_INCRBY; break; } if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) { r->type = MSG_REQ_REDIS_LINDEX; break; } if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) { r->type = MSG_REQ_REDIS_LPUSHX; break; } if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_LRANGE; break; } if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) { r->type = MSG_REQ_REDIS_RPUSHX; break; } if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) { r->type = MSG_REQ_REDIS_SETBIT; break; } if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) { r->type = MSG_REQ_REDIS_SINTER; break; } if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) { r->type = MSG_REQ_REDIS_STRLEN; break; } if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) { r->type = MSG_REQ_REDIS_SUNION; break; } if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_ZCOUNT; break; } if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_ZRANGE; break; } if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZSCORE; break; } break; case 7: if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) { r->type = MSG_REQ_REDIS_PERSIST; break; } if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) { r->type = MSG_REQ_REDIS_PEXPIRE; break; } if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) { r->type = MSG_REQ_REDIS_HEXISTS; break; } if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) { r->type = MSG_REQ_REDIS_HGETALL; break; } if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_HINCRBY; break; } if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) { r->type = MSG_REQ_REDIS_LINSERT; break; } if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_ZINCRBY; break; } if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) { r->type = MSG_REQ_REDIS_EVALSHA; break; } if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_RESTORE; break; } if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_PFCOUNT; break; } if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) { r->type = MSG_REQ_REDIS_PFMERGE; break; } break; case 8: if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { r->type = MSG_REQ_REDIS_EXPIREAT; break; } if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_BITCOUNT; break; } if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_GETRANGE; break; } if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_SETRANGE; break; } if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) { r->type = MSG_REQ_REDIS_SMEMBERS; break; } if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) { r->type = MSG_REQ_REDIS_ZREVRANK; break; } break; case 9: if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { r->type = MSG_REQ_REDIS_PEXPIREAT; break; } if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) { r->type = MSG_REQ_REDIS_RPOPLPUSH; break; } if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) { r->type = MSG_REQ_REDIS_SISMEMBER; break; } if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_ZREVRANGE; break; } if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_ZLEXCOUNT; break; } break; case 10: if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_SDIFFSTORE; break; } case 11: if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { r->type = MSG_REQ_REDIS_INCRBYFLOAT; break; } if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_SINTERSTORE; break; } if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) { r->type = MSG_REQ_REDIS_SRANDMEMBER; break; } if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_SUNIONSTORE; break; } if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZINTERSTORE; break; } if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZUNIONSTORE; break; } if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { r->type = MSG_REQ_REDIS_ZRANGEBYLEX; break; } break; case 12: if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { r->type = MSG_REQ_REDIS_HINCRBYFLOAT; break; } break; case 13: if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZRANGEBYSCORE; break; } break; case 14: if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { r->type = MSG_REQ_REDIS_ZREMRANGEBYLEX; break; } break; case 15: if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { r->type = MSG_REQ_REDIS_ZREMRANGEBYRANK; break; } break; case 16: if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZREMRANGEBYSCORE; break; } if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZREVRANGEBYSCORE; break; } break; default: break; } if (r->type == MSG_UNKNOWN) { log_error("parsed unsupported command '%.*s'", p - m, m); goto error; } log_debug(LOG_VERB, "parsed command '%.*s'", p - m, m); state = SW_REQ_TYPE_LF; break; case SW_REQ_TYPE_LF: switch (ch) { case LF: if (redis_argz(r)) { goto done; } else if (redis_argeval(r)) { state = SW_ARG1_LEN; } else { state = SW_KEY_LEN; } break; default: goto error; } break; case SW_KEY_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->token = p; r->rlen = 0; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if (r->rlen == 0) { log_error("parsed bad req %"PRIu64" of type %d with empty " "key", r->id, r->type); goto error; } if (r->rlen >= mbuf_data_size()) { log_error("parsed bad req %"PRIu64" of type %d with key " "length %d that greater than or equal to maximum" " redis key length of %d", r->id, r->type, r->rlen, mbuf_data_size()); goto error; } if (r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_KEY_LEN_LF; } else { goto error; } break; case SW_KEY_LEN_LF: switch (ch) { case LF: state = SW_KEY; break; default: goto error; } break; case SW_KEY: if (r->token == NULL) { r->token = p; } m = r->token + r->rlen; if (m >= b->last) { m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } else { /* got a key */ struct keypos *kpos; p = m; /* move forward by rlen bytes */ r->rlen = 0; m = r->token; r->token = NULL; kpos = array_push(r->keys); if (kpos == NULL) { goto enomem; } kpos->start = m; kpos->end = p; state = SW_KEY_LF; } break; case SW_KEY_LF: switch (ch) { case LF: if (redis_arg0(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_arg1(r)) { if (r->rnarg != 1) { goto error; } state = SW_ARG1_LEN; } else if (redis_arg2(r)) { if (r->rnarg != 2) { goto error; } state = SW_ARG1_LEN; } else if (redis_arg3(r)) { if (r->rnarg != 3) { goto error; } state = SW_ARG1_LEN; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARG1_LEN; } else if (redis_argx(r)) { if (r->rnarg == 0) { goto done; } state = SW_KEY_LEN; } else if (redis_argkvx(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARG1_LEN; } else if (redis_argeval(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARG1_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARG1_LEN_LF; } else { goto error; } break; case SW_ARG1_LEN_LF: switch (ch) { case LF: state = SW_ARG1; break; default: goto error; } break; case SW_ARG1: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_ARG1_LF; break; case SW_ARG1_LF: switch (ch) { case LF: if (redis_arg1(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_arg2(r)) { if (r->rnarg != 1) { goto error; } state = SW_ARG2_LEN; } else if (redis_arg3(r)) { if (r->rnarg != 2) { goto error; } state = SW_ARG2_LEN; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else if (redis_argeval(r)) { if (r->rnarg < 2) { goto error; } state = SW_ARG2_LEN; } else if (redis_argkvx(r)) { if (r->rnarg == 0) { goto done; } state = SW_KEY_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARG2_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARG2_LEN_LF; } else { goto error; } break; case SW_ARG2_LEN_LF: switch (ch) { case LF: state = SW_ARG2; break; default: goto error; } break; case SW_ARG2: if (r->token == NULL && redis_argeval(r)) { /* * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must * be tokenized and stored in contiguous memory. */ r->token = p; } m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; if (redis_argeval(r)) { uint32_t nkey; uint8_t *chp; /* * For EVAL/EVALSHA, we need to find the integer value of this * argument. It tells us the number of keys in the script, and * we need to error out if number of keys is 0. At this point, * both p and m point to the end of the argument and r->token * points to the start. */ if (p - r->token < 1) { goto error; } for (nkey = 0, chp = r->token; chp < p; chp++) { if (isdigit(*chp)) { nkey = nkey * 10 + (uint32_t)(*chp - '0'); } else { goto error; } } if (nkey == 0) { goto error; } r->token = NULL; } state = SW_ARG2_LF; break; case SW_ARG2_LF: switch (ch) { case LF: if (redis_arg2(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_arg3(r)) { if (r->rnarg != 1) { goto error; } state = SW_ARG3_LEN; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else if (redis_argeval(r)) { if (r->rnarg < 1) { goto error; } state = SW_KEY_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARG3_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARG3_LEN_LF; } else { goto error; } break; case SW_ARG3_LEN_LF: switch (ch) { case LF: state = SW_ARG3; break; default: goto error; } break; case SW_ARG3: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_ARG3_LF; break; case SW_ARG3_LF: switch (ch) { case LF: if (redis_arg3(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARGN_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARGN_LEN_LF; } else { goto error; } break; case SW_ARGN_LEN_LF: switch (ch) { case LF: state = SW_ARGN; break; default: goto error; } break; case SW_ARGN: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_ARGN_LF; break; case SW_ARGN_LF: switch (ch) { case LF: if (redis_argn(r) || redis_argeval(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else { goto error; } break; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->token = NULL; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; enomem: r->result = MSG_PARSE_ERROR; r->state = state; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "out of memory on parse req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } /* * Reference: http://redis.io/topics/protocol * * Redis will reply to commands with different kinds of replies. It is * possible to check the kind of reply from the first byte sent by the * server: * - with a single line reply the first byte of the reply will be "+" * - with an error message the first byte of the reply will be "-" * - with an integer number the first byte of the reply will be ":" * - with bulk reply the first byte of the reply will be "$" * - with multi-bulk reply the first byte of the reply will be "*" * * 1). Status reply (or single line reply) is in the form of a single line * string starting with "+" terminated by "\r\n". * 2). Error reply are similar to status replies. The only difference is * that the first byte is "-" instead of "+". * 3). Integer reply is just a CRLF terminated string representing an * integer, and prefixed by a ":" byte. * 4). Bulk reply is used by server to return a single binary safe string. * The first reply line is a "$" byte followed by the number of bytes * of the actual reply, followed by CRLF, then the actual data bytes, * followed by additional two bytes for the final CRLF. If the requested * value does not exist the bulk reply will use the special value '-1' * as the data length. * 5). Multi-bulk reply is used by the server to return many binary safe * strings (bulks) with the initial line indicating how many bulks that * will follow. The first byte of a multi bulk reply is always *. */ void redis_parse_rsp(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_STATUS, SW_ERROR, SW_INTEGER, SW_INTEGER_START, SW_BULK, SW_BULK_LF, SW_BULK_ARG, SW_BULK_ARG_LF, SW_MULTIBULK, SW_MULTIBULK_NARG_LF, SW_MULTIBULK_ARGN_LEN, SW_MULTIBULK_ARGN_LEN_LF, SW_MULTIBULK_ARGN, SW_MULTIBULK_ARGN_LF, SW_RUNTO_CRLF, SW_ALMOST_DONE, SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(!r->request); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing marker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: r->type = MSG_UNKNOWN; switch (ch) { case '+': p = p - 1; /* go back by 1 byte */ r->type = MSG_RSP_REDIS_STATUS; state = SW_STATUS; break; case '-': r->type = MSG_RSP_REDIS_ERROR; p = p - 1; /* go back by 1 byte */ state = SW_ERROR; break; case ':': r->type = MSG_RSP_REDIS_INTEGER; p = p - 1; /* go back by 1 byte */ state = SW_INTEGER; break; case '$': r->type = MSG_RSP_REDIS_BULK; p = p - 1; /* go back by 1 byte */ state = SW_BULK; break; case '*': r->type = MSG_RSP_REDIS_MULTIBULK; p = p - 1; /* go back by 1 byte */ state = SW_MULTIBULK; break; default: goto error; } break; case SW_STATUS: /* rsp_start <- p */ state = SW_RUNTO_CRLF; break; case SW_ERROR: /* rsp_start <- p */ state = SW_RUNTO_CRLF; break; case SW_INTEGER: /* rsp_start <- p */ state = SW_INTEGER_START; r->integer = 0; break; case SW_INTEGER_START: if (ch == CR) { state = SW_ALMOST_DONE; } else if (ch == '-') { ; } else if (isdigit(ch)) { r->integer = r->integer * 10 + (uint32_t)(ch - '0'); } else { goto error; } break; case SW_RUNTO_CRLF: switch (ch) { case CR: state = SW_ALMOST_DONE; break; default: break; } break; case SW_ALMOST_DONE: switch (ch) { case LF: /* rsp_end <- p */ goto done; default: goto error; } break; case SW_BULK: if (r->token == NULL) { if (ch != '$') { goto error; } /* rsp_start <- p */ r->token = p; r->rlen = 0; } else if (ch == '-') { /* handles null bulk reply = '$-1' */ state = SW_RUNTO_CRLF; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1) { goto error; } r->token = NULL; state = SW_BULK_LF; } else { goto error; } break; case SW_BULK_LF: switch (ch) { case LF: state = SW_BULK_ARG; break; default: goto error; } break; case SW_BULK_ARG: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_BULK_ARG_LF; break; case SW_BULK_ARG_LF: switch (ch) { case LF: goto done; default: goto error; } break; case SW_MULTIBULK: if (r->token == NULL) { if (ch != '*') { goto error; } r->token = p; /* rsp_start <- p */ r->narg_start = p; r->rnarg = 0; } else if (ch == '-') { state = SW_RUNTO_CRLF; } else if (isdigit(ch)) { r->rnarg = r->rnarg * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1) { goto error; } r->narg = r->rnarg; r->narg_end = p; r->token = NULL; state = SW_MULTIBULK_NARG_LF; } else { goto error; } break; case SW_MULTIBULK_NARG_LF: switch (ch) { case LF: if (r->rnarg == 0) { /* response is '*0\r\n' */ goto done; } state = SW_MULTIBULK_ARGN_LEN; break; default: goto error; } break; case SW_MULTIBULK_ARGN_LEN: if (r->token == NULL) { /* * From: http://redis.io/topics/protocol, a multi bulk reply * is used to return an array of other replies. Every element * of a multi bulk reply can be of any kind, including a * nested multi bulk reply. * * Here, we only handle a multi bulk reply element that * are either integer reply or bulk reply. * * there is a special case for sscan/hscan/zscan, these command * replay a nested multi-bulk with a number and a multi bulk like this: * * - mulit-bulk * - cursor * - mulit-bulk * - val1 * - val2 * - val3 * * in this case, there is only one sub-multi-bulk, * and it's the last element of parent, * we can handle it like tail-recursive. * */ if (ch == '*') { /* for sscan/hscan/zscan only */ p = p - 1; /* go back by 1 byte */ state = SW_MULTIBULK; break; } if (ch != '$' && ch != ':') { goto error; } r->token = p; r->rlen = 0; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == '-') { ; } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } if ((r->rlen == 1 && (p - r->token) == 3) || *r->token == ':') { /* handles not-found reply = '$-1' or integer reply = ':' */ r->rlen = 0; state = SW_MULTIBULK_ARGN_LF; } else { state = SW_MULTIBULK_ARGN_LEN_LF; } r->rnarg--; r->token = NULL; } else { goto error; } break; case SW_MULTIBULK_ARGN_LEN_LF: switch (ch) { case LF: state = SW_MULTIBULK_ARGN; break; default: goto error; } break; case SW_MULTIBULK_ARGN: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p += r->rlen; /* move forward by rlen bytes */ r->rlen = 0; state = SW_MULTIBULK_ARGN_LF; break; case SW_MULTIBULK_ARGN_LF: switch (ch) { case LF: if (r->rnarg == 0) { goto done; } state = SW_MULTIBULK_ARGN_LEN; break; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->token = NULL; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad rsp %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } /* * copy one bulk from src to dst * * if dst == NULL, we just eat the bulk * * */ static rstatus_t redis_copy_bulk(struct msg *dst, struct msg *src) { struct mbuf *mbuf, *nbuf; uint8_t *p; uint32_t len = 0; uint32_t bytes = 0; rstatus_t status; for (mbuf = STAILQ_FIRST(&src->mhdr); mbuf && mbuf_empty(mbuf); mbuf = STAILQ_FIRST(&src->mhdr)) { mbuf_remove(&src->mhdr, mbuf); mbuf_put(mbuf); } mbuf = STAILQ_FIRST(&src->mhdr); if (mbuf == NULL) { return NC_ERROR; } p = mbuf->pos; ASSERT(*p == '$'); p++; if (p[0] == '-' && p[1] == '1') { len = 1 + 2 + CRLF_LEN; /* $-1\r\n */ p = mbuf->pos + len; } else { len = 0; for (; p < mbuf->last && isdigit(*p); p++) { len = len * 10 + (uint32_t)(*p - '0'); } len += CRLF_LEN * 2; len += (p - mbuf->pos); } bytes = len; /* copy len bytes to dst */ for (; mbuf;) { if (mbuf_length(mbuf) <= len) { /* steal this buf from src to dst */ nbuf = STAILQ_NEXT(mbuf, next); mbuf_remove(&src->mhdr, mbuf); if (dst != NULL) { mbuf_insert(&dst->mhdr, mbuf); } len -= mbuf_length(mbuf); mbuf = nbuf; } else { /* split it */ if (dst != NULL) { status = msg_append(dst, mbuf->pos, len); if (status != NC_OK) { return status; } } mbuf->pos += len; break; } } if (dst != NULL) { dst->mlen += bytes; } src->mlen -= bytes; log_debug(LOG_VVERB, "redis_copy_bulk copy bytes: %d", bytes); return NC_OK; } /* * Pre-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'mget' or 'del' and all the * responses to the fragmented request vector hasn't been received */ void redis_pre_coalesce(struct msg *r) { struct msg *pr = r->peer; /* peer request */ struct mbuf *mbuf; ASSERT(!r->request); ASSERT(pr->request); if (pr->frag_id == 0) { /* do nothing, if not a response to a fragmented request */ return; } pr->frag_owner->nfrag_done++; switch (r->type) { case MSG_RSP_REDIS_INTEGER: /* only redis 'del' fragmented request sends back integer reply */ ASSERT(pr->type == MSG_REQ_REDIS_DEL); mbuf = STAILQ_FIRST(&r->mhdr); /* * Our response parser guarantees that the integer reply will be * completely encapsulated in a single mbuf and we should skip over * all the mbuf contents and discard it as the parser has already * parsed the integer reply and stored it in msg->integer */ ASSERT(mbuf == STAILQ_LAST(&r->mhdr, mbuf, next)); ASSERT(r->mlen == mbuf_length(mbuf)); r->mlen -= mbuf_length(mbuf); mbuf_rewind(mbuf); /* accumulate the integer value in frag_owner of peer request */ pr->frag_owner->integer += r->integer; break; case MSG_RSP_REDIS_MULTIBULK: /* only redis 'mget' fragmented request sends back multi-bulk reply */ ASSERT(pr->type == MSG_REQ_REDIS_MGET); mbuf = STAILQ_FIRST(&r->mhdr); /* * Muti-bulk reply can span over multiple mbufs and in each reply * we should skip over the narg token. Our response parser * guarantees thaat the narg token and the immediately following * '\r\n' will exist in a contiguous region in the first mbuf */ ASSERT(r->narg_start == mbuf->pos); ASSERT(r->narg_start < r->narg_end); r->narg_end += CRLF_LEN; r->mlen -= (uint32_t)(r->narg_end - r->narg_start); mbuf->pos = r->narg_end; break; case MSG_RSP_REDIS_STATUS: if (pr->type == MSG_REQ_REDIS_MSET) { /* MSET segments */ mbuf = STAILQ_FIRST(&r->mhdr); r->mlen -= mbuf_length(mbuf); mbuf_rewind(mbuf); } break; default: /* * Valid responses for a fragmented request are MSG_RSP_REDIS_INTEGER or, * MSG_RSP_REDIS_MULTIBULK. For an invalid response, we send out -ERR * with EINVAL errno */ mbuf = STAILQ_FIRST(&r->mhdr); log_hexdump(LOG_ERR, mbuf->pos, mbuf_length(mbuf), "rsp fragment " "with unknown type %d", r->type); pr->error = 1; pr->err = EINVAL; break; } } static rstatus_t redis_append_key(struct msg *r, uint8_t *key, uint32_t keylen) { uint32_t len; struct mbuf *mbuf; uint8_t printbuf[32]; struct keypos *kpos; /* 1. keylen */ len = (uint32_t)nc_snprintf(printbuf, sizeof(printbuf), "$%d\r\n", keylen); mbuf = msg_ensure_mbuf(r, len); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_copy(mbuf, printbuf, len); r->mlen += len; /* 2. key */ mbuf = msg_ensure_mbuf(r, keylen); if (mbuf == NULL) { return NC_ENOMEM; } kpos = array_push(r->keys); if (kpos == NULL) { return NC_ENOMEM; } kpos->start = mbuf->last; kpos->end = mbuf->last + keylen; mbuf_copy(mbuf, key, keylen); r->mlen += keylen; /* 3. CRLF */ mbuf = msg_ensure_mbuf(r, CRLF_LEN); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_copy(mbuf, (uint8_t *)CRLF, CRLF_LEN); r->mlen += (uint32_t)CRLF_LEN; return NC_OK; } /* * input a msg, return a msg chain. * ncontinuum is the number of backend redis/memcache server * * the original msg will be fragment into at most ncontinuum fragments. * all the keys map to the same backend will group into one fragment. * * frag_id: * a unique fragment id for all fragments of the message vector. including the orig msg. * * frag_owner: * All fragments of the message use frag_owner point to the orig msg * * frag_seq: * the map from each key to it's fragment, (only in the orig msg) * * For example, a message vector with 3 keys: * * get key1 key2 key3 * * suppose we have 2 backend server, and the map is: * * key1 => backend 0 * key2 => backend 1 * key3 => backend 0 * * it will fragment like this: * * +-----------------+ * | msg vector | * |(original msg) | * |key1, key2, key3 | * +-----------------+ * * frag_owner * /--------------------------------------+ * frag_owner / | * /-----------+ | /------------+ frag_owner | * | | | | | | * | v v v | | * +--------------------+ +---------------------+ +----+----------------+ * | frag_id = 10 | | frag_id = 10 | | frag_id = 10 | * | nfrag = 3 | | nfrag = 0 | | nfrag = 0 | * | frag_seq = x x x | | key1, key3 | | key2 | * +------------|-|-|---+ +---------------------+ +---------------------+ * | | | ^ ^ ^ * | \ \ | | | * | \ ----------+ | | * +---\---------------+ | * ------------------------------------------+ * */ static rstatus_t redis_fragment_argx(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq, uint32_t key_step) { struct mbuf *mbuf; struct msg **sub_msgs; uint32_t i; rstatus_t status; ASSERT(array_n(r->keys) == (r->narg - 1) / key_step); sub_msgs = nc_zalloc(ncontinuum * sizeof(*sub_msgs)); if (sub_msgs == NULL) { return NC_ENOMEM; } ASSERT(r->frag_seq == NULL); r->frag_seq = nc_alloc(array_n(r->keys) * sizeof(*r->frag_seq)); if (r->frag_seq == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } mbuf = STAILQ_FIRST(&r->mhdr); mbuf->pos = mbuf->start; /* * This code is based on the assumption that '*narg\r\n$4\r\nMGET\r\n' is located * in a contiguous location. * This is always true because we have capped our MBUF_MIN_SIZE at 512 and * whenever we have multiple messages, we copy the tail message into a new mbuf */ for (i = 0; i < 3; i++) { /* eat *narg\r\n$4\r\nMGET\r\n */ for (; *(mbuf->pos) != '\n';) { mbuf->pos++; } mbuf->pos++; } r->frag_id = msg_gen_frag_id(); r->nfrag = 0; r->frag_owner = r; for (i = 0; i < array_n(r->keys); i++) { /* for each key */ struct msg *sub_msg; struct keypos *kpos = array_get(r->keys, i); uint32_t idx = msg_backend_idx(r, kpos->start, kpos->end - kpos->start); if (sub_msgs[idx] == NULL) { sub_msgs[idx] = msg_get(r->owner, r->request, r->redis); if (sub_msgs[idx] == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } } r->frag_seq[i] = sub_msg = sub_msgs[idx]; sub_msg->narg++; status = redis_append_key(sub_msg, kpos->start, kpos->end - kpos->start); if (status != NC_OK) { nc_free(sub_msgs); return status; } if (key_step == 1) { /* mget,del */ continue; } else { /* mset */ status = redis_copy_bulk(NULL, r); /* eat key */ if (status != NC_OK) { nc_free(sub_msgs); return status; } status = redis_copy_bulk(sub_msg, r); if (status != NC_OK) { nc_free(sub_msgs); return status; } sub_msg->narg++; } } for (i = 0; i < ncontinuum; i++) { /* prepend mget header, and forward it */ struct msg *sub_msg = sub_msgs[i]; if (sub_msg == NULL) { continue; } if (r->type == MSG_REQ_REDIS_MGET) { status = msg_prepend_format(sub_msg, "*%d\r\n$4\r\nmget\r\n", sub_msg->narg + 1); } else if (r->type == MSG_REQ_REDIS_DEL) { status = msg_prepend_format(sub_msg, "*%d\r\n$3\r\ndel\r\n", sub_msg->narg + 1); } else if (r->type == MSG_REQ_REDIS_MSET) { status = msg_prepend_format(sub_msg, "*%d\r\n$4\r\nmset\r\n", sub_msg->narg + 1); } else { NOT_REACHED(); } if (status != NC_OK) { nc_free(sub_msgs); return status; } sub_msg->type = r->type; sub_msg->frag_id = r->frag_id; sub_msg->frag_owner = r->frag_owner; TAILQ_INSERT_TAIL(frag_msgq, sub_msg, m_tqe); r->nfrag++; } nc_free(sub_msgs); return NC_OK; } rstatus_t redis_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq) { switch (r->type) { case MSG_REQ_REDIS_MGET: case MSG_REQ_REDIS_DEL: return redis_fragment_argx(r, ncontinuum, frag_msgq, 1); case MSG_REQ_REDIS_MSET: return redis_fragment_argx(r, ncontinuum, frag_msgq, 2); default: return NC_OK; } } rstatus_t redis_reply(struct msg *r) { struct msg *response = r->peer; ASSERT(response != NULL); switch (r->type) { case MSG_REQ_REDIS_PING: return msg_append(response, (uint8_t *)"+PONG\r\n", 7); default: NOT_REACHED(); return NC_ERROR; } } void redis_post_coalesce_mset(struct msg *request) { struct msg *response = request->peer; rstatus_t status; status = msg_append(response, (uint8_t *)"+OK\r\n", 5); if (status != NC_OK) { response->error = 1; /* mark this msg as err */ response->err = errno; } } void redis_post_coalesce_del(struct msg *request) { struct msg *response = request->peer; rstatus_t status; status = msg_prepend_format(response, ":%d\r\n", request->integer); if (status != NC_OK) { response->error = 1; response->err = errno; } } static void redis_post_coalesce_mget(struct msg *request) { struct msg *response = request->peer; struct msg *sub_msg; rstatus_t status; uint32_t i; status = msg_prepend_format(response, "*%d\r\n", request->narg - 1); if (status != NC_OK) { /* * the fragments is still in c_conn->omsg_q, we have to discard all of them, * we just close the conn here */ response->owner->err = 1; return; } for (i = 0; i < array_n(request->keys); i++) { /* for each key */ sub_msg = request->frag_seq[i]->peer; /* get it's peer response */ if (sub_msg == NULL) { response->owner->err = 1; return; } status = redis_copy_bulk(response, sub_msg); if (status != NC_OK) { response->owner->err = 1; return; } } } /* * Post-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'mget' or 'del' and all the * responses to the fragmented request vector has been received and * the fragmented request is consider to be done */ void redis_post_coalesce(struct msg *r) { struct msg *pr = r->peer; /* peer response */ ASSERT(!pr->request); ASSERT(r->request && (r->frag_owner == r)); if (r->error || r->ferror) { /* do nothing, if msg is in error */ return; } switch (r->type) { case MSG_REQ_REDIS_MGET: return redis_post_coalesce_mget(r); case MSG_REQ_REDIS_DEL: return redis_post_coalesce_del(r); case MSG_REQ_REDIS_MSET: return redis_post_coalesce_mset(r); default: NOT_REACHED(); } }