[
  {
    "path": ".clang-format.example",
    "content": "# This may be useful with https://clang.llvm.org/docs/ClangFormat.html#script-for-patch-reformatting\n# but some of clang's reformattings may conflict with notes/c-style-guide.txt, which takes precedence.\n#\n# This is deliberately named .clang-format.example instead of .clang-format to avoid editors \n# unexpectedly using this to reformatting entire files because of these shortcomings.\n# It can be copied into .clang-format to use this for local development selectively.\n#\n# Note: this clang format file has many shortcomings.\n# Attempting to apply this automatically to everything may make code less readable.\n# However, this may be useful for spot checking new code,\n# or if you're not certain of indentation style or general spacing/wrapping rules\n#\n# Known shortcomings:\n# - Some places exceed the 80 column limit deliberately for readability, e.g. help strings or error messages or function prototypes. (BreakStringLiterals helps preserve some of those)\n# - Some places deliberately put blocks on a single line when there are a lot of similar blocks.\n#   AllowShortBlocksOnASingleLine is not useful.\n# - Some places deliberately put blocks on a single line when there are a lot of similar blocks.\n# - No good way to eliminate space before and after PRIu64 and other macros for adjacent string literal concatenation\n# - clang-format is not aware of macros, some of which have different styles from functions.\n# - Function declarations are not typically aligned\n# - Some variable declarations are aligned and others aren't on a case by case basis\n# - The choice of function argument grouping should depends on which function arguments are semantically related,\n#   not just on fitting within 80 columns.\n\nAllowShortBlocksOnASingleLine: true\nBasedOnStyle: LLVM\nAlwaysBreakAfterDefinitionReturnType: All\nUseTab: Never\nIndentWidth: 4\nTabWidth: 4\nColumnLimit: 80\nBreakBeforeBraces: Linux\nSortIncludes: false\n\nBreakStringLiterals: false\n# BitFieldColonSpacing is too new to work in clang-format 11\n# https://releases.llvm.org/11.0.0/tools/clang/docs/ClangFormatStyleOptions.html\n# Latest: https://clang.llvm.org/docs/ClangFormatStyleOptions.html\n#\n# BitFieldColonSpacing: None\n#\n# XXX no way to treat the `*` indicating a value is a pointer as part of the aligned name for the declaration\n# XXX function declarations are not typically aligned\nAlignConsecutiveDeclarations: true\nAlignConsecutiveMacros: true\n"
  },
  {
    "path": ".dockerignore",
    "content": "Dockerfile*\n**/Dockerfile*\n*.gz\nsrc/nutredis\nsrc/nutcracker\ntests/_binaries/*\n\n### Entries copied from .gitignore\n# pyc\n*.pyc\n\n# Compiled Object files\n*.lo\n*.o\n\n# Compiled Dynamic libraries\n*.so\n\n# Compiled Static libraries\n*.la\n*.a\n\n# Compiled misc\n*.dep\n*.gcda\n*.gcno\n*.gcov\n\n# Packages\n*.tar.gz\n*.tar.bz2\n\n# Logs\n*.log\n\n# Temporary\n*.swp\n*.~\n*.project\n*.cproject\n\n# Core and executable\ncore*\nnutcracker\n\n# extracted yaml\n!/contrib/yaml-0.2.5.tar.gz\n\n# Autotools\n.deps\n.libs\n\n/aclocal.m4\n/autom4te.cache\n/stamp-h1\n/autoscan.log\n/libtool\n"
  },
  {
    "path": ".editorconfig",
    "content": "# https://editorconfig.org/\n\nroot = true\n\n[*]\ntrim_trailing_whitespace = true\ninsert_final_newline     = true\nend_of_line              = lf\ncharset                  = utf-8\n\n# See twemproxy/notes/c-styleguide.txt\n[*.c,*.h]\ntab_width                = 4\nindent_size              = 4\nindent_style             = space\n# indent_brace_style depends on function vs conditional\nmax_line_length          = 80\nspaces_around_brackets   = none\nspaces_around_operators  = true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "One line summary of the issue here.\n\n### Expected behavior\n\nAs concisely as possible, describe the expected behavior.\n\n### Actual behavior\n\nAs concisely as possible, describe the observed behavior.\n\n### Steps to reproduce the behavior\n\nPlease list all relevant steps to reproduce the observed behavior."
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "Problem\n\nExplain the context and why you're making that change.  What is the\nproblem you're trying to solve? In some cases there is not a problem\nand this can be thought of being the motivation for your change.\n\nSolution\n\nDescribe the modifications you've done.\n\nResult\n\nWhat will change as a result of your pull request? Note that sometimes\nthis section is unnecessary because it is self-explanatory based on\nthe solution.\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "# This is a basic workflow to help you get started with Actions\n\nname: CI\n\n# Controls when the action will run.\non:\n  # Triggers the workflow on push or pull request events but only for the master branch\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job called \"build\"\n  build:\n    # The type of runner that the job will run on\n    runs-on: ubuntu-latest\n\n    # See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-environment-variables-in-a-matrix\n    strategy:\n      # Run to completion even if one redis version has failures\n      fail-fast: false\n      matrix:\n       include:\n         - REDIS_VER: 3.0.7\n         - REDIS_VER: 3.2.13\n         - REDIS_VER: 4.0.14\n         - REDIS_VER: 5.0.12\n         - REDIS_VER: 6.2.4\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - uses: actions/checkout@v2\n\n      # Runs a single command using the runners shell\n      - name: Build and test in docker\n        run: bash ./test_in_docker.sh ${{ matrix.REDIS_VER }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# pyc\n*.pyc\n\n# Compiled Object files\n*.lo\n*.o\n\n# Compiled Dynamic libraries\n*.so\n\n# Compiled Static libraries\n*.la\n*.a\n\n# Compiled misc\n*.dep\n*.gcda\n*.gcno\n*.gcov\n\n# Packages\n*.tar.gz\n*.tar.bz2\n\n# Logs\n*.log\n\n# Temporary\n*.swp\n*.~\n*.project\n*.cproject\n\n# Core and executable\ncore*\nnutcracker\n\n# extracted yaml\n!/contrib/yaml-0.2.5.tar.gz\n\n# Autotools\n.deps\n.libs\n\n/aclocal.m4\n/autom4te.cache\n/stamp-h1\n/autoscan.log\n/libtool\n\n/config/config.guess\n/config/config.sub\n/config/depcomp\n/config/install-sh\n/config/ltmain.sh\n/config/missing\n/config\n\n/config.h\n/config.h.in\n/config.h.in~\n/config.log\n/config.status\n/configure.scan\n/configure\n\nMakefile\nMakefile.in\n\n# The .clang-format.example file may be copied here for use with -style=file\n.clang-format\n\ntest_all\n*.trs\ntags\n\n"
  },
  {
    "path": "ChangeLog",
    "content": " 2021-13-07  Tyson Andre  <tysonandre775@hotmail.com>\n    * twemproxy: version 0.5.0 release\n\t  Same as 0.5.0-RC1\n\n 2021-06-07  Tyson Andre  <tysonandre775@hotmail.com>\n    * twemproxy: version 0.5.0-RC1 release\n\t  Add 'tcpkeepalive' pool boolean config flag setting\n\t  to enable tcp keepalive (charsyam, manju)\n\t  Support redis bitpos command (clark kang)\n\t  Fix parsing of redis error response for error type with no space,\n\t  add tests (tyson, tom dalton)\n\t  Update integration tests, add C unit test suite for 'make check' (tyson)\n\t  Increase the maximum host length+port+identifier to 273\n\t  in ketama_update (李广博)\n\t  Always initialize file permissions field when listening on a unix domain\n\t  socket (tyson)\n\t  Use number of servers instead of number of points on the continuum when\n\t  sharding requests to backend services to improve sharding performance\n\t  and fix potential invalid memory access when all hosts were ejected\n\t  from a pool. (tyson)\n\t  Optimize performance of deletion of single redis keys (vincentve)\n\t  Don't fragment memcache/redis get commands when they only have a single\n\t  key (improves performance and error handling of single key case) (tyson)\n\t  Don't let requests hang when there is a dns error when processing a\n\t  fragmented request (e.g. multiget with multiple keys) (tyson)\n\t  Allow extra parameters for redis spop (charsyam)\n\t  Update documentation and README (various)\n\t  Fix memory leak bug for redis mset (deep011)\n\t  Support arbitrarily deep nested redis multi-bulk\n\t  responses (nested arrays) (qingping209, tyson)\n\t  Upgrade from libyaml 0.1.4 to 0.2.5 (tyson)\n\t  Fix compiler warnings about wrong conversion specifiers in format\n\t  strings for logging (tyson)\n\t  Log the async backend used and any debug options in the\n\t  '--help'/'--version' output.\n\t  Add support for many more new redis commands and updates to existing\n\t  redis commands (tyson)\n\t  Optimization: Skip hashing and choosing server index when a pool has\n\t  exactly one server (tyson)\n\t  Support memcache 'version' requests by proxying the request to a single\n\t  backend memcache server to fetch the server version. (tyson)\n\t  Make error messages for creating the stats server during startup clearer. (tyson)\n\n 2015-22-06  Manju Rajashekhar  <manj@cs.stanford.edu>\n    * twemproxy: version 0.4.1 release\n      backend server hostnames are resolved lazily\n      redis_auth is only valid for a redis pool\n      getaddrinfo returns non-zero +ve value on error\n      fix-hang-when-command-only (charsyam)\n      fix bug crash when get command without key and whitespace (charsyam)\n      mark server as failed on protocol level transiet failures like -OOM, -LOADING, etc\n      implemented support for parsing fine grained redis error response\n      remove redundant conditional judgement in rbtree deletion (leo ma)\n      fix bug mset has invalid pair (charsyam)\n      temp fix a core on kqueue (idning)\n      support \"touch\" command for memcached (panmiaocai)\n      fix redis parse rsp bug (charsyam)\n      SORT command can take multiple arguments. So it should be part of redis_argn() and not redis_arg0()\n      remove incorrect assert because client could send data after sending a quit request which must be discarded\n      allow file permissions to be set for UNIX domain listening socket (ori liveneh)\n      return error if formatted is greater than mbuf size by using nc_vsnprintf() in msg_prepend_format()\n      fix req_make_reply on msg_get, mark it as response (idning)\n      redis database select upon connect (arne claus)\n      redis_auth (charsyam)\n      allow null key(empty key) (idning)\n      fix core on invalid mset like \"mset a a a\" (idning)\n\n2014-18-10  idning <idning@gmail.com>\n    * twemproxy: version 0.4.0 release\n      mget improve (idning)\n      many new commands supported: LEX, PFADD, PFMERGE, SORT, PING, QUIT, SCAN... (mattrobenolt, areina, idning)\n      handle max open file limit(allenlz)\n      add notice-log and use ms time in log(idning)\n      fix bug in string_compare (andyqzb)\n      fix deadlock in sighandler (idning)\n\n2013-20-12  Manju Rajashekhar  <manj@cs.stanford.edu>\n    * twemproxy: version 0.3.0 release\n      SRANDMEMBER support for the optional count argument (mkhq)\n      Handle case where server responds while the request is still being sent (jdi-tagged)\n      event ports (solaris/smartos) support\n      add timestamp when the server was ejected\n      support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat)\n      kqueue (bsd) support (ferenyx)\n      fix parsing redis response to accept integer reply (charsyam)\n\n2013-23-04  Manju Rajashekhar  <manj@cs.stanford.edu>\n    * twemproxy: version 0.2.4 release\n      redis keys must be less than mbuf_data_size() in length (fifsky)\n      Adds support for DUMP/RESTORE commands in Redis (remotezygote)\n      Use of the weight value in the modula distribution (mezzatto)\n      Add support to unix socket connections to servers (mezzatto)\n      only check for duplicate server name and not 'host:port:weight' when 'name' is configured\n      crc16 hash support added (mezzatto)\n\n2013-31-01  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.2.3 release\n      RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon)\n      EVAL and EVALSHA support (ferenyx)\n      exit 1 if configuration file is invalid (cofyc)\n      return non-zero exit status when nutcracker cannot start for some reason\n      use server names in stats (charsyam)\n      Fix failure to resolve long FQDN name resolve (conmame)\n      add support for hash tags\n\n2012-18-10  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.2.2 release\n      fix the off-by-one error when calculating redis key length\n\n2012-12-10  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.2.1 release\n      don't use buf in conf_add_server\n      allow an optional instance name for consistent hashing (charsyam)\n      add --stats-addr=S option\n      add stats-bind-any -a option (charsyam)\n\n2012-12-03  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.2.0 release\n      add -D or --describe-stats command-line argument to print stats description\n      redis support in twemproxy\n      setup pre/post splitcopy and pre/post coalesce handlers in msg struct\n      memcache pre_splitcopy, post_splitcopy, pre_coalesce and post_coalesce handlers\n      every fragment of a msg vector keeps track of the first/last fragment, number of fragments and fragment owner\n      set up msg parser handler for memcache connections\n      refactor parsing code and create header file nc_proto.h\n      stats_listen should use st->addr as the listening address string\n      delete stats tracking memcache requests and responses; stats module no longer tracks protocol related stats\n\n2012-10-27  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.1.20 release\n      on msg_repair, msg->pos should point to nbuf->pos and not nbuf->last\n      refactor memcache parsing code into proto directory\n      add redis option to configuration file\n      fix macro definition strXcmp error for big endian\n      fix log_hexdump and loga_hexdump\n\n2012-07-31  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.1.19 release\n      close server connection on a stray response (yashh, bmatheny)\n\n2012-06-19  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.1.18 release\n      command line option to set mbuf chunk size\n\n2012-05-09  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.1.17 release\n      use _exit(0) instead of exit(0) when daemonizing\n      use loga instead of log_stderr in nc_stacktrace\n\n2012-02-09  Manju Rajashekhar  <manj@twitter.com>\n    * twemproxy: version 0.1.16 release\n      twemproxy (aka nutcracker) is a fast and lightweight proxy for memcached protocol.\n\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM gcc\n\nCOPY . /usr/src/twemproxy\nWORKDIR /usr/src/twemproxy\nRUN \\\n  autoreconf -h && \\\n  autoreconf -fvi && \\\n  ./configure && \\\n  make && \\\n  make install\n\nENTRYPOINT [ \"nutcracker\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS"
  },
  {
    "path": "Makefile.am",
    "content": "MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.h.in config.h.in~ stamp-h.in\n\nACLOCAL_AMFLAGS = -I m4\n\nSUBDIRS = contrib src\n\ndist_man_MANS = man/nutcracker.8\n\nEXTRA_DIST = README.md NOTICE LICENSE ChangeLog conf scripts notes\n"
  },
  {
    "path": "NOTICE",
    "content": "twemproxy is a fast and lightweight proxy for memcached protocol\nCopyright (C) 2012 Twitter, Inc.\n\nPortions of twemproxy were inspired from nginx: http://nginx.org/\n\nThe implementation of generic array (nc_array.[ch]) and red black tree\n(nc_rbtree.[ch]) also comes from nginx-0.8.55.\n\n/*\n * Copyright (C) 2002-2010 Igor Sysoev\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\nThe generic queue implementation comes from BSD <sys/queue.h>\n\n/*\n * Copyright (c) 1991, 1993\n *   The Regents of the University of California.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. All advertising materials mentioning features or use of this software\n *    must display the following acknowledgement:\n *\tThis product includes software developed by the University of\n *\tCalifornia, Berkeley and its contributors.\n * 4. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\nThe implementation of consistent hashing and individual hash algorithms were\nborrowed from libmemcached.\n\nCopyright (c) 2011, Data Differential (http://datadifferential.com/)\nCopyright (c) 2007-2010, TangentOrg (Brian Aker)\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\n    * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\n    * Neither the name of TangentOrg nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe source also includes libyaml (yaml-0.2.5) in contrib/ directory\n\nCopyright (c) 2006 Kirill Simonov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# twemproxy (nutcracker) [![Build Status](https://github.com/twitter/twemproxy/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/twitter/twemproxy/actions/workflows/main.yml?query=branch%3Amaster)\n\n**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 built primarily to reduce the number of connections to the caching servers on the backend. This, together with protocol pipelining and sharding enables you to horizontally scale your distributed caching architecture.\n\n## Build\n\nTo build twemproxy 0.5.0+ from [distribution tarball](https://github.com/twitter/twemproxy/releases):\n\n    $ ./configure\n    $ make\n    $ sudo make install\n\nTo build twemproxy 0.5.0+ from [distribution tarball](https://github.com/twitter/twemproxy/releases) in _debug mode_:\n\n    $ CFLAGS=\"-ggdb3 -O0\" ./configure --enable-debug=full\n    $ make\n    $ sudo make install\n\nTo build twemproxy from source with _debug logs enabled_ and _assertions enabled_:\n\n    $ git clone git@github.com:twitter/twemproxy.git\n    $ cd twemproxy\n    $ autoreconf -fvi\n    $ ./configure --enable-debug=full\n    $ make\n    $ src/nutcracker -h\n\nA quick checklist:\n\n+ Use newer version of gcc (older version of gcc has problems)\n+ Use CFLAGS=\"-O1\" ./configure && make\n+ Use CFLAGS=\"-O3 -fno-strict-aliasing\" ./configure && make\n+ `autoreconf -fvi && ./configure` needs `automake` and `libtool` to be installed\n\n`make check` will run unit tests.\n\n### Older Releases\n\nDistribution tarballs for older twemproxy releases (<= 0.4.1) can be found on [Google Drive](https://drive.google.com/open?id=0B6pVMMV5F5dfMUdJV25abllhUWM&authuser=0).\nThe build steps are the same (`./configure; make; sudo make install`).\n\n## Features\n\n+ Fast.\n+ Lightweight.\n+ Maintains persistent server connections.\n+ Keeps connection count on the backend caching servers low.\n+ Enables pipelining of requests and responses.\n+ Supports proxying to multiple servers.\n+ Supports multiple server pools simultaneously.\n+ Shard data automatically across multiple servers.\n+ Implements the complete [memcached ascii](notes/memcache.md) and [redis](notes/redis.md) protocol.\n+ Easy configuration of server pools through a YAML file.\n+ Supports multiple hashing modes including consistent hashing and distribution.\n+ Can be configured to disable nodes on failures.\n+ Observability via stats exposed on the stats monitoring port.\n+ Works with Linux, *BSD, OS X and SmartOS (Solaris)\n\n## Help\n\n    Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file]\n                      [-c conf file] [-s stats port] [-a stats addr]\n                      [-i stats interval] [-p pid file] [-m mbuf size]\n\n    Options:\n      -h, --help             : this help\n      -V, --version          : show version and exit\n      -t, --test-conf        : test configuration for syntax errors and exit\n      -d, --daemonize        : run as a daemon\n      -D, --describe-stats   : print stats description and exit\n      -v, --verbose=N        : set logging level (default: 5, min: 0, max: 11)\n      -o, --output=S         : set logging file (default: stderr)\n      -c, --conf-file=S      : set configuration file (default: conf/nutcracker.yml)\n      -s, --stats-port=N     : set stats monitoring port (default: 22222)\n      -a, --stats-addr=S     : set stats monitoring ip (default: 0.0.0.0)\n      -i, --stats-interval=N : set stats aggregation interval in msec (default: 30000 msec)\n      -p, --pid-file=S       : set pid file (default: off)\n      -m, --mbuf-size=N      : set size of mbuf chunk in bytes (default: 16384 bytes)\n\n## Zero Copy\n\nIn twemproxy, 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.\n\nFurthermore, 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 twemproxy can support. A large mbuf size reduces the number of read syscalls made by twemproxy when reading requests or responses. However, with a large mbuf size, every active connection would use up 16K bytes of buffer which might be an issue when twemproxy is handling large number of concurrent connections from clients. When twemproxy 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.\n\n## Configuration\n\nTwemproxy 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 twemproxy manages. The configuration files parses and understands the following keys:\n\n+ **listen**: The listening address and port (name:port or ip:port) or an absolute path to sock file (e.g. /var/run/nutcracker.sock) for this server pool.\n+ **client_connections**: The maximum number of connections allowed from redis clients. Unlimited by default, though OS-imposed limitations will still apply.\n+ **hash**: The name of the hash function. Possible values are:\n  + one_at_a_time\n  + md5\n  + crc16\n  + crc32 (crc32 implementation compatible with [libmemcached](http://libmemcached.org/))\n  + crc32a (correct crc32 implementation as per the spec)\n  + fnv1_64\n  + fnv1a_64 (default)\n  + fnv1_32\n  + fnv1a_32\n  + hsieh\n  + murmur\n  + jenkins\n+ **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.\n+ **distribution**: The key distribution mode for choosing backend servers based on the computed hash value. Possible values are:\n  + ketama (default, recommended. An implementation of https://en.wikipedia.org/wiki/Consistent_hashing)\n  + modula (use hash modulo number of servers to choose the backend)\n  + random (choose a random backend for each key of each request)\n+ **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.\n+ **backlog**: The TCP backlog argument. Defaults to 512.\n+ **tcpkeepalive**: A boolean value that controls if tcp keepalive is enabled for connections to servers. Defaults to false.\n+ **preconnect**: A boolean value that controls if twemproxy should preconnect to all the servers in this pool on process start. Defaults to false.\n+ **redis**: A boolean value that controls if a server pool speaks redis or memcached protocol. Defaults to false.\n+ **redis_auth**: Authenticate to the Redis server on connect.\n+ **redis_db**: The DB number to use on the pool servers. Defaults to 0. Note: Twemproxy will always present itself to clients as DB 0.\n+ **server_connections**: The maximum number of connections that can be opened to each server. By default, we open at most 1 server connection.\n+ **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.\n+ **server_retry_timeout**: The timeout value in msec to wait for before retrying on a temporarily ejected server, when auto_eject_hosts is set to true. Defaults to 30000 msec.\n+ **server_failure_limit**: The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject_hosts is set to true. Defaults to 2.\n+ **servers**: A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool.\n\n\nFor 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 the redis protocol, while pool gamma, delta and omega speak memcached protocol.\n\n    alpha:\n      listen: 127.0.0.1:22121\n      hash: fnv1a_64\n      distribution: ketama\n      auto_eject_hosts: true\n      redis: true\n      server_retry_timeout: 2000\n      server_failure_limit: 1\n      servers:\n       - 127.0.0.1:6379:1\n\n    beta:\n      listen: 127.0.0.1:22122\n      hash: fnv1a_64\n      hash_tag: \"{}\"\n      distribution: ketama\n      auto_eject_hosts: false\n      timeout: 400\n      redis: true\n      servers:\n       - 127.0.0.1:6380:1 server1\n       - 127.0.0.1:6381:1 server2\n       - 127.0.0.1:6382:1 server3\n       - 127.0.0.1:6383:1 server4\n\n    gamma:\n      listen: 127.0.0.1:22123\n      hash: fnv1a_64\n      distribution: ketama\n      timeout: 400\n      backlog: 1024\n      preconnect: true\n      auto_eject_hosts: true\n      server_retry_timeout: 2000\n      server_failure_limit: 3\n      servers:\n       - 127.0.0.1:11212:1\n       - 127.0.0.1:11213:1\n\n    delta:\n      listen: 127.0.0.1:22124\n      hash: fnv1a_64\n      distribution: ketama\n      timeout: 100\n      auto_eject_hosts: true\n      server_retry_timeout: 2000\n      server_failure_limit: 1\n      servers:\n       - 127.0.0.1:11214:1\n       - 127.0.0.1:11215:1\n       - 127.0.0.1:11216:1\n       - 127.0.0.1:11217:1\n       - 127.0.0.1:11218:1\n       - 127.0.0.1:11219:1\n       - 127.0.0.1:11220:1\n       - 127.0.0.1:11221:1\n       - 127.0.0.1:11222:1\n       - 127.0.0.1:11223:1\n\n    omega:\n      listen: /tmp/gamma 0666\n      hash: hsieh\n      distribution: ketama\n      auto_eject_hosts: false\n      servers:\n       - 127.0.0.1:11214:100000\n       - 127.0.0.1:11215:1\n\nFinally, to make writing a syntactically correct configuration file easier, twemproxy provides a command-line argument `-t` or `--test-conf` that can be used to test the YAML configuration file for any syntax error.\n\n## Observability\n\nObservability in twemproxy is through logs and stats.\n\nTwemproxy exposes stats at the granularity of server pool and servers per pool through the stats monitoring port by responding with the raw data over TCP. 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  using the `-D` or `--describe-stats` command-line argument.\n\n    $ nutcracker --describe-stats\n\n    pool stats:\n      client_eof          \"# eof on client connections\"\n      client_err          \"# errors on client connections\"\n      client_connections  \"# active client connections\"\n      server_ejects       \"# times backend server was ejected\"\n      forward_error       \"# times we encountered a forwarding error\"\n      fragments           \"# fragments created from a multi-vector request\"\n\n    server stats:\n      server_eof          \"# eof on server connections\"\n      server_err          \"# errors on server connections\"\n      server_timedout     \"# timeouts on server connections\"\n      server_connections  \"# active server connections\"\n      requests            \"# requests\"\n      request_bytes       \"total request bytes\"\n      responses           \"# responses\"\n      response_bytes      \"total response bytes\"\n      in_queue            \"# requests in incoming queue\"\n      in_queue_bytes      \"current request bytes in incoming queue\"\n      out_queue           \"# requests in outgoing queue\"\n      out_queue_bytes     \"current request bytes in outgoing queue\"\n\nSee [`notes/debug.txt`](notes/debug.txt) for examples of how to read the stats from the stats port.\n\nLogging in twemproxy is only available when twemproxy is built with logging enabled. By default logs are written to stderr. Twemproxy can also be configured to write logs to a specific file through the `-o` or `--output` command-line argument. On a running twemproxy, 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.\n\n## Pipelining\n\nTwemproxy 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.\n\nFor example, if twemproxy 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, twemproxy 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`.\n\nPipelining is the reason why twemproxy ends up doing better in terms of throughput even though it introduces an extra hop between the client and server.\n\n## Deployment\n\nIf you are deploying twemproxy in production, you might consider reading through the [recommendation document](notes/recommendation.md) to understand the parameters you could tune in twemproxy to run it efficiently in the production environment.\n\n## Utils\n+ [collectd-plugin](https://github.com/bewie/collectd-twemproxy)\n+ [munin-plugin](https://github.com/eveiga/contrib/tree/nutcracker/plugins/nutcracker)\n+ [twemproxy-ganglia-module](https://github.com/ganglia/gmond_python_modules/tree/master/twemproxy)\n+ [nagios checks](https://github.com/wanelo/nagios-checks/blob/master/check_twemproxy)\n+ [circonus](https://github.com/wanelo-chef/nad-checks/blob/master/recipes/twemproxy.rb)\n+ [puppet module](https://github.com/wuakitv/puppet-twemproxy)\n+ [nutcracker-web](https://github.com/kontera-technologies/nutcracker-web)\n+ [redis-twemproxy agent](https://github.com/Stono/redis-twemproxy-agent)\n+ [sensu-metrics](https://github.com/sensu-plugins/sensu-plugins-twemproxy/blob/master/bin/metrics-twemproxy.rb)\n+ [redis-mgr](https://github.com/idning/redis-mgr)\n+ [smitty for twemproxy failover](https://github.com/areina/smitty)\n+ [Beholder, a Python agent for twemproxy failover](https://github.com/Serekh/beholder)\n+ [chef cookbook](https://supermarket.getchef.com/cookbooks/twemproxy)\n+ [twemsentinel](https://github.com/yak0/twemsentinel)\n\n## Companies using Twemproxy in Production\n+ [Twitter](https://twitter.com/)\n+ [Wikimedia](https://www.wikimedia.org/)\n+ [Pinterest](http://pinterest.com/)\n+ [Snapchat](http://www.snapchat.com/)\n+ [Flickr](https://www.flickr.com)\n+ [Yahoo!](https://www.yahoo.com)\n+ [Tumblr](https://www.tumblr.com/)\n+ [Vine](http://vine.co/)\n+ [Wayfair](http://www.wayfair.com/)\n+ [Kiip](http://www.kiip.me/)\n+ [Wuaki.tv](https://wuaki.tv/)\n+ [Wanelo](http://wanelo.com/)\n+ [Kontera](http://kontera.com/)\n+ [Bright](http://www.bright.com/)\n+ [56.com](http://www.56.com/)\n+ [Digg](http://digg.com/)\n+ [Gawkermedia](http://advertising.gawker.com/)\n+ [3scale.net](http://3scale.net)\n+ [Ooyala](http://www.ooyala.com)\n+ [Twitch](http://twitch.tv)\n+ [Socrata](http://www.socrata.com/)\n+ [Hootsuite](http://hootsuite.com/)\n+ [Trivago](http://www.trivago.com/)\n+ [Machinezone](http://www.machinezone.com)\n+ [Path](https://path.com)\n+ [AOL](http://engineering.aol.com/)\n+ [Soysuper](https://soysuper.com/)\n+ [Vinted](http://vinted.com/)\n+ [Poshmark](https://poshmark.com/)\n+ [FanDuel](https://www.fanduel.com/)\n+ [Bloomreach](http://bloomreach.com/)\n+ [Hootsuite](https://hootsuite.com)\n+ [Tradesy](https://www.tradesy.com/)\n+ [Uber](http://uber.com) ([details](http://highscalability.com/blog/2015/9/14/how-uber-scales-their-real-time-market-platform.html))\n+ [Greta](https://greta.io/)\n\n## Issues and Support\n\nHave a bug or a question? Please create an issue here on GitHub!\n\nhttps://github.com/twitter/twemproxy/issues\n\n## Committers\n\n* Manju Rajashekhar ([@manju](https://twitter.com/manju))\n* Lin Yang ([@idning](https://github.com/idning))\n* Tyson Andre ([@TysonAndre](https://github.com/TysonAndre))\n\nThank you to all of our [contributors](https://github.com/twitter/twemproxy/graphs/contributors)!\n\n## License\n\nCopyright 2012 Twitter, Inc.\n\nLicensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0\n"
  },
  {
    "path": "ci/Dockerfile",
    "content": "# Dockerfile to create a slower debug build of nutcracker with assertions enabled\n# for continuous integration checks.\n# ARGS: REDIS_VER\n# Also see test_in_docker.sh\nFROM centos:7\n\nENV LAST_MODIFIED_DATE 2021-04-09\n\nRUN yum install -y \\\n        https://repo.ius.io/ius-release-el7.rpm \\\n        https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm\n\n# socat Allow tests to open log files\n# which (used below)\n# python-setuptools for pip\nRUN yum install -y \\\n        tar git gcc make tcl \\\n        autoconf automake libtool wget \\\n        memcached \\\n        socat \\\n        which \\\n        python36u python36u-pip && \\\n    yum clean all\n\n# Install nosetest dependencies\nRUN pip3.6 install nose && \\\n        pip3.6 install 'git+https://github.com/andymccurdy/redis-py.git@3.5.3' && \\\n        pip3.6 install 'git+https://github.com/linsomniac/python-memcached.git@1.58'\n# I can't install redis or redis-server in centos:7 (didn't add package), adding to centos 6\n# Install redis and redis sentinel, needed for unit tests\n# RUN yum install -y redis redis-sentinel\n\nARG REDIS_VER=3.2.11\nRUN wget https://github.com/redis/redis/archive/$REDIS_VER.tar.gz && \\\n\ttar zxvf $REDIS_VER.tar.gz && \\\n\tpushd redis-$REDIS_VER && \\\n\t\tmake install && \\\n\tpopd && \\\n\trm -r redis-*\n\n# This will build twemproxy with compilation flags for running unit tests, integration tests.\n# Annoyingly, this can't add multiple directories at once.\nADD conf /usr/src/twemproxy/conf\nADD contrib /usr/src/twemproxy/contrib\nADD man /usr/src/twemproxy/man\nADD m4 /usr/src/twemproxy/m4\nADD notes /usr/src/twemproxy/notes\nADD scripts /usr/src/twemproxy/scripts\nADD src /usr/src/twemproxy/src\nADD ChangeLog configure.ac Makefile.am LICENSE NOTICE README.md /usr/src/twemproxy/\n\nWORKDIR /usr/src/twemproxy\n\n\nADD ci/build-nutcracker.sh /usr/local/bin/build-nutcracker.sh\nRUN /usr/local/bin/build-nutcracker.sh\n\n# Add the tests after adding source files, which makes it easy to quickly test changes to unit tests.\nADD tests /usr/src/twemproxy/tests\n\n# Not installing redis utilities, since we're not running those tests.\nRUN mkdir tests/_binaries -p\n\nRUN ln -nsf $PWD/src/nutcracker tests/_binaries/nutcracker && \\\n    cp `which redis-server` tests/_binaries/redis-server && \\\n    cp `which redis-server` tests/_binaries/redis-sentinel && \\\n    cp `which memcached` tests/_binaries/memcached && \\\n    cp `which redis-cli` tests/_binaries/redis-cli\n\nWORKDIR /usr/src/twemproxy/tests\n\n# Allow tests to open log files\nRUN chmod -R a+w log/\nRUN cat /etc/passwd\nRUN chown -R daemon:daemon /usr/src/twemproxy\nUSER daemon\n"
  },
  {
    "path": "ci/build-nutcracker.sh",
    "content": "#!/usr/bin/env bash\n\nset -xeu\nfunction cleanup {\n    rm -f *.gz\n}\ntrap cleanup EXIT\ntrap cleanup INT\n\ncleanup\n\nexport CFLAGS=\"-O3 -fno-strict-aliasing -I/usr/lib/x86_64-redhat-linux6E/include -B /usr/lib/x86_64-redhat-linux6E/lib64\"\n# TODO: Figure out how to make this apply only to the contrib/ directory. Maybe override the yaml directory's Makefile.am after extracting it.\n# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119\nCFLAGS+=\" -Werror -Wall -Wno-pointer-sign -Wno-sign-conversion -Wno-missing-braces -Wno-unused-value -Wno-builtin-declaration-mismatch -Wno-maybe-uninitialized\"\nexport LDFLAGS=\"-lc_nonshared\"\ncd /usr/src/twemproxy\nautoreconf -fvi\n./configure --enable-debug=yes --prefix=/usr/src/twemproxy/work/usr\nmake -j5\nmake install\n"
  },
  {
    "path": "conf/nutcracker.leaf.yml",
    "content": "leaf:\n  listen: 127.0.0.1:22121\n  hash: fnv1a_64\n  distribution: ketama\n  auto_eject_hosts: true\n  server_retry_timeout: 2000\n  server_failure_limit: 1\n  servers:\n   - 127.0.0.1:11212:1\n   - 127.0.0.1:11213:1\n"
  },
  {
    "path": "conf/nutcracker.root.yml",
    "content": "root:\n  listen: 127.0.0.1:22120\n  hash: fnv1a_64\n  distribution: ketama\n  preconnect: true\n  auto_eject_hosts: false\n  servers:\n   - 127.0.0.1:22121:1\n"
  },
  {
    "path": "conf/nutcracker.yml",
    "content": "alpha:\n  listen: 127.0.0.1:22121\n  hash: fnv1a_64\n  distribution: ketama\n  auto_eject_hosts: true\n  redis: true\n  server_retry_timeout: 2000\n  server_failure_limit: 1\n  servers:\n   - 127.0.0.1:6379:1\n\nbeta:\n  listen: 127.0.0.1:22122\n  hash: fnv1a_64\n  hash_tag: \"{}\"\n  distribution: ketama\n  auto_eject_hosts: false\n  timeout: 400\n  redis: true\n  servers:\n   - 127.0.0.1:6380:1 server1\n   - 127.0.0.1:6381:1 server2\n   - 127.0.0.1:6382:1 server3\n   - 127.0.0.1:6383:1 server4\n\ngamma:\n  listen: 127.0.0.1:22123\n  hash: fnv1a_64\n  distribution: ketama\n  timeout: 400\n  backlog: 1024\n  preconnect: true\n  auto_eject_hosts: true\n  server_retry_timeout: 2000\n  server_failure_limit: 3\n  servers:\n   - 127.0.0.1:11212:1\n   - 127.0.0.1:11213:1\n\ndelta:\n  listen: 127.0.0.1:22124\n  hash: fnv1a_64\n  distribution: ketama\n  timeout: 100\n  auto_eject_hosts: true\n  server_retry_timeout: 2000\n  server_failure_limit: 1\n  servers:\n   - 127.0.0.1:11214:1\n   - 127.0.0.1:11215:1\n   - 127.0.0.1:11216:1\n   - 127.0.0.1:11217:1\n   - 127.0.0.1:11218:1\n   - 127.0.0.1:11219:1\n   - 127.0.0.1:11220:1\n   - 127.0.0.1:11221:1\n   - 127.0.0.1:11222:1\n   - 127.0.0.1:11223:1\n\nomega:\n  listen: /tmp/gamma\n  hash: hsieh\n  distribution: ketama\n  auto_eject_hosts: false\n  servers:\n   - 127.0.0.1:11214:100000\n   - 127.0.0.1:11215:1\n"
  },
  {
    "path": "configure.ac",
    "content": "# Define the package version numbers and the bug reporting address\nm4_define([NC_MAJOR], 0)\nm4_define([NC_MINOR], 5)\nm4_define([NC_PATCH], 0)\nm4_define([NC_BUGS], [https://github.com/twitter/twemproxy/issues])\n\n# Initialize autoconf\nAC_PREREQ([2.64])\nAC_INIT([nutcracker], [NC_MAJOR.NC_MINOR.NC_PATCH], [NC_BUGS])\nAC_CONFIG_SRCDIR([src/nc.c])\nAC_CONFIG_AUX_DIR([config])\nAC_CONFIG_HEADERS([config.h:config.h.in])\nAC_CONFIG_MACRO_DIR([m4])\n\n# Initialize automake\nAM_INIT_AUTOMAKE([1.9 foreign])\n\n# Define macro variables for the package version numbers\nAC_DEFINE(NC_VERSION_MAJOR, NC_MAJOR, [Define the major version number])\nAC_DEFINE(NC_VERSION_MINOR, NC_MINOR, [Define the minor version number])\nAC_DEFINE(NC_VERSION_PATCH, NC_PATCH, [Define the patch version number])\nAC_DEFINE(NC_VERSION_STRING, \"NC_MAJOR.NC_MINOR.NC_PATCH\", [Define the version string])\n\n# Checks for language\nAC_LANG([C])\n\n# Checks for programs\nAC_PROG_AWK\nAC_PROG_CC\nAC_PROG_CPP\nAC_PROG_CXX\nAC_PROG_INSTALL\nAC_PROG_LN_S\nAC_PROG_MAKE_SET\nAC_PROG_RANLIB\nAC_PROG_LIBTOOL\n\n# Checks for typedefs, structures, and compiler characteristics\nAC_C_INLINE\nAC_TYPE_INT8_T\nAC_TYPE_INT16_T\nAC_TYPE_INT32_T\nAC_TYPE_INT64_T\nAC_TYPE_INTMAX_T\nAC_TYPE_INTPTR_T\nAC_TYPE_UINT8_T\nAC_TYPE_UINT16_T\nAC_TYPE_UINT32_T\nAC_TYPE_UINT64_T\nAC_TYPE_UINTMAX_T\nAC_TYPE_UINTPTR_T\nAC_TYPE_OFF_T\nAC_TYPE_PID_T\nAC_TYPE_SIZE_T\nAC_TYPE_SSIZE_T\n\nAC_C_BIGENDIAN(\n  [],\n  [AC_DEFINE(HAVE_LITTLE_ENDIAN, 1, [Define to 1 if machine is little endian])],\n  [AC_MSG_ERROR([endianess of this machine is unknown])],\n  [AC_MSG_ERROR([universial endianess not supported])]\n)\n\n# Checks for header files\nAC_HEADER_STDBOOL\nAC_CHECK_HEADERS([fcntl.h float.h limits.h stddef.h stdlib.h string.h unistd.h])\nAC_CHECK_HEADERS([inttypes.h stdint.h])\nAC_CHECK_HEADERS([sys/ioctl.h sys/time.h sys/uio.h])\nAC_CHECK_HEADERS([sys/socket.h sys/un.h netinet/in.h arpa/inet.h netdb.h])\nAC_CHECK_HEADERS([execinfo.h],\n  [AC_DEFINE(HAVE_BACKTRACE, [1], [Define to 1 if backtrace is supported])], [])\nAC_CHECK_HEADERS([sys/epoll.h], [], [])\nAC_CHECK_HEADERS([sys/event.h], [], [])\n\n# Checks for libraries\nAC_CHECK_LIB([m], [pow])\nAC_CHECK_LIB([pthread], [pthread_create])\n\n# Checks for library functions\nAC_FUNC_FORK\nAC_FUNC_MALLOC\nAC_FUNC_REALLOC\nAC_CHECK_FUNCS([dup2 gethostname gettimeofday strerror])\nAC_CHECK_FUNCS([socket])\nAC_CHECK_FUNCS([memchr memmove memset])\nAC_CHECK_FUNCS([strchr strndup strtoul])\nAC_CHECK_FUNCS([strdup])\n\nAC_CACHE_CHECK([if epoll works], [ac_cv_epoll_works],\n  AC_TRY_RUN([\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/epoll.h>\nint\nmain(int argc, char **argv)\n{\n    int fd;\n\n    fd = epoll_create(256);\n    if (fd < 0) {\n        perror(\"epoll_create:\");\n        exit(1);\n    }\n    exit(0);\n}\n  ], [ac_cv_epoll_works=yes], [ac_cv_epoll_works=no]))\nAS_IF([test \"x$ac_cv_epoll_works\" = \"xyes\"],\n  [AC_DEFINE([HAVE_EPOLL], [1], [Define to 1 if epoll is supported])], [])\n\nAC_CACHE_CHECK([if kqueue works], [ac_cv_kqueue_works],\n  AC_TRY_RUN([\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/event.h>\n#include <sys/time.h>\nint\nmain(int argc, char **argv)\n{\n    int fd;\n\n    fd = kqueue();\n    if (fd < 0) {\n        perror(\"kqueue:\");\n        exit(1);\n    }\n    exit(0);\n}\n  ], [ac_cv_kqueue_works=yes], [ac_cv_kqueue_works=no]))\nAS_IF([test \"x$ac_cv_kqueue_works\" = \"xyes\"],\n  [AC_DEFINE([HAVE_KQUEUE], [1], [Define to 1 if kqueue is supported])], [])\n\nAC_CACHE_CHECK([if event ports works], [ac_cv_evports_works],\n  AC_TRY_RUN([\n#include <stdio.h>\n#include <stdlib.h>\n#include <port.h>\nint\nmain(int argc, char **argv)\n{\n    int fd;\n\n    fd = port_create();\n    if (fd < 0) {\n        perror(\"port_create:\");\n        exit(1);\n    }\n    exit(0);\n}\n  ], [ac_cv_evports_works=yes], [ac_cv_evports_works=no]))\nAS_IF([test \"x$ac_cv_evports_works\" = \"xyes\"],\n  [AC_DEFINE([HAVE_EVENT_PORTS], [1], [Define to 1 if event ports is supported])], [])\n\nAS_IF([test \"x$ac_cv_epoll_works\" = \"xno\" &&\n       test \"x$ac_cv_kqueue_works\" = \"xno\" &&\n       test \"x$ac_cv_evports_works\" = \"xno\"],\n  [AC_MSG_ERROR([either epoll or kqueue or event ports support is required])], [])\n\nAM_CONDITIONAL([OS_LINUX], [test \"x$ac_cv_epoll_works\" = \"xyes\"])\nAM_CONDITIONAL([OS_BSD], [test \"x$ac_cv_kqueue_works\" = \"xyes\"])\nAM_CONDITIONAL([OS_SOLARIS], [test \"x$ac_cv_evports_works\" = \"xyes\"])\nAM_CONDITIONAL([OS_FREEBSD], [test \"$(uname -v | cut -c 1-10)\" == \"FreeBSD 10\"])\n\n# Package options\nAC_MSG_CHECKING([whether to enable debug logs and asserts])\nAC_ARG_ENABLE([debug],\n  [AS_HELP_STRING(\n    [--enable-debug=@<:@full|yes|log|no@:>@],\n    [enable debug logs and asserts @<:@default=no@:>@])\n  ],\n  [],\n  [enable_debug=no])\nAS_CASE([x$enable_debug],\n  [xfull], [AC_DEFINE([HAVE_ASSERT_PANIC], [1],\n                      [Define to 1 if panic on an assert is enabled])\n            AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled])\n           ],\n  [xyes], [AC_DEFINE([HAVE_ASSERT_LOG], [1],\n                     [Define to 1 if log on an assert is enabled])\n           AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled])\n          ],\n  [xlog], [AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled])],\n  [xno], [],\n  [AC_MSG_FAILURE([invalid value ${enable_debug} for --enable-debug])])\nAC_MSG_RESULT($enable_debug)\n\nAC_MSG_CHECKING([whether to disable stats])\nAC_ARG_ENABLE([stats],\n  [AS_HELP_STRING(\n    [--disable-stats],\n    [disable stats])\n  ],\n  [disable_stats=yes],\n  [disable_stats=no])\nAS_IF([test \"x$disable_stats\" = xyes],\n  [],\n  [AC_DEFINE([HAVE_STATS], [1], [Define to 1 if stats is not disabled])])\nAC_MSG_RESULT($disable_stats)\n\n# Untar the yaml-0.2.5 in contrib/ before config.status is rerun\nAC_CONFIG_COMMANDS_PRE([tar xvfz contrib/yaml-0.2.5.tar.gz -C contrib])\n\n# Call yaml-0.2.5 ./configure recursively\nAC_CONFIG_SUBDIRS([contrib/yaml-0.2.5])\n\n# Define Makefiles\nAC_CONFIG_FILES([Makefile\n                 contrib/Makefile\n                 src/Makefile\n                 src/hashkit/Makefile\n                 src/proto/Makefile\n                 src/event/Makefile])\n\n# Generate the \"configure\" script\nAC_OUTPUT\n"
  },
  {
    "path": "contrib/Makefile.am",
    "content": "SUBDIRS = yaml-0.2.5\n\nEXTRA_DIST = yaml-0.2.5.tar.gz\n"
  },
  {
    "path": "contrib/yaml-0.2.5/.gitignore",
    "content": "# Ignore everything\n*\n\n# Except me\n!.gitignore\n"
  },
  {
    "path": "m4/.gitignore",
    "content": "# Ignore everything\n*\n\n# Except me\n!.gitignore\n"
  },
  {
    "path": "man/nutcracker.8",
    "content": ".TH NUTCRACKER 8 \"June 13, 2013\"\n.SH NAME\nnutcracker \\- Fast, light-weight proxy for memcached and Redis\n.SH SYNOPSIS\n.B nutcracker\n.RI [ options ]\n.SH DESCRIPTION\n\\fBnutcracker\\fP, also known as \\fBtwemproxy\\fP (pronounced \"two-em-proxy\"), is\na fast and lightweight proxy for the memcached and Redis protocols.\n.PP\nIt was primarily built to reduce the connection count on backend caching\nservers, but it has a number of features, such as:\n.IP \\[bu]\nMaintains persistent server connections to backend servers.\n.IP \\[bu]\nEnables pipelining of requests and responses.\n.IP \\[bu]\nSupports multiple server pools simultaneously.\n.IP \\[bu]\nShard data automatically across multiple servers.\n.IP \\[bu]\nSupports multiple hashing modes including consistent hashing and\ndistribution.\n.IP \\[bu]\nHigh-availability by disabling nodes on failures.\n.IP \\[bu]\nObservability through stats exposed on stats monitoring port.\n.SH OPTIONS\n.TP\n.BR \\-h \", \" \\-\\-help\nShow usage information and exit.\n.TP\n.BR \\-V \", \" \\-\\-version\nShow version and exit.\n.TP\n.BR \\-t \", \" \\-\\-test-conf\nTest configuration for syntax errors and exit.\n.TP\n.BR \\-D \", \" \\-\\-describe-stats\nPrint stats description and exit.\n.TP\n.BR \\-v \", \" \\-\\-verbose=\\fIN\\fP\nSet logging level to \\fIN\\fP. (default: 5, min: 0, max: 11)\n.TP\n.BR \\-o \", \" \\-\\-output=\\fIfilename\\fP\nSet logging file to \\fIfilename\\fP.\n.TP\n.BR \\-c \", \" \\-\\-conf-file=\\fIfilename\\fP\nSet configuration file to \\fIfilename\\fP.\n.TP\n.BR \\-s \", \" \\-\\-stats-port=\\fIport\\fP\nSet stats monitoring port to \\fIport\\fP.\n(default: 22222)\n.TP\n.BR \\-a \", \" \\-\\-stats-addr=\\fIaddress\\fP\nSet stats monitoring IP to \\fIaddress\\fP.\n(default: 0.0.0.0)\n.TP\n.BR \\-i \", \" \\-\\-stats-interval=\\fIinterval\\fP\nSet stats aggregation interval in msec to \\fIinterval\\fP.\n(default: 30000 msec)\n.TP\n.BR \\-m \", \" \\-\\-mbuf-size=\\fIsize\\fP\nSet size of mbuf chunk in bytes to \\fIsize\\fP. (default: 16384 bytes)\n.TP\n.BR \\-d \", \" \\-\\-daemonize\nRun as a daemon.\n.TP\n.BR \\-p \", \" \\-\\-pid-file=\\fIfilename\\fP\nSet pid file to \\fIfilename\\fP.\n.SH SEE ALSO\n.BR memcached (8),\n.BR redis-server (1)\n.br\n.SH AUTHOR\nnutcracker was written by Twitter, Inc.\n"
  },
  {
    "path": "notes/c-styleguide.txt",
    "content": "- No literal tabs. Expand tabs to 4 spaces.\n- Indentation is 4 spaces.\n- No more than 3 levels of indentation, otherwise you should think about\n  refactoring your code.\n- Use one statement per line.\n- Make sure that your editor does not leave space at the end of the line.\n- snake_case for variable, function and file names.\n- Use your own judgment when naming variables and functions. Be as Spartan\n  as possible. E.g.: Using name like this_variable_is_a_temporary_counter\n  will usually be frowned upon.\n- Don’t use local variables or parameters that shadow global identifiers.\n  GCC’s ‘-Wshadow’ option can help you to detect this problem.\n\n- Avoid using int, char, short, long. Instead use int8_t uint8_t, int16_t,\n  uint16_t, int32_t, uint32_t, int64_t, uint64_t, which are available in\n  <stdint.h>. However, when interfacing with system calls and libraries\n  you cannot get away from using int and char.\n- Use bool for boolean variables. You have to include <stdbool.h>\n- Avoid using a bool as type for struct member names. Instead use unsigned\n  1-bit bit field. E.g.:\n  struct foo {\n      unsigned is_bar:1;\n  };\n- Always use size_t type when dealing with sizes of objects or memory ranges.\n- Your code should be 64-bit and 32-bit friendly. Bear in mind problems\n  of printing, comparisons, and structure alignment. You have to include\n  <inttypes.h> to get generic format specifier macros for printing.\n\n- 80 column line limit.\n- If you have to wrap a long statement (> 80 column), put the operator at the\n  end of the line and indent the next line at the same column as the arguments\n  in the previous column. E.g.:\n    while (cnt < 20 && this_variable_name_is_too_long &&\n           ep != NULL) {\n        z = a + really + long + statement + that + needs + three + lines +\n            gets + indented + on + the + same + column + as + the +\n            previous + column\n    }\n\n    and:\n\n    int a = function(param_a, param_b, param_c, param_d, param_e, param_f,\n                     param_g, param_h, param_i, param_j, param_k, param_l);\n\n- Always use braces for all conditional blocks (if, switch, for, while, do).\n  This holds good even for single statement conditional blocks. E.g.:\n    if (cond) {\n        stmt;\n    }\n- Placement of braces for non-function statement blocks - put opening brace\n  last on the line and closing brace first. E.g.:\n    if (x is true) {\n        we do y\n    }\n- Placement of brace for functions - put the opening brace at the beginning\n  of the next line and closing brace first. This is useful because several\n  tools look for opening brace in column one to find beginning of C\n  functions. E.g.:\nint\nfunction(int x)\n{\n    body of the function\n}\n\n- Closing brace is empty on a line of its own, except in cases where it is\n  followed by a continuation of the same statement, i.e. a \"while\" in a\n  do-statement or an \"else\" in an if-statement, like this:\n    do {\n        body of do-loop\n    } while (condition);\n\n    and,\n\n    if (x == y) {\n        ..\n    } else if (x > y) {\n        ...\n    } else {\n        ....\n    }\n\n- Column align switch keyword and the corresponding case/default keyword. E.g.:\n    switch (alphabet) {\n    case 'a':\n    case 'b':\n        printf(\"I am a or b\\n\");\n        break;\n\n    default:\n        break;\n    }\n\n- Forever loops are done with for, and not while. E.g.:\n    for (;;) {\n        stmt;\n    }\n\n- Don't use a space after a function name.\n- Do not needlessly surround the return expression with parentheses.\n- Use space after keywords. Exceptions are sizeof, typeof, alignof and\n  __attribute__, which look like functions.\n- Do not add spaces around (inside) parenthesized expressions.\n    s = sizeof( sizeof(*p)) ); /* bad example */\n    s = sizeof(sizeof(*p));    /* good example */\n- Casts should not be followed by space. E.g.:\n  int q = *(int *)&p\n- There is no need to type cast when assigning a void pointer to a non-void\n  pointer, or vice versa.\n- Avoid using goto statements. However there are some exceptions to this rule\n  when a single goto label within a function and one or more goto statements\n  come in handy when a function exits from multiple locations and some common\n  work such as cleanup has to be done. E.g.:\nint\nfun(void)\n{\n    int result = 0;\n    char *buffer;\n\n    buffer = malloc(1024);\n    if (buffer == NULL) {\n        return -1;\n    }\n\n    if (condition1) {\n        while (loop1) {\n            ...\n        }\n        result = 1;\n        goto out;\n    }\n\n    ...\nout:\n    free(buffer);\n    return result;\n}\n- When declaring pointer data, use '*' adjacent to the data name and not\n  adjacent to the type name. E.g.:\n    int\n    function(int *p)\n    {\n        char *p;\n        <body of the function>\n    }\n- Use one space around (on each side of) most binary and ternary operators,\n  such as any of these:\n    =  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :\n  but no space after unary operators:\n    &  *  +  -  ~  !  sizeof typeof alignof  __attribute__  defined\n  no space before the postfix increment & decrement unary operators:\n    ++  --\n  and no space around the '.' and \"->\" structure member operators.\n\n- 0 and NULL; use 0 for integers, 0.0 for doubles, NULL for pointers, and\n  '\\0' for chars.\n- Test pointers against NULL. E.g, use:\n    if (p == NULL)\n\n    not:\n\n    !(p)\n\n- Do not use ! for tests unless it is a boolean. E.g. use:\n    if (*p == '\\0')\n\n    not:\n\n    if (!*p)\n\n- Don't use assignments inside if or while-conditions. E.g, use:\n\n    struct foo *foo;\n    foo = malloc(sizeof(*foo));\n    if (foo == NULL) {\n        return -1\n    }\n\n    not:\n\n    struct foo *foo;\n    if ((foo = malloc(sizeof(*foo))) == NULL) {\n        return -1;\n    }\n\n- Don't ever use typedef for structure types. Typedefs are problematic\n  because they do not properly hide their underlying type; for example you\n  need to know if the typedef is the structure itself or a pointer to the\n  structure. In addition they must be declared exactly once, whereas an\n  incomplete structure type can be mentioned as many times as necessary.\n  Typedefs are difficult to use in stand-alone header files: the header\n  that defines the typedef must be included before the header that uses it,\n  or by the header that uses it (which causes namespace pollution), or\n  there must be a back-door mechanism for obtaining the typedef.\n- The only exception for using a typedef is when you are defining a type\n  for a function pointer or a type for an enum. E.g.:\n\n  typedef void (*foo_handler_t)(int, void *);\n\n  or:\n\n  typedef enum types {\n      TYPE_1,\n      TYPE_2\n  } types_t;\n\n- Use just one variable declaration per line when variables are part of a\n  struct. This leaves you room for a small comment on each item, explaining\n  its use. Declarations should also be aligned. E.g., use:\n\n  struct foo {\n    int      *foo_a;   /* comment for foo_a */\n    int      foo_b;    /* comment for foo_b */\n    unsigned foo_c:1;  /* comment for foo_c */\n  };\n\n  and not:\n\n  struct foo {\n    int *foo_a, foo_b;\n    unsigned foo_c:1;\n  };\n\n- For variable declaration outside a struct, either collect all the\n  declarations of the same type on a single line, or use one variable\n  per line if the variables purpose needs to be commented. E.g.:\n  char *a, *b, c;\n\n  or:\n\n  char *a, *b;\n  char c;       /* comments for c */\n\n- Avoid magic numbers because no-one has a clue (including the author) of\n  what it means after a month.\n\n- Function definitions should start the name of the function in column\n  one. This is useful because it makes searching for function definitions\n  fairly trivial. E.g.:\nstatic char *\nconcat(char *s1, char *s2)\n{\n  body of the function\n}\n\n- Function and variables local to a file should be static.\n- Separate two successive functions with one blank line.\n- Include parameter names with their datatypes in function declaration. E.g.:\nvoid function(int param);\n\n- Functions should be short and sweet, and do just one thing. They should\n  fit on one or two screenfuls of text (80 x 24 screen size), and do one\n  thing and do that well.\n  The maximum length of a function is inversely proportional to the\n  complexity and indentation level of that function. So, if you have a\n  conceptually simple function that is just one long (but simple)\n  case-statement, where you have to do lots of small things for a lot of\n  different cases, it's OK to have a longer function.\n  Another measure of the function is the number of local variables. They\n  shouldn't exceed 5-10, or you're doing something wrong. Re-think the\n  function, and split it into smaller pieces. A human brain can\n  generally easily keep track of about 7 different things, anything more\n  and it gets confused. You know you're brilliant, but maybe you'd like\n  to understand what you did 2 weeks from now.\n- Use const for function parameters passed by reference, if the passed\n  pointer has no side effect.\n\n- C style comments only. Don't use // for single line comments. Instead\n  use /* ... */ style.\n- For multi-line comments use the following style\n    /*\n     * This is the preferred style for multi-line\n     * comments in the Linux kernel source code.\n     * Please use it consistently.\n     *\n     * Description:  A column of asterisks on the left side,\n     * with beginning and ending almost-blank lines.\n     */\n\n- To comment out block of code spanning several lines use preprocessor\n  directive \"#ifdef 0 ... #endif\"\n\n- Please write a brief comment at the start of each source file, with the\n  file name and a line or two about the overall purpose of the file.\n\n- All major functions should have comments describing what they do at the\n  head of the function. Avoid putting comments in the function body unless\n  absolutely needed. If possible, add a comment on what sorts of arguments\n  the function gets, and what the possible values of arguments mean and\n  what they are used for and the significance of return value if there is\n  one. It is not necessary to duplicate in words the meaning of the C\n  argument declarations, if a C type is being used in its customary fashion.\n  If there is anything nonstandard about its use (such as an argument of\n  type char * which is really the address of the second character of a\n  string, not the first), or any possible values that would not work the\n  way one would expect (such as, that strings containing newlines are not\n  guaranteed to work), be sure to say so. E.g.:\n\n/*\n * Try to acquire a physical address lock while a pmap is locked. If we\n * fail to trylock we unlock and lock the pmap directly and cache the\n * locked pa in *locked. The caller should then restart their loop in case\n * the virtual to physical mapping has changed.\n *\n * Returns 0 on success and -1 on failure.\n */\nint\nvm_page_pa_tryrelock(pmap_t pmap, vm_paddr_t pa, vm_paddr_t *locked)\n{\n    ...\n\n- The comment on a function is much clearer if you use the argument names\n  to speak about the argument values. The variable name itself should be\n  lower case, but write it in upper case when you are speaking about the\n  value rather than the variable itself. Thus, “the inode number NODE_NUM”\n  rather than “an inode”.\n\n- Every struct definition should have an accompanying comment that\n  describes what it is for and how it should be used.\n\n- Finally, while comments are absolutely important to keep the code readable,\n  remember that the best code is self-documenting. Giving sensible names to\n  types and variables is much better than using obscure names that you must\n  then explain through comments.\n\n- Recommend using UPPERCASE for macro names. However, sometimes using\n  lowercase for macro names makes sense when macros masquerade as well-known\n  function calls. E.g., it makes sense to write the wrapper for the\n  standard free() function in lowercase to keep the readability\n  consistent:\n\n#define my_free(_p) do {    \\\n    free(_p);               \\\n    (_p) = NULL;            \\\n} while (0)\n\n- Use enums when defining more than one related constants. All enumeration\n  values are in UPPERCASE.\n- Avoid macros as much as possible and use inline functions, enums and const\n  variables wherever you can.\n- For macros encapsulating compound statements, right justify the backslashes\n  and enclose it in do { ... } while (0)\n- For parameterized macros, all the parameters used in the macro body must\n  be surrounded by parentheses. E.g.:\n  #define ADD_1(_x) ((_x) + 1)\n\n- Use sizeof(varname) instead of sizeof(type) whenever possible. E.g.:\n  char *p;\n  p = malloc(sizeof(*p));   /* good example */\n  p = malloc(sizeof(char)); /* bad example */\n\n- All variables should be declared at the beginning of a scope block {..}.\n  It is even preferred to declare all variables at the beginning of the\n  function so that all the local variable declarations is in one place and\n  we can see the comprehensive list in one glance.\n- Global structs should be declared at the top of the file in which they\n  are used, or in separate header files if they are used in multiple\n  source files.\n- Declarations of external functions and functions to appear later in the\n  source file should all go in one place near the beginning of the file,\n  somewhere before the first function definition in the file or else\n  should go in a header file.\n- Use of extern should be considered as evil, if it is used in header files\n  to reference global variables.\n- Don’t put extern declarations inside functions.\n\n- Usually every *.c file should have an associated *.h file. There are some\n  exceptions to this rule, such as unit tests and small *.c files containing\n  just the main() function.\n- Every header file in the source code must have preprocessor conditional\n  to prevent the header file from being scanned multiple times and avoiding\n  mutual dependency cycles. Alternatively you can use #pragma once directive,\n  as it avoids name clashes and increases the compile speed. E.g., for a\n  header file named foo.h, the entire contents of the header file must be\n  between the guard macros as follows:\n\n#ifndef _FOO_H_\n#define _FOO_H_\n...\n#endif /* _FOO_H_ */\n\nOr,\n\n#pragma once\n#ifndef _FOO_H_\n#define _FOO_H_\n...\n#endif /* _FOO_H_ */\n\n- Don't use #include when a forward declaration would suffice.\n- Functions defined in header files should be static inline.\n\n- Don’t make the program ugly just to placate GCC when extra warnings options\n  such as ‘-Wconversion’ or ‘-Wundef’ are used. These options can help in\n  finding bugs, but they can also generate so many false alarms that that\n  it hurts readability to silence them with unnecessary casts, wrappers, and\n  other complications.\n\n- Conditional compilation: when supporting configuration options already\n  known when building your program we prefer using if (... ) over conditional\n  compilation, as in the former case the compiler is able to perform more\n  extensive checking of all possible code paths. E.g., use:\n\n  if (HAS_FOO)\n    ...\n  else\n    ...\n\ninstead of:\n\n  #ifdef HAS_FOO\n    ...\n  #else\n    ...\n  #endif\n\n  A modern compiler such as GCC will generate exactly the same code in both\n  cases and of course, the former method assumes that HAS_FOO is defined as\n  either 0 or 1.\n\n- Finally, rules are rules. Sometimes they are sensible and sometimes not\n  and regardless of your preference, we would like you to follow them.\n  A project is easier to follow if all project contributors follow the style\n  rules so that they can all read and understand everyone's code easily. But\n  remember, like all good rules, they are exceptions where it makes sense not\n  to be too rigid on the grounds of common sense and consistency!\n"
  },
  {
    "path": "notes/debug.txt",
    "content": "- strace\n  strace -o strace.txt -ttT -s 1024 -p `pgrep nutcracker`\n\n- libyaml (yaml-0.2.5)\n\n  - yaml tokens:\n\n  0  YAML_NO_TOKEN,\n  1  YAML_STREAM_START_TOKEN,\n  2  YAML_STREAM_END_TOKEN,\n  3  YAML_VERSION_DIRECTIVE_TOKEN,\n  4  YAML_TAG_DIRECTIVE_TOKEN,\n  5  YAML_DOCUMENT_START_TOKEN,\n  6  YAML_DOCUMENT_END_TOKEN,\n  7  YAML_BLOCK_SEQUENCE_START_TOKEN,\n  8  YAML_BLOCK_MAPPING_START_TOKEN,\n  9  YAML_BLOCK_END_TOKEN,\n  10 YAML_FLOW_SEQUENCE_START_TOKEN,\n  11 YAML_FLOW_SEQUENCE_END_TOKEN,\n  12 YAML_FLOW_MAPPING_START_TOKEN,\n  13 YAML_FLOW_MAPPING_END_TOKEN,\n  14 YAML_BLOCK_ENTRY_TOKEN,\n  15 YAML_FLOW_ENTRY_TOKEN,\n  16 YAML_KEY_TOKEN,\n  17 YAML_VALUE_TOKEN,\n  18 YAML_ALIAS_TOKEN,\n  19 YAML_ANCHOR_TOKEN,\n  20 YAML_TAG_TOKEN,\n  21 YAML_SCALAR_TOKEN\n\n  - yaml events\n\n  0  YAML_NO_EVENT,\n  1  YAML_STREAM_START_EVENT,\n  2  YAML_STREAM_END_EVENT,\n  3  YAML_DOCUMENT_START_EVENT,\n  4  YAML_DOCUMENT_END_EVENT,\n  5  YAML_ALIAS_EVENT,\n  6  YAML_SCALAR_EVENT,\n  7  YAML_SEQUENCE_START_EVENT,\n  8  YAML_SEQUENCE_END_EVENT,\n  9  YAML_MAPPING_START_EVENT,\n  10 YAML_MAPPING_END_EVENT\n\n- sys/queue.h\n\n  queue.h is a generic linked list library adapted from BSD. It has three\n  macro knobs that are useful for debugging:\n\n  - QUEUE_MACRO_SCRUB nullifies links (next and prev pointers) of deleted\n    elements and catches cases where we are attempting to do operations\n    on an element that has already been unlinked.\n  - QUEUE_MACRO_TRACE keeps track of __FILE__ and __LINE__ of last two\n    updates to the list data structure.\n  - QUEUE_MACRO_ASSERT verifies the sanity of list data structure on every\n    operation.\n\n- valgrind\n  valgrind --tool=memcheck --leak-check=yes <program>\n\n- Core dump\n  ulimit -c unlimited\n\n- Generate ENOMEM to test \"Out of Memory\"\n  ulimit -m <size>    # limit maximum memory size\n  ulimit -v <size>    # limit virtual memory\n\n- get nutcracker stats\n  printf \"\" | socat  - TCP:localhost:22222 | tee stats.txt\n  printf \"\" | nc localhost 22222 | python -mjson.tool\n\n- Signalling and Logging\n  SIGTTIN - To up the log level\n  SIGTTOU - To down the log level\n  SIGHUP  - To reopen log file\n\n- Error codes:\n  http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_2.html\n  /usr/include/asm-generic/errno-base.h\n  /usr/include/asm-generic/errno.h\n\n- epoll (linux)\n\n  union epoll_data {\n      void *ptr;\n      int fd;\n      uint32_t u32;\n      uint64_t u64;\n  };\n\n  struct epoll_event {\n      uint32_t          events;  /* epoll events */\n      struct epoll_data data;    /* user data variable */\n  };\n\n  /* events */\n  EPOLLIN       = 0x001,\n  EPOLLPRI      = 0x002,\n  EPOLLOUT      = 0x004,\n  EPOLLERR      = 0x008,\n  EPOLLHUP      = 0x010,\n  EPOLLRDNORM   = 0x040,\n  EPOLLRDBAND   = 0x080,\n  EPOLLWRNORM   = 0x100,\n  EPOLLWRBAND   = 0x200,\n  EPOLLMSG      = 0x400,\n  EPOLLRDHUP    = 0x2000,\n  EPOLLONESHOT  = (1 << 30),\n  EPOLLET       = (1 << 31)\n\n  /* opcodes */\n  EPOLL_CTL_ADD = 1 /* add a file descriptor to the interface */\n  EPOLL_CTL_DEL = 2 /* remove a file descriptor from the interface */\n  EPOLL_CTL_MOD = 3 /* change file descriptor epoll_event structure */\n\n- kqueue (bsd)\n\n  struct kevent {\n      uintptr_t   ident;    /* identifier for this event */\n      int16_t     filter;   /* filter for event */\n      uint16_t    flags;    /* general flags */\n      uint32_t    fflags;   /* filter-specific flags */\n      intptr_t    data;     /* filter-specific data */\n      void        *udata;   /* opaque user data identifier */\n  };\n\n  /* flags / events */\n  EV_ADD        = 0x0001    /* action - add event to kq (implies enable) */\n  EV_DELETE     = 0x0002    /* action - delete event from kq */\n  EV_ENABLE     = 0x0004    /* action - enable event */\n  EV_DISABLE    = 0x0008    /* action - disable event (not reported) */\n  EV_RECEIPT    = 0x0040    /* action - force EV_ERROR on success, data == 0 */\n\n  EV_ONESHOT    = 0x0010    /* flags - only report one occurrence */\n  EV_CLEAR      = 0x0020    /* flags - clear event state after reporting */\n  EV_DISPATCH   = 0x0080    /* flags - disable event after reporting */\n  EV_SYSFLAGS   = 0xF000    /* flags - reserved by system */\n  EV_FLAG0      = 0x1000    /* flags - filter-specific flag */\n  EV_FLAG1      = 0x2000    /* flags - filter-specific flag */\n\n  EV_EOF        = 0x8000    /* returned values - EOF detected */\n  EV_ERROR      = 0x4000    /* returned values - error, data contains errno */\n\n  /* filters */\n  EVFILT_READ       (-1)    /* readable */\n  EVFILT_WRITE      (-2)    /* writable */\n  EVFILT_AIO        (-3)    /* attached to aio requests */\n  EVFILT_VNODE      (-4)    /* attached to vnodes */\n  EVFILT_PROC       (-5)    /* attached to struct proc */\n  EVFILT_SIGNAL     (-6)    /* attached to struct proc */\n  EVFILT_TIMER      (-7)    /* timers */\n  EVFILT_MACHPORT   (-8)    /* mach portsets */\n  EVFILT_FS         (-9)    /* filesystem events */\n  EVFILT_USER       (-10)   /* user events */\n  EVFILT_VM         (-12)   /* virtual memory events */\n\n  EV_CLEAR behaves like EPOLLET because it resets the event after it is\n  returned; without this flag, the event would be repeatedly returned.\n\n- poll (unix)\n\n  POLLIN       0x001    /* there is data to read */\n  POLLPRI      0x002    /* there is urgent data to read */\n  POLLOUT      0x004    /* writing now will not block */\n\n  POLLRDNORM   0x040    /* normal data may be read */\n  POLLRDBAND   0x080    /* priority data may be read */\n  POLLWRNORM   0x100    /* writing now will not block */\n  POLLWRBAND   0x200    /* priority data may be written */\n\n  POLLMSG      0x400\n  POLLREMOVE  0x1000\n  POLLRDHUP   0x2000\n\n  POLLERR\t   0x008    /* error condition */\n  POLLHUP      0x010    /* hung up */\n  POLLNVAL     0x020    /* invalid polling request */\n\n- event ports (solaris)\n\n  typedef struct port_event {\n      int         portev_events;  /* event data is source specific */\n      ushort_t    portev_source;  /* event source */\n      ushort_t    portev_pad;     /* port internal use */\n      uintptr_t   portev_object;  /* source specific object */\n      void        *portev_user;   /* user cookie */\n  } port_event_t;\n\n  /* port sources */\n  PORT_SOURCE_AIO     1\n  PORT_SOURCE_TIMER   2\n  PORT_SOURCE_USER    3\n  PORT_SOURCE_FD      4\n  PORT_SOURCE_ALERT   5\n  PORT_SOURCE_MQ      6\n  PORT_SOURCE_FILE    7\n"
  },
  {
    "path": "notes/memcache.md",
    "content": "## Memcache Command Support\n\n### Request\n\n- Twemproxy implements only the memached ASCII commands\n- Binary commands are currently unsupported\n\n#### Ascii Storage Command\n\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                   |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        set        |    Yes     | set <key> <flags> <expiry> <datalen> [noreply]\\r\\n<data>\\r\\n             |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        add        |    Yes     | add <key> <flags> <expiry> <datalen> [noreply]\\r\\n<data>\\r\\n             |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      replace      |    Yes     | replace <key> <flags> <expiry> <datalen> [noreply]\\r\\n<data>\\r\\n         |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      append       |    Yes     | append <key> <flags> <expiry> <datalen> [noreply]\\r\\n<data>\\r\\n          |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      prepend      |    Yes     | prepend <key> <flags> <expiry> <datalen> [noreply]\\r\\n<data>\\r\\n         |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |       cas         |    Yes     | cas <key> <flags> <expiry> <datalen> <cas> [noreply]\\r\\n<data>\\r\\n       |\n    +-------------------+------------+--------------------------------------------------------------------------+\n\n* Where,\n  * <flags>   - uint32_t : data specific client side flags\n  * <expiry>  - uint32_t : expiration time (in seconds)\n  * <datalen> - uint32_t : size of the data (in bytes)\n  * <data>    - uint8_t[]: data block\n  * <cas>     - uint64_t\n\n#### Ascii Retrieval Command\n\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                   |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        get        |    Yes     | get <key> [<key>]+\\r\\n                                                   |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        gets       |    Yes     | gets <key> [<key>]+\\r\\n                                                  |\n    +-------------------+------------+--------------------------------------------------------------------------+\n\n#### Ascii Delete\n\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                   |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        delete     |    Yes     | delete <key> [noreply]\\r\\n                                               |\n    +-------------------+------------+--------------------------------------------------------------------------+\n\n#### Ascii Arithmetic Command\n\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                   |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        incr       |    Yes     | incr <key> <value> [noreply]\\r\\n                                         |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        decr       |    Yes     | decr <key> <value> [noreply]\\r\\n                                         |\n    +-------------------+------------+--------------------------------------------------------------------------+\n\n* Where,\n  * <value> - uint64_t\n\n#### Ascii Misc Command\n\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                   |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |       touch       |    Yes     | touch <key> <expiry>[noreply]\\r\\n                                        |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        gat        |  Planned   | gat <expiry> <key>+\\r\\n                                                  |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        gats       |  Planned   | gats <expiry> <key>+\\r\\n                                                 |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |        quit       |    Yes     | quit\\r\\n                                                                 |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      flush_all    |    No      | flush_all [<delay>] [noreply]\\r\\n                                        |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      version      |    Yes     | version\\r\\n                                                              |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |      verbosity    |    No      | verbosity <num> [noreply]\\r\\n                                            |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |       stats       |    No      | stats\\r\\n                                                                |\n    +-------------------+------------+--------------------------------------------------------------------------+\n    |       stats       |    No      | stats <args>\\r\\n                                                         |\n    +-------------------+------------+--------------------------------------------------------------------------+\n\n### Response\n\n#### Error Responses\n\n    ERROR\\r\\n\n    CLIENT_ERROR [error]\\r\\n\n    SERVER_ERROR [error]\\r\\n\n\n    Where,\n    - ERROR means client sent a non-existent command name\n    - CLIENT_ERROR means that command sent by the client does not conform to the protocol\n    - SERVER_ERROR means that there was an error on the server side that made processing of the command impossible\n\n#### Storage Command Responses\n\n    STORED\\r\\n\n    NOT_STORED\\r\\n\n    EXISTS\\r\\n\n    NOT_FOUND\\r\\n\n\n    Where,\n    - STORED indicates success.\n    - NOT_STORED indicates the data was not stored because condition for an add or replace wasn't met.\n    - EXISTS indicates that the item you are trying to store with a cas has been modified since you last fetched it.\n    - NOT_FOUND indicates that the item you are trying to store with a cas does not exist.\n\n#### Delete Command Responses\n\n    NOT_FOUND\\r\\n\n    DELETED\\r\\n\n\n#### Retrieval Responses\n\n    END\\r\\n\n    VALUE <key> <flags> <datalen> [<cas>]\\r\\n<data>\\r\\nEND\\r\\n\n    VALUE <key> <flags> <datalen> [<cas>]\\r\\n<data>\\r\\n[VALUE <key> <flags> <datalen> [<cas>]\\r\\n<data>]+\\r\\nEND\\r\\n\n\n#### Arithmetic Responses\n\n    NOT_FOUND\\r\\n\n    <value>\\r\\n\n\n    Where,\n    - <value> - uint64_t : new key value after incr or decr operation\n\n#### Touch Command Responses\n\n    NOT_FOUND\\r\\n\n    TOUCHED\\r\\n\n\n#### Statistics Response\n\n    [STAT <name> <value>\\r\\n]+END\\r\\n\n\n#### Misc Responses\n\n    OK\\r\\n\n    VERSION <version>\\r\\n\n\n### Notes\n\n- set always creates mapping irrespective of whether it is present on not.\n- add, adds only if the mapping is not present\n- replace, only replaces if the mapping is present\n- append and prepend command ignore flags and expiry values\n- noreply instructs the server to not send the reply even if there is an error.\n- decr of 0 is 0, while incr of UINT64_MAX is 0\n- maximum length of the key is 250 characters\n- expiry of 0 means that item never expires, though it could be evicted from the cache\n- non-zero expiry is either unix time (# seconds since 01/01/1970) or,\n  offset in seconds from the current time (< 60 x 60 x 24 x 30 seconds = 30 days)\n- expiry time is with respect to the server (not client)\n- <datalen> can be zero and when it is, the <data> block is empty.\n- Thoughts:\n  - ascii protocol is easier to debug - think using strace or tcpdump to see\n    protocol on the wire, Or using telnet or netcat or socat to build memcache\n    requests and responses\n    https://stackoverflow.com/questions/2525188/are-binary-protocols-dead\n  - nutcracker will support the more efficient meta-text protocol after the protocol\n    is marked as stable and memcached servers using it have had several releases.\n  - https://news.ycombinator.com/item?id=1712788\n"
  },
  {
    "path": "notes/recommendation.md",
    "content": "If you are deploying nutcracker in your production environment, here are a few recommendations that might be worth considering.\n\n## Log Level\n\nBy 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 --verbose=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.\n\nAt 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.\n\n    [Thu Aug  2 00:03:09 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54009'\n    [Thu Aug  2 00:03:09 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11211:1'\n    [Thu Aug  2 00:03:09 2012] nc_core.c:270 req 1 on s 8 timedout\n    [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\n    [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\n    [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\n    [Thu Aug  2 00:03:10 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 35\n    [Thu Aug  2 00:03:10 2012] nc_request.c:334 c 7 is done\n    [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\n    [Thu Aug  2 00:03:11 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54011'\n    [Thu Aug  2 00:03:11 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11212:1'\n    [Thu Aug  2 00:03:12 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 8\n    [Thu Aug  2 00:03:12 2012] nc_request.c:334 c 7 is done\n    [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\n\nTo enable debug logging, you have to compile nutcracker with logging enabled using --enable-debug=log configure option.\n\n## Liveness\n\nFailures 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:\n\n    resilient_pool:\n      auto_eject_hosts: true\n      server_retry_timeout: 30000\n      server_failure_limit: 3\n\nEnabling `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.\n\nNote 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.\n\nTo 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.\n\n## Timeout\n\nIt is always a good idea to configure nutcracker `timeout:` for every server pool, rather than purely relying on client-side timeouts. Eg:\n\n    resilient_pool_with_timeout:\n      auto_eject_hosts: true\n      server_retry_timeout: 30000\n      server_failure_limit: 3\n      timeout: 400\n\nRelying 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.\n\nBy 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` (memcached) or `-ERR Connection timed out\\r\\n` (redis) is sent back to the client.\n\n## Error Response\n\nWhenever a request encounters failure on a server we usually send to the client a response with the general form - `SERVER_ERROR <errno description>\\r\\n` (memcached) or `-ERR <errno description>` (redis).\n\nFor example, when a memcache server is down, this error response is usually:\n\n+ `SERVER_ERROR Connection refused\\r\\n` or,\n+ `SERVER_ERROR Connection reset by peer\\r\\n`\n\nWhen the request timedout, the response is usually:\n\n+ `SERVER_ERROR Connection timed out\\r\\n`\n\nSeeing 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.\n\n## read, writev and mbuf\n\nAll 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.\n\nIf nutcracker is meant to handle a large number of concurrent client connections, you should set the mbuf size to 512 or 1K bytes.\n\n## How to interpret mbuf-size=N argument?\n\nEvery 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.\n\nA 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.\n\nSo, 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.\n\nFurthermore, 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\n\nThis 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\n\n## Maximum Key Length\nThe 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.\n\n## Node Names for Consistent Hashing\n\nThe server cluster in twemproxy can either be specified as list strings in format 'host:port:weight' or 'host:port:weight name'.\n\n    servers:\n     - 127.0.0.1:6379:1\n     - 127.0.0.1:6380:1\n     - 127.0.0.1:6381:1\n     - 127.0.0.1:6382:1\n\nOr,\n\n    servers:\n     - 127.0.0.1:6379:1 server1\n     - 127.0.0.1:6380:1 server2\n     - 127.0.0.1:6381:1 server3\n     - 127.0.0.1:6382:1 server4\n\n\nIn 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.\n\nNote that when using node names for consistent hashing, twemproxy ignores the weight value in the 'host:port:weight name' format string.\n\n## Hash Tags\n\n[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.\n\nFor example, the configuration of server pool _beta_, also 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.\n\n    beta:\n      listen: 127.0.0.1:22122\n      hash: fnv1a_64\n      hash_tag: \"{}\"\n      distribution: ketama\n      auto_eject_hosts: false\n      timeout: 400\n      redis: true\n      servers:\n       - 127.0.0.1:6380:1 server1\n       - 127.0.0.1:6381:1 server2\n       - 127.0.0.1:6382:1 server3\n       - 127.0.0.1:6383:1 server4\n\n## Graphing Cache-pool State\n\nWhen 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:\n\n- **server_eof** which is incremented when server closes the connection normally which should not happen because we use persistent connections.\n- **server_timedout** is incremented when the connection / request to server timedout.\n- **server_err** is incremented for any other kinds of errors.\n\nSo, on a given server, the cumulative number of times a server is ejected can be computed as:\n\n```c\n(server_err + server_timedout + server_eof) / server_failure_limit\n```\n\nA diff of the above value between two successive time intervals would generate a nice timeseries graph for ejected servers.\n\nYou can also graph the timestamp at which any given server was ejected by graphing `server_ejected_at` stat.\n\n## server_connections: > 1\n\nBy 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`.\n\nTo 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.\n"
  },
  {
    "path": "notes/redis.md",
    "content": "## Redis Command Support\n\n### Keys Command\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |        DEL        |    Yes     | DEL key [key …]                                                                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       DUMP        |    Yes     | DUMP key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      EXISTS       |    Yes     | EXISTS key                                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      EXPIRE       |    Yes     | EXPIRE key seconds                                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     EXPIREAT      |    Yes     | EXPIREAT key timestamp                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       KEYS        |    No      | KEYS pattern                                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      MIGRATE      |    No      | MIGRATE host port key destination-db timeout                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       MOVE        |    No      | MOVE key db                                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      OBJECT       |    No      | OBJECT subcommand [arguments [arguments …]]                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PERSIST      |    Yes     | PERSIST key                                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PEXPIRE      |    Yes     | PEXPIRE key milliseconds                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     PEXPIREAT     |    Yes     | PEXPIREAT key milliseconds-timestamp                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PTTL         |    Yes     | PTTL key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     RANDOMKEY     |    No      | RANDOMKEY                                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      RENAME       |    No      | RENAME key newkey                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     RENAMENX      |    No      | RENAMENX key newkey                                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      RESTORE      |    Yes     | RESTORE key ttl serialized-value                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SORT         |    Yes     | SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       TTL         |    Yes     | TTL key                                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      TYPE         |    Yes     | TYPE key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SCAN         |    No      | SCAN cursor [MATCH pattern] [COUNT count]                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n### Strings Command\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      APPEND       |    Yes     | APPEND key value                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     BITCOUNT      |    Yes     | BITCOUNT key [start end]                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     BITFIELD      |    Yes     | BITFIELD key [GET] [SET] [INCRBY] [WRAP|SAT|FAIL]                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       BITOP       |    No      | BITOP operation destkey key [key …]                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      BITPOS       |    Yes     | BITPOS key bit [start] [end]                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       DECR        |    Yes     | DECR key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      DECRBY       |    Yes     | DECRBY key decrement                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |        GET        |    Yes     | GET key                                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GETBIT       |    Yes     | GETBIT key offset                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GETDEL       |    Yes     | GETDEL key                                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       GETEX       |    Yes     | GETEX key [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp|PERSIST]                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     GETRANGE      |    Yes     | GETRANGE key start end                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GETSET       |    Yes     | GETSET key value                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       INCR        |    Yes     | INCR key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      INCRBY       |    Yes     | INCRBY key increment                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    INCRBYFLOAT    |    Yes     | INCRBYFLOAT key increment                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       MGET        |    Yes     | MGET key [key …]                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       MSET        |    Yes     | MSET key value [key value …]                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      MSETNX       |    No      | MSETNX key value [key value …]                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PSETEX       |    Yes     | PSETEX key milliseconds value                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |        SET        |    Yes     | SET key value [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp|KEEPTTL] [NX|XX] [GET]         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SETBIT       |    Yes     | SETBIT key offset value                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SETEX       |    Yes     | SETEX key seconds value                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SETNX       |    Yes     | SETNX key value                                                                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SETRANGE      |    Yes     | SETRANGE key offset value                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      STRALGO      |    No      | STRALGO LCS algo-specific-argument [algo-specific-argument …]                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      STRLEN       |    Yes     | STRLEN key                                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n* MSET support is not Atomic\n\n### Hashes\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       HDEL        |    Yes     | HDEL key field [field ...]                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HEXISTS      |    Yes     | HEXISTS key field                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       HGET        |    Yes     | HGET key field                                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HGETALL      |    Yes     | HGETALL key                                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HINCRBY      |    Yes     | HINCRBY key field increment                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    HINCRBYFLOAT   |    Yes     | HINCRBYFLOAT key field increment                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HKEYS        |    Yes     | HKEYS key                                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HLEN         |    Yes     | HLEN key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HMGET        |    Yes     | HMGET key field [field ...]                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HMSET        |    Yes     | HMSET key field value [field value ...]                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HSET         |    Yes     | HSET key field value                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HSETNX       |    Yes     | HSETNX key field value                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HVALS        |    Yes     | HVALS key                                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      HSCAN        |    Yes     | HSCAN key cursor [MATCH pattern] [COUNT count]                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n### Lists\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      BLMOVE       |    No      | BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       BLPOP       |    No      | BLPOP key [key …] timeout                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       BRPOP       |    No      | BRPOP key [key …] timeout                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    BRPOPLPUSH     |    No      | BRPOPLPUSH source destination timeout                                                                               |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      LINDEX       |    Yes     | LINDEX key index                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      LINSERT      |    Yes     | LINSERT key BEFORE|AFTER pivot element                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LLEN        |    Yes     | LLEN key                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LMOVE       |    Yes     | LMOVE source destination LEFT|RIGHT LEFT|RIGHT                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LPOP        |    Yes     | LPOP key [count]                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LPOS        |    Yes     | LPOS key element [RANK] [COUNT] [MAXLEN]                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LPUSH       |    Yes     | LPUSH key element [element …]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      LPUSHX       |    Yes     | LPUSHX key element [element …]                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      LRANGE       |    Yes     | LRANGE key start stop                                                                                               |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LREM        |    Yes     | LREM key count element                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LSET        |    Yes     | LSET key index element                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       LTRIM       |    Yes     | LTRIM key start stop                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       RPOP        |    Yes     | RPOP key [count]                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     RPOPLPUSH     |    Yes*    | RPOPLPUSH source destination                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       RPUSH       |    Yes     | RPUSH key element [element …]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      RPUSHX       |    Yes     | RPUSHX key element [element …]                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n* RPOPLPUSH support requires that source and destination keys hash to the same server. You can ensure this by using the same [hashtag](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\n\n### Sets\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SADD        |    Yes     | SADD key member [member …]                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SCARD       |    Yes     | SCARD key                                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SDIFF       |    Yes*    | SDIFF key [key …]                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SDIFFSTORE     |    Yes*    | SDIFFSTORE destination key [key …]                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SINTER       |    Yes*    | SINTER key [key …]                                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SINTERSTORE    |    Yes*    | SINTERSTORE destination key [key …]                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SISMEMBER     |    Yes     | SISMEMBER key member                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SMEMBERS      |    Yes     | SMEMBERS key                                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SMISMEMBER     |    Yes     | SMISMEMBER key member [member …]                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SMOVE       |    Yes*    | SMOVE source destination member                                                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SPOP        |    Yes     | SPOP key [count]                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SRANDMEMBER    |    Yes     | SRANDMEMBER key [count]                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SREM        |    Yes     | SREM key member [member …]                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       SSCAN       |    Yes     | SSCAN key cursor [MATCH] [COUNT]                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SUNION       |    Yes*    | SUNION key [key …]                                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SUNIONSTORE    |    Yes*    | SUNIONSTORE destination key [key …]                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n* 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](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.\n\n\n### Sorted Sets\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     BZPOPMAX      |    No      | BZPOPMAX key [key …] timeout                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     BZPOPMIN      |    No      | BZPOPMIN key [key …] timeout                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ZADD        |    Yes     | ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member …]                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ZCARD       |    Yes     | ZCARD key                                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZCOUNT       |    Yes     | ZCOUNT key min max                                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ZDIFF       |    Yes*    | ZDIFF numkeys key [key …] [WITHSCORES]                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    ZDIFFSTORE     |    Yes*    | ZDIFFSTORE destination numkeys key [key …]                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZINCRBY      |    Yes     | ZINCRBY key increment member                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZINTER       |    Yes*    | ZINTER numkeys key [key …] [WEIGHTS] [SUM|MIN|MAX] [WITHSCORES]                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    ZINTERSTORE    |    Yes*    | ZINTERSTORE destination numkeys key [key …] [WEIGHTS] [SUM|MIN|MAX]                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     ZLEXCOUNT     |    Yes     | ZLEXCOUNT key min max                                                                                               |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZMSCORE      |    Yes     | ZMSCORE key member [member …]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZPOPMAX      |    Yes     | ZPOPMAX key [count]                                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZPOPMIN      |    Yes     | ZPOPMIN key [count]                                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    ZRANDMEMBER    |    Yes     | ZRANDMEMBER key [options]                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZRANGE       |    Yes     | ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT] [WITHSCORES]                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    ZRANGEBYLEX    |    Yes     | ZRANGEBYLEX key min max [LIMIT]                                                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |   ZRANGEBYSCORE   |    Yes     | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    ZRANGESTORE    |    Yes     | ZRANGESTORE dst src min max [BYSCORE|BYLEX] [REV] [LIMIT]                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ZRANK       |    Yes     | ZRANK key member                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ZREM        |    Yes     | ZREM key member [member …]                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |  ZREMRANGEBYLEX   |    Yes     | ZREMRANGEBYLEX key min max                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |  ZREMRANGEBYRANK  |    Yes     | ZREMRANGEBYRANK key start stop                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    | ZREMRANGEBYSCORE  |    Yes     | ZREMRANGEBYSCORE key min max                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     ZREVRANGE     |    Yes     | ZREVRANGE key start stop [WITHSCORES]                                                                               |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |  ZREVRANGEBYLEX   |    Yes     | ZREVRANGEBYLEX key max min [LIMIT]                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    | ZREVRANGEBYSCORE  |    Yes     | ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT]                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     ZREVRANK      |    Yes     | ZREVRANK key member                                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ZSCAN       |    Yes     | ZSCAN key cursor [MATCH] [COUNT]                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZSCORE       |    Yes     | ZSCORE key member                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      ZUNION       |    Yes*    | ZUNION numkeys key [key …] [WEIGHTS] [SUM|MIN|MAX] [WITHSCORES]                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    ZUNIONSTORE    |    Yes*    | ZUNIONSTORE destination numkeys key [key …] [WEIGHTS] [SUM|MIN|MAX]                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n* ZINTERSTORE and ZUNIONSTORE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](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.\n\n### HyperLogLog\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       PFADD       |    Yes     | PFADD key element [element …]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PFCOUNT      |    Yes     | PFCOUNT key [key …]                                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PFMERGE      |    Yes*    | PFMERGE destkey sourcekey [sourcekey …]                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n* PFMERGE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](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.\n\n### Geo Command\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GEOADD       |    Yes     | GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member …]                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GEODIST      |    Yes     | GEODIST key member1 member2 [m|km|ft|mi]                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GEOHASH      |    Yes     | GEOHASH key member [member …]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      GEOPOS       |    Yes     | GEOPOS key member [member …]                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     GEORADIUS     |    Yes     | GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [count] [ASC|DESC] [STORE] [STOREDIST]|\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    | GEORADIUSBYMEMBER |    Yes     | GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [count] [ASC|DESC] [STORE] [STOREDIST]|\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     GEOSEARCH     |    Yes     | GEOSEARCH key [FROMMEMBER] [FROMLONLAT] [circle] [box] [ASC|DESC] [count] [WITHCOORD] [WITHDIST] [WITHHASH]         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |  GEOSEARCHSTORE   |    Yes     | GEOSEARCHSTORE destination source [FROMMEMBER] [FROMLONLAT] [circle] [box] [ASC|DESC] [count] [WITHCOORD] [WITHDIST] [WITHHASH] [STOREDIST]|\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n### Pub/Sub\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    PSUBSCRIBE     |    No      | PSUBSCRIBE pattern [pattern …]                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PUBLISH      |    No      | PUBLISH channel message                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      PUBSUB       |    No      | PUBSUB subcommand [argument [argument …]]                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |   PUNSUBSCRIBE    |    No      | PUNSUBSCRIBE [pattern [pattern …]]                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SUBSCRIBE     |    No      | SUBSCRIBE channel [channel …]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    UNSUBSCRIBE    |    No      | UNSUBSCRIBE [channel [channel …]]                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n### Transactions\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      DISCARD      |    No      | DISCARD                                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       EXEC        |    No      | EXEC                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       MULTI       |    No      | MULTI                                                                                                               |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      UNWATCH      |    No      | UNWATCH                                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       WATCH       |    No      | WATCH key [key …]                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n### Scripting\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       EVAL        |    Yes*    | EVAL script numkeys key [key …] arg [arg …]                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      EVALSHA      |    Yes*    | EVALSHA sha1 numkeys key [key …] arg [arg …]                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |   SCRIPT DEBUG    |    No      | SCRIPT DEBUG YES|SYNC|NO                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |   SCRIPT EXISTS   |    No      | SCRIPT EXISTS sha1 [sha1 …]                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |   SCRIPT FLUSH    |    No      | SCRIPT FLUSH [ASYNC|SYNC]                                                                                           |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SCRIPT KILL    |    No      | SCRIPT KILL                                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    SCRIPT LOAD    |    No      | SCRIPT LOAD script                                                                                                  |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n * 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](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\n\n### Connection\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       AUTH        |    No      | AUTH password                                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       ECHO        |    No      | ECHO message                                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       PING        |    Yes     | PING                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |       QUIT        |    Yes     | QUIT                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SELECT       |    No      | SELECT index                                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n### Server\n\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    BGREWRITEAOF   |    No      | BGREWRITEAOF                                                                                                        |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      BGSAVE       |    No      | BGSAVE                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    CLIENT KILL    |    No      | CLIENT KILL ip:port                                                                                                 |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    CLIENT LIST    |    No      | CLIENT LIST                                                                                                         |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    CONFIG GET     |    No      | CONFIG GET parameter                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    CONFIG SET     |    No      | CONFIG SET parameter value                                                                                          |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |  CONFIG RESETSTAT |    No      | CONFIG RESETSTAT                                                                                                    |\n    +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------+\n    |     DBSIZE        |    No      | DBSIZE                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    DEBUG OBJECT   |    No      | DEBUG OBJECT key                                                                                                    |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |    DEBUG SEGFAULT |    No      | DEBUG SEGFAULT                                                                                                      |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     FLUSHALL      |    No      | FLUSHALL                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     FLUSHDB       |    No      | FLUSHDB                                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      INFO         |    No      | INFO                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     LASTSAVE      |    No      | LASTSAVE                                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     MONITOR       |    No      | MONITOR                                                                                                             |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SAVE         |    No      | SAVE                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SHUTDOWN      |    No      | SHUTDOWN [NOSAVE] [SAVE]                                                                                            |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SLAVEOF       |    No      | SLAVEOF host port                                                                                                   |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     SLOWLOG       |    No      | SLOWLOG subcommand [argument]                                                                                       |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      SYNC         |    No      | SYNC                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      TIME         |    No      | TIME                                                                                                                |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     COMMAND       |    Yes     | COMMAND                                                                                                     |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |     LOLWUT        |    Yes     | LOLWUT [ argument ...]                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n\n* COMMAND is forwarded to an arbitrarily chosen redis backend for the benefit of tools such as `redis-cli`.\n  It assumes that all proxied redis servers support the same backend.\n  There is no post-processing; Commands that are not supported by twemproxy are not filtered out.\n* LOLWUT is also forwarded to an arbitrarily chosen redis backend because it does not depend on the state of the database and the presence of the command would not affect production applications.\n  It can be used as a reference for adding future commands with no keys and a variable-length argument list.\n  Other commands such as `TIME` continue to be unsupported. They may vary based on state of the database (e.g. out of sync clocks). Using EVAL with a key known to be on the specific backend server can be done instead.\n\n## Note\n\n- redis commands are not case sensitive\n- only vectored commands 'MGET key [key ...]', 'MSET key value [key value ...]', 'DEL key [key ...]', 'UNLINK key [key ...]', 'EXISTS key [key ...]'  needs to be fragmented\n\n## Performance\n\n### Setup\n\n+ redis-server running on machine A.\n+ nutcracker running on machine A as a local proxy to redis-server.\n+ redis-benchmark running on machine B.\n+ machine A != machine B.\n+ nutcracker built with --enable-debug=no\n+ nutcracker running with mbuf-size of 512 (-m 512)\n+ redis-server built from redis 2.6 branch\n\n### redis-benchmark against redis-server\n\n    $ redis-benchmark -h <machine-A> -q -t set,get,incr,lpush,lpop,sadd,spop,lpush,lrange -c 100 -p 6379\n    SET: 89285.71 requests per second\n    GET: 92592.59 requests per second\n    INCR: 89285.71 requests per second\n    LPUSH: 90090.09 requests per second\n    LPOP: 90090.09 requests per second\n    SADD: 90090.09 requests per second\n    SPOP: 93457.95 requests per second\n    LPUSH (needed to benchmark LRANGE): 89285.71 requests per second\n    LRANGE_100 (first 100 elements): 36496.35 requests per second\n    LRANGE_300 (first 300 elements): 15748.03 requests per second\n    LRANGE_500 (first 450 elements): 11135.86 requests per second\n    LRANGE_600 (first 600 elements): 8650.52 requests per second\n\n### redis-benchmark against nutcracker proxing redis-server\n\n    $ redis-benchmark -h <machine-A> -q -t set,get,incr,lpush,lpop,sadd,spop,lpush,lrange -c 100 -p 22121\n    SET: 85470.09 requests per second\n    GET: 86956.52 requests per second\n    INCR: 85470.09 requests per second\n    LPUSH: 84745.77 requests per second\n    LPOP: 86206.90 requests per second\n    SADD: 84745.77 requests per second\n    SPOP: 86956.52 requests per second\n    LPUSH (needed to benchmark LRANGE): 84745.77 requests per second\n    LRANGE_100 (first 100 elements): 29761.90 requests per second\n    LRANGE_300 (first 300 elements): 12376.24 requests per second\n    LRANGE_500 (first 450 elements): 8605.85 requests per second\n    LRANGE_600 (first 600 elements): 6587.62 requests per second\n\n## redis-auth feature\n\n+ you can enable redis-auth for a pool with 'redis_auth':\n\n        alpha:\n          listen: 127.0.0.1:22121\n          hash: fnv1a_64\n          distribution: ketama\n          redis: true\n          redis_auth: testpass\n\n+ notice:\n    + *MUST* set all redis with a same passwd, and all twemproxy with the same passwd\n    + Length of password should be less than 256\n\n## redis-sentinel feature\n\n+ You can configure sentinel for a pool with 'sentinels' to let twemproxy works with sentinel:\n\n        sigma:\n          listen: 127.0.0.1:22125\n          hash: fnv1a_64\n          distribution: ketama\n          auto_eject_hosts: false\n          redis: true\n          server_retry_timeout: 2000\n          server_failure_limit: 1\n          servers:\n            - 127.0.0.1:6379:1 server1\n            - 127.0.0.1:6380:1 server2\n          sentinels:\n            - 127.0.0.1:26379:1\n            - 127.0.0.1:26380:1\n            - 127.0.0.1:26381:1\n\n+ notice:\n    + You should configure all the sentinels you used. Twemproxy will connect to the alive sentinels when some are down\n    + Weight of sentinel is not used. Twemproxy keeps it because of the server load code reuse\n\nSee [sentinel.md](./sentinel.md) for more details.\n"
  },
  {
    "path": "notes/socket.txt",
    "content": "- int listen(int sockfd, int backlog);\n\n  Linux: The  backlog  argument defines the maximum length to which the\n  queue of pending connections for sockfd may grow.  If a connection\n  request arrives when the queue is full, the client may receive an error\n  with an indication of ECONNREFUSED or, if the underlying protocol\n  supports retransmission, the request may be ignored so that a later\n  reattempt at connection succeeds.\n\n  backlog specifies the queue length for completely established sockets\n  waiting to be accepted, instead of the number of incomplete connection\n  requests. The maximum length of the queue for incomplete sockets can\n  be set using /proc/sys/net/ipv4/tcp_max_syn_backlog.\n\n  If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn,\n  then it is silently truncated to that value; the default value in this\n  file is 128.  In kernels before 2.4.25, this limit was a hard coded value,\n  SOMAXCONN, with the value 128.\n\n  BSD: The backlog argument defines the maximum length the queue of pending\n  connections may grow to.  The real maximum queue length will be 1.5 times\n  more than the value specified in the backlog argument.  A subsequent\n  listen() system call on the listening socket allows the caller to change\n  the maximum queue length using a new backlog argument.  If a connection\n  request arrives with the queue full the client may receive an error with\n  an indication of ECONNREFUSED, or, in the case of TCP, the connection\n  will be silently dropped.\n\n  The listen() system call appeared in 4.2BSD.  The ability to configure\n  the maximum backlog at run-time, and to use a negative backlog to request\n  the maximum allowable value, was introduced in FreeBSD 2.2.\n\n- SO_LINGER (linger) socket option\n\n  This option specifies what should happen when the socket of a type that\n  promises reliable delivery still has untransmitted messages when it is\n  closed\n\n  struct linger {\n      int l_onoff;  /* nonzero to linger on close  */\n      int l_linger; /* time to linger (in secs)  */\n  };\n\n  l_onoff = 0 (default), then l_linger value is ignored and close returns\n  immediately. But if there is any data still remaining in the socket send\n  buffer, the system will try to deliver the data to the peer\n\n  l_onoff = nonzero, then close blocks until data is transmitted or the\n  l_linger timeout period expires\n  a) l_linger = 0, TCP aborts connection, discards any data still remaining\n     in the socket send buffer and sends RST to peer. This avoids the\n     TCP's TIME_WAIT state\n  b) l_linger = nonzero, then kernel will linger when socket is closed. If\n     there is any pending data in the socket send buffer, the kernel waits\n     until all the data is sent and acknowledged by peer TCP, or the\n     linger time expires\n\n  If a socket is set as nonblocking, it will not wait for close to complete\n  even if linger time is nonzero\n\n- TIME_WAIT state\n\n  The end that performs active close i.e. the end that sends the first FIN\n  goes into TIME_WAIT state. After a FIN packet is sent to the peer and\n  after that peers FIN/ACK arrvies and is ACKed, we go into a TIME_WAIT\n  state. The duration that the end point remains in this state is 2 x MSL\n  (maximum segment lifetime). The reason that the duration of the TIME_WAIT\n  state is 2 x MSL is because the maximum amount of time a packet can wander\n  around a network is assumed to be MSL seconds. The factor of 2 is for the\n  round-trip. The recommended value for MSL is 120 seconds, but Berkeley\n  derived implementations normally use 30 seconds instead. This means a\n  TIME_WAIT delay is between 1 and 4 minutes.\n\n  For Linux, the TIME_WAIT state duration is 1 minute (net/tcp.h):\n  #define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT\n                                    * state, about 60 seconds */\n\n  TIME_WAIT state on client, combined with limited number of ephermeral ports\n  available for TCP connections severely limits the rate at which new\n  connections to the server can be created. On Linux, by default ephemeral\n  ports are in the range of 32768 to 61000:\n\n  $ cat /proc/sys/net/ipv4/ip_local_port_range\n  32768 61000\n\n  So with a TIME_WAIT state duration of 1 minute, the maximum sustained rate\n  for any client is ~470 new connections per second\n\n- TCP keepalive\n\n  TCP keepalive packet (TCP packet with no data and the ACK flag turned on)\n  is used to assert that connection is still up and running. This is useful\n  because if the remote peer goes away without closing their connection, the\n  keepalive probe will detect this and notice that the connection is broken\n  even if there is no traffic on it.\n\n  Imagine, the following scenario: You have a valid TCP connection established\n  between two endpoints A and B. B terminates abnormally (think kernel panic\n  or unplugging of network cable) without sending anything over the network\n  to notify A that connection is broken. A, from its side, is ready to\n  receive data, and has no idea that B has gone away. Now B comes back up\n  again, and while A knows about a connection with B and still thinks that it\n  active, B has no such idea. A tries to send data to B over a dead\n  connection, and B replies with an RST packet, causing A to finally close\n  the connection. So, without a keepalive probe A would never close the\n  connection if it never sent data over it.\n\n- There are four socket functions that pass a socket address structure from\n  the process to the kernel - bind, connect, sendmsg and sendto. These\n  function are also responsible for passing the length of the sockaddr that\n  they are passing (socklen_t).\n  There are five socket functions that pass a socket from the kernel to the\n  process - accept, recvfrom, recvmsg, getpeername, getsockname. The kernel\n  is also responsible for returning the length of the sockaddr struct that\n  it returns back to the userspace\n\n  Different sockaddr structs:\n  1. sockaddr_in\n  2. sockaddr_in6\n  3. sockaddr_un\n\n  Special types of in_addr_t\n  /* Address to accept any incoming messages */\n  #define INADDR_ANY ((in_addr_t) 0x00000000)\n\n  /* Address to send to all hosts */\n  #define INADDR_BROADCAST ((in_addr_t) 0xffffffff)\n\n  /* Address indicating an error return */\n  #define INADDR_NONE ((in_addr_t) 0xffffffff)\n\n"
  },
  {
    "path": "scripts/benchmark-mget.py",
    "content": "#!/usr/bin/env python\n#coding: utf-8\n#file   : test_mget.py\n#author : ning\n#date   : 2014-04-01 13:15:48\n\nimport os\nimport re\nimport commands\n\nports = [\n    4001,  # before improve\n    4000,  # after improve\n    2000   # redis\n]\n\ndef system(cmd):\n    return commands.getoutput(cmd)\n\ndef extra(regex, text):\n    match = re.search(regex, text, re.DOTALL)\n    if match:\n        return match.group(1)\n\ndef testit():\n    for mget_size in [10, 100, 1000, 10000]:\n        for port in ports:\n            cnt = 100*1000 / mget_size\n            clients = 50\n            if mget_size == 10000:\n                clients = 2\n            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)\n            #print cmd\n            rst = system(cmd)\n\n            #100.00% <= 2 milliseconds\n            #28089.89 requests per second\n            rtime = extra('100.00% <= (\\d+) milliseconds', rst)\n            qps = extra('([\\.\\d]+) requests per second', rst)\n\n            print 'mget_size=%d on %d: pqs: %s, rtime: %s' % (mget_size, port, qps, rtime)\n\ntestit()\n"
  },
  {
    "path": "scripts/extract_redis_commands_argcounts.php",
    "content": "#!/usr/bin/env php\n<?php\n/**\n * @author Tyson Andre\n *\n * Heuristics to extract commands from redis-doc and determine what group of commands\n * they'd fall under for twemproxy's request parsing logic.\n */\n\nif (count($argv) !== 2) {\n    echo \"Usage: ${argv[0]} commands.json\\n\";\n    echo \"commands.json can be downloaded from https://github.com/redis/redis-doc\\n\";\n    exit(1);\n}\n$path = $argv[1];\n$contents = file_get_contents($path);\nif (!is_string($contents)) {\n    echo \"Failed to read $path\\n\";\n    exit(1);\n}\n$commands = json_decode($contents, true);\nuasort($commands, fn($a, $b) => version_compare($b['since'], $a['since']));\n\nconst INFINITE_ARGS = 100000;\n\nfunction categorize_arg(array $arg, string $commandName): array {\n    $min = 1;\n    $max = 1;\n    if ($arg['multiple']) {\n        $min = 0;\n        $max = INFINITE_ARGS;\n    }\n    if ($arg['optional']) {\n        $min = 0;\n    }\n    if ($arg['type'] === 'key') {\n        return ['min_key' => $min, 'max_key' => $max];\n    }\n    return ['min_arg' => $min, 'max_arg' => $max];\n}\n\nfunction categorize(array $command, string $commandName): string {\n    $minKeyCount = 0;\n    $maxKeyCount = 0;\n    $minArgCount = 0;\n    $maxArgCount = 0;\n    $arguments = $command['arguments'] ?? [];\n    foreach ($arguments as $arg) {\n        $data = categorize_arg($arg, $commandName);\n        $minKeyCount += ($data['min_key'] ?? 0);\n        $maxKeyCount += ($data['max_key'] ?? 0);\n        $minArgCount += ($data['min_arg'] ?? 0);\n        $maxArgCount += ($data['max_arg'] ?? 0);\n    }\n    if (in_array($commandName, ['DEL', 'MGET', 'MSET', 'TOUCH', 'UNLINK'])) {\n        return \"keyn\";\n    }\n    if ($maxKeyCount > $minKeyCount || $maxArgCount > $minArgCount) {\n        // return \"key${minKeyCount}_argx\";\n        return \"key1_argx\";\n    }\n    // Assume that\n    // min=max for arg and key\n    if ($minArgCount > 0 && $minKeyCount >= 2) {\n        return \"key1_arg\" . ($minArgCount + $maxKeyCount - 1);\n    }\n    return \"key${minKeyCount}_arg\" . $minArgCount;\n}\n\nconst KEY1 = [\n    'PERSIST',\n    'PTTL',\n    'TTL',\n    'TYPE',\n    'DUMP',\n\n    'DECR',\n    'GET',\n    'GETDEL',\n    'INCR',\n    'STRLEN',\n\n    'HGETALL',\n    'HKEYS',\n    'HLEN',\n    'HVALS',\n\n    'LLEN',\n\n    'SCARD',\n    'SMEMBERS',\n\n    'ZCARD',\n    // 'AUTH',\n];\n\nconst KEY1_ARG1 = [\n    'EXPIRE',\n    'EXPIREAT',\n    'PEXPIRE',\n    'PEXPIREAT',\n    'MOVE',\n\n    'APPEND',\n    'DECRBY',\n    'GETBIT',\n    'GETSET',\n    'INCRBY',\n    'INCRBYFLOAT',\n    'SETNX',\n\n    'HEXISTS',\n    'HGET',\n    'HSTRLEN',\n\n    'LINDEX',\n    'RPOPLPUSH',\n\n    'SISMEMBER',\n\n    'ZRANK',\n    'ZREVRANK',\n    'ZSCORE',\n];\n\nconst KEY1_ARG2 = [\n    'GETRANGE',\n    'PSETEX',\n    'SETBIT',\n    'SETEX',\n    'SETRANGE',\n\n    'HINCRBY',\n    'HINCRBYFLOAT',\n    'HSETNX',\n\n    'LRANGE',\n    'LREM',\n    'LSET',\n    'LTRIM',\n\n    'SMOVE',\n\n    'ZCOUNT',\n    'ZLEXCOUNT',\n    'ZINCRBY',\n    'ZREMRANGEBYLEX',\n    'ZREMRANGEBYRANK',\n    'ZREMRANGEBYSCORE',\n];\n\nconst KEY1_ARG3 = [\n    'LINSERT',\n    'LMOVE',\n];\n\nconst KEY1_ARGN = [\n    'SORT',\n\n    'BITCOUNT',\n    'BITPOS',\n    'BITFIELD',\n    'BITOP',\n\n    'EXISTS',\n    'GETEX',\n    'SET',\n\n    'HDEL',\n    'HMGET',\n    'HMSET',\n    'HSCAN',\n    'HSET',\n    'HRANDFIELD',\n\n    'LPUSH',\n    'LPUSHX',\n    'RPUSH',\n    'RPUSHX',\n    'LPOP',\n    'RPOP',\n    'LPOS',\n\n    'SADD',\n    'SDIFF',\n    'SDIFFSTORE',\n    'SINTER',\n    'SINTERSTORE',\n    'SREM',\n    'SUNION',\n    'SUNIONSTORE',\n    'SRANDMEMBER',\n    'SSCAN',\n    'SPOP',\n    'SMISMEMBER',\n\n    'PFADD',\n    'PFMERGE',\n    'PFCOUNT',\n\n    'ZADD',\n    'ZDIFF',\n    'ZDIFFSTORE',\n    'ZINTER',\n    'ZINTERSTORE',\n    'ZMSCORE',\n    'ZPOPMAX',\n    'ZPOPMIN',\n    'ZRANDMEMBER',\n    'ZRANGE',\n    'ZRANGEBYLEX',\n    'ZRANGEBYSCORE',\n    'ZRANGESTORE',\n    'ZREM',\n    'ZREVRANGE',\n    'ZREVRANGEBYLEX',\n    'ZREVRANGEBYSCORE',\n    'ZSCAN',\n    'ZUNION',\n    'ZUNIONSTORE',\n\n    'GEODIST',\n    'GEOPOS',\n    'GEOHASH',\n    'GEOADD',\n    'GEOSEARCH',\n\n    'RESTORE',\n];\n\nconst EXPECTED_MAPS = [\n    'key1_arg0' => KEY1,\n    'key1_arg1' => KEY1_ARG1,\n    'key1_arg2' => KEY1_ARG2,\n    'key1_arg3' => KEY1_ARG3,\n    'key1_argx' => KEY1_ARGN,\n];\n\nfunction compute_types(): array {\n    global $commands;\n    $types = [];\n    foreach ($commands as $name => $cmd) {\n        // printf(\"%s: %s\\n\", $name, json_encode($cmd, JSON_PRETTY_PRINT));\n        try {\n            $type = categorize($cmd, $name);\n        } catch (Exception $e) {\n            $type = \"unknown: {$e->getMessage()} \" . json_encode($cmd);\n        }\n        $types[$name] = $type;\n    }\n    return $types;\n}\n\nfunction dump_mismatched_argument_types(array $types, array $commands): void {\n    foreach (EXPECTED_MAPS as $expected => $maps) {\n        foreach ($maps as $key) {\n            $actual = $types[$key];\n            if ($actual !== $expected) {\n                echo \"Unexpected type for $key: got $actual, want $expected: \" . json_encode($commands[$key]['arguments']) . \"\\n\";\n            }\n        }\n        foreach ($types as $other_name => $type) {\n            if ($type === $expected && !in_array($other_name, $maps)) {\n                $command = $commands[$other_name];\n                echo \"Expected $other_name in $expected: \" . json_encode($command['arguments']) . \"\\n\";\n                echo \"> \" . $command['group'] . \": \" . $command['summary'] . \"\\n\\n\";\n            }\n        }\n    }\n}\n\nfunction render_arg(array $argument): string {\n    if ($argument['optional'] ?? false) {\n        unset($argument['optional']);\n        return '[' . render_arg($argument) . ']';\n    }\n    if ($argument['enum'] ?? null) {\n        return implode('|', $argument['enum']);\n    }\n    if ($argument['command'] ?? null) {\n        return $argument['command'];\n    }\n    $name = $argument['name'];\n\n    $repr = is_array($name) ? implode(' ', $name) : $name;\n    if ($argument['multiple'] ?? false) {\n        return \"$repr [$repr …]\";\n    }\n    return $repr;\n}\n\nfunction render_command(string $name, array $command): string {\n    $repr = $name;\n    foreach ($command['arguments'] ?? [] as $argument) {\n        $repr .= ' ' . render_arg($argument);\n    }\n    return $repr;\n}\n\nfunction center_pad(string $name, int $len) {\n    if (mb_strlen($name) >= $len) {\n        return $name;\n    }\n    $name = str_repeat(' ', ($len - mb_strlen($name)) >> 1) . $name;\n    $name .= str_repeat(' ', $len - mb_strlen($name));\n    return $name;\n}\n\nfunction right_pad(string $name, int $len) {\n    if (mb_strlen($name) >= $len) {\n        return $name;\n    }\n    $name .= str_repeat(' ', $len - mb_strlen($name));\n    return $name;\n}\n\nfunction dump_table(array $commands) {\n    $header = <<<EOT\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\n    |      Command      | Supported? | Format                                                                                                              |\n    +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+\nEOT;\n    echo $header . \"\\n\";\n    $rowLine = explode(\"\\n\", $header)[0];\n    ksort($commands);\n    $parts = explode('+', $rowLine);\n    $nameLen = strlen($parts[1]);\n    $supportsLen = strlen($parts[2]);\n    $commandLen = strlen($parts[3]);\n    foreach ($commands as $name => $command) {\n        $key = center_pad($name, 19);\n        $commandRepr = render_command($name, $command);\n        $supports = 'Yes';\n        printf(\"    |%s|%s|%s|\\n\", center_pad($name, $nameLen), center_pad($supports, $supportsLen), right_pad(' ' . $commandRepr, $commandLen));\n        echo $rowLine . \"\\n\";\n\n    }\n    echo \"\\n\";\n}\n\nfunction dump_table_groups(array $commands): void {\n    $groups = [];\n    foreach ($commands as $name => $command) {\n        $groups[$command['group']][$name] = $command;\n    }\n    foreach ($groups as $groupName => $group) {\n        printf(\"### %s Command\\n\\n\", $groupName);\n\n        dump_table($group);\n    }\n}\n\n$types = compute_types();\nforeach ($types as $name => $type) {\n    printf(\"%s: %s\\n\", $name, $type);\n}\n\ndump_mismatched_argument_types($types, $commands);\ndump_table_groups($commands);\n"
  },
  {
    "path": "scripts/makesrpm.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# Create a source RPM package\n# Author: Elvin Sindrilaru <esindril@cern.ch> 2015\n#-------------------------------------------------------------------------------\nRCEXP='^[0-9]+\\.[0-9]+\\.[0-9]+\\-rc.*$'\n\n#-------------------------------------------------------------------------------\n# Color console messages\n#-------------------------------------------------------------------------------\nNORMAL=$(tput sgr0)\nGREEN=$(tput setaf 2; tput bold)\nRED=$(tput setaf 1)\n\nfunction green() {\n    echo -e \"$GREEN$*$NORMAL\"\n}\n\nfunction red() {\n    echo -e \"$RED$*$NORMAL\"\n}\n\n#-------------------------------------------------------------------------------\n# Find a program\n#-------------------------------------------------------------------------------\nfunction findProg()\n{\n  for prog in $@; do\n    if test -x \"`which $prog 2>/dev/null`\"; then\n      echo $prog\n      break\n    fi\n  done\n}\n\n#-------------------------------------------------------------------------------\n# Print help\n#-------------------------------------------------------------------------------\nfunction printHelp()\n{\n  echo \"Usage:\"                                              1>&2\n  echo \"${0} [--help] [--source PATH] [--output PATH]\"       1>&2\n  echo \"  --help        prints this message\"                 1>&2\n  echo \"  --source PATH specify the root of the source tree\" 1>&2\n  echo \"                defaults to ../\"                     1>&2\n  echo \"  --output PATH the directory where the source rpm\"  1>&2\n  echo \"                should be stored, defaults to .\"     1>&2\n}\n\n#-------------------------------------------------------------------------------\n# Parse the commandline\n#-------------------------------------------------------------------------------\nSOURCEPATH=\"../\"\nOUTPUTPATH=\".\"\nPRINTHELP=0\n\nwhile test ${#} -ne 0; do\n  if test x${1} = x--help; then\n    PRINTHELP=1\n  elif test x${1} = x--source; then\n    if test ${#} -lt 2; then\n      echo \"--source parameter needs an argument\" 1>&2\n      exit 1\n    fi\n    SOURCEPATH=${2}\n    shift\n  elif test x${1} = x--output; then\n    if test ${#} -lt 2; then\n      echo \"--output parameter needs an argument\" 1>&2\n      exit 1\n    fi\n    OUTPUTPATH=${2}\n    shift\n  else\n    PRINTHELP=1\n  fi\n  shift\ndone\n\nif test $PRINTHELP -eq 1; then\n  printHelp\n  exit 0\nfi\n\necho \"[i] Working on: $SOURCEPATH\"\necho \"[i] Storing the output to: $OUTPUTPATH\"\n\n#-------------------------------------------------------------------------------\n# Check if the source and the output dirs exist\n#-------------------------------------------------------------------------------\nif test ! -d $SOURCEPATH -o ! -r $SOURCEPATH; then\n  red \"[!] Source path does not exist or is not readable\" 1>&2\n  exit 2\nfi\n\nif test ! -d $OUTPUTPATH -o ! -w $OUTPUTPATH; then\n  red \"[!] Output path does not exist or is not writeable\" 1>&2\n  exit 2\nfi\n\n#-------------------------------------------------------------------------------\n# Check if we have all the necassary components\n#-------------------------------------------------------------------------------\nif test x`findProg rpmbuild` = x; then\n  red \"[!] Unable to find rpmbuild, aborting...\" 1>&2\n  exit 1\nfi\n\nif test x`findProg git` = x; then\n  red \"[!] Unable to find git, aborting...\" 1>&2\n  exit 1\nfi\n\nif test x`findProg awk` = x; then\n  red \"[!] Unable to find awk, aborting...\" 1>&2\n  exit 1\nfi\n\n#-------------------------------------------------------------------------------\n# Check if the source is a git repository\n#-------------------------------------------------------------------------------\nif test ! -d $SOURCEPATH/.git; then\n  red \"[!] I can only work with a git repository\" 1>&2\n  exit 2\nfi\n\n#-------------------------------------------------------------------------------\n# Get VERSION from the spec file\n#-------------------------------------------------------------------------------\nSPEC_FILE=\"nutcracker.spec\"\n\nif test -e \"$SPEC_FILE\"; then\n    VERSION=\"$(grep \"Version:\" $SPEC_FILE | awk '{print $2;}')\"\nelse\n    red \"[!] Unable to get version from spec file.\" 1>&2\n    exit 4\nfi\n\necho \"[i] Working with version: $VERSION\"\n\nif test x${VERSION:0:1} = x\"v\"; then\n  VERSION=${VERSION:1}\nfi\n\n#-------------------------------------------------------------------------------\n# Deal with release candidates\n#-------------------------------------------------------------------------------\nRELEASE=1\n\nif test x`echo $VERSION | egrep $RCEXP` != x; then\n  RELEASE=0.`echo $VERSION | sed 's/.*-rc/rc/'`\n  VERSION=`echo $VERSION | sed 's/-rc.*//'`\nfi\n\nVERSION=`echo $VERSION | sed 's/-/./g'`\necho \"[i] RPM compliant version: $VERSION-$RELEASE\"\n\n#-------------------------------------------------------------------------------\n# Create a tempdir and copy the files there\n#-------------------------------------------------------------------------------\n# exit on any error\nset -e\n\nTEMPDIR=`mktemp -d /tmp/nutcracker.srpm.XXXXXXXXXX`\nRPMSOURCES=$TEMPDIR/rpmbuild/SOURCES\nmkdir -p $RPMSOURCES\nmkdir -p $TEMPDIR/rpmbuild/SRPMS\necho \"[i] Working in: $TEMPDIR\" 1>&2\n\n#-------------------------------------------------------------------------------\n# Make a tarball of the latest commit on the branch\n#-------------------------------------------------------------------------------\n# no more exiting on error\nset +e\n\nCWD=$PWD\ncd $SOURCEPATH\nCOMMIT=`git log --pretty=format:\"%H\" -1`\n\nif test $? -ne 0; then\n  red \"[!] Unable to figure out the git commit hash\" 1>&2\n  exit 5\nfi\n\ngit archive --prefix=nutcracker-\"$VERSION\"/ --format=tar $COMMIT | gzip -9fn > \\\n      $RPMSOURCES/nutcracker-\"$VERSION\".tar.gz\n\nif test $? -ne 0; then\n  red \"[!] Unable to create the source tarball\" 1>&2\n  exit 6\nfi\n\ncd $CWD\n\n#-------------------------------------------------------------------------------\n# Build the source RPM\n#-------------------------------------------------------------------------------\necho \"[i] Creating the source RPM...\"\n\n# Dirty, dirty hack!\necho \"%_sourcedir $RPMSOURCES\" >> $TEMPDIR/rpmmacros\nrpmbuild --define \"_topdir $TEMPDIR/rpmbuild\"    \\\n\t --define \"%_sourcedir $RPMSOURCES\"      \\\n\t --define \"%_srcrpmdir %{_topdir}/SRPMS\" \\\n\t --define \"_source_filedigest_algorithm md5\" \\\n\t --define \"_binary_filedigest_algorithm md5\" \\\n  -bs nutcracker.spec > $TEMPDIR/log\nif test $? -ne 0; then\n  red \"[!] RPM creation failed\" 1>&2\n  exit 8\nfi\n\ncp $TEMPDIR/rpmbuild/SRPMS/nutcracker*.src.rpm $OUTPUTPATH\nrm -rf $TEMPDIR\n\ngreen \"[i] Done.\"\n"
  },
  {
    "path": "scripts/multi_get.sh",
    "content": "#!/bin/sh\n\nport=22123\nsocatopt=\"-t 20 -T 20 -b 8193 -d -d \"\nkey=\"\"\nkeys=\"\"\nget_command=\"\"\n\n# build\nfor i in `seq 1 512`; do\n    if [ `expr $i % 2` -eq \"0\" ]; then\n        key=\"foo\"\n    else\n        key=\"bar\"\n    fi\n    key=`printf \"%s%d\" \"${key}\" \"${i}\"`\n    keys=`printf \"%s %s\" \"${keys}\" \"${key}\"`\ndone\n\nget_command=\"get ${keys}\\r\\n\"\nprintf \"%b\" \"$get_command\"\n\n# read\nfor i in `seq 1 16`; do\n    printf \"%b\" \"${get_command}\" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 &\ndone\n"
  },
  {
    "path": "scripts/nutcracker.init",
    "content": "#! /bin/sh\n#\n# chkconfig: - 55 45\n# description:  Twitter's twemproxy nutcracker\n# processname: nutcracker\n# config: /etc/sysconfig/nutcracker\n\n# Source function library.\n. /etc/rc.d/init.d/functions\n\nUSER=\"nobody\"\nOPTIONS=\"-d -c /etc/nutcracker/nutcracker.yml\"\n\nif [ -f /etc/sysconfig/nutcracker ];then\n    . /etc/sysconfig/nutcracker\nfi\n\n# Check that networking is up.\nif [ \"$NETWORKING\" = \"no\" ]\nthen\n    exit 0\nfi\n\nRETVAL=0\nprog=\"nutcracker\"\n\nstart () {\n    echo -n $\"Starting $prog: \"\n    #Test the config before start.\n    daemon --user ${USER} ${prog} $OPTIONS -t >/dev/null  2>&1\n    RETVAL=$?\n    if [ $RETVAL -ne 0 ] ; then\n        echo  \"Config check fail! Please  use 'nutcracker -c /etc/nutcracker/nutcracker.yml' for detail.\"\n        echo_failure;\n        echo; \n        exit 1 \n    fi\n\n    daemon --user ${USER} ${prog} $OPTIONS\n    RETVAL=$?\n    echo\n    [ $RETVAL -eq 0 ] && touch /var/lock/subsys/${prog}\n}\nstop () {\n    echo -n $\"Stopping $prog: \"\n    killproc ${prog}\n    RETVAL=$?\n    echo\n    if [ $RETVAL -eq 0 ] ; then\n        rm -f /var/lock/subsys/${prog}\n    fi\n}\n\nrestart () {\n    stop\n    start\n}\n\n\n# See how we were called.\ncase \"$1\" in\n  start)\n    start\n    ;;\n  stop)\n    stop\n    ;;\n  status)\n    status ${prog}\n    ;;\n  restart|reload)\n    restart\n    ;;\n  condrestart)\n    [ -f /var/lock/subsys/nutcracker ] && restart || :\n    ;;\n  *)\n    echo $\"Usage: $0 {start|stop|status|restart|reload|condrestart}\"\n    exit 1\nesac\n\nexit $?\n\n"
  },
  {
    "path": "scripts/nutcracker.init.debian",
    "content": "#!/bin/sh\n### BEGIN INIT INFO\n# Provides:          nutcracker\n# Required-Start:    $network $remote_fs $local_fs\n# Required-Stop:     $network $remote_fs $local_fs\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n# Short-Description: Stop/start nutcracker\n### END INIT INFO\n\nPATH=/sbin:/usr/sbin:/bin:/usr/bin\nDESC=nutcracker\nNAME=nutcracker\nUSER=nobody\nCONFFILE=/opt/nutcracker/etc/$NAME.yml\nLOGFILE=/opt/nutcracker/log/nutcracker.log\nDAEMON=/opt/nutcracker/sbin/nutcracker\nPIDFILE=/var/run/nutcracker/$NAME.pid\nSTATSPORT=22222\nDAEMON_ARGS=\"-c $CONFFILE -o $LOGFILE -p $PIDFILE -s $STATSPORT -v 11 -m 2048 -d\"\n#DAEMON_ARGS=\"-c $CONFFILE -p $PIDFILE -s $STATSPORT -d\"\nSCRIPTNAME=/etc/init.d/$NAME\n\nulimit -Hn 100000\nulimit -Sn 100000\n\n[ -x $DAEMON ] || exit 0\n\n[ -r /etc/default/$NAME ] && . /etc/default/$NAME\n\n. /lib/init/vars.sh\n\n. /lib/lsb/init-functions\n\ndo_start()\n{\n    mkdir -p /var/run/nutcracker\n    touch $PIDFILE\n    chown $USER:$USER -R /var/run/nutcracker\n    chmod 755 /var/run/nutcracker\n\n    echo -n \"Starting ${NAME}: \"\n    start-stop-daemon --start --quiet -m --pidfile $PIDFILE --chuid $USER:$USER --exec $DAEMON -- \\\n        $DAEMON_ARGS\n        case \"$?\" in\n            0|1) echo \"STARTED.\" ;;\n            2) echo \"FAILED.\" ;;\n        esac\n}\n\ndo_stop()\n{\n    echo -n \"Stopping ${NAME}: \"\n    start-stop-daemon --stop --quiet --pidfile $PIDFILE --exec $DAEMON || true\n\n    case \"$?\" in\n        0|1) echo \"STOPPED.\";;\n        2) echo \"FAILED.\" ;;\n    esac\n}\n\ncase \"$1\" in\n    start)\n        do_start\n    ;;\n    stop)\n        do_stop\n    ;;\n    status)\n      status_of_proc -p $PIDFILE \"$DAEMON\" nutcracker && exit 0 || exit $?\n    ;;\n    restart)\n      do_stop\n      do_start\n    ;;\n    *)\n        echo \"Usage: $SCRIPTNAME {start|stop|status|restart}\" >&2\n        exit 3\n        ;;\nesac\n\nexit\n$RETVAL\n"
  },
  {
    "path": "scripts/nutcracker.spec",
    "content": "Summary: Twitter's nutcracker redis and memcached proxy\nName: nutcracker\nVersion: 0.5.0\nRelease: 1\n\nURL: https://github.com/twitter/twemproxy/\nSource0: %{name}-%{version}.tar.gz\nLicense: Apache License 2.0\nGroup: System Environment/Libraries\nPackager: Tom Parrott <tomp@tomp.co.uk>\nBuildRoot: %{_tmppath}/%{name}-root\n\nBuildRequires: autoconf\nBuildRequires: automake\nBuildRequires: libtool\n\n%description\ntwemproxy (pronounced \"two-em-proxy\"), aka nutcracker is a fast and lightweight proxy for memcached and redis protocol.\nIt was primarily built to reduce the connection count on the backend caching servers. This, together with protocol\npipelining and sharding enables you to horizontally scale your distributed caching architecture.\n\n%prep\n%setup -q\n%if 0%{?rhel} == 6\nsed -i 's/2.64/2.63/g' configure.ac\n%endif\nautoreconf -fvi\n\n%build\n\n%configure\n%__make\n\n%install\n[ %{buildroot} != \"/\" ] && rm -rf %{buildroot}\n\n%makeinstall PREFIX=%{buildroot}\n\n#Install init script\n%{__install} -p -D -m 0755 scripts/%{name}.init %{buildroot}%{_initrddir}/%{name}\n\n#Install example config file\n%{__install} -p -D -m 0644 conf/%{name}.yml %{buildroot}%{_sysconfdir}/%{name}/%{name}.yml\n\n%post\n/sbin/chkconfig --add %{name}\n\n%preun\nif [ $1 = 0 ]; then\n /sbin/service %{name} stop > /dev/null 2>&1\n /sbin/chkconfig --del %{name}\nfi\n\n%clean\n[ %{buildroot} != \"/\" ] && rm -rf %{buildroot}\n\n%files\n%defattr(-,root,root,-)\n%if 0%{?rhel} >= 6\n/usr/sbin/nutcracker\n%else\n/usr/bin/nutcracker\n%endif\n%{_initrddir}/%{name}\n%{_mandir}/man8/nutcracker.8.gz\n%config(noreplace)%{_sysconfdir}/%{name}/%{name}.yml\n\n%changelog\n* Tue Jul 06 2021 Tyson Andre  <tysonandre775@hotmail.com>\n- twemproxy: version 0.5.0 release\n- Same as 0.5.0-RC1\n\n* Tue Jul 06 2021 Tyson Andre  <tysonandre775@hotmail.com>\n- twemproxy: version 0.5.0-RC1 release\n- Add 'tcpkeepalive' pool boolean config flag setting to enable tcp keepalive (charsyam, manju)\n- Support redis bitpos command (clark kang)\n- Fix parsing of redis error response for error type with no space, add tests (tyson, tom dalton)\n- Update integration tests, add C unit test suite for 'make check' (tyson)\n- Increase the maximum host length+port+identifier to 273 in ketama_update (李广博)\n- Always initialize file permissions field when listening on a unix domain socket (tyson)\n- Use number of servers instead of number of points on the continuum when sharding requests to backend services to improve sharding performance and fix potential invalid memory access when all hosts were ejected from a pool. (tyson)\n- Optimize performance of deletion of single redis keys (vincentve)\n- Don't fragment memcache/redis get commands when they only have a single key (improves performance and error handling of single key case) (tyson)\n- Don't let requests hang when there is a dns error when processing a fragmented request (e.g. multiget with multiple keys) (tyson)\n- Allow extra parameters for redis spop (charsyam)\n- Update documentation and README (various)\n- Fix memory leak bug for redis mset (deep011)\n- Support arbitrarily deep nested redis multi-bulk responses (nested arrays) (qingping209, tyson)\n- Upgrade from libyaml 0.1.4 to 0.2.5 (tyson)\n- Fix compiler warnings about wrong conversion specifiers in format strings for logging (tyson)\n- Log the async backend used and any debug options in the '--help'/'--version' output.\n- Add support for many more new redis commands and updates to existing redis commands (tyson)\n- Optimization: Skip hashing and choosing server index when a pool has exactly one server (tyson)\n- Support memcache 'version' requests by proxying the request to a single backend memcache server to fetch the server version. (tyson)\n- Make error messages for creating the stats server during startup clearer. (tyson)\n\n* Mon Jun 22 2015  Manju Rajashekhar  <manj@cs.stanford.edu>\n- twemproxy: version 0.4.1 release\n- backend server hostnames are resolved lazily\n- redis_auth is only valid for a redis pool\n- getaddrinfo returns non-zero +ve value on error\n- fix-hang-when-command-only (charsyam)\n- fix bug crash when get command without key and whitespace (charsyam)\n- mark server as failed on protocol level transiet failures like -OOM, -LOADING, etc\n- implemented support for parsing fine grained redis error response\n- remove redundant conditional judgement in rbtree deletion (leo ma)\n- fix bug mset has invalid pair (charsyam)\n- fix bug mset has invalid pair (charsyam)\n- temp fix a core on kqueue (idning)\n- support \"touch\" command for memcached (panmiaocai)\n- fix redis parse rsp bug (charsyam)\n- SORT command can take multiple arguments. So it should be part of redis_argn() and not redis_arg0()\n- remove incorrect assert because client could send data after sending a quit request which must be discarded\n- allow file permissions to be set for UNIX domain listening socket (ori liveneh)\n- return error if formatted is greater than mbuf size by using nc_vsnprintf() in msg_prepend_format()\n- fix req_make_reply on msg_get, mark it as response (idning)\n- redis database select upon connect (arne claus)\n- redis_auth (charsyam)\n- allow null key(empty key) (idning)\n- fix core on invalid mset like \"mset a a a\" (idning)\n\n* Tue Oct 14 2014 idning <idning@gmail.com>\n- twemproxy: version 0.4.0 release\n- mget improve (idning)\n- many new commands supported: LEX, PFADD, PFMERGE, SORT, PING, QUIT, SCAN... (mattrobenolt, areina, idning)\n- handle max open file limit(allenlz)\n- add notice-log and use ms time in log(idning)\n- fix bug in string_compare (andyqzb)\n- fix deadlock in sighandler (idning)\n\n* Fri Dec 20 2013  Manju Rajashekhar  <manj@cs.stanford.edu>\n- twemproxy: version 0.3.0 release\n- SRANDMEMBER support for the optional count argument (mkhq)\n- Handle case where server responds while the request is still being sent (jdi-tagged)\n- event ports (solaris/smartos) support\n- add timestamp when the server was ejected\n- support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat)\n- kqueue (bsd) support (ferenyx)\n- fix parsing redis response to accept integer reply (charsyam)\n\n* Tue Jul 30 2013 Tait Clarridge <tait@clarridge.ca>\n- Rebuild SPEC to work with CentOS\n- Added buildrequires if building with mock/koji\n\n* Tue Apr 23 2013  Manju Rajashekhar  <manj@cs.stanford.edu>\n- twemproxy: version 0.2.4 release\n- redis keys must be less than mbuf_data_size() in length (fifsky)\n- Adds support for DUMP/RESTORE commands in Redis (remotezygote)\n- Use of the weight value in the modula distribution (mezzatto)\n- Add support to unix socket connections to servers (mezzatto)\n- only check for duplicate server name and not 'host:port:weight' when 'name' is configured\n- crc16 hash support added (mezzatto)\n\n* Thu Jan 31 2013  Manju Rajashekhar  <manj@twitter.com>\n- twemproxy: version 0.2.3 release\n- RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon)\n- EVAL and EVALSHA support (ferenyx)\n- exit 1 if configuration file is invalid (cofyc)\n- return non-zero exit status when nutcracker cannot start for some reason\n- use server names in stats (charsyam)\n- Fix failure to resolve long FQDN name resolve (conmame)\n- add support for hash tags\n\n* Thu Oct 18 2012  Manju Rajashekhar  <manj@twitter.com>\n- twemproxy: version 0.2.2 release\n- fix the off-by-one error when calculating redis key length\n\n* Fri Oct 12 2012  Manju Rajashekhar  <manj@twitter.com>\n- twemproxy: version 0.2.1 release\n- don't use buf in conf_add_server\n- allow an optional instance name for consistent hashing (charsyam)\n- add --stats-addr=S option\n- add stats-bind-any -a option (charsyam)\n"
  },
  {
    "path": "scripts/pipelined_read.sh",
    "content": "#!/bin/sh\n\nsocatopt=\"-t 4 -T 4 -b 8193 -d -d \"\n\nget_commands=\"\"\n\n# build\nfor i in `seq 1 128`; do\n    if [ `expr $i % 2` -eq \"0\" ]; then\n        key=\"foo\"\n    else\n        key=\"bar\"\n    fi\n    key=`printf \"%s%d\" \"${key}\" \"${i}\"`\n\n    get_command=\"get ${key}\\r\\n\"\n    get_commands=`printf \"%s%s\" \"${get_commands}\" \"${get_command}\"`\ndone\n\n# read\nfor i in `seq 1 64`; do\n    printf \"%b\" \"$get_commands\" | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 &\ndone\n"
  },
  {
    "path": "scripts/pipelined_write.sh",
    "content": "#!/bin/sh\n\nsocatopt=\"-t 1 -T 1 -b 16384\"\n\nval=`echo 6^6^6 | bc`\nval=`printf \"%s\" \"${val}\"`\nvallen=`printf \"%s\" \"${val}\" | wc -c`\nset_command=\"\"\nset_commands=\"\"\n\n# build\nfor i in `seq 1 64`; do\n    if [ `expr $i % 2` -eq \"0\" ]; then\n        key=\"foo\"\n    else\n        key=\"bar\"\n    fi\n    key=`printf \"%s%d\" \"${key}\" \"${i}\"`\n\n    set_command=\"set ${key} 0 0 ${vallen}\\r\\n${val}\\r\\n\"\n    set_commands=`printf \"%s%s\" \"${set_commands}\" \"${set_command}\"`\ndone\n\nprintf \"%b\" \"$set_commands\" > /tmp/socat.input\n\n# write\nfor i in `seq 1 16`; do\n    cat /tmp/socat.input | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-down,nonblock=1 &\ndone\n"
  },
  {
    "path": "scripts/populate_memcached.sh",
    "content": "#!/bin/sh\n\nport=22123\nsocatopt=\"-t 1 -T 1 -b 65537\"\n\nval=`echo 6^6^6 | bc`\nval=`printf \"%s\\r\\n\" \"${val}\"`\nvallen=`printf \"%s\" \"${val}\" | wc -c`\nset_command=\"\"\n\n# build\nfor i in `seq 1 512`; do\n    if [ `expr $i % 2` -eq \"0\" ]; then\n        key=\"foo\"\n    else\n        key=\"bar\"\n    fi\n    key=`printf \"%s%d\" \"${key}\" \"${i}\"`\n\n    set_command=\"set ${key} 0 0 ${vallen}\\r\\n${val}\\r\\n\"\n\n    printf \"%b\" \"$set_command\" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-down,nonblock=1 &\ndone\n\n"
  },
  {
    "path": "scripts/redis-check.py",
    "content": "import redis\n\nrange=100\nfactor=32\nport=22121\n\nr = redis.StrictRedis(host='localhost', port=port, db=0)\n\n# lrange\nprint [r.lrange('lfoo', 0, x) for x in xrange(1, range)]\nprint [r.lpush('lfoo', str(x)*factor) for x in xrange(1, range)]\nprint [r.lrange('lfoo', 0, x) for x in xrange(1, range)]\nprint r.delete('lfoo')\n\n# del\nprint [r.set('foo' + str(x), str(x)*factor) for x in xrange(1, range)]\nkeys = ['foo' + str(x) for x in xrange(1, range)]\nprint [r.delete(keys) for x in xrange(1, range)]\n\n# mget\nprint [r.set('foo' + str(x), str(x)*100) for x in xrange(1, range)]\nkeys = ['foo' + str(x) for x in xrange(1, range)]\nprint [r.mget(keys) for x in xrange(1, range)]\n"
  },
  {
    "path": "scripts/redis-check.sh",
    "content": "#!/bin/sh\n\nport=6379\nport=22121\n\ndebug=\"-v -d\"\ndebug=\"-d\"\n\ntimeout=\"-t 1\"\ntimeout=\"\"\n\n# keys\n\nprintf '\\ndel\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\ndump\\n'\nprintf '*2\\r\\n$4\\r\\ndump\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$4\\r\\ndump\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nexists\\n'\nprintf '*2\\r\\n$6\\r\\nexists\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$6\\r\\nexists\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nexpire\\n'\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\npersist\\n'\nprintf '*2\\r\\n$7\\r\\npersist\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$7\\r\\npersist\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$7\\r\\npersist\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nexpireat\\n'\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nexpire\\n'\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nrestore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\n\nprintf '\\npttl\\n'\nprintf '*2\\r\\n$4\\r\\npttl\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$4\\r\\npttl\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nttl\\n'\nprintf '*2\\r\\n$4\\r\\npttl\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\nttl\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nttl\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\ntype\\n'\nprintf '*2\\r\\n$4\\r\\ntype\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\ntype\\r\\n$3\\r\\noof\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\n# strings\n\nprintf '\\nappend\\n'\nprintf '*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\nprintf '*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\n\nprintf '\\nbitcount\\n'\nprintf '*2\\r\\n$8\\r\\nbitcount\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\n\nprintf '\\ndecr\\n'\nprintf '*2\\r\\n$4\\r\\ndecr\\r\\n$7\\r\\ncounter\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\ndecrby\\n'\nprintf '*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\n\nprintf '\\nget\\n'\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$16\\r\\nnon-existent-key\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\ngetbit\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\ngetrange\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\ngetset\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nincr\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\ncounter\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nincr\\r\\n$7\\r\\ncounter\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nincrby\\n'\nprintf '*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\n\nprintf '\\nincrbyfloat\\n'\nprintf '*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\n\nprintf '\\nmget\\n'\nprintf '*2\\r\\n$4\\r\\nmget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$4\\r\\nmget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\n\nprintf '\\npsetex\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nset\\n'\nprintf '*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\n\nprintf '\\nsetbit\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\npsetex\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsetnx\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsetrange\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$3\\r\\nget\\r\\n$3\\r\\nfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\n# hashes\n\nprintf '\\nhdel\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nhexists\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nhget\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nhgetall\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhincrby\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhincrbyfloat\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhkeys\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$5\\r\\nhkeys\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$5\\r\\nhkeys\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$5\\r\\nhkeys\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhlen\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nhlen\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$4\\r\\nhlen\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhmget\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nhmset\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhset\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhsetnx\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nhvals\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$5\\r\\nhvals\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$5\\r\\nhvals\\r\\n$4\\r\\nhfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\n# lists\n\nprintf '\\nlindex\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nlinsert\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nllen\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nllen\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$4\\r\\nllen\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nlpop\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nlpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$4\\r\\nlpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nlpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nlpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nlpush\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nlpushx\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nlrange\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nlrem\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nlset\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nltrim\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\n\nprintf '\\nrpop\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nrpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$4\\r\\nrpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nrpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nrpop\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nrpoplpush\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{lfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{lfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nrpush\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nrpushx\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nlfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\n# sets\n\nprintf '\\nsadd\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nscard\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$5\\r\\nscard\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$5\\r\\nscard\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsdiff\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nsdiffstore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$7\\r\\n{sfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsinter\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nsinterstore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$7\\r\\n{sfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsismember\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nsmembers\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsmove\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nspop\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$4\\r\\nspop\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$4\\r\\nspop\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nsrandmember\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$11\\r\\nsrandmember\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$11\\r\\nsrandmember\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$11\\r\\nsrandmember\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$11\\r\\nsrandmember\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\n\nprintf '\\nsrem\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nsfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nsunion\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nsunionstore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{sfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{sfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$8\\r\\nsmembers\\r\\n$7\\r\\n{sfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\n# sorted sets\n\nprintf '\\nzadd\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzcard\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$5\\r\\nzcard\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$5\\r\\nzcard\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\nzcount\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzincrby\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\n\nprintf '\\nzinterstore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{zfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{zfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{zfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrange\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrangebyscore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrank\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrem\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzremrangebyrank\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzremrangebyscore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrevrange\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrevrangebyscore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrevrank\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzscore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzunionstore\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{zfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{zfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{zfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzlexcount\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzrangebylex\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\nprintf '\\nzremrangebylex\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\nzfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\n# hyperloglog\n\nprintf '\\npfadd\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\npfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\npfcount\\r\\n$4\\r\\npfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\npfcount\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$4\\r\\npfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$7\\r\\npfcount\\r\\n$4\\r\\npfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*2\\r\\n$7\\r\\npfcount\\r\\n$4\\r\\npfoo\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\nprintf '\\npfmerge\\n'\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$6\\r\\n{pfoo}\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{pfoo}2\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*2\\r\\n$3\\r\\ndel\\r\\n$7\\r\\n{pfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*2\\r\\n$7\\r\\npfcount\\r\\n$7\\r\\n{pfoo}3\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n\n# scripting\n\nprintf '\\neval\\n'\nprintf '*2\\r\\n$4\\r\\neval\\r\\n$10\\r\\nreturn 123\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\nprintf '*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\n\n# quit\nprintf '\\nquit\\n'\nprintf '*1\\r\\n$4\\r\\nquit\\r\\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close\n"
  },
  {
    "path": "src/Makefile.am",
    "content": "MAINTAINERCLEANFILES = Makefile.in\n\nAM_CPPFLAGS =\nif !OS_SOLARIS\nAM_CPPFLAGS += -D_GNU_SOURCE\nendif\nAM_CPPFLAGS += -I $(top_srcdir)/src/hashkit\nAM_CPPFLAGS += -I $(top_srcdir)/src/proto\nAM_CPPFLAGS += -I $(top_srcdir)/src/event\nAM_CPPFLAGS += -I $(top_srcdir)/contrib/yaml-0.2.5/include\n\nAM_CFLAGS =\n# about -fno-strict-aliasing: https://github.com/twitter/twemproxy/issues/276\nAM_CFLAGS += -fno-strict-aliasing\nAM_CFLAGS += -Wall -Wshadow\nAM_CFLAGS += -Wpointer-arith\nAM_CFLAGS += -Winline\nAM_CFLAGS += -Wunused-function -Wunused-variable -Wunused-value\nAM_CFLAGS += -Wno-unused-parameter -Wno-unused-value\nAM_CFLAGS += -Wconversion -Wsign-compare\nAM_CFLAGS += -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Wmissing-declarations\nAM_CFLAGS += -Wno-format-zero-length\n\nAM_LDFLAGS =\nAM_LDFLAGS += -lm -lpthread -rdynamic\nif OS_SOLARIS\nAM_LDFLAGS += -lnsl -lsocket\nendif\nif OS_FREEBSD\nAM_LDFLAGS += -lexecinfo\nendif\n\nSUBDIRS = hashkit proto event\n\nsbin_PROGRAMS = nutcracker\n\nnutcracker_SOURCES =\t\t\t\\\n\tnc_core.c nc_core.h\t\t\\\n\tnc_connection.c nc_connection.h\t\\\n\tnc_client.c nc_client.h\t\t\\\n\tnc_server.c nc_server.h\t\t\\\n\tnc_proxy.c nc_proxy.h\t\t\\\n\tnc_message.c nc_message.h\t\\\n\tnc_request.c\t\t\t\\\n\tnc_response.c\t\t\t\\\n\tnc_mbuf.c nc_mbuf.h\t\t\\\n\tnc_conf.c nc_conf.h\t\t\\\n\tnc_stats.c nc_stats.h\t\t\\\n\tnc_signal.c nc_signal.h\t\t\\\n\tnc_rbtree.c nc_rbtree.h\t\t\\\n\tnc_log.c nc_log.h\t\t\\\n\tnc_string.c nc_string.h\t\t\\\n\tnc_array.c nc_array.h\t\t\\\n\tnc_util.c nc_util.h\t\t\\\n\tnc_queue.h\t\t\t\\\n\tnc.c\n\nnutcracker_LDADD = $(top_builddir)/src/hashkit/libhashkit.a\nnutcracker_LDADD += $(top_builddir)/src/proto/libproto.a\nnutcracker_LDADD += $(top_builddir)/src/event/libevent.a\nnutcracker_LDADD += $(top_builddir)/contrib/yaml-0.2.5/src/.libs/libyaml.a\n\nTESTS = test_all\nbin_PROGRAMS = test_all\n\ntest_all_SOURCES = test_all.c \\\n\tnc_core.c nc_core.h\t\t\\\n\tnc_connection.c nc_connection.h\t\\\n\tnc_client.c nc_client.h\t\t\\\n\tnc_server.c nc_server.h\t\t\\\n\tnc_proxy.c nc_proxy.h\t\t\\\n\tnc_message.c nc_message.h\t\\\n\tnc_request.c\t\t\t\\\n\tnc_response.c\t\t\t\\\n\tnc_mbuf.c nc_mbuf.h\t\t\\\n\tnc_conf.c nc_conf.h\t\t\\\n\tnc_stats.c nc_stats.h\t\t\\\n\tnc_signal.c nc_signal.h\t\t\\\n\tnc_rbtree.c nc_rbtree.h\t\t\\\n\tnc_log.c nc_log.h\t\t\\\n\tnc_string.c nc_string.h\t\t\\\n\tnc_array.c nc_array.h\t\t\\\n\tnc_util.c nc_util.h\t\t\\\n\tnc_queue.h\n\ntest_all_LDADD = $(top_builddir)/src/hashkit/libhashkit.a\ntest_all_LDADD += $(top_builddir)/src/proto/libproto.a\ntest_all_LDADD += $(top_builddir)/src/event/libevent.a\ntest_all_LDADD += $(top_builddir)/contrib/yaml-0.2.5/src/.libs/libyaml.a\n"
  },
  {
    "path": "src/event/Makefile.am",
    "content": "MAINTAINERCLEANFILES = Makefile.in\n\nAM_CPPFLAGS = -I $(top_srcdir)/src\n\nAM_CFLAGS = -Wall -Wshadow\nAM_CFLAGS += -Wno-unused-parameter -Wno-unused-value\n\nnoinst_LIBRARIES = libevent.a\n\nnoinst_HEADERS = nc_event.h\n\nlibevent_a_SOURCES =\t\\\n\tnc_epoll.c\t\\\n\tnc_kqueue.c\t\\\n\tnc_evport.c\n\n"
  },
  {
    "path": "src/event/nc_epoll.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\n#ifdef NC_HAVE_EPOLL\n\n#include <sys/epoll.h>\n\nstruct event_base *\nevent_base_create(int nevent, event_cb_t cb)\n{\n    struct event_base *evb;\n    int status, ep;\n    struct epoll_event *event;\n\n    ASSERT(nevent > 0);\n\n    ep = epoll_create(nevent);\n    if (ep < 0) {\n        log_error(\"epoll create of size %d failed: %s\", nevent, strerror(errno));\n        return NULL;\n    }\n\n    event = nc_calloc(nevent, sizeof(*event));\n    if (event == NULL) {\n        status = close(ep);\n        if (status < 0) {\n            log_error(\"close e %d failed, ignored: %s\", ep, strerror(errno));\n        }\n        return NULL;\n    }\n\n    evb = nc_alloc(sizeof(*evb));\n    if (evb == NULL) {\n        nc_free(event);\n        status = close(ep);\n        if (status < 0) {\n            log_error(\"close e %d failed, ignored: %s\", ep, strerror(errno));\n        }\n        return NULL;\n    }\n\n    evb->ep = ep;\n    evb->event = event;\n    evb->nevent = nevent;\n    evb->cb = cb;\n\n    log_debug(LOG_INFO, \"e %d with nevent %d\", evb->ep, evb->nevent);\n\n    return evb;\n}\n\nvoid\nevent_base_destroy(struct event_base *evb)\n{\n    int status;\n\n    if (evb == NULL) {\n        return;\n    }\n\n    ASSERT(evb->ep > 0);\n\n    nc_free(evb->event);\n\n    status = close(evb->ep);\n    if (status < 0) {\n        log_error(\"close e %d failed, ignored: %s\", evb->ep, strerror(errno));\n    }\n    evb->ep = -1;\n\n    nc_free(evb);\n}\n\nint\nevent_add_in(struct event_base *evb, struct conn *c)\n{\n    int status;\n    struct epoll_event event;\n    int ep = evb->ep;\n\n    ASSERT(ep > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n\n    if (c->recv_active) {\n        return 0;\n    }\n\n    event.events = (uint32_t)(EPOLLIN | EPOLLET);\n    event.data.ptr = c;\n\n    status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event);\n    if (status < 0) {\n        log_error(\"epoll ctl on e %d sd %d failed: %s\", ep, c->sd,\n                  strerror(errno));\n    } else {\n        c->recv_active = 1;\n    }\n\n    return status;\n}\n\nint\nevent_del_in(struct event_base *evb, struct conn *c)\n{\n    return 0;\n}\n\nint\nevent_add_out(struct event_base *evb, struct conn *c)\n{\n    int status;\n    struct epoll_event event;\n    int ep = evb->ep;\n\n    ASSERT(ep > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n\n    if (c->send_active) {\n        return 0;\n    }\n\n    event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET);\n    event.data.ptr = c;\n\n    status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event);\n    if (status < 0) {\n        log_error(\"epoll ctl on e %d sd %d failed: %s\", ep, c->sd,\n                  strerror(errno));\n    } else {\n        c->send_active = 1;\n    }\n\n    return status;\n}\n\nint\nevent_del_out(struct event_base *evb, struct conn *c)\n{\n    int status;\n    struct epoll_event event;\n    int ep = evb->ep;\n\n    ASSERT(ep > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n\n    if (!c->send_active) {\n        return 0;\n    }\n\n    event.events = (uint32_t)(EPOLLIN | EPOLLET);\n    event.data.ptr = c;\n\n    status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event);\n    if (status < 0) {\n        log_error(\"epoll ctl on e %d sd %d failed: %s\", ep, c->sd,\n                  strerror(errno));\n    } else {\n        c->send_active = 0;\n    }\n\n    return status;\n}\n\nint\nevent_add_conn(struct event_base *evb, struct conn *c)\n{\n    int status;\n    struct epoll_event event;\n    int ep = evb->ep;\n\n    ASSERT(ep > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n\n    event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET);\n    event.data.ptr = c;\n\n    status = epoll_ctl(ep, EPOLL_CTL_ADD, c->sd, &event);\n    if (status < 0) {\n        log_error(\"epoll ctl on e %d sd %d failed: %s\", ep, c->sd,\n                  strerror(errno));\n    } else {\n        c->send_active = 1;\n        c->recv_active = 1;\n    }\n\n    return status;\n}\n\nint\nevent_del_conn(struct event_base *evb, struct conn *c)\n{\n    int status;\n    int ep = evb->ep;\n\n    ASSERT(ep > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n\n    status = epoll_ctl(ep, EPOLL_CTL_DEL, c->sd, NULL);\n    if (status < 0) {\n        log_error(\"epoll ctl on e %d sd %d failed: %s\", ep, c->sd,\n                  strerror(errno));\n    } else {\n        c->recv_active = 0;\n        c->send_active = 0;\n    }\n\n    return status;\n}\n\nint\nevent_wait(struct event_base *evb, int timeout)\n{\n    int ep = evb->ep;\n    struct epoll_event *event = evb->event;\n    int nevent = evb->nevent;\n\n    ASSERT(ep > 0);\n    ASSERT(event != NULL);\n    ASSERT(nevent > 0);\n\n    for (;;) {\n        int i, nsd;\n\n        nsd = epoll_wait(ep, event, nevent, timeout);\n        if (nsd > 0) {\n            for (i = 0; i < nsd; i++) {\n                struct epoll_event *ev = &evb->event[i];\n                uint32_t events = 0;\n\n                log_debug(LOG_VVERB, \"epoll %04\"PRIX32\" triggered on conn %p\",\n                          ev->events, ev->data.ptr);\n\n                if (ev->events & EPOLLERR) {\n                    events |= EVENT_ERR;\n                }\n\n                if (ev->events & (EPOLLIN | EPOLLHUP)) {\n                    events |= EVENT_READ;\n                }\n\n                if (ev->events & EPOLLOUT) {\n                    events |= EVENT_WRITE;\n                }\n\n                if (evb->cb != NULL) {\n                    evb->cb(ev->data.ptr, events);\n                }\n            }\n            return nsd;\n        }\n\n        if (nsd == 0) {\n            if (timeout == -1) {\n               log_error(\"epoll wait on e %d with %d events and %d timeout \"\n                         \"returned no events\", ep, nevent, timeout);\n                return -1;\n            }\n\n            return 0;\n        }\n\n        if (errno == EINTR) {\n            continue;\n        }\n\n        log_error(\"epoll wait on e %d with %d events failed: %s\", ep, nevent,\n                  strerror(errno));\n        return -1;\n    }\n\n    NOT_REACHED();\n}\n\nvoid\nevent_loop_stats(event_stats_cb_t cb, void *arg)\n{\n    struct stats *st = arg;\n    int status, ep;\n    struct epoll_event ev;\n\n    ep = epoll_create(1);\n    if (ep < 0) {\n        log_error(\"epoll create failed: %s\", strerror(errno));\n        return;\n    }\n\n    ev.data.fd = st->sd;\n    ev.events = EPOLLIN;\n\n    status = epoll_ctl(ep, EPOLL_CTL_ADD, st->sd, &ev);\n    if (status < 0) {\n        log_error(\"epoll ctl on e %d sd %d failed: %s\", ep, st->sd,\n                  strerror(errno));\n        goto error;\n    }\n\n    for (;;) {\n        int n;\n\n        n = epoll_wait(ep, &ev, 1, st->interval);\n        if (n < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            log_error(\"epoll wait on e %d with m %d failed: %s\", ep,\n                      st->sd, strerror(errno));\n            break;\n        }\n\n        cb(st, &n);\n    }\n\nerror:\n    status = close(ep);\n    if (status < 0) {\n        log_error(\"close e %d failed, ignored: %s\", ep, strerror(errno));\n    }\n    ep = -1;\n}\n\n#endif /* NC_HAVE_EPOLL */\n"
  },
  {
    "path": "src/event/nc_event.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_EVENT_H_\n#define _NC_EVENT_H_\n\n#include <nc_core.h>\n\n#define EVENT_SIZE  1024\n\n#define EVENT_READ  0x0000ff\n#define EVENT_WRITE 0x00ff00\n#define EVENT_ERR   0xff0000\n\ntypedef int (*event_cb_t)(void *, uint32_t);\ntypedef void (*event_stats_cb_t)(void *, void *);\n\n#ifdef NC_HAVE_KQUEUE\n\nstruct event_base {\n    int           kq;          /* kernel event queue descriptor */\n\n    struct kevent *change;     /* change[] - events we want to monitor */\n    int           nchange;     /* # change */\n\n    struct kevent *event;      /* event[] - events that were triggered */\n    int           nevent;      /* # event */\n    int           nreturned;   /* # event placed in event[] */\n    int           nprocessed;  /* # event processed from event[] */\n\n    event_cb_t    cb;          /* event callback */\n};\n\n#elif NC_HAVE_EPOLL\n\nstruct event_base {\n    int                ep;      /* epoll descriptor */\n\n    struct epoll_event *event;  /* event[] - events that were triggered */\n    int                nevent;  /* # event */\n\n    event_cb_t         cb;      /* event callback */\n};\n\n#elif NC_HAVE_EVENT_PORTS\n\n#include <port.h>\n\nstruct event_base {\n    int          evp;     /* event port descriptor */\n\n    port_event_t *event;  /* event[] - events that were triggered */\n    int          nevent;  /* # event */\n\n    event_cb_t   cb;      /* event callback */\n};\n\n#else\n# error missing scalable I/O event notification mechanism\n#endif\n\nstruct event_base *event_base_create(int size, event_cb_t cb);\nvoid event_base_destroy(struct event_base *evb);\n\nint event_add_in(struct event_base *evb, struct conn *c);\nint event_del_in(struct event_base *evb, struct conn *c);\nint event_add_out(struct event_base *evb, struct conn *c);\nint event_del_out(struct event_base *evb, struct conn *c);\nint event_add_conn(struct event_base *evb, struct conn *c);\nint event_del_conn(struct event_base *evb, struct conn *c);\nint event_wait(struct event_base *evb, int timeout);\nvoid event_loop_stats(event_stats_cb_t cb, void *arg);\n\n#endif /* _NC_EVENT_H */\n"
  },
  {
    "path": "src/event/nc_evport.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2013 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\n#ifdef NC_HAVE_EVENT_PORTS\n\n#include <port.h>\n#include <poll.h>\n\nstruct event_base *\nevent_base_create(int nevent, event_cb_t cb)\n{\n    struct event_base *evb;\n    int status, evp;\n    port_event_t *event;\n\n    ASSERT(nevent > 0);\n\n    evp = port_create();\n    if (evp < 0) {\n        log_error(\"port create failed: %s\", strerror(errno));\n        return NULL;\n    }\n\n    event = nc_calloc(nevent, sizeof(*event));\n    if (event == NULL) {\n        status = close(evp);\n        if (status < 0) {\n            log_error(\"close evp %d failed, ignored: %s\", evp, strerror(errno));\n        }\n        return NULL;\n    }\n\n    evb = nc_alloc(sizeof(*evb));\n    if (evb == NULL) {\n        nc_free(event);\n        status = close(evp);\n        if (status < 0) {\n            log_error(\"close evp %d failed, ignored: %s\", evp, strerror(errno));\n        }\n        return NULL;\n    }\n\n    evb->evp = evp;\n    evb->event = event;\n    evb->nevent = nevent;\n    evb->cb = cb;\n\n    log_debug(LOG_INFO, \"evp %d with nevent %d\", evb->evp, evb->nevent);\n\n    return evb;\n}\n\nvoid\nevent_base_destroy(struct event_base *evb)\n{\n    int status;\n\n    if (evb == NULL) {\n        return;\n    }\n\n    ASSERT(evb->evp >= 0);\n\n    nc_free(evb->event);\n\n    status = close(evb->evp);\n    if (status < 0) {\n        log_error(\"close evp %d failed, ignored: %s\", evb->evp, strerror(errno));\n    }\n    evb->evp = -1;\n\n    nc_free(evb);\n}\n\nint\nevent_add_in(struct event_base *evb, struct conn *c)\n{\n    return 0;\n}\n\nint\nevent_del_in(struct event_base *evb, struct conn *c)\n{\n    return 0;\n}\n\nint\nevent_add_out(struct event_base *evb, struct conn *c)\n{\n    int status;\n    int evp = evb->evp;\n\n    ASSERT(evp > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n\n    if (c->send_active) {\n        return 0;\n    }\n\n    status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN | POLLOUT, c);\n    if (status < 0) {\n        log_error(\"port associate on evp %d sd %d failed: %s\", evp, c->sd,\n                  strerror(errno));\n    } else {\n        c->send_active = 1;\n    }\n\n    return status;\n}\n\nint\nevent_del_out(struct event_base *evb, struct conn *c)\n{\n    int status;\n    int evp = evb->evp;\n\n    ASSERT(evp > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n\n    if (!c->send_active) {\n        return 0;\n    }\n\n    status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN, c);\n    if (status < 0) {\n        log_error(\"port associate on evp %d sd %d failed: %s\", evp, c->sd,\n                  strerror(errno));\n    } else {\n        c->send_active = 0;\n    }\n\n    return status;\n}\n\nint\nevent_add_conn(struct event_base *evb, struct conn *c)\n{\n    int status;\n    int evp = evb->evp;\n\n    ASSERT(evp > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(!c->recv_active);\n    ASSERT(!c->send_active);\n\n    status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN | POLLOUT, c);\n    if (status < 0) {\n        log_error(\"port associate on evp %d sd %d failed: %s\", evp, c->sd,\n                  strerror(errno));\n    } else {\n        c->send_active = 1;\n        c->recv_active = 1;\n    }\n\n    return status;\n}\n\nint\nevent_del_conn(struct event_base *evb, struct conn *c)\n{\n    int status;\n    int evp = evb->evp;\n\n    ASSERT(evp > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n\n    if (!c->send_active && !c->recv_active) {\n        return 0;\n    }\n\n    /*\n     * Removes the association of an object with a port. The association\n     * is also removed if the port gets closed.\n     *\n     * On failure, we check for ENOENT errno because it is likely that we\n     * are deleting this connection after it was returned from the event\n     * loop and before we had a chance of reactivating it by calling\n     * port_associate() on it.\n     */\n    status = port_dissociate(evp, PORT_SOURCE_FD, c->sd);\n    if (status < 0 && errno != ENOENT) {\n        log_error(\"port dissociate evp %d sd %d failed: %s\", evp, c->sd,\n                  strerror(errno));\n        return status;\n    }\n\n    c->recv_active = 0;\n    c->send_active = 0;\n\n    return 0;\n}\n\nstatic int\nevent_reassociate(struct event_base *evb, struct conn *c)\n{\n    int status, events;\n    int evp = evb->evp;\n\n    ASSERT(evp > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n\n    if (c->send_active) {\n        events = POLLIN | POLLOUT;\n    } else {\n        events = POLLIN;\n    }\n\n    status = port_associate(evp, PORT_SOURCE_FD, c->sd, events , c);\n    if (status < 0) {\n        log_error(\"port associate on evp %d sd %d failed: %s\", evp, c->sd,\n                  strerror(errno));\n    }\n\n    return status;\n}\n\nint\nevent_wait(struct event_base *evb, int timeout)\n{\n    int evp = evb->evp;\n    port_event_t *event = evb->event;\n    int nevent = evb->nevent;\n    struct timespec ts, *tsp;\n\n    ASSERT(evp > 0);\n    ASSERT(event != NULL);\n    ASSERT(nevent > 0);\n\n    /* port_getn should block indefinitely if timeout < 0 */\n    if (timeout < 0) {\n        tsp = NULL;\n    } else {\n        tsp = &ts;\n        tsp->tv_sec = timeout / 1000LL;\n        tsp->tv_nsec = (timeout % 1000LL) * 1000000LL;\n    }\n\n    for (;;) {\n        int i, status;\n        unsigned int nreturned = 1;\n\n        /*\n         * port_getn() retrieves multiple events from a port. A port_getn()\n         * call will block until at least nreturned events is triggered. On\n         * a successful return event[] is populated with triggered events\n         * up to the maximum sized allowed by nevent. The number of entries\n         * actually placed in event[] is saved in nreturned, which may be\n         * more than what we asked for but less than nevent.\n         */\n        status = port_getn(evp, event, nevent, &nreturned, tsp);\n        if (status < 0) {\n            if (errno == EINTR || errno == EAGAIN) {\n                continue;\n            }\n\n            /*\n             * ETIME - The time interval expired before the expected number\n             * of events have been posted to the port or nreturned is updated\n             * with the number of returned port_event_t structures in event[]\n             */\n            if (errno != ETIME) {\n                log_error(\"port getn on evp %d with %d events failed: %s\", evp,\n                          nevent, strerror(errno));\n                return -1;\n            }\n        }\n\n        if (nreturned > 0) {\n            for (i = 0; i < nreturned; i++) {\n                port_event_t *ev = &evb->event[i];\n                uint32_t events = 0;\n\n                log_debug(LOG_VVERB, \"port %04\"PRIX32\" from source %d \"\n                          \"triggered on conn %p\", ev->portev_events,\n                          ev->portev_source, ev->portev_user);\n\n                if (ev->portev_events & POLLERR) {\n                    events |= EVENT_ERR;\n                }\n\n                if (ev->portev_events & POLLIN) {\n                    events |= EVENT_READ;\n                }\n\n                if (ev->portev_events & POLLOUT) {\n                    events |= EVENT_WRITE;\n                }\n\n                if (evb->cb != NULL && events != 0) {\n                    status = evb->cb(ev->portev_user, events);\n                    if (status < 0) {\n                        continue;\n                    }\n\n                    /*\n                     * When an event for a PORT_SOURCE_FD object is retrieved,\n                     * the object no longer has an association with the port.\n                     * The event can be processed without the possibility that\n                     * another thread can retrieve a subsequent event for the\n                     * same object. After processing of the file descriptor\n                     * is completed, the port_associate() function can be\n                     * called to reassociate the object with the port.\n                     *\n                     * If the descriptor is still capable of accepting data,\n                     * this reassociation is required for the reactivation of\n                     * the data detection.\n                     */\n                    event_reassociate(evb, ev->portev_user);\n                }\n            }\n\n            return nreturned;\n        }\n\n        if (timeout == -1) {\n            log_error(\"port getn on evp %d with %d events and %d timeout \"\n                      \"returned no events\", evp, nevent, timeout);\n            return -1;\n        }\n\n        return 0;\n    }\n\n    NOT_REACHED();\n}\n\nvoid\nevent_loop_stats(event_stats_cb_t cb, void *arg)\n{\n    struct stats *st = arg;\n    int status, evp;\n    port_event_t event;\n    struct timespec ts, *tsp;\n\n    evp = port_create();\n    if (evp < 0) {\n        log_error(\"port create failed: %s\", strerror(errno));\n        return;\n    }\n\n    status = port_associate(evp, PORT_SOURCE_FD, st->sd, POLLIN, NULL);\n    if (status < 0) {\n        log_error(\"port associate on evp %d sd %d failed: %s\", evp, st->sd,\n                  strerror(errno));\n        goto error;\n    }\n\n    /* port_getn should block indefinitely if st->interval < 0 */\n    if (st->interval < 0) {\n        tsp = NULL;\n    } else {\n        tsp = &ts;\n        tsp->tv_sec = st->interval / 1000LL;\n        tsp->tv_nsec = (st->interval % 1000LL) * 1000000LL;\n    }\n\n\n    for (;;) {\n        unsigned int nreturned = 1;\n\n        status = port_getn(evp, &event, 1, &nreturned, tsp);\n        if (status != NC_OK) {\n            if (errno == EINTR || errno == EAGAIN) {\n                continue;\n            }\n\n            if (errno != ETIME) {\n                log_error(\"port getn on evp %d with m %d failed: %s\", evp,\n                          st->sd, strerror(errno));\n                goto error;\n            }\n        }\n\n        ASSERT(nreturned <= 1);\n\n        if (nreturned == 1) {\n            /* re-associate monitoring descriptor with the port */\n            status = port_associate(evp, PORT_SOURCE_FD, st->sd, POLLIN, NULL);\n            if (status < 0) {\n                log_error(\"port associate on evp %d sd %d failed: %s\", evp, st->sd,\n                          strerror(errno));\n            }\n        }\n\n        cb(st, &nreturned);\n    }\n\nerror:\n    status = close(evp);\n    if (status < 0) {\n        log_error(\"close evp %d failed, ignored: %s\", evp, strerror(errno));\n    }\n    evp = -1;\n}\n\n#endif /* NC_HAVE_EVENT_PORTS */\n"
  },
  {
    "path": "src/event/nc_kqueue.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\n#ifdef NC_HAVE_KQUEUE\n\n#include <sys/event.h>\n\nstruct event_base *\nevent_base_create(int nevent, event_cb_t cb)\n{\n    struct event_base *evb;\n    int status, kq;\n    struct kevent *change, *event;\n\n    ASSERT(nevent > 0);\n\n    kq = kqueue();\n    if (kq < 0) {\n        log_error(\"kqueue failed: %s\", strerror(errno));\n        return NULL;\n    }\n\n    change = nc_calloc(nevent, sizeof(*change));\n    if (change == NULL) {\n        status = close(kq);\n        if (status < 0) {\n            log_error(\"close kq %d failed, ignored: %s\", kq, strerror(errno));\n        }\n        return NULL;\n    }\n\n    event = nc_calloc(nevent, sizeof(*event));\n    if (event == NULL) {\n        nc_free(change);\n        status = close(kq);\n        if (status < 0) {\n            log_error(\"close kq %d failed, ignored: %s\", kq, strerror(errno));\n        }\n        return NULL;\n    }\n\n    evb = nc_alloc(sizeof(*evb));\n    if (evb == NULL) {\n        nc_free(change);\n        nc_free(event);\n        status = close(kq);\n        if (status < 0) {\n            log_error(\"close kq %d failed, ignored: %s\", kq, strerror(errno));\n        }\n        return NULL;\n    }\n\n    evb->kq = kq;\n    evb->change = change;\n    evb->nchange = 0;\n    evb->event = event;\n    evb->nevent = nevent;\n    evb->nreturned = 0;\n    evb->nprocessed = 0;\n    evb->cb = cb;\n\n    log_debug(LOG_INFO, \"kq %d with nevent %d\", evb->kq, evb->nevent);\n\n    return evb;\n}\n\nvoid\nevent_base_destroy(struct event_base *evb)\n{\n    int status;\n\n    if (evb == NULL) {\n        return;\n    }\n\n    ASSERT(evb->kq > 0);\n\n    nc_free(evb->change);\n    nc_free(evb->event);\n\n    status = close(evb->kq);\n    if (status < 0) {\n        log_error(\"close kq %d failed, ignored: %s\", evb->kq, strerror(errno));\n    }\n    evb->kq = -1;\n\n    nc_free(evb);\n}\n\nint\nevent_add_in(struct event_base *evb, struct conn *c)\n{\n    struct kevent *event;\n\n    ASSERT(evb->kq > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(evb->nchange < evb->nevent);\n\n    if (c->recv_active) {\n        return 0;\n    }\n\n    event = &evb->change[evb->nchange++];\n    EV_SET(event, c->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, c);\n\n    c->recv_active = 1;\n\n    return 0;\n}\n\nint\nevent_del_in(struct event_base *evb, struct conn *c)\n{\n    struct kevent *event;\n\n    ASSERT(evb->kq > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(evb->nchange < evb->nevent);\n\n    if (!c->recv_active) {\n        return 0;\n    }\n\n    event = &evb->change[evb->nchange++];\n    EV_SET(event, c->sd, EVFILT_READ, EV_DELETE, 0, 0, c);\n\n    c->recv_active = 0;\n\n    return 0;\n}\n\nint\nevent_add_out(struct event_base *evb, struct conn *c)\n{\n    struct kevent *event;\n\n    ASSERT(evb->kq > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n    ASSERT(evb->nchange < evb->nevent);\n\n    if (c->send_active) {\n        return 0;\n    }\n\n    event = &evb->change[evb->nchange++];\n    EV_SET(event, c->sd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, c);\n\n    c->send_active = 1;\n\n    return 0;\n}\n\nint\nevent_del_out(struct event_base *evb, struct conn *c)\n{\n    struct kevent *event;\n\n    ASSERT(evb->kq > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(c->recv_active);\n    ASSERT(evb->nchange < evb->nevent);\n\n    if (!c->send_active) {\n        return 0;\n    }\n\n    event = &evb->change[evb->nchange++];\n    EV_SET(event, c->sd, EVFILT_WRITE, EV_DELETE, 0, 0, c);\n\n    c->send_active = 0;\n\n    return 0;\n}\n\nint\nevent_add_conn(struct event_base *evb, struct conn *c)\n{\n    ASSERT(evb->kq > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(!c->recv_active);\n    ASSERT(!c->send_active);\n    ASSERT(evb->nchange < evb->nevent);\n\n    event_add_in(evb, c);\n    event_add_out(evb, c);\n\n    return 0;\n}\n\nint\nevent_del_conn(struct event_base *evb, struct conn *c)\n{\n    int i;\n\n    ASSERT(evb->kq > 0);\n    ASSERT(c != NULL);\n    ASSERT(c->sd > 0);\n    ASSERT(evb->nchange < evb->nevent);\n\n    event_del_out(evb, c);\n    event_del_in(evb, c);\n\n    /*\n     * Now, eliminate pending events for c->sd (there should be at most one\n     * other event). This is important because we will close c->sd and free\n     * c when we return.\n     */\n    for (i = evb->nprocessed + 1; i < evb->nreturned; i++) {\n        struct kevent *ev = &evb->event[i];\n        if (ev->ident == (uintptr_t)c->sd) {\n            ev->flags = 0;\n            ev->filter = 0;\n            break;\n        }\n    }\n\n    return 0;\n}\n\nint\nevent_wait(struct event_base *evb, int timeout)\n{\n    int kq = evb->kq;\n    struct timespec ts, *tsp;\n\n    ASSERT(kq > 0);\n\n    /* kevent should block indefinitely if timeout < 0 */\n    if (timeout < 0) {\n        tsp = NULL;\n    } else {\n        tsp = &ts;\n        tsp->tv_sec = timeout / 1000LL;\n        tsp->tv_nsec = (timeout % 1000LL) * 1000000LL;\n    }\n\n    for (;;) {\n        /*\n         * kevent() is used both to register new events with kqueue, and to\n         * retrieve any pending events. Changes that should be applied to the\n         * kqueue are given in the change[] and any returned events are placed\n         * in event[], up to the maximum sized allowed by nevent. The number\n         * of entries actually placed in event[] is returned by the kevent()\n         * call and saved in nreturned.\n         *\n         * Events are registered with the system by the application via a\n         * struct kevent, and an event is uniquely identified with the system\n         * by a (kq, ident, filter) tuple. This means that there can be only\n         * one (ident, filter) pair for a given kqueue.\n         */\n        evb->nreturned = kevent(kq, evb->change, evb->nchange, evb->event,\n                                evb->nevent, tsp);\n        evb->nchange = 0;\n        if (evb->nreturned > 0) {\n            for (evb->nprocessed = 0; evb->nprocessed < evb->nreturned;\n                evb->nprocessed++) {\n                struct kevent *ev = &evb->event[evb->nprocessed];\n                uint32_t events = 0;\n\n                log_debug(LOG_VVERB, \"kevent %04\"PRIX32\" with filter %d \"\n                          \"triggered on sd %d\", ev->flags, ev->filter,\n                          ev->ident);\n\n                /*\n                 * If an error occurs while processing an element of the\n                 * change[] and there is enough room in the event[], then the\n                 * event event will be placed in the eventlist with EV_ERROR\n                 * set in flags and the system error(errno) in data.\n                 */\n                if (ev->flags & EV_ERROR) {\n                   /*\n                    * Error messages that can happen, when a delete fails.\n                    *   EBADF happens when the file descriptor has been closed\n                    *   ENOENT when the file descriptor was closed and then\n                    *   reopened.\n                    *   EINVAL for some reasons not understood; EINVAL\n                    *   should not be returned ever; but FreeBSD does :-\\\n                    * An error is also indicated when a callback deletes an\n                    * event we are still processing. In that case the data\n                    * field is set to ENOENT.\n                    */\n                    if (ev->data == EBADF || ev->data == EINVAL ||\n                        ev->data == ENOENT || ev->data == EINTR) {\n                        continue;\n                    }\n                    events |= EVENT_ERR;\n                }\n\n                if (ev->filter == EVFILT_READ) {\n                    events |= EVENT_READ;\n                }\n\n                if (ev->filter == EVFILT_WRITE) {\n                    events |= EVENT_WRITE;\n                }\n\n                if (evb->cb != NULL && events != 0) {\n                    evb->cb(ev->udata, events);\n                }\n            }\n            return evb->nreturned;\n        }\n\n        if (evb->nreturned == 0) {\n            if (timeout == -1) {\n               log_error(\"kevent on kq %d with %d events and %d timeout \"\n                         \"returned no events\", kq, evb->nevent, timeout);\n                return -1;\n            }\n\n            return 0;\n        }\n\n        if (errno == EINTR) {\n            continue;\n        }\n\n        log_error(\"kevent on kq %d with %d events failed: %s\", kq, evb->nevent,\n                  strerror(errno));\n        return -1;\n    }\n\n    NOT_REACHED();\n}\n\nvoid\nevent_loop_stats(event_stats_cb_t cb, void *arg)\n{\n    struct stats *st = arg;\n    int status, kq;\n    struct kevent change, event;\n    struct timespec ts, *tsp;\n\n    kq = kqueue();\n    if (kq < 0) {\n        log_error(\"kqueue failed: %s\", strerror(errno));\n        return;\n    }\n\n    EV_SET(&change, st->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, NULL);\n\n    /* kevent should block indefinitely if st->interval < 0 */\n    if (st->interval < 0) {\n        tsp = NULL;\n    } else {\n        tsp = &ts;\n        tsp->tv_sec = st->interval / 1000LL;\n        tsp->tv_nsec = (st->interval % 1000LL) * 1000000LL;\n    }\n\n    for (;;) {\n        int nreturned;\n\n        nreturned = kevent(kq, &change, 1, &event, 1, tsp);\n        if (nreturned < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            log_error(\"kevent on kq %d with m %d failed: %s\", kq, st->sd,\n                      strerror(errno));\n            goto error;\n        }\n\n        ASSERT(nreturned <= 1);\n\n        if (nreturned == 1) {\n            struct kevent *ev = &event;\n\n            if (ev->flags & EV_ERROR) {\n                if (ev->data == EINTR) {\n                    continue;\n                }\n                log_error(\"kevent on kq %d with m %d failed: %s\", kq, st->sd,\n                          strerror(ev->data));\n                goto error;\n            }\n        }\n\n        cb(st, &nreturned);\n    }\n\nerror:\n    status = close(kq);\n    if (status < 0) {\n        log_error(\"close kq %d failed, ignored: %s\", kq, strerror(errno));\n    }\n    kq = -1;\n}\n\n#endif /* NC_HAVE_KQUEUE */\n"
  },
  {
    "path": "src/hashkit/Makefile.am",
    "content": "MAINTAINERCLEANFILES = Makefile.in\n\nAM_CPPFLAGS = -I $(top_srcdir)/src\n\nAM_CFLAGS = -Wall -Wshadow\nAM_CFLAGS += -Wno-unused-parameter -Wno-unused-value\n\nnoinst_LIBRARIES = libhashkit.a\n\nnoinst_HEADERS = nc_hashkit.h\n\nlibhashkit_a_SOURCES =\t\t\\\n\tnc_crc16.c\t\t\\\n\tnc_crc32.c\t\t\\\n\tnc_fnv.c\t\t\\\n\tnc_hsieh.c\t\t\\\n\tnc_jenkins.c\t\t\\\n\tnc_ketama.c\t\t\\\n\tnc_md5.c\t\t\\\n\tnc_modula.c\t\t\\\n\tnc_murmur.c\t\t\\\n\tnc_one_at_a_time.c\t\\\n\tnc_random.c\n"
  },
  {
    "path": "src/hashkit/nc_crc16.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\nstatic const uint16_t crc16tab[256] = {\n  0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,\n  0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,\n  0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,\n  0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,\n  0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,\n  0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,\n  0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,\n  0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,\n  0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,\n  0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,\n  0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,\n  0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,\n  0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,\n  0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,\n  0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,\n  0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,\n  0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,\n  0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,\n  0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,\n  0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,\n  0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,\n  0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,\n  0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,\n  0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,\n  0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,\n  0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,\n  0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,\n  0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,\n  0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,\n  0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,\n  0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,\n  0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,\n};\n\nuint32_t\nhash_crc16(const char *key, size_t key_length)\n{\n    uint64_t x;\n    uint32_t crc = 0;\n\n    for (x=0; x < key_length; x++) {\n        crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *key++) & 0x00ff];\n    }\n\n    return crc;\n}\n"
  },
  {
    "path": "src/hashkit/nc_crc32.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * The crc32 functions and data was originally written by Spencer\n * Garrett <srg@quick.com> and was gleaned from the PostgreSQL source\n * tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at\n * src/usr.bin/cksum/crc32.c.\n */\n\n#include <nc_core.h>\n\nstatic const uint32_t crc32tab[256] = {\n    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,\n    0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,\n    0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,\n    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,\n    0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,\n    0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,\n    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,\n    0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,\n    0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,\n    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,\n    0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,\n    0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,\n    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,\n    0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,\n    0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,\n    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,\n    0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,\n    0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,\n    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,\n    0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,\n    0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,\n    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,\n    0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,\n    0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,\n    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,\n    0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,\n    0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,\n    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,\n    0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,\n    0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,\n    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,\n    0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,\n    0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,\n    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,\n    0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,\n    0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,\n    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,\n    0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,\n    0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,\n    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,\n    0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,\n    0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,\n    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,\n    0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,\n    0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,\n    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,\n    0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,\n    0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,\n    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,\n    0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,\n    0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,\n    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,\n    0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,\n    0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,\n    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,\n    0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,\n    0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,\n    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,\n    0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,\n    0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,\n    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,\n    0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,\n    0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,\n    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,\n};\n\n/*\n * CRC-32 implementation compatible with libmemcached library. Unfortunately\n * this implementation does not return CRC-32 as per spec.\n */\nuint32_t\nhash_crc32(const char *key, size_t key_length)\n{\n    uint64_t x;\n    uint32_t crc = UINT32_MAX;\n\n    for (x = 0; x < key_length; x++) {\n        crc = (crc >> 8) ^ crc32tab[(crc ^ (uint64_t)key[x]) & 0xff];\n    }\n\n    return ((~crc) >> 16) & 0x7fff;\n}\n\nuint32_t\nhash_crc32a(const char *key, size_t key_length)\n{\n    const uint8_t *p = (const uint8_t *)key;\n    uint32_t crc;\n\n    crc = ~0U;\n    while (key_length--) {\n        crc = crc32tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);\n    }\n\n    return crc ^ ~0U;\n}\n"
  },
  {
    "path": "src/hashkit/nc_fnv.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\nstatic const uint64_t FNV_64_INIT = UINT64_C(0xcbf29ce484222325);\nstatic const uint64_t FNV_64_PRIME = UINT64_C(0x100000001b3);\nstatic const uint32_t FNV_32_INIT = 2166136261UL;\nstatic const uint32_t FNV_32_PRIME = 16777619;\n\nuint32_t\nhash_fnv1_64(const char *key, size_t key_length)\n{\n    uint64_t hash = FNV_64_INIT;\n    size_t x;\n\n    for (x = 0; x < key_length; x++) {\n      hash *= FNV_64_PRIME;\n      hash ^= (uint64_t)key[x];\n    }\n\n    return (uint32_t)hash;\n}\n\nuint32_t\nhash_fnv1a_64(const char *key, size_t key_length)\n{\n    uint32_t hash = (uint32_t) FNV_64_INIT;\n    size_t x;\n\n    for (x = 0; x < key_length; x++) {\n      uint32_t val = (uint32_t)key[x];\n      hash ^= val;\n      hash *= (uint32_t) FNV_64_PRIME;\n    }\n\n    return hash;\n}\n\nuint32_t\nhash_fnv1_32(const char *key, size_t key_length)\n{\n    uint32_t hash = FNV_32_INIT;\n    size_t x;\n\n    for (x = 0; x < key_length; x++) {\n      uint32_t val = (uint32_t)key[x];\n      hash *= FNV_32_PRIME;\n      hash ^= val;\n    }\n\n    return hash;\n}\n\nuint32_t\nhash_fnv1a_32(const char *key, size_t key_length)\n{\n    uint32_t hash = FNV_32_INIT;\n    size_t x;\n\n    for (x= 0; x < key_length; x++) {\n      uint32_t val = (uint32_t)key[x];\n      hash ^= val;\n      hash *= FNV_32_PRIME;\n    }\n\n    return hash;\n}\n"
  },
  {
    "path": "src/hashkit/nc_hashkit.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_HASHKIT_H_\n#define _NC_HASHKIT_H_\n\n#include <nc_core.h>\n#include <nc_server.h>\n\n#define HASH_CODEC(ACTION)                      \\\n    ACTION( HASH_ONE_AT_A_TIME, one_at_a_time ) \\\n    ACTION( HASH_MD5,           md5           ) \\\n    ACTION( HASH_CRC16,         crc16         ) \\\n    ACTION( HASH_CRC32,         crc32         ) \\\n    ACTION( HASH_CRC32A,        crc32a        ) \\\n    ACTION( HASH_FNV1_64,       fnv1_64       ) \\\n    ACTION( HASH_FNV1A_64,      fnv1a_64      ) \\\n    ACTION( HASH_FNV1_32,       fnv1_32       ) \\\n    ACTION( HASH_FNV1A_32,      fnv1a_32      ) \\\n    ACTION( HASH_HSIEH,         hsieh         ) \\\n    ACTION( HASH_MURMUR,        murmur        ) \\\n    ACTION( HASH_JENKINS,       jenkins       ) \\\n\n#define DIST_CODEC(ACTION)                      \\\n    ACTION( DIST_KETAMA,        ketama        ) \\\n    ACTION( DIST_MODULA,        modula        ) \\\n    ACTION( DIST_RANDOM,        random        ) \\\n\n#define DEFINE_ACTION(_hash, _name) _hash,\ntypedef enum hash_type {\n    HASH_CODEC( DEFINE_ACTION )\n    HASH_SENTINEL\n} hash_type_t;\n#undef DEFINE_ACTION\n\n#define DEFINE_ACTION(_dist, _name) _dist,\ntypedef enum dist_type {\n    DIST_CODEC( DEFINE_ACTION )\n    DIST_SENTINEL\n} dist_type_t;\n#undef DEFINE_ACTION\n\nuint32_t hash_one_at_a_time(const char *key, size_t key_length);\nvoid md5_signature(const unsigned char *key, unsigned int length, unsigned char *result);\nuint32_t hash_md5(const char *key, size_t key_length);\nuint32_t hash_crc16(const char *key, size_t key_length);\nuint32_t hash_crc32(const char *key, size_t key_length);\nuint32_t hash_crc32a(const char *key, size_t key_length);\nuint32_t hash_fnv1_64(const char *key, size_t key_length);\nuint32_t hash_fnv1a_64(const char *key, size_t key_length);\nuint32_t hash_fnv1_32(const char *key, size_t key_length);\nuint32_t hash_fnv1a_32(const char *key, size_t key_length);\nuint32_t hash_hsieh(const char *key, size_t key_length);\nuint32_t hash_jenkins(const char *key, size_t length);\nuint32_t hash_murmur(const char *key, size_t length);\n\nrstatus_t ketama_update(struct server_pool *pool);\nuint32_t ketama_dispatch(const struct continuum *continuum, uint32_t ncontinuum, uint32_t hash);\nrstatus_t modula_update(struct server_pool *pool);\nuint32_t modula_dispatch(const struct continuum *continuum, uint32_t ncontinuum, uint32_t hash);\nrstatus_t random_update(struct server_pool *pool);\nuint32_t random_dispatch(const struct continuum *continuum, uint32_t ncontinuum, uint32_t hash);\nuint32_t ketama_hash(const char *key, size_t key_length, uint32_t alignment);\n\n#endif\n"
  },
  {
    "path": "src/hashkit/nc_hsieh.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * By Paul Hsieh (C) 2004, 2005.  Covered under the Paul Hsieh\n * derivative license.\n * See: http://www.azillionmonkeys.com/qed/weblicense.html for license\n * details.\n * http://www.azillionmonkeys.com/qed/hash.html\n*/\n\n#include <nc_core.h>\n\n#undef get16bits\n#if (defined(__GNUC__) && defined(__i386__))\n#define get16bits(d) (*((const uint16_t *) (d)))\n#endif\n\n#if !defined (get16bits)\n#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\\\n                      +(uint32_t)(((const uint8_t *)(d))[0]) )\n#endif\n\nuint32_t\nhash_hsieh(const char *key, size_t key_length)\n{\n    uint32_t hash = 0, tmp;\n    int rem;\n\n    if (key_length <= 0 || key == NULL) {\n        return 0;\n    }\n\n    rem = key_length & 3;\n    key_length >>= 2;\n\n    /* Main loop */\n    for (;key_length > 0; key_length--) {\n        hash += get16bits (key);\n        tmp = (get16bits (key+2) << 11) ^ hash;\n        hash = (hash << 16) ^ tmp;\n        key += 2*sizeof (uint16_t);\n        hash += hash >> 11;\n    }\n\n    /* Handle end cases */\n    switch (rem) {\n    case 3:\n        hash += get16bits (key);\n        hash ^= hash << 16;\n        hash ^= (uint32_t)key[sizeof (uint16_t)] << 18;\n        hash += hash >> 11;\n        break;\n\n    case 2:\n        hash += get16bits (key);\n        hash ^= hash << 11;\n        hash += hash >> 17;\n        break;\n\n    case 1:\n        hash += (unsigned char)(*key);\n        hash ^= hash << 10;\n        hash += hash >> 1;\n\n    default:\n        break;\n    }\n\n    /* Force \"avalanching\" of final 127 bits */\n    hash ^= hash << 3;\n    hash += hash >> 5;\n    hash ^= hash << 4;\n    hash += hash >> 17;\n    hash ^= hash << 25;\n    hash += hash >> 6;\n\n    return hash;\n}\n"
  },
  {
    "path": "src/hashkit/nc_jenkins.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * By Bob Jenkins, 2006.  bob_jenkins@burtleburtle.net.  You may use this\n * code any way you wish, private, educational, or commercial.  It's free.\n * Use for hash table lookup, or anything where one collision in 2^^32 is\n * acceptable.  Do NOT use for cryptographic purposes.\n * http://burtleburtle.net/bob/hash/index.html\n *\n * Modified by Brian Pontz for libmemcached\n * TODO:\n * Add big endian support\n */\n\n#include <nc_core.h>\n\n#define hashsize(n) ((uint32_t)1<<(n))\n#define hashmask(n) (hashsize(n)-1)\n#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))\n\n#define mix(a,b,c) \\\n{ \\\n  a -= c;  a ^= rot(c, 4);  c += b; \\\n  b -= a;  b ^= rot(a, 6);  a += c; \\\n  c -= b;  c ^= rot(b, 8);  b += a; \\\n  a -= c;  a ^= rot(c,16);  c += b; \\\n  b -= a;  b ^= rot(a,19);  a += c; \\\n  c -= b;  c ^= rot(b, 4);  b += a; \\\n}\n\n#define final(a,b,c) \\\n{ \\\n  c ^= b; c -= rot(b,14); \\\n  a ^= c; a -= rot(c,11); \\\n  b ^= a; b -= rot(a,25); \\\n  c ^= b; c -= rot(b,16); \\\n  a ^= c; a -= rot(c,4);  \\\n  b ^= a; b -= rot(a,14); \\\n  c ^= b; c -= rot(b,24); \\\n}\n\n#define JENKINS_INITVAL 13\n\n/*\n * jenkins_hash() -- hash a variable-length key into a 32-bit value\n *  k       : the key (the unaligned variable-length array of bytes)\n *  length  : the length of the key, counting by bytes\n *  initval : can be any 4-byte value\n * Returns a 32-bit value.  Every bit of the key affects every bit of\n * the return value.  Two keys differing by one or two bits will have\n * totally different hash values.\n\n * The best hash table sizes are powers of 2.  There is no need to do\n * mod a prime (mod is sooo slow!).  If you need less than 32 bits,\n * use a bitmask.  For example, if you need only 10 bits, do\n *   h = (h & hashmask(10));\n * In which case, the hash table should have hashsize(10) elements.\n */\n\nuint32_t\nhash_jenkins(const char *key, size_t length)\n{\n  uint32_t a,b,c;                                          /* internal state */\n  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */\n\n  /* Set up the internal state */\n  a = b = c = 0xdeadbeef + ((uint32_t)length) + JENKINS_INITVAL;\n\n  u.ptr = key;\n#ifndef WORDS_BIGENDIAN\n  if ((u.i & 0x3) == 0)\n  {\n    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */\n\n    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */\n    while (length > 12)\n    {\n      a += k[0];\n      b += k[1];\n      c += k[2];\n      mix(a,b,c);\n      length -= 12;\n      k += 3;\n    }\n\n    /*----------------------------- handle the last (probably partial) block */\n    /*\n     * \"k[2]&0xffffff\" actually reads beyond the end of the string, but\n     * then masks off the part it's not allowed to read.  Because the\n     * string is aligned, the masked-off tail is in the same word as the\n     * rest of the string.  Every machine with memory protection I've seen\n     * does it on word boundaries, so is OK with this.  But VALGRIND will\n     * still catch it and complain.  The masking trick does make the hash\n     * noticeably faster for short strings (like English words).\n     */\n    switch(length)\n    {\n    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;\n    case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;\n    case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;\n    case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;\n    case 8 : b+=k[1]; a+=k[0]; break;\n    case 7 : b+=k[1]&0xffffff; a+=k[0]; break;\n    case 6 : b+=k[1]&0xffff; a+=k[0]; break;\n    case 5 : b+=k[1]&0xff; a+=k[0]; break;\n    case 4 : a+=k[0]; break;\n    case 3 : a+=k[0]&0xffffff; break;\n    case 2 : a+=k[0]&0xffff; break;\n    case 1 : a+=k[0]&0xff; break;\n    case 0 : return c;              /* zero length strings require no mixing */\n    default: return c;\n    }\n\n  }\n  else if ((u.i & 0x1) == 0)\n  {\n    const uint16_t *k = (const uint16_t *)key;         /* read 16-bit chunks */\n    const uint8_t  *k8;\n\n    /*--------------- all but last block: aligned reads and different mixing */\n    while (length > 12)\n    {\n      a += k[0] + (((uint32_t)k[1])<<16);\n      b += k[2] + (((uint32_t)k[3])<<16);\n      c += k[4] + (((uint32_t)k[5])<<16);\n      mix(a,b,c);\n      length -= 12;\n      k += 6;\n    }\n\n    /*----------------------------- handle the last (probably partial) block */\n    k8 = (const uint8_t *)k;\n    switch(length)\n    {\n    case 12: c+=k[4]+(((uint32_t)k[5])<<16);\n             b+=k[2]+(((uint32_t)k[3])<<16);\n             a+=k[0]+(((uint32_t)k[1])<<16);\n             break;\n    case 11: c+=((uint32_t)k8[10])<<16;     /* fall through */\n    case 10: c+=k[4];\n             b+=k[2]+(((uint32_t)k[3])<<16);\n             a+=k[0]+(((uint32_t)k[1])<<16);\n             break;\n    case 9 : c+=k8[8];                      /* fall through */\n    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);\n             a+=k[0]+(((uint32_t)k[1])<<16);\n             break;\n    case 7 : b+=((uint32_t)k8[6])<<16;      /* fall through */\n    case 6 : b+=k[2];\n             a+=k[0]+(((uint32_t)k[1])<<16);\n             break;\n    case 5 : b+=k8[4];                      /* fall through */\n    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);\n             break;\n    case 3 : a+=((uint32_t)k8[2])<<16;      /* fall through */\n    case 2 : a+=k[0];\n             break;\n    case 1 : a+=k8[0];\n             break;\n    case 0 : return c;                     /* zero length requires no mixing */\n    default: return c;\n    }\n\n  }\n  else\n  {                        /* need to read the key one byte at a time */\n#endif /* little endian */\n    const uint8_t *k = (const uint8_t *)key;\n\n    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */\n    while (length > 12)\n    {\n      a += k[0];\n      a += ((uint32_t)k[1])<<8;\n      a += ((uint32_t)k[2])<<16;\n      a += ((uint32_t)k[3])<<24;\n      b += k[4];\n      b += ((uint32_t)k[5])<<8;\n      b += ((uint32_t)k[6])<<16;\n      b += ((uint32_t)k[7])<<24;\n      c += k[8];\n      c += ((uint32_t)k[9])<<8;\n      c += ((uint32_t)k[10])<<16;\n      c += ((uint32_t)k[11])<<24;\n      mix(a,b,c);\n      length -= 12;\n      k += 12;\n    }\n\n    /*-------------------------------- last block: affect all 32 bits of (c) */\n    switch(length)                   /* all the case statements fall through */\n    {\n    case 12: c+=((uint32_t)k[11])<<24;\n    case 11: c+=((uint32_t)k[10])<<16;\n    case 10: c+=((uint32_t)k[9])<<8;\n    case 9 : c+=k[8];\n    case 8 : b+=((uint32_t)k[7])<<24;\n    case 7 : b+=((uint32_t)k[6])<<16;\n    case 6 : b+=((uint32_t)k[5])<<8;\n    case 5 : b+=k[4];\n    case 4 : a+=((uint32_t)k[3])<<24;\n    case 3 : a+=((uint32_t)k[2])<<16;\n    case 2 : a+=((uint32_t)k[1])<<8;\n    case 1 : a+=k[0];\n             break;\n    case 0 : return c;\n    default : return c;\n    }\n#ifndef WORDS_BIGENDIAN\n  }\n#endif\n\n  final(a,b,c);\n  return c;\n}\n"
  },
  {
    "path": "src/hashkit/nc_ketama.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_hashkit.h>\n\n#define KETAMA_CONTINUUM_ADDITION   10  /* # extra slots to build into continuum */\n#define KETAMA_POINTS_PER_SERVER    160 /* 40 points per hash */\n#define KETAMA_MAX_HOSTLEN          273 /* 273 is 255(domain or ip)+1(:)+5(port)+1(-)+10(uint32)+1(\\0) */\n\nuint32_t\nketama_hash(const char *key, size_t key_length, uint32_t alignment)\n{\n    unsigned char results[16];\n\n    md5_signature((unsigned char*)key, key_length, results);\n\n    return ((uint32_t) (results[3 + alignment * 4] & 0xFF) << 24)\n        | ((uint32_t) (results[2 + alignment * 4] & 0xFF) << 16)\n        | ((uint32_t) (results[1 + alignment * 4] & 0xFF) << 8)\n        | (results[0 + alignment * 4] & 0xFF);\n}\n\nstatic int\nketama_item_cmp(const void *t1, const void *t2)\n{\n    const struct continuum *ct1 = t1, *ct2 = t2;\n\n    if (ct1->value == ct2->value) {\n        return 0;\n    } else if (ct1->value > ct2->value) {\n        return 1;\n    } else {\n        return -1;\n    }\n}\n\nrstatus_t\nketama_update(struct server_pool *pool)\n{\n    uint32_t nserver;             /* # server - live and dead */\n    uint32_t nlive_server;        /* # live server */\n    uint32_t pointer_per_server;  /* pointers per server proportional to weight */\n    uint32_t pointer_per_hash;    /* pointers per hash */\n    uint32_t pointer_counter;     /* # pointers on continuum */\n    uint32_t pointer_index;       /* pointer index */\n    uint32_t points_per_server;   /* points per server */\n    uint32_t continuum_index;     /* continuum index */\n    uint32_t continuum_addition;  /* extra space in the continuum */\n    uint32_t server_index;        /* server index */\n    uint32_t value;               /* continuum value */\n    uint32_t total_weight;        /* total live server weight */\n    int64_t now;                  /* current timestamp in usec */\n\n    ASSERT(array_n(&pool->server) > 0);\n\n    now = nc_usec_now();\n    if (now < 0) {\n        return NC_ERROR;\n    }\n\n    /*\n     * Count live servers and total weight, and also update the next time to\n     * rebuild the distribution\n     */\n    nserver = array_n(&pool->server);\n    nlive_server = 0;\n    total_weight = 0;\n    pool->next_rebuild = 0LL;\n    for (server_index = 0; server_index < nserver; server_index++) {\n        struct server *server = array_get(&pool->server, server_index);\n\n        if (pool->auto_eject_hosts) {\n            if (server->next_retry <= now) {\n                server->next_retry = 0LL;\n                nlive_server++;\n            } else if (pool->next_rebuild == 0LL ||\n                       server->next_retry < pool->next_rebuild) {\n                pool->next_rebuild = server->next_retry;\n            }\n        } else {\n            nlive_server++;\n        }\n\n        ASSERT(server->weight > 0);\n\n        /* count weight only for live servers */\n        if (!pool->auto_eject_hosts || server->next_retry <= now) {\n            total_weight += server->weight;\n        }\n    }\n\n    pool->nlive_server = nlive_server;\n\n    if (nlive_server == 0) {\n        log_debug(LOG_DEBUG, \"no live servers for pool %\"PRIu32\" '%.*s'\",\n                  pool->idx, pool->name.len, pool->name.data);\n\n        return NC_OK;\n    }\n    log_debug(LOG_DEBUG, \"%\"PRIu32\" of %\"PRIu32\" servers are live for pool \"\n              \"%\"PRIu32\" '%.*s'\", nlive_server, nserver, pool->idx,\n              pool->name.len, pool->name.data);\n\n    continuum_addition = KETAMA_CONTINUUM_ADDITION;\n    points_per_server = KETAMA_POINTS_PER_SERVER;\n    /*\n     * Allocate the continuum for the pool, the first time, and every time we\n     * add a new server to the pool\n     */\n    if (nlive_server > pool->nserver_continuum) {\n        struct continuum *continuum;\n        uint32_t nserver_continuum = nlive_server + continuum_addition;\n        uint32_t ncontinuum = nserver_continuum * points_per_server;\n\n        continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum);\n        if (continuum == NULL) {\n            return NC_ENOMEM;\n        }\n\n        pool->continuum = continuum;\n        pool->nserver_continuum = nserver_continuum;\n        /* pool->ncontinuum is initialized later as it could be <= ncontinuum */\n    }\n\n    /*\n     * Build a continuum with the servers that are live and points from\n     * these servers that are proportial to their weight\n     */\n    continuum_index = 0;\n    pointer_counter = 0;\n    for (server_index = 0; server_index < nserver; server_index++) {\n        struct server *server;\n        float pct;\n\n        server = array_get(&pool->server, server_index);\n\n        if (pool->auto_eject_hosts && server->next_retry > now) {\n            continue;\n        }\n\n        pct = (float)server->weight / (float)total_weight;\n        pointer_per_server = (uint32_t) ((floorf((float) (pct * KETAMA_POINTS_PER_SERVER / 4 * (float)nlive_server + 0.0000000001))) * 4);\n        pointer_per_hash = 4;\n\n        log_debug(LOG_VERB, \"%.*s weight %\"PRIu32\" of %\"PRIu32\" \"\n                  \"pct %0.5f points per server %\"PRIu32\"\",\n                  server->name.len, server->name.data, server->weight,\n                  total_weight, pct, pointer_per_server);\n\n        for (pointer_index = 1;\n             pointer_index <= pointer_per_server / pointer_per_hash;\n             pointer_index++) {\n\n            char host[KETAMA_MAX_HOSTLEN]= \"\";\n            size_t hostlen;\n            uint32_t x;\n\n            hostlen = snprintf(host, KETAMA_MAX_HOSTLEN, \"%.*s-%u\",\n                               server->name.len, server->name.data,\n                               pointer_index - 1);\n            if (hostlen >= KETAMA_MAX_HOSTLEN) {\n                // > The generated string has a length of at most n-1, leaving space for the additional terminating null character.\n                // Not really important since this should never get hit in practice according to https://devblogs.microsoft.com/oldnewthing/20120412-00/?p=7873\n                hostlen = KETAMA_MAX_HOSTLEN - 1;\n                log_error(\"Unexpectedly forced to truncate a hostname in ketama pool to %d characters for %.*s\", KETAMA_MAX_HOSTLEN - 1, KETAMA_MAX_HOSTLEN - 1, host);\n            }\n\n            for (x = 0; x < pointer_per_hash; x++) {\n                value = ketama_hash(host, hostlen, x);\n                pool->continuum[continuum_index].index = server_index;\n                pool->continuum[continuum_index++].value = value;\n            }\n        }\n        pointer_counter += pointer_per_server;\n    }\n\n    pool->ncontinuum = pointer_counter;\n    qsort(pool->continuum, pool->ncontinuum, sizeof(*pool->continuum),\n          ketama_item_cmp);\n\n    for (pointer_index = 0;\n         pointer_index < ((nlive_server * KETAMA_POINTS_PER_SERVER) - 1);\n         pointer_index++) {\n        if (pointer_index + 1 >= pointer_counter) {\n            break;\n        }\n        ASSERT(pool->continuum[pointer_index].value <=\n               pool->continuum[pointer_index + 1].value);\n    }\n\n    log_debug(LOG_VERB, \"updated pool %\"PRIu32\" '%.*s' with %\"PRIu32\" of \"\n              \"%\"PRIu32\" servers live in %\"PRIu32\" slots and %\"PRIu32\" \"\n              \"active points in %\"PRIu32\" slots\", pool->idx,\n              pool->name.len, pool->name.data, nlive_server, nserver,\n              pool->nserver_continuum, pool->ncontinuum,\n              (pool->nserver_continuum + continuum_addition) * points_per_server);\n\n    return NC_OK;\n}\n\nuint32_t\nketama_dispatch(const struct continuum *continuum, uint32_t ncontinuum, uint32_t hash)\n{\n    const struct continuum *begin, *end, *left, *right, *middle;\n\n    ASSERT(continuum != NULL);\n    ASSERT(ncontinuum != 0);\n\n    begin = left = continuum;\n    end = right = continuum + ncontinuum;\n\n    while (left < right) {\n        middle = left + (right - left) / 2;\n        if (middle->value < hash) {\n          left = middle + 1;\n        } else {\n          right = middle;\n        }\n    }\n\n    if (right == end) {\n        right = begin;\n    }\n\n    return right->index;\n}\n"
  },
  {
    "path": "src/hashkit/nc_md5.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\n/*\n * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.\n * MD5 Message-Digest Algorithm (RFC 1321).\n *\n * Homepage: http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5\n *\n * Author: Alexander Peslyak, better known as Solar Designer <solar at openwall.com>\n */\n\n#include <string.h>\n\ntypedef unsigned int MD5_u32plus;\n\ntypedef struct {\n    MD5_u32plus lo, hi;\n    MD5_u32plus a, b, c, d;\n    unsigned char buffer[64];\n    MD5_u32plus block[16];\n} MD5_CTX;\n\n/*\n * The basic MD5 functions.\n *\n * F and G are optimized compared to their RFC 1321 definitions for\n * architectures that lack an AND-NOT instruction, just like in Colin Plumb's\n * implementation.\n */\n#define F(x, y, z)\t\t\t((z) ^ ((x) & ((y) ^ (z))))\n#define G(x, y, z)\t\t\t((y) ^ ((z) & ((x) ^ (y))))\n#define H(x, y, z)\t\t\t((x) ^ (y) ^ (z))\n#define I(x, y, z)\t\t\t((y) ^ ((x) | ~(z)))\n\n/*\n * The MD5 transformation for all four rounds.\n */\n#define STEP(f, a, b, c, d, x, t, s)                            \\\n\t(a) += f((b), (c), (d)) + (x) + (t);                        \\\n\t(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s))));  \\\n\t(a) += (b);\n\n/*\n * SET reads 4 input bytes in little-endian byte order and stores them\n * in a properly aligned word in host byte order.\n *\n * The check for little-endian architectures that tolerate unaligned\n * memory accesses is just an optimization.  Nothing will break if it\n * doesn't work.\n */\n#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)\n#define SET(n) \\\n    (*(MD5_u32plus *)&ptr[(n) * 4])\n#define GET(n) \\\n    SET(n)\n#else\n#define SET(n)                                  \\\n    (ctx->block[(n)] =                          \\\n    (MD5_u32plus)ptr[(n) * 4] |                 \\\n    ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) |      \\\n    ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) |     \\\n    ((MD5_u32plus)ptr[(n) * 4 + 3] << 24))\n#define GET(n) \\\n    (ctx->block[(n)])\n#endif\n\n/*\n * This processes one or more 64-byte data blocks, but does NOT update\n * the bit counters.  There are no alignment requirements.\n */\nstatic const void *\nbody(MD5_CTX *ctx, const void *data, unsigned long size)\n{\n    const unsigned char *ptr;\n    MD5_u32plus a, b, c, d;\n    MD5_u32plus saved_a, saved_b, saved_c, saved_d;\n\n    ptr = data;\n\n    a = ctx->a;\n    b = ctx->b;\n    c = ctx->c;\n    d = ctx->d;\n\n\tdo {\n        saved_a = a;\n        saved_b = b;\n        saved_c = c;\n        saved_d = d;\n\n        /* Round 1 */\n        STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)\n        STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)\n        STEP(F, c, d, a, b, SET(2), 0x242070db, 17)\n        STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)\n        STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)\n        STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)\n        STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)\n        STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)\n        STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)\n        STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)\n        STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)\n        STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)\n        STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)\n        STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)\n        STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)\n        STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)\n\n        /* Round 2 */\n        STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)\n        STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)\n        STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)\n        STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)\n        STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)\n        STEP(G, d, a, b, c, GET(10), 0x02441453, 9)\n        STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)\n        STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)\n        STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)\n        STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)\n        STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)\n        STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)\n        STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)\n        STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)\n        STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)\n        STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)\n\n        /* Round 3 */\n        STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)\n        STEP(H, d, a, b, c, GET(8), 0x8771f681, 11)\n        STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)\n        STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23)\n        STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)\n        STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11)\n        STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)\n        STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23)\n        STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)\n        STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11)\n        STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)\n        STEP(H, b, c, d, a, GET(6), 0x04881d05, 23)\n        STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)\n        STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11)\n        STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)\n        STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23)\n\n        /* Round 4 */\n        STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)\n        STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)\n        STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)\n        STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)\n        STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)\n        STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)\n        STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)\n        STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)\n        STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)\n        STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)\n        STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)\n        STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)\n        STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)\n        STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)\n        STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)\n        STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)\n\n        a += saved_a;\n        b += saved_b;\n        c += saved_c;\n        d += saved_d;\n\n        ptr += 64;\n    } while (size -= 64);\n\n    ctx->a = a;\n    ctx->b = b;\n    ctx->c = c;\n    ctx->d = d;\n\n    return ptr;\n}\n\nvoid\nMD5_Init(MD5_CTX *ctx)\n{\n    ctx->a = 0x67452301;\n    ctx->b = 0xefcdab89;\n    ctx->c = 0x98badcfe;\n    ctx->d = 0x10325476;\n\n    ctx->lo = 0;\n    ctx->hi = 0;\n}\n\nvoid\nMD5_Update(MD5_CTX *ctx, const void *data, unsigned long size)\n{\n    MD5_u32plus saved_lo;\n    unsigned long used, free;\n\n    saved_lo = ctx->lo;\n    if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) {\n        ctx->hi++;\n    }\n    ctx->hi += size >> 29;\n\n    used = saved_lo & 0x3f;\n\n    if (used) {\n        free = 64 - used;\n\n        if (size < free) {\n            memcpy(&ctx->buffer[used], data, size);\n            return;\n        }\n\n        memcpy(&ctx->buffer[used], data, free);\n        data = (const unsigned char *)data + free;\n        size -= free;\n        body(ctx, ctx->buffer, 64);\n    }\n\n    if (size >= 64) {\n        data = body(ctx, data, size & ~(unsigned long)0x3f);\n        size &= 0x3f;\n    }\n\n    memcpy(ctx->buffer, data, size);\n}\n\nvoid\nMD5_Final(unsigned char *result, MD5_CTX *ctx)\n{\n    unsigned long used, free;\n\n    used = ctx->lo & 0x3f;\n\n    ctx->buffer[used++] = 0x80;\n\n    free = 64 - used;\n\n    if (free < 8) {\n        memset(&ctx->buffer[used], 0, free);\n        body(ctx, ctx->buffer, 64);\n        used = 0;\n        free = 64;\n    }\n\n    memset(&ctx->buffer[used], 0, free - 8);\n\n    ctx->lo <<= 3;\n    ctx->buffer[56] = ctx->lo;\n    ctx->buffer[57] = ctx->lo >> 8;\n    ctx->buffer[58] = ctx->lo >> 16;\n    ctx->buffer[59] = ctx->lo >> 24;\n    ctx->buffer[60] = ctx->hi;\n    ctx->buffer[61] = ctx->hi >> 8;\n    ctx->buffer[62] = ctx->hi >> 16;\n    ctx->buffer[63] = ctx->hi >> 24;\n\n    body(ctx, ctx->buffer, 64);\n\n    result[0] = ctx->a;\n    result[1] = ctx->a >> 8;\n    result[2] = ctx->a >> 16;\n    result[3] = ctx->a >> 24;\n    result[4] = ctx->b;\n    result[5] = ctx->b >> 8;\n    result[6] = ctx->b >> 16;\n    result[7] = ctx->b >> 24;\n    result[8] = ctx->c;\n    result[9] = ctx->c >> 8;\n    result[10] = ctx->c >> 16;\n    result[11] = ctx->c >> 24;\n    result[12] = ctx->d;\n    result[13] = ctx->d >> 8;\n    result[14] = ctx->d >> 16;\n    result[15] = ctx->d >> 24;\n\n    memset(ctx, 0, sizeof(*ctx));\n}\n\n/*\n * Just a simple method for getting the signature\n * result must be == 16\n */\nvoid\nmd5_signature(const unsigned char *key, unsigned long length, unsigned char *result)\n{\n    MD5_CTX my_md5;\n\n    MD5_Init(&my_md5);\n    (void)MD5_Update(&my_md5, key, length);\n    MD5_Final(result, &my_md5);\n}\n\nuint32_t\nhash_md5(const char *key, size_t key_length)\n{\n    unsigned char results[16];\n\n    md5_signature((const unsigned char*)key, (unsigned long)key_length, results);\n\n    return ((uint32_t) (results[3] & 0xFF) << 24) |\n           ((uint32_t) (results[2] & 0xFF) << 16) |\n           ((uint32_t) (results[1] & 0xFF) << 8) |\n           (results[0] & 0xFF);\n}\n"
  },
  {
    "path": "src/hashkit/nc_modula.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_hashkit.h>\n\n#define MODULA_CONTINUUM_ADDITION   10  /* # extra slots to build into continuum */\n#define MODULA_POINTS_PER_SERVER    1\n\nrstatus_t\nmodula_update(struct server_pool *pool)\n{\n    uint32_t nserver;             /* # server - live and dead */\n    uint32_t nlive_server;        /* # live server */\n    uint32_t pointer_per_server;  /* pointers per server proportional to weight */\n    uint32_t pointer_counter;     /* # pointers on continuum */\n    uint32_t points_per_server;   /* points per server */\n    uint32_t continuum_index;     /* continuum index */\n    uint32_t continuum_addition;  /* extra space in the continuum */\n    uint32_t server_index;        /* server index */\n    uint32_t weight_index;        /* weight index */\n    uint32_t total_weight;        /* total live server weight */\n    int64_t now;                  /* current timestamp in usec */\n\n    now = nc_usec_now();\n    if (now < 0) {\n        return NC_ERROR;\n    }\n\n    nserver = array_n(&pool->server);\n    nlive_server = 0;\n    total_weight = 0;\n    pool->next_rebuild = 0LL;\n\n    for (server_index = 0; server_index < nserver; server_index++) {\n        struct server *server = array_get(&pool->server, server_index);\n\n        if (pool->auto_eject_hosts) {\n            if (server->next_retry <= now) {\n                server->next_retry = 0LL;\n                nlive_server++;\n            } else if (pool->next_rebuild == 0LL ||\n                       server->next_retry < pool->next_rebuild) {\n                pool->next_rebuild = server->next_retry;\n            }\n        } else {\n            nlive_server++;\n        }\n\n        ASSERT(server->weight > 0);\n\n        /* count weight only for live servers */\n        if (!pool->auto_eject_hosts || server->next_retry <= now) {\n            total_weight += server->weight;\n        }\n    }\n\n    pool->nlive_server = nlive_server;\n\n    if (nlive_server == 0) {\n        ASSERT(pool->continuum != NULL);\n        ASSERT(pool->ncontinuum != 0);\n\n        log_debug(LOG_DEBUG, \"no live servers for pool %\"PRIu32\" '%.*s'\",\n                  pool->idx, pool->name.len, pool->name.data);\n\n        return NC_OK;\n    }\n    log_debug(LOG_DEBUG, \"%\"PRIu32\" of %\"PRIu32\" servers are live for pool \"\n              \"%\"PRIu32\" '%.*s'\", nlive_server, nserver, pool->idx,\n              pool->name.len, pool->name.data);\n\n    continuum_addition = MODULA_CONTINUUM_ADDITION;\n    points_per_server = MODULA_POINTS_PER_SERVER;\n\n    /*\n     * Allocate the continuum for the pool, the first time, and every time we\n     * add a new server to the pool\n     */\n    if (total_weight > pool->nserver_continuum) {\n        struct continuum *continuum;\n        uint32_t nserver_continuum = total_weight + MODULA_CONTINUUM_ADDITION;\n        uint32_t ncontinuum = nserver_continuum *  MODULA_POINTS_PER_SERVER;\n\n        continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum);\n        if (continuum == NULL) {\n            return NC_ENOMEM;\n        }\n\n        pool->continuum = continuum;\n        pool->nserver_continuum = nserver_continuum;\n        /* pool->ncontinuum is initialized later as it could be <= ncontinuum */\n    }\n\n    /* update the continuum with the servers that are live */\n    continuum_index = 0;\n    pointer_counter = 0;\n    for (server_index = 0; server_index < nserver; server_index++) {\n        struct server *server = array_get(&pool->server, server_index);\n\n        if (pool->auto_eject_hosts && server->next_retry > now) {\n            continue;\n        }\n\n        for (weight_index = 0; weight_index < server->weight; weight_index++) {\n            pointer_per_server = 1;\n\n            pool->continuum[continuum_index].index = server_index;\n            pool->continuum[continuum_index++].value = 0;\n\n            pointer_counter += pointer_per_server;\n        }\n    }\n    pool->ncontinuum = pointer_counter;\n\n    log_debug(LOG_VERB, \"updated pool %\"PRIu32\" '%.*s' with %\"PRIu32\" of \"\n              \"%\"PRIu32\" servers live in %\"PRIu32\" slots and %\"PRIu32\" \"\n              \"active points in %\"PRIu32\" slots\", pool->idx,\n              pool->name.len, pool->name.data, nlive_server, nserver,\n              pool->nserver_continuum, pool->ncontinuum,\n              (pool->nserver_continuum + continuum_addition) * points_per_server);\n\n    return NC_OK;\n\n}\n\nuint32_t\nmodula_dispatch(const struct continuum *continuum, uint32_t ncontinuum, uint32_t hash)\n{\n    const struct continuum *c;\n\n    ASSERT(continuum != NULL);\n    ASSERT(ncontinuum != 0);\n\n    c = continuum + hash % ncontinuum;\n\n    return c->index;\n}\n"
  },
  {
    "path": "src/hashkit/nc_murmur.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * \"Murmur\" hash provided by Austin, tanjent@gmail.com\n * http://murmurhash.googlepages.com/\n *\n * Note - This code makes a few assumptions about how your machine behaves -\n *\n * 1. We can read a 4-byte value from any address without crashing\n * 2. sizeof(int) == 4\n *\n * And it has a few limitations -\n * 1. It will not work incrementally.\n * 2. It will not produce the same results on little-endian and big-endian\n *  machines.\n *\n *  Updated to murmur2 hash - BP\n */\n\n#include <nc_core.h>\n\nuint32_t\nhash_murmur(const char *key, size_t length)\n{\n    /*\n     * 'm' and 'r' are mixing constants generated offline.  They're not\n     * really 'magic', they just happen to work well.\n     */\n\n    const unsigned int m = 0x5bd1e995;\n    const uint32_t seed = (0xdeadbeef * (uint32_t)length);\n    const int r = 24;\n\n\n    /* Initialize the hash to a 'random' value */\n\n    uint32_t h = seed ^ (uint32_t)length;\n\n    /* Mix 4 bytes at a time into the hash */\n\n    const unsigned char * data = (const unsigned char *)key;\n\n    while (length >= 4) {\n        unsigned int k = *(unsigned int *)data;\n\n        k *= m;\n        k ^= k >> r;\n        k *= m;\n\n        h *= m;\n        h ^= k;\n\n        data += 4;\n        length -= 4;\n    }\n\n    /* Handle the last few bytes of the input array */\n\n    switch(length) {\n    case 3:\n        h ^= ((uint32_t)data[2]) << 16;\n\n    case 2:\n        h ^= ((uint32_t)data[1]) << 8;\n\n    case 1:\n        h ^= data[0];\n        h *= m;\n\n    default:\n        break;\n    };\n\n    /*\n     * Do a few final mixes of the hash to ensure the last few bytes are\n     * well-incorporated.\n     */\n\n    h ^= h >> 13;\n    h *= m;\n    h ^= h >> 15;\n\n    return h;\n}\n"
  },
  {
    "path": "src/hashkit/nc_one_at_a_time.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * HashKit\n * Copyright (C) 2009 Brian Aker\n * All rights reserved.\n *\n * Use and distribution licensed under the BSD license.  See\n * the COPYING file in the parent directory for full text.\n */\n\n/*\n * This has is Jenkin's \"One at A time Hash\".\n * http://en.wikipedia.org/wiki/Jenkins_hash_function\n */\n\n#include <nc_core.h>\n\nuint32_t\nhash_one_at_a_time(const char *key, size_t key_length)\n{\n    const char *ptr = key;\n    uint32_t value = 0;\n\n    while (key_length--) {\n        uint32_t val = (uint32_t) *ptr++;\n        value += val;\n        value += (value << 10);\n        value ^= (value >> 6);\n    }\n    value += (value << 3);\n    value ^= (value >> 11);\n    value += (value << 15);\n\n    return value;\n}\n"
  },
  {
    "path": "src/hashkit/nc_random.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_hashkit.h>\n\n#define RANDOM_CONTINUUM_ADDITION   10  /* # extra slots to build into continuum */\n#define RANDOM_POINTS_PER_SERVER    1\n\nrstatus_t\nrandom_update(struct server_pool *pool)\n{\n    uint32_t nserver;             /* # server - live and dead */\n    uint32_t nlive_server;        /* # live server */\n    uint32_t pointer_per_server;  /* pointers per server proportional to weight */\n    uint32_t pointer_counter;     /* # pointers on continuum */\n    uint32_t points_per_server;   /* points per server */\n    uint32_t continuum_index;     /* continuum index */\n    uint32_t continuum_addition;  /* extra space in the continuum */\n    uint32_t server_index;        /* server index */\n    int64_t now;                  /* current timestamp in usec */\n\n    now = nc_usec_now();\n    if (now < 0) {\n        return NC_ERROR;\n    }\n\n    nserver = array_n(&pool->server);\n    nlive_server = 0;\n    pool->next_rebuild = 0LL;\n\n    for (server_index = 0; server_index < nserver; server_index++) {\n        struct server *server = array_get(&pool->server, server_index);\n\n        if (pool->auto_eject_hosts) {\n            if (server->next_retry <= now) {\n                server->next_retry = 0LL;\n                nlive_server++;\n            } else if (pool->next_rebuild == 0LL ||\n                       server->next_retry < pool->next_rebuild) {\n                pool->next_rebuild = server->next_retry;\n            }\n        } else {\n            nlive_server++;\n        }\n    }\n\n    pool->nlive_server = nlive_server;\n\n    if (nlive_server == 0) {\n        ASSERT(pool->continuum != NULL);\n        ASSERT(pool->ncontinuum != 0);\n\n        log_debug(LOG_DEBUG, \"no live servers for pool %\"PRIu32\" '%.*s'\",\n                  pool->idx, pool->name.len, pool->name.data);\n\n        return NC_OK;\n    }\n    log_debug(LOG_DEBUG, \"%\"PRIu32\" of %\"PRIu32\" servers are live for pool \"\n              \"%\"PRIu32\" '%.*s'\", nlive_server, nserver, pool->idx,\n              pool->name.len, pool->name.data);\n\n    continuum_addition = RANDOM_CONTINUUM_ADDITION;\n    points_per_server = RANDOM_POINTS_PER_SERVER;\n\n    /*\n     * Allocate the continuum for the pool, the first time, and every time we\n     * add a new server to the pool\n     */\n    if (nlive_server > pool->nserver_continuum) {\n        struct continuum *continuum;\n        uint32_t nserver_continuum = nlive_server + RANDOM_CONTINUUM_ADDITION;\n        uint32_t ncontinuum = nserver_continuum *  RANDOM_POINTS_PER_SERVER;\n\n        continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum);\n        if (continuum == NULL) {\n            return NC_ENOMEM;\n        }\n\n        srandom((uint32_t)time(NULL));\n\n        pool->continuum = continuum;\n        pool->nserver_continuum = nserver_continuum;\n        /* pool->ncontinuum is initialized later as it could be <= ncontinuum */\n    }\n\n    /* update the continuum with the servers that are live */\n    continuum_index = 0;\n    pointer_counter = 0;\n    for (server_index = 0; server_index < nserver; server_index++) {\n        struct server *server = array_get(&pool->server, server_index);\n\n        if (pool->auto_eject_hosts && server->next_retry > now) {\n            continue;\n        }\n\n        pointer_per_server = 1;\n\n        pool->continuum[continuum_index].index = server_index;\n        pool->continuum[continuum_index++].value = 0;\n\n        pointer_counter += pointer_per_server;\n    }\n    pool->ncontinuum = pointer_counter;\n\n    log_debug(LOG_VERB, \"updated pool %\"PRIu32\" '%.*s' with %\"PRIu32\" of \"\n              \"%\"PRIu32\" servers live in %\"PRIu32\" slots and %\"PRIu32\" \"\n              \"active points in %\"PRIu32\" slots\", pool->idx,\n              pool->name.len, pool->name.data, nlive_server, nserver,\n              pool->nserver_continuum, pool->ncontinuum,\n              (pool->nserver_continuum + continuum_addition) * points_per_server);\n\n    return NC_OK;\n\n}\n\nuint32_t\nrandom_dispatch(const struct continuum *continuum, uint32_t ncontinuum, uint32_t hash)\n{\n    const struct continuum *c;\n\n    ASSERT(continuum != NULL);\n    ASSERT(ncontinuum != 0);\n\n    c = continuum + random() % ncontinuum;\n\n    return c->index;\n}\n"
  },
  {
    "path": "src/nc.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <signal.h>\n#include <getopt.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/utsname.h>\n\n#include <nc_core.h>\n#include <nc_conf.h>\n#include <nc_signal.h>\n\n#define NC_CONF_PATH        \"conf/nutcracker.yml\"\n\n#define NC_LOG_DEFAULT      LOG_NOTICE\n#define NC_LOG_MIN          LOG_EMERG\n#define NC_LOG_MAX          LOG_PVERB\n#define NC_LOG_PATH         NULL\n\n#define NC_STATS_PORT       STATS_PORT\n#define NC_STATS_ADDR       STATS_ADDR\n#define NC_STATS_INTERVAL   STATS_INTERVAL\n\n#define NC_PID_FILE         NULL\n\n#define NC_MBUF_SIZE        MBUF_SIZE\n#define NC_MBUF_MIN_SIZE    MBUF_MIN_SIZE\n#define NC_MBUF_MAX_SIZE    MBUF_MAX_SIZE\n\nstatic int show_help;\nstatic int show_version;\nstatic int test_conf;\nstatic int daemonize;\nstatic int describe_stats;\n\nstatic const struct option long_options[] = {\n    { \"help\",           no_argument,        NULL,   'h' },\n    { \"version\",        no_argument,        NULL,   'V' },\n    { \"test-conf\",      no_argument,        NULL,   't' },\n    { \"daemonize\",      no_argument,        NULL,   'd' },\n    { \"describe-stats\", no_argument,        NULL,   'D' },\n    { \"verbose\",        required_argument,  NULL,   'v' },\n    { \"output\",         required_argument,  NULL,   'o' },\n    { \"conf-file\",      required_argument,  NULL,   'c' },\n    { \"stats-port\",     required_argument,  NULL,   's' },\n    { \"stats-interval\", required_argument,  NULL,   'i' },\n    { \"stats-addr\",     required_argument,  NULL,   'a' },\n    { \"pid-file\",       required_argument,  NULL,   'p' },\n    { \"mbuf-size\",      required_argument,  NULL,   'm' },\n    { NULL,             0,                  NULL,    0  }\n};\n\nstatic const char short_options[] = \"hVtdDv:o:c:s:i:a:p:m:\";\n\nstatic rstatus_t\nnc_daemonize(int dump_core)\n{\n    rstatus_t status;\n    pid_t pid, sid;\n    int fd;\n\n    pid = fork();\n    switch (pid) {\n    case -1:\n        log_error(\"fork() failed: %s\", strerror(errno));\n        return NC_ERROR;\n\n    case 0:\n        break;\n\n    default:\n        /* parent terminates */\n        _exit(0);\n    }\n\n    /* 1st child continues and becomes the session leader */\n\n    sid = setsid();\n    if (sid < 0) {\n        log_error(\"setsid() failed: %s\", strerror(errno));\n        return NC_ERROR;\n    }\n\n    if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {\n        log_error(\"signal(SIGHUP, SIG_IGN) failed: %s\", strerror(errno));\n        return NC_ERROR;\n    }\n\n    pid = fork();\n    switch (pid) {\n    case -1:\n        log_error(\"fork() failed: %s\", strerror(errno));\n        return NC_ERROR;\n\n    case 0:\n        break;\n\n    default:\n        /* 1st child terminates */\n        _exit(0);\n    }\n\n    /* 2nd child continues */\n\n    /* change working directory */\n    if (dump_core == 0) {\n        status = chdir(\"/\");\n        if (status < 0) {\n            log_error(\"chdir(\\\"/\\\") failed: %s\", strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    /* clear file mode creation mask */\n    umask(0);\n\n    /* redirect stdin, stdout and stderr to \"/dev/null\" */\n\n    fd = open(\"/dev/null\", O_RDWR);\n    if (fd < 0) {\n        log_error(\"open(\\\"/dev/null\\\") failed: %s\", strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = dup2(fd, STDIN_FILENO);\n    if (status < 0) {\n        log_error(\"dup2(%d, STDIN) failed: %s\", fd, strerror(errno));\n        close(fd);\n        return NC_ERROR;\n    }\n\n    status = dup2(fd, STDOUT_FILENO);\n    if (status < 0) {\n        log_error(\"dup2(%d, STDOUT) failed: %s\", fd, strerror(errno));\n        close(fd);\n        return NC_ERROR;\n    }\n\n    status = dup2(fd, STDERR_FILENO);\n    if (status < 0) {\n        log_error(\"dup2(%d, STDERR) failed: %s\", fd, strerror(errno));\n        close(fd);\n        return NC_ERROR;\n    }\n\n    if (fd > STDERR_FILENO) {\n        status = close(fd);\n        if (status < 0) {\n            log_error(\"close(%d) failed: %s\", fd, strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    return NC_OK;\n}\n\nstatic void\nnc_print_run(const struct instance *nci)\n{\n    int status;\n    struct utsname name;\n\n    status = uname(&name);\n    if (status < 0) {\n        loga(\"nutcracker-%s started on pid %d\", NC_VERSION_STRING, nci->pid);\n    } else {\n        loga(\"nutcracker-%s built for %s %s %s started on pid %d\",\n             NC_VERSION_STRING, name.sysname, name.release, name.machine,\n             nci->pid);\n    }\n\n    loga(\"run, rabbit run / dig that hole, forget the sun / \"\n         \"and when at last the work is done / don't sit down / \"\n         \"it's time to dig another one\");\n}\n\nstatic void\nnc_print_done(void)\n{\n    loga(\"done, rabbit done\");\n}\n\nstatic void\nnc_show_usage(void)\n{\n    log_stderr(\n        \"Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file]\" CRLF\n        \"                  [-c conf file] [-s stats port] [-a stats addr]\" CRLF\n        \"                  [-i stats interval] [-p pid file] [-m mbuf size]\" CRLF\n        \"\");\n    log_stderr(\n        \"Options:\" CRLF\n        \"  -h, --help             : this help\" CRLF\n        \"  -V, --version          : show version and exit\" CRLF\n        \"  -t, --test-conf        : test configuration for syntax errors and exit\" CRLF\n        \"  -d, --daemonize        : run as a daemon\" CRLF\n        \"  -D, --describe-stats   : print stats description and exit\");\n    log_stderr(\n        \"  -v, --verbose=N        : set logging level (default: %d, min: %d, max: %d)\" CRLF\n        \"  -o, --output=S         : set logging file (default: %s)\" CRLF\n        \"  -c, --conf-file=S      : set configuration file (default: %s)\" CRLF\n        \"  -s, --stats-port=N     : set stats monitoring port (default: %d)\" CRLF\n        \"  -a, --stats-addr=S     : set stats monitoring ip (default: %s)\" CRLF\n        \"  -i, --stats-interval=N : set stats aggregation interval in msec (default: %d msec)\" CRLF\n        \"  -p, --pid-file=S       : set pid file (default: %s)\" CRLF\n        \"  -m, --mbuf-size=N      : set size of mbuf chunk in bytes (default: %d bytes)\" CRLF\n        \"\",\n        NC_LOG_DEFAULT, NC_LOG_MIN, NC_LOG_MAX,\n        NC_LOG_PATH != NULL ? NC_LOG_PATH : \"stderr\",\n        NC_CONF_PATH,\n        NC_STATS_PORT, NC_STATS_ADDR, NC_STATS_INTERVAL,\n        NC_PID_FILE != NULL ? NC_PID_FILE : \"off\",\n        NC_MBUF_SIZE);\n}\n\nstatic rstatus_t\nnc_create_pidfile(struct instance *nci)\n{\n    char pid[NC_UINTMAX_MAXLEN];\n    int fd, pid_len;\n    ssize_t n;\n\n    fd = open(nci->pid_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);\n    if (fd < 0) {\n        log_error(\"opening pid file '%s' failed: %s\", nci->pid_filename,\n                  strerror(errno));\n        return NC_ERROR;\n    }\n    nci->pidfile = 1;\n\n    pid_len = nc_snprintf(pid, NC_UINTMAX_MAXLEN, \"%d\", nci->pid);\n\n    n = nc_write(fd, pid, pid_len);\n    if (n < 0) {\n        log_error(\"write to pid file '%s' failed: %s\", nci->pid_filename,\n                  strerror(errno));\n        return NC_ERROR;\n    }\n\n    close(fd);\n\n    return NC_OK;\n}\n\nstatic void\nnc_remove_pidfile(struct instance *nci)\n{\n    int status;\n\n    status = unlink(nci->pid_filename);\n    if (status < 0) {\n        log_error(\"unlink of pid file '%s' failed, ignored: %s\",\n                  nci->pid_filename, strerror(errno));\n    }\n}\n\nstatic void\nnc_set_default_options(struct instance *nci)\n{\n    int status;\n\n    nci->ctx = NULL;\n\n    nci->log_level = NC_LOG_DEFAULT;\n    nci->log_filename = NC_LOG_PATH;\n\n    nci->conf_filename = NC_CONF_PATH;\n\n    nci->stats_port = NC_STATS_PORT;\n    nci->stats_addr = NC_STATS_ADDR;\n    nci->stats_interval = NC_STATS_INTERVAL;\n\n    status = nc_gethostname(nci->hostname, NC_MAXHOSTNAMELEN);\n    if (status < 0) {\n        log_warn(\"gethostname failed, ignored: %s\", strerror(errno));\n        nc_snprintf(nci->hostname, NC_MAXHOSTNAMELEN, \"unknown\");\n    }\n    nci->hostname[NC_MAXHOSTNAMELEN - 1] = '\\0';\n\n    nci->mbuf_chunk_size = NC_MBUF_SIZE;\n\n    nci->pid = (pid_t)-1;\n    nci->pid_filename = NULL;\n    nci->pidfile = 0;\n}\n\nstatic rstatus_t\nnc_get_options(int argc, char **argv, struct instance *nci)\n{\n    int c, value;\n\n    opterr = 0;\n\n    for (;;) {\n        c = getopt_long(argc, argv, short_options, long_options, NULL);\n        if (c == -1) {\n            /* no more options */\n            break;\n        }\n\n        switch (c) {\n        case 'h':\n            show_version = 1;\n            show_help = 1;\n            break;\n\n        case 'V':\n            show_version = 1;\n            break;\n\n        case 't':\n            test_conf = 1;\n            break;\n\n        case 'd':\n            daemonize = 1;\n            break;\n\n        case 'D':\n            describe_stats = 1;\n            show_version = 1;\n            break;\n\n        case 'v':\n            value = nc_atoi(optarg, strlen(optarg));\n            if (value < 0) {\n                log_stderr(\"nutcracker: option -v requires a number\");\n                return NC_ERROR;\n            }\n            nci->log_level = value;\n            break;\n\n        case 'o':\n            nci->log_filename = optarg;\n            break;\n\n        case 'c':\n            nci->conf_filename = optarg;\n            break;\n\n        case 's':\n            value = nc_atoi(optarg, strlen(optarg));\n            if (value < 0) {\n                log_stderr(\"nutcracker: option -s requires a number\");\n                return NC_ERROR;\n            }\n            if (!nc_valid_port(value)) {\n                log_stderr(\"nutcracker: option -s value %d is not a valid \"\n                           \"port\", value);\n                return NC_ERROR;\n            }\n\n            nci->stats_port = (uint16_t)value;\n            break;\n\n        case 'i':\n            value = nc_atoi(optarg, strlen(optarg));\n            if (value < 0) {\n                log_stderr(\"nutcracker: option -i requires a number\");\n                return NC_ERROR;\n            }\n\n            nci->stats_interval = value;\n            break;\n\n        case 'a':\n            nci->stats_addr = optarg;\n            break;\n\n        case 'p':\n            nci->pid_filename = optarg;\n            break;\n\n        case 'm':\n            value = nc_atoi(optarg, strlen(optarg));\n            if (value <= 0) {\n                log_stderr(\"nutcracker: option -m requires a non-zero number\");\n                return NC_ERROR;\n            }\n\n            if (value < NC_MBUF_MIN_SIZE || value > NC_MBUF_MAX_SIZE) {\n                log_stderr(\"nutcracker: mbuf chunk size must be between %d and\"\n                           \" %d bytes\", NC_MBUF_MIN_SIZE, NC_MBUF_MAX_SIZE);\n                return NC_ERROR;\n            }\n\n            nci->mbuf_chunk_size = (size_t)value;\n            break;\n\n        case '?':\n            switch (optopt) {\n            case 'o':\n            case 'c':\n            case 'p':\n                log_stderr(\"nutcracker: option -%c requires a file name\",\n                           optopt);\n                break;\n\n            case 'm':\n            case 'v':\n            case 's':\n            case 'i':\n                log_stderr(\"nutcracker: option -%c requires a number\", optopt);\n                break;\n\n            case 'a':\n                log_stderr(\"nutcracker: option -%c requires a string\", optopt);\n                break;\n\n            default:\n                log_stderr(\"nutcracker: invalid option -- '%c'\", optopt);\n                break;\n            }\n            return NC_ERROR;\n\n        default:\n            log_stderr(\"nutcracker: invalid option -- '%c'\", optopt);\n            return NC_ERROR;\n\n        }\n    }\n\n    return NC_OK;\n}\n\n/*\n * Returns true if configuration file has a valid syntax, otherwise\n * returns false\n */\nstatic bool\nnc_test_conf(const struct instance *nci)\n{\n    struct conf *cf;\n\n    cf = conf_create(nci->conf_filename);\n    if (cf == NULL) {\n        log_stderr(\"nutcracker: configuration file '%s' syntax is invalid\",\n                   nci->conf_filename);\n        return false;\n    }\n\n    conf_destroy(cf);\n\n    log_stderr(\"nutcracker: configuration file '%s' syntax is ok\",\n               nci->conf_filename);\n    return true;\n}\n\nstatic rstatus_t\nnc_pre_run(struct instance *nci)\n{\n    rstatus_t status;\n\n    status = log_init(nci->log_level, nci->log_filename);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    if (daemonize) {\n        status = nc_daemonize(1);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    nci->pid = getpid();\n\n    status = signal_init();\n    if (status != NC_OK) {\n        return status;\n    }\n\n    if (nci->pid_filename) {\n        status = nc_create_pidfile(nci);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    nc_print_run(nci);\n\n    return NC_OK;\n}\n\nstatic void\nnc_post_run(struct instance *nci)\n{\n    if (nci->pidfile) {\n        nc_remove_pidfile(nci);\n    }\n\n    signal_deinit();\n\n    nc_print_done();\n\n    log_deinit();\n}\n\nstatic void\nnc_run(struct instance *nci)\n{\n    rstatus_t status;\n    struct context *ctx;\n\n    ctx = core_start(nci);\n    if (ctx == NULL) {\n        return;\n    }\n\n    /* run rabbit run */\n    for (;;) {\n        status = core_loop(ctx);\n        if (status != NC_OK) {\n            break;\n        }\n    }\n\n    core_stop(ctx);\n}\n\nint\nmain(int argc, char **argv)\n{\n    rstatus_t status;\n    struct instance nci;\n\n    nc_set_default_options(&nci);\n\n    status = nc_get_options(argc, argv, &nci);\n    if (status != NC_OK) {\n        nc_show_usage();\n        exit(1);\n    }\n\n    if (show_version) {\n        log_stderr(\"This is nutcracker-%s\", NC_VERSION_STRING);\n#if NC_HAVE_EPOLL\n        log_stderr(\"async event backend: epoll\");\n#elif NC_HAVE_KQUEUE\n        log_stderr(\"async event backend: kqueue\");\n#elif NC_HAVE_EVENT_PORTS\n        log_stderr(\"async event backend: event_ports\");\n#else\n        log_stderr(\"async event backend: unknown\");\n#endif\n#if HAVE_ASSERT_PANIC || HAVE_ASSERT_LOG\n        log_stderr(\"debugging assertions are enabled (--enable-debug=yes|full), nutcracker may be less efficient\");\n#endif\n        // Log a blank line after the version\n        log_stderr(\"\");\n\n        if (show_help) {\n            nc_show_usage();\n        }\n\n        if (describe_stats) {\n            stats_describe();\n        }\n\n        exit(0);\n    }\n\n    if (test_conf) {\n        if (!nc_test_conf(&nci)) {\n            exit(1);\n        }\n        exit(0);\n    }\n\n    status = nc_pre_run(&nci);\n    if (status != NC_OK) {\n        nc_post_run(&nci);\n        exit(1);\n    }\n\n    nc_run(&nci);\n\n    nc_post_run(&nci);\n\n    exit(1);\n}\n"
  },
  {
    "path": "src/nc_array.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n\n#include <nc_core.h>\n\nstruct array *\narray_create(uint32_t n, size_t size)\n{\n    struct array *a;\n\n    ASSERT(n != 0 && size != 0);\n\n    a = nc_alloc(sizeof(*a));\n    if (a == NULL) {\n        return NULL;\n    }\n\n    a->elem = nc_alloc(n * size);\n    if (a->elem == NULL) {\n        nc_free(a);\n        return NULL;\n    }\n\n    a->nelem = 0;\n    a->size = size;\n    a->nalloc = n;\n\n    return a;\n}\n\nvoid\narray_destroy(struct array *a)\n{\n    array_deinit(a);\n    nc_free(a);\n}\n\nrstatus_t\narray_init(struct array *a, uint32_t n, size_t size)\n{\n    ASSERT(n != 0 && size != 0);\n\n    a->elem = nc_alloc(n * size);\n    if (a->elem == NULL) {\n        return NC_ENOMEM;\n    }\n\n    a->nelem = 0;\n    a->size = size;\n    a->nalloc = n;\n\n    return NC_OK;\n}\n\nvoid\narray_deinit(struct array *a)\n{\n    ASSERT(a->nelem == 0);\n\n    if (a->elem != NULL) {\n        nc_free(a->elem);\n    }\n}\n\nuint32_t\narray_idx(const struct array *a, const void *elem)\n{\n    const uint8_t *p, *q;\n    uint32_t off, idx;\n\n    ASSERT(elem >= a->elem);\n\n    p = a->elem;\n    q = elem;\n    off = (uint32_t)(q - p);\n\n    ASSERT(off % (uint32_t)a->size == 0);\n\n    idx = off / (uint32_t)a->size;\n\n    return idx;\n}\n\nvoid *\narray_push(struct array *a)\n{\n    void *elem, *new;\n    size_t size;\n\n    if (a->nelem == a->nalloc) {\n\n        /* the array is full; allocate new array */\n        size = a->size * a->nalloc;\n        new = nc_realloc(a->elem, 2 * size);\n        if (new == NULL) {\n            return NULL;\n        }\n\n        a->elem = new;\n        a->nalloc *= 2;\n    }\n\n    elem = (uint8_t *)a->elem + a->size * a->nelem;\n    a->nelem++;\n\n    return elem;\n}\n\nvoid *\narray_pop(struct array *a)\n{\n    void *elem;\n\n    ASSERT(a->nelem != 0);\n\n    a->nelem--;\n    elem = (uint8_t *)a->elem + a->size * a->nelem;\n\n    return elem;\n}\n\nvoid *\narray_get(const struct array *a, uint32_t idx)\n{\n    void *elem;\n\n    ASSERT(a->nelem != 0);\n    ASSERT(idx < a->nelem);\n\n    elem = (uint8_t *)a->elem + (a->size * idx);\n\n    return elem;\n}\n\nvoid *\narray_top(const struct array *a)\n{\n    ASSERT(a->nelem != 0);\n\n    return array_get(a, a->nelem - 1);\n}\n\nvoid\narray_swap(struct array *a, struct array *b)\n{\n    struct array tmp;\n\n    tmp = *a;\n    *a = *b;\n    *b = tmp;\n}\n\n/*\n * Sort nelem elements of the array in ascending order based on the\n * compare comparator.\n */\nvoid\narray_sort(struct array *a, array_compare_t compare)\n{\n    ASSERT(a->nelem != 0);\n\n    qsort(a->elem, a->nelem, a->size, compare);\n}\n\n/*\n * Calls the func once for each element in the array as long as func returns\n * success. On failure short-circuits and returns the error status.\n */\nrstatus_t\narray_each(const struct array *a, array_each_t func, void *data)\n{\n    uint32_t i, nelem;\n\n    ASSERT(array_n(a) != 0);\n    ASSERT(func != NULL);\n\n    for (i = 0, nelem = array_n(a); i < nelem; i++) {\n        void *elem = array_get(a, i);\n        rstatus_t status;\n\n        status = func(elem, data);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    return NC_OK;\n}\n"
  },
  {
    "path": "src/nc_array.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_ARRAY_H_\n#define _NC_ARRAY_H_\n\n#include <nc_core.h>\n\ntypedef int (*array_compare_t)(const void *, const void *);\ntypedef rstatus_t (*array_each_t)(void *, void *);\n\nstruct array {\n    uint32_t nelem;  /* # element */\n    void     *elem;  /* element */\n    size_t   size;   /* element size */\n    uint32_t nalloc; /* # allocated element */\n};\n\n#define null_array { 0, NULL, 0, 0 }\n\nstatic inline void\narray_null(struct array *a)\n{\n    a->nelem = 0;\n    a->elem = NULL;\n    a->size = 0;\n    a->nalloc = 0;\n}\n\nstatic inline void\narray_set(struct array *a, void *elem, size_t size, uint32_t nalloc)\n{\n    a->nelem = 0;\n    a->elem = elem;\n    a->size = size;\n    a->nalloc = nalloc;\n}\n\nstatic inline uint32_t\narray_n(const struct array *a)\n{\n    return a->nelem;\n}\n\nstruct array *array_create(uint32_t n, size_t size);\nvoid array_destroy(struct array *a);\nrstatus_t array_init(struct array *a, uint32_t n, size_t size);\nvoid array_deinit(struct array *a);\n\nuint32_t array_idx(const struct array *a, const void *elem);\nvoid *array_push(struct array *a);\nvoid *array_pop(struct array *a);\nvoid *array_get(const struct array *a, uint32_t idx);\nvoid *array_top(const struct array *a);\nvoid array_swap(struct array *a, struct array *b);\nvoid array_sort(struct array *a, array_compare_t compare);\nrstatus_t array_each(const struct array *a, array_each_t func, void *data);\n\n#endif\n"
  },
  {
    "path": "src/nc_client.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_client.h>\n\nvoid\nclient_ref(struct conn *conn, void *owner)\n{\n    struct server_pool *pool = owner;\n\n    ASSERT(conn->client && !conn->proxy);\n    ASSERT(conn->owner == NULL);\n\n    /*\n     * We use null pointer as the sockaddr argument in the accept() call as\n     * we are not interested in the address of the peer for the accepted\n     * connection\n     */\n    conn->family = 0;\n    conn->addrlen = 0;\n    conn->addr = NULL;\n\n    pool->nc_conn_q++;\n    TAILQ_INSERT_TAIL(&pool->c_conn_q, conn, conn_tqe);\n\n    /* owner of the client connection is the server pool */\n    conn->owner = owner;\n\n    log_debug(LOG_VVERB, \"ref conn %p owner %p into pool '%.*s'\", conn, pool,\n              pool->name.len, pool->name.data);\n}\n\nvoid\nclient_unref(struct conn *conn)\n{\n    struct server_pool *pool;\n\n    ASSERT(conn->client && !conn->proxy);\n    ASSERT(conn->owner != NULL);\n\n    pool = conn->owner;\n    conn->owner = NULL;\n\n    ASSERT(pool->nc_conn_q != 0);\n    pool->nc_conn_q--;\n    TAILQ_REMOVE(&pool->c_conn_q, conn, conn_tqe);\n\n    log_debug(LOG_VVERB, \"unref conn %p owner %p from pool '%.*s'\", conn,\n              pool, pool->name.len, pool->name.data);\n}\n\nbool\nclient_active(const struct conn *conn)\n{\n    ASSERT(conn->client && !conn->proxy);\n\n    ASSERT(TAILQ_EMPTY(&conn->imsg_q));\n\n    if (!TAILQ_EMPTY(&conn->omsg_q)) {\n        log_debug(LOG_VVERB, \"c %d is active\", conn->sd);\n        return true;\n    }\n\n    if (conn->rmsg != NULL) {\n        log_debug(LOG_VVERB, \"c %d is active\", conn->sd);\n        return true;\n    }\n\n    if (conn->smsg != NULL) {\n        log_debug(LOG_VVERB, \"c %d is active\", conn->sd);\n        return true;\n    }\n\n    log_debug(LOG_VVERB, \"c %d is inactive\", conn->sd);\n\n    return false;\n}\n\nstatic void\nclient_close_stats(struct context *ctx, struct server_pool *pool, err_t err,\n                   unsigned eof)\n{\n    stats_pool_decr(ctx, pool, client_connections);\n\n    if (eof) {\n        stats_pool_incr(ctx, pool, client_eof);\n        return;\n    }\n\n    switch (err) {\n    case EPIPE:\n    case ETIMEDOUT:\n    case ECONNRESET:\n    case ECONNABORTED:\n    case ENOTCONN:\n    case ENETDOWN:\n    case ENETUNREACH:\n    case EHOSTDOWN:\n    case EHOSTUNREACH:\n    default:\n        stats_pool_incr(ctx, pool, client_err);\n        break;\n    }\n}\n\nvoid\nclient_close(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    struct msg *msg, *nmsg; /* current and next message */\n\n    ASSERT(conn->client && !conn->proxy);\n\n    client_close_stats(ctx, conn->owner, conn->err, conn->eof);\n\n    if (conn->sd < 0) {\n        conn->unref(conn);\n        conn_put(conn);\n        return;\n    }\n\n    msg = conn->rmsg;\n    if (msg != NULL) {\n        conn->rmsg = NULL;\n\n        ASSERT(msg->peer == NULL);\n        ASSERT(msg->request && !msg->done);\n\n        log_debug(LOG_INFO, \"close c %d discarding pending req %\"PRIu64\" len \"\n                  \"%\"PRIu32\" type %d\", conn->sd, msg->id, msg->mlen,\n                  msg->type);\n\n        req_put(msg);\n    }\n\n    ASSERT(conn->smsg == NULL);\n    ASSERT(TAILQ_EMPTY(&conn->imsg_q));\n\n    for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) {\n        nmsg = TAILQ_NEXT(msg, c_tqe);\n\n        /* dequeue the message (request) from client outq */\n        conn->dequeue_outq(ctx, conn, msg);\n\n        if (msg->done) {\n            log_debug(LOG_INFO, \"close c %d discarding %s req %\"PRIu64\" len \"\n                      \"%\"PRIu32\" type %d\", conn->sd,\n                      msg->error ? \"error\": \"completed\", msg->id, msg->mlen,\n                      msg->type);\n            req_put(msg);\n        } else {\n            msg->swallow = 1;\n\n            ASSERT(msg->request);\n            ASSERT(msg->peer == NULL);\n\n            log_debug(LOG_INFO, \"close c %d schedule swallow of req %\"PRIu64\" \"\n                      \"len %\"PRIu32\" type %d\", conn->sd, msg->id, msg->mlen,\n                      msg->type);\n        }\n    }\n    ASSERT(TAILQ_EMPTY(&conn->omsg_q));\n\n    conn->unref(conn);\n\n    status = close(conn->sd);\n    if (status < 0) {\n        log_error(\"close c %d failed, ignored: %s\", conn->sd, strerror(errno));\n    }\n    conn->sd = -1;\n\n    conn_put(conn);\n}\n"
  },
  {
    "path": "src/nc_client.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_CLIENT_H_\n#define _NC_CLIENT_H_\n\n#include <nc_core.h>\n\nbool client_active(const struct conn *conn);\nvoid client_ref(struct conn *conn, void *owner);\nvoid client_unref(struct conn *conn);\nvoid client_close(struct context *ctx, struct conn *conn);\n\n#endif\n"
  },
  {
    "path": "src/nc_conf.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n#include <nc_conf.h>\n#include <nc_server.h>\n#include <proto/nc_proto.h>\n\n#define DEFINE_ACTION(_hash, _name) string(#_name),\nstatic const struct string hash_strings[] = {\n    HASH_CODEC( DEFINE_ACTION )\n    null_string\n};\n#undef DEFINE_ACTION\n\n#define DEFINE_ACTION(_hash, _name) hash_##_name,\nstatic const hash_t hash_algos[] = {\n    HASH_CODEC( DEFINE_ACTION )\n    NULL\n};\n#undef DEFINE_ACTION\n\n#define DEFINE_ACTION(_dist, _name) string(#_name),\nstatic const struct string dist_strings[] = {\n    DIST_CODEC( DEFINE_ACTION )\n    null_string\n};\n#undef DEFINE_ACTION\n\nstatic const struct command conf_commands[] = {\n    { string(\"listen\"),\n      conf_set_listen,\n      offsetof(struct conf_pool, listen) },\n\n    { string(\"hash\"),\n      conf_set_hash,\n      offsetof(struct conf_pool, hash) },\n\n    { string(\"hash_tag\"),\n      conf_set_hashtag,\n      offsetof(struct conf_pool, hash_tag) },\n\n    { string(\"distribution\"),\n      conf_set_distribution,\n      offsetof(struct conf_pool, distribution) },\n\n    { string(\"timeout\"),\n      conf_set_num,\n      offsetof(struct conf_pool, timeout) },\n\n    { string(\"backlog\"),\n      conf_set_num,\n      offsetof(struct conf_pool, backlog) },\n\n    { string(\"client_connections\"),\n      conf_set_num,\n      offsetof(struct conf_pool, client_connections) },\n\n    { string(\"redis\"),\n      conf_set_bool,\n      offsetof(struct conf_pool, redis) },\n\n    { string(\"tcpkeepalive\"),\n      conf_set_bool,\n      offsetof(struct conf_pool, tcpkeepalive) },\n\n    { string(\"reuseport\"),\n      conf_set_bool,\n      offsetof(struct conf_pool, reuseport) },\n\n    { string(\"redis_auth\"),\n      conf_set_string,\n      offsetof(struct conf_pool, redis_auth) },\n\n    { string(\"redis_db\"),\n      conf_set_num,\n      offsetof(struct conf_pool, redis_db) },\n\n    { string(\"preconnect\"),\n      conf_set_bool,\n      offsetof(struct conf_pool, preconnect) },\n\n    { string(\"auto_eject_hosts\"),\n      conf_set_bool,\n      offsetof(struct conf_pool, auto_eject_hosts) },\n\n    { string(\"server_connections\"),\n      conf_set_num,\n      offsetof(struct conf_pool, server_connections) },\n\n    { string(\"server_retry_timeout\"),\n      conf_set_num,\n      offsetof(struct conf_pool, server_retry_timeout) },\n\n    { string(\"server_failure_limit\"),\n      conf_set_num,\n      offsetof(struct conf_pool, server_failure_limit) },\n\n    { string(\"servers\"),\n      conf_add_server,\n      offsetof(struct conf_pool, server) },\n\n    null_command\n};\n\nstatic const struct string true_str = string(\"true\");\nstatic const struct string false_str = string(\"false\");\n\nstatic void\nconf_server_init(struct conf_server *cs)\n{\n    string_init(&cs->pname);\n    string_init(&cs->name);\n    string_init(&cs->addrstr);\n    cs->port = 0;\n    cs->weight = 0;\n\n    memset(&cs->info, 0, sizeof(cs->info));\n\n    cs->valid = 0;\n\n    log_debug(LOG_VVERB, \"init conf server %p\", cs);\n}\n\nstatic void\nconf_server_deinit(struct conf_server *cs)\n{\n    string_deinit(&cs->pname);\n    string_deinit(&cs->name);\n    string_deinit(&cs->addrstr);\n    cs->valid = 0;\n    log_debug(LOG_VVERB, \"deinit conf server %p\", cs);\n}\n\nrstatus_t\nconf_server_each_transform(void *elem, void *data)\n{\n    struct conf_server *cs = elem;\n    struct array *server = data;\n    struct server *s;\n\n    ASSERT(cs->valid);\n\n    s = array_push(server);\n    ASSERT(s != NULL);\n\n    s->idx = array_idx(server, s);\n    s->owner = NULL;\n\n    s->pname = cs->pname;\n    s->name = cs->name;\n    s->addrstr = cs->addrstr;\n    s->port = (uint16_t)cs->port;\n    s->weight = (uint32_t)cs->weight;\n\n    nc_memcpy(&s->info, &cs->info, sizeof(cs->info));\n\n    s->ns_conn_q = 0;\n    TAILQ_INIT(&s->s_conn_q);\n\n    s->next_retry = 0LL;\n    s->failure_count = 0;\n\n    log_debug(LOG_VERB, \"transform to server %\"PRIu32\" '%.*s'\",\n              s->idx, s->pname.len, s->pname.data);\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nconf_pool_init(struct conf_pool *cp, const struct string *name)\n{\n    rstatus_t status;\n\n    string_init(&cp->name);\n\n    string_init(&cp->listen.pname);\n    string_init(&cp->listen.name);\n    string_init(&cp->redis_auth);\n    cp->listen.port = 0;\n    memset(&cp->listen.info, 0, sizeof(cp->listen.info));\n    cp->listen.valid = 0;\n\n    cp->hash = CONF_UNSET_HASH;\n    string_init(&cp->hash_tag);\n    cp->distribution = CONF_UNSET_DIST;\n\n    cp->timeout = CONF_UNSET_NUM;\n    cp->backlog = CONF_UNSET_NUM;\n\n    cp->client_connections = CONF_UNSET_NUM;\n\n    cp->redis = CONF_UNSET_NUM;\n    cp->tcpkeepalive = CONF_UNSET_NUM;\n    cp->reuseport = CONF_UNSET_NUM;\n    cp->redis_db = CONF_UNSET_NUM;\n    cp->preconnect = CONF_UNSET_NUM;\n    cp->auto_eject_hosts = CONF_UNSET_NUM;\n    cp->server_connections = CONF_UNSET_NUM;\n    cp->server_retry_timeout = CONF_UNSET_NUM;\n    cp->server_failure_limit = CONF_UNSET_NUM;\n\n    array_null(&cp->server);\n\n    cp->valid = 0;\n\n    status = string_duplicate(&cp->name, name);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = array_init(&cp->server, CONF_DEFAULT_SERVERS,\n                        sizeof(struct conf_server));\n    if (status != NC_OK) {\n        string_deinit(&cp->name);\n        return status;\n    }\n\n    log_debug(LOG_VVERB, \"init conf pool %p, '%.*s'\", cp, name->len, name->data);\n\n    return NC_OK;\n}\n\nstatic void\nconf_pool_deinit(struct conf_pool *cp)\n{\n    string_deinit(&cp->name);\n\n    string_deinit(&cp->listen.pname);\n    string_deinit(&cp->listen.name);\n\n    if (cp->redis_auth.len > 0) {\n        string_deinit(&cp->redis_auth);\n    }\n\n    while (array_n(&cp->server) != 0) {\n        conf_server_deinit(array_pop(&cp->server));\n    }\n    array_deinit(&cp->server);\n\n    log_debug(LOG_VVERB, \"deinit conf pool %p\", cp);\n}\n\nrstatus_t\nconf_pool_each_transform(void *elem, void *data)\n{\n    rstatus_t status;\n    struct conf_pool *cp = elem;\n    struct array *server_pool = data;\n    struct server_pool *sp;\n\n    ASSERT(cp->valid);\n\n    sp = array_push(server_pool);\n    ASSERT(sp != NULL);\n\n    sp->idx = array_idx(server_pool, sp);\n    sp->ctx = NULL;\n\n    sp->p_conn = NULL;\n    sp->nc_conn_q = 0;\n    TAILQ_INIT(&sp->c_conn_q);\n\n    array_null(&sp->server);\n    sp->ncontinuum = 0;\n    sp->nserver_continuum = 0;\n    sp->continuum = NULL;\n    sp->nlive_server = 0;\n    sp->next_rebuild = 0LL;\n\n    sp->name = cp->name;\n    sp->addrstr = cp->listen.pname;\n    sp->port = (uint16_t)cp->listen.port;\n\n    nc_memcpy(&sp->info, &cp->listen.info, sizeof(cp->listen.info));\n    sp->perm = cp->listen.perm;\n\n    sp->key_hash_type = cp->hash;\n    sp->key_hash = hash_algos[cp->hash];\n    sp->dist_type = cp->distribution;\n    sp->hash_tag = cp->hash_tag;\n\n    sp->tcpkeepalive = cp->tcpkeepalive ? 1 : 0;\n    sp->reuseport = cp->reuseport ? 1 : 0;\n\n    sp->redis = cp->redis ? 1 : 0;\n    sp->timeout = cp->timeout;\n    sp->backlog = cp->backlog;\n    sp->redis_db = cp->redis_db;\n\n    sp->redis_auth = cp->redis_auth;\n    sp->require_auth = cp->redis_auth.len > 0 ? 1 : 0;\n\n    sp->client_connections = (uint32_t)cp->client_connections;\n    sp->server_connections = (uint32_t)cp->server_connections;\n    sp->server_retry_timeout = (int64_t)cp->server_retry_timeout * 1000LL;\n    sp->server_failure_limit = (uint32_t)cp->server_failure_limit;\n    sp->auto_eject_hosts = cp->auto_eject_hosts ? 1 : 0;\n    sp->preconnect = cp->preconnect ? 1 : 0;\n\n    status = server_init(&sp->server, &cp->server, sp);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    log_debug(LOG_VERB, \"transform to pool %\"PRIu32\" '%.*s'\", sp->idx,\n              sp->name.len, sp->name.data);\n\n    return NC_OK;\n}\n\nstatic void\nconf_dump(const struct conf *cf)\n{\n    uint32_t i, j, npool, nserver;\n    struct conf_pool *cp;\n    struct string *s;\n\n    npool = array_n(&cf->pool);\n    if (npool == 0) {\n        return;\n    }\n\n    log_debug(LOG_VVERB, \"%\"PRIu32\" pools in configuration file '%s'\", npool,\n              cf->fname);\n\n    for (i = 0; i < npool; i++) {\n        cp = array_get(&cf->pool, i);\n\n        log_debug(LOG_VVERB, \"%.*s\", cp->name.len, cp->name.data);\n        log_debug(LOG_VVERB, \"  listen: %.*s\",\n                  cp->listen.pname.len, cp->listen.pname.data);\n        log_debug(LOG_VVERB, \"  timeout: %d\", cp->timeout);\n        log_debug(LOG_VVERB, \"  backlog: %d\", cp->backlog);\n        log_debug(LOG_VVERB, \"  hash: %d\", cp->hash);\n        log_debug(LOG_VVERB, \"  hash_tag: \\\"%.*s\\\"\", cp->hash_tag.len,\n                  cp->hash_tag.data);\n        log_debug(LOG_VVERB, \"  distribution: %d\", cp->distribution);\n        log_debug(LOG_VVERB, \"  client_connections: %d\",\n                  cp->client_connections);\n        log_debug(LOG_VVERB, \"  redis: %d\", cp->redis);\n        log_debug(LOG_VVERB, \"  preconnect: %d\", cp->preconnect);\n        log_debug(LOG_VVERB, \"  auto_eject_hosts: %d\", cp->auto_eject_hosts);\n        log_debug(LOG_VVERB, \"  server_connections: %d\",\n                  cp->server_connections);\n        log_debug(LOG_VVERB, \"  server_retry_timeout: %d\",\n                  cp->server_retry_timeout);\n        log_debug(LOG_VVERB, \"  server_failure_limit: %d\",\n                  cp->server_failure_limit);\n\n        nserver = array_n(&cp->server);\n        log_debug(LOG_VVERB, \"  servers: %\"PRIu32\"\", nserver);\n\n        for (j = 0; j < nserver; j++) {\n            s = array_get(&cp->server, j);\n            log_debug(LOG_VVERB, \"    %.*s\", s->len, s->data);\n        }\n    }\n}\n\nstatic rstatus_t\nconf_yaml_init(struct conf *cf)\n{\n    int rv;\n\n    ASSERT(!cf->valid_parser);\n\n    rv = fseek(cf->fh, 0L, SEEK_SET);\n    if (rv < 0) {\n        log_error(\"conf: failed to seek to the beginning of file '%s': %s\",\n                  cf->fname, strerror(errno));\n        return NC_ERROR;\n    }\n\n    rv = yaml_parser_initialize(&cf->parser);\n    if (!rv) {\n        log_error(\"conf: failed (err %d) to initialize yaml parser\",\n                  cf->parser.error);\n        return NC_ERROR;\n    }\n\n    yaml_parser_set_input_file(&cf->parser, cf->fh);\n    cf->valid_parser = 1;\n\n    return NC_OK;\n}\n\nstatic void\nconf_yaml_deinit(struct conf *cf)\n{\n    if (cf->valid_parser) {\n        yaml_parser_delete(&cf->parser);\n        cf->valid_parser = 0;\n    }\n}\n\nstatic rstatus_t\nconf_token_next(struct conf *cf)\n{\n    int rv;\n\n    ASSERT(cf->valid_parser && !cf->valid_token);\n\n    rv = yaml_parser_scan(&cf->parser, &cf->token);\n    if (!rv) {\n        log_error(\"conf: failed (err %d) to scan next token\", cf->parser.error);\n        return NC_ERROR;\n    }\n    cf->valid_token = 1;\n\n    return NC_OK;\n}\n\nstatic void\nconf_token_done(struct conf *cf)\n{\n    ASSERT(cf->valid_parser);\n\n    if (cf->valid_token) {\n        yaml_token_delete(&cf->token);\n        cf->valid_token = 0;\n    }\n}\n\nstatic rstatus_t\nconf_event_next(struct conf *cf)\n{\n    int rv;\n\n    ASSERT(cf->valid_parser && !cf->valid_event);\n\n    rv = yaml_parser_parse(&cf->parser, &cf->event);\n    if (!rv) {\n        log_error(\"conf: failed (err %d) to get next event\", cf->parser.error);\n        return NC_ERROR;\n    }\n    cf->valid_event = 1;\n\n    return NC_OK;\n}\n\nstatic void\nconf_event_done(struct conf *cf)\n{\n    if (cf->valid_event) {\n        yaml_event_delete(&cf->event);\n        cf->valid_event = 0;\n    }\n}\n\nstatic rstatus_t\nconf_push_scalar(struct conf *cf)\n{\n    rstatus_t status;\n    struct string *value;\n    uint8_t *scalar;\n    uint32_t scalar_len;\n\n    scalar = cf->event.data.scalar.value;\n    scalar_len = (uint32_t)cf->event.data.scalar.length;\n    if (scalar_len == 0) {\n        return NC_ERROR;\n    }\n\n    log_debug(LOG_VVERB, \"push '%.*s'\", scalar_len, scalar);\n\n    value = array_push(&cf->arg);\n    if (value == NULL) {\n        return NC_ENOMEM;\n    }\n    string_init(value);\n\n    status = string_copy(value, scalar, scalar_len);\n    if (status != NC_OK) {\n        array_pop(&cf->arg);\n        return status;\n    }\n\n    return NC_OK;\n}\n\nstatic void\nconf_pop_scalar(struct conf *cf)\n{\n    struct string *value;\n\n    value = array_pop(&cf->arg);\n    log_debug(LOG_VVERB, \"pop '%.*s'\", value->len, value->data);\n    string_deinit(value);\n}\n\nstatic rstatus_t\nconf_handler(struct conf *cf, void *data)\n{\n    const struct command *cmd;\n    struct string *key, *value;\n    uint32_t narg;\n\n    if (array_n(&cf->arg) == 1) {\n        value = array_top(&cf->arg);\n        log_debug(LOG_VVERB, \"conf handler on '%.*s'\", value->len, value->data);\n        return conf_pool_init(data, value);\n    }\n\n    narg = array_n(&cf->arg);\n    value = array_get(&cf->arg, narg - 1);\n    key = array_get(&cf->arg, narg - 2);\n\n    log_debug(LOG_VVERB, \"conf handler on %.*s: %.*s\", key->len, key->data,\n              value->len, value->data);\n\n    for (cmd = conf_commands; cmd->name.len != 0; cmd++) {\n        const char *rv;\n\n        if (string_compare(key, &cmd->name) != 0) {\n            continue;\n        }\n\n        rv = cmd->set(cf, cmd, data);\n        if (rv != CONF_OK) {\n            log_error(\"conf: directive \\\"%.*s\\\" %s\", key->len, key->data, rv);\n            return NC_ERROR;\n        }\n\n        return NC_OK;\n    }\n\n    log_error(\"conf: directive \\\"%.*s\\\" is unknown\", key->len, key->data);\n\n    return NC_ERROR;\n}\n\nstatic rstatus_t\nconf_begin_parse(struct conf *cf)\n{\n    rstatus_t status;\n    bool done;\n\n    ASSERT(cf->sound && !cf->parsed);\n    ASSERT(cf->depth == 0);\n\n    status = conf_yaml_init(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    done = false;\n    do {\n        status = conf_event_next(cf);\n        if (status != NC_OK) {\n            return status;\n        }\n\n        log_debug(LOG_VVERB, \"next begin event %d\", cf->event.type);\n\n        switch (cf->event.type) {\n        case YAML_STREAM_START_EVENT:\n        case YAML_DOCUMENT_START_EVENT:\n            break;\n\n        case YAML_MAPPING_START_EVENT:\n            ASSERT(cf->depth < CONF_MAX_DEPTH);\n            cf->depth++;\n            done = true;\n            break;\n\n        default:\n            NOT_REACHED();\n        }\n\n        conf_event_done(cf);\n\n    } while (!done);\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nconf_end_parse(struct conf *cf)\n{\n    rstatus_t status;\n    bool done;\n\n    ASSERT(cf->sound && !cf->parsed);\n    ASSERT(cf->depth == 0);\n\n    done = false;\n    do {\n        status = conf_event_next(cf);\n        if (status != NC_OK) {\n            return status;\n        }\n\n        log_debug(LOG_VVERB, \"next end event %d\", cf->event.type);\n\n        switch (cf->event.type) {\n        case YAML_STREAM_END_EVENT:\n            done = true;\n            break;\n\n        case YAML_DOCUMENT_END_EVENT:\n            break;\n\n        default:\n            NOT_REACHED();\n        }\n\n        conf_event_done(cf);\n    } while (!done);\n\n    conf_yaml_deinit(cf);\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nconf_parse_core(struct conf *cf, void *data)\n{\n    rstatus_t status;\n    bool done, leaf, new_pool;\n\n    ASSERT(cf->sound);\n\n    status = conf_event_next(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    log_debug(LOG_VVERB, \"next event %d depth %\"PRIu32\" seq %d\", cf->event.type,\n              cf->depth, cf->seq);\n\n    done = false;\n    leaf = false;\n    new_pool = false;\n\n    switch (cf->event.type) {\n    case YAML_MAPPING_END_EVENT:\n        cf->depth--;\n        if (cf->depth == 1) {\n            conf_pop_scalar(cf);\n        } else if (cf->depth == 0) {\n            done = true;\n        }\n        break;\n\n    case YAML_MAPPING_START_EVENT:\n        cf->depth++;\n        break;\n\n    case YAML_SEQUENCE_START_EVENT:\n        cf->seq = 1;\n        break;\n\n    case YAML_SEQUENCE_END_EVENT:\n        conf_pop_scalar(cf);\n        cf->seq = 0;\n        break;\n\n    case YAML_SCALAR_EVENT:\n        status = conf_push_scalar(cf);\n        if (status != NC_OK) {\n            break;\n        }\n\n        /* take appropriate action */\n        if (cf->seq) {\n            /* for a sequence, leaf is at CONF_MAX_DEPTH */\n            ASSERT(cf->depth == CONF_MAX_DEPTH);\n            leaf = true;\n        } else if (cf->depth == CONF_ROOT_DEPTH) {\n            /* create new conf_pool */\n            data = array_push(&cf->pool);\n            if (data == NULL) {\n                status = NC_ENOMEM;\n                break;\n           }\n           new_pool = true;\n        } else if (array_n(&cf->arg) == cf->depth + 1) {\n            /* for {key: value}, leaf is at CONF_MAX_DEPTH */\n            ASSERT(cf->depth == CONF_MAX_DEPTH);\n            leaf = true;\n        }\n        break;\n\n    default:\n        NOT_REACHED();\n        break;\n    }\n\n    conf_event_done(cf);\n\n    if (status != NC_OK) {\n        return status;\n    }\n\n    if (done) {\n        /* terminating condition */\n        return NC_OK;\n    }\n\n    if (leaf || new_pool) {\n        status = conf_handler(cf, data);\n\n        if (leaf) {\n            conf_pop_scalar(cf);\n            if (!cf->seq) {\n                conf_pop_scalar(cf);\n            }\n        }\n\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    return conf_parse_core(cf, data);\n}\n\nstatic rstatus_t\nconf_parse(struct conf *cf)\n{\n    rstatus_t status;\n\n    ASSERT(cf->sound && !cf->parsed);\n    ASSERT(array_n(&cf->arg) == 0);\n\n    status = conf_begin_parse(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = conf_parse_core(cf, NULL);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = conf_end_parse(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    cf->parsed = 1;\n\n    return NC_OK;\n}\n\nstatic struct conf *\nconf_open(const char *filename)\n{\n    rstatus_t status;\n    struct conf *cf;\n    FILE *fh;\n\n    fh = fopen(filename, \"r\");\n    if (fh == NULL) {\n        log_error(\"conf: failed to open configuration '%s': %s\", filename,\n                  strerror(errno));\n        return NULL;\n    }\n\n    cf = nc_alloc(sizeof(*cf));\n    if (cf == NULL) {\n        fclose(fh);\n        return NULL;\n    }\n\n    status = array_init(&cf->arg, CONF_DEFAULT_ARGS, sizeof(struct string));\n    if (status != NC_OK) {\n        nc_free(cf);\n        fclose(fh);\n        return NULL;\n    }\n\n    status = array_init(&cf->pool, CONF_DEFAULT_POOL, sizeof(struct conf_pool));\n    if (status != NC_OK) {\n        array_deinit(&cf->arg);\n        nc_free(cf);\n        fclose(fh);\n        return NULL;\n    }\n\n    cf->fname = filename;\n    cf->fh = fh;\n    cf->depth = 0;\n    /* parser, event, and token are initialized later */\n    cf->seq = 0;\n    cf->valid_parser = 0;\n    cf->valid_event = 0;\n    cf->valid_token = 0;\n    cf->sound = 0;\n    cf->parsed = 0;\n    cf->valid = 0;\n\n    log_debug(LOG_VVERB, \"opened conf '%s'\", filename);\n\n    return cf;\n}\n\nstatic rstatus_t\nconf_validate_document(struct conf *cf)\n{\n    rstatus_t status;\n    uint32_t count;\n    bool done;\n\n    status = conf_yaml_init(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    count = 0;\n    done = false;\n    do {\n        yaml_document_t document;\n        yaml_node_t *node;\n        int rv;\n\n        rv = yaml_parser_load(&cf->parser, &document);\n        if (!rv) {\n            log_error(\"conf: failed (err %d) to get the next yaml document\",\n                      cf->parser.error);\n            conf_yaml_deinit(cf);\n            return NC_ERROR;\n        }\n\n        node = yaml_document_get_root_node(&document);\n        if (node == NULL) {\n            done = true;\n        } else {\n            count++;\n        }\n\n        yaml_document_delete(&document);\n    } while (!done);\n\n    conf_yaml_deinit(cf);\n\n    if (count != 1) {\n        log_error(\"conf: '%s' must contain only 1 document; found %\"PRIu32\" \"\n                  \"documents\", cf->fname, count);\n        return NC_ERROR;\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nconf_validate_tokens(struct conf *cf)\n{\n    rstatus_t status;\n    bool done, error;\n    int type;\n\n    status = conf_yaml_init(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    done = false;\n    error = false;\n    do {\n        status = conf_token_next(cf);\n        if (status != NC_OK) {\n            return status;\n        }\n        type = cf->token.type;\n\n        switch (type) {\n        case YAML_NO_TOKEN:\n            error = true;\n            log_error(\"conf: no token (%d) is disallowed\", type);\n            break;\n\n        case YAML_VERSION_DIRECTIVE_TOKEN:\n            error = true;\n            log_error(\"conf: version directive token (%d) is disallowed\", type);\n            break;\n\n        case YAML_TAG_DIRECTIVE_TOKEN:\n            error = true;\n            log_error(\"conf: tag directive token (%d) is disallowed\", type);\n            break;\n\n        case YAML_DOCUMENT_START_TOKEN:\n            error = true;\n            log_error(\"conf: document start token (%d) is disallowed\", type);\n            break;\n\n        case YAML_DOCUMENT_END_TOKEN:\n            error = true;\n            log_error(\"conf: document end token (%d) is disallowed\", type);\n            break;\n\n        case YAML_FLOW_SEQUENCE_START_TOKEN:\n            error = true;\n            log_error(\"conf: flow sequence start token (%d) is disallowed\", type);\n            break;\n\n        case YAML_FLOW_SEQUENCE_END_TOKEN:\n            error = true;\n            log_error(\"conf: flow sequence end token (%d) is disallowed\", type);\n            break;\n\n        case YAML_FLOW_MAPPING_START_TOKEN:\n            error = true;\n            log_error(\"conf: flow mapping start token (%d) is disallowed\", type);\n            break;\n\n        case YAML_FLOW_MAPPING_END_TOKEN:\n            error = true;\n            log_error(\"conf: flow mapping end token (%d) is disallowed\", type);\n            break;\n\n        case YAML_FLOW_ENTRY_TOKEN:\n            error = true;\n            log_error(\"conf: flow entry token (%d) is disallowed\", type);\n            break;\n\n        case YAML_ALIAS_TOKEN:\n            error = true;\n            log_error(\"conf: alias token (%d) is disallowed\", type);\n            break;\n\n        case YAML_ANCHOR_TOKEN:\n            error = true;\n            log_error(\"conf: anchor token (%d) is disallowed\", type);\n            break;\n\n        case YAML_TAG_TOKEN:\n            error = true;\n            log_error(\"conf: tag token (%d) is disallowed\", type);\n            break;\n\n        case YAML_BLOCK_SEQUENCE_START_TOKEN:\n        case YAML_BLOCK_MAPPING_START_TOKEN:\n        case YAML_BLOCK_END_TOKEN:\n        case YAML_BLOCK_ENTRY_TOKEN:\n            break;\n\n        case YAML_KEY_TOKEN:\n        case YAML_VALUE_TOKEN:\n        case YAML_SCALAR_TOKEN:\n            break;\n\n        case YAML_STREAM_START_TOKEN:\n            break;\n\n        case YAML_STREAM_END_TOKEN:\n            done = true;\n            log_debug(LOG_VVERB, \"conf '%s' has valid tokens\", cf->fname);\n            break;\n\n        default:\n            error = true;\n            log_error(\"conf: unknown token (%d) is disallowed\", type);\n            break;\n        }\n\n        conf_token_done(cf);\n    } while (!done && !error);\n\n    conf_yaml_deinit(cf);\n\n    return !error ? NC_OK : NC_ERROR;\n}\n\nstatic rstatus_t\nconf_validate_structure(struct conf *cf)\n{\n    rstatus_t status;\n    int type, depth;\n    uint32_t i, count[CONF_MAX_DEPTH + 1];\n    bool done, error, seq;\n\n    status = conf_yaml_init(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    done = false;\n    error = false;\n    seq = false;\n    depth = 0;\n    for (i = 0; i < CONF_MAX_DEPTH + 1; i++) {\n        count[i] = 0;\n    }\n\n    /*\n     * Validate that the configuration conforms roughly to the following\n     * yaml tree structure:\n     *\n     * keyx:\n     *   key1: value1\n     *   key2: value2\n     *   seq:\n     *     - elem1\n     *     - elem2\n     *     - elem3\n     *   key3: value3\n     *\n     * keyy:\n     *   key1: value1\n     *   key2: value2\n     *   seq:\n     *     - elem1\n     *     - elem2\n     *     - elem3\n     *   key3: value3\n     */\n    do {\n        status = conf_event_next(cf);\n        if (status != NC_OK) {\n            return status;\n        }\n\n        type = cf->event.type;\n\n        log_debug(LOG_VVERB, \"next event %d depth %d seq %d\", type, depth, seq);\n\n        switch (type) {\n        case YAML_STREAM_START_EVENT:\n        case YAML_DOCUMENT_START_EVENT:\n            break;\n\n        case YAML_DOCUMENT_END_EVENT:\n            break;\n\n        case YAML_STREAM_END_EVENT:\n            done = true;\n            break;\n\n        case YAML_MAPPING_START_EVENT:\n            if (depth == CONF_ROOT_DEPTH && count[depth] != 1) {\n                error = true;\n                log_error(\"conf: '%s' has more than one \\\"key:value\\\" at depth\"\n                          \" %d\", cf->fname, depth);\n            } else if (depth >= CONF_MAX_DEPTH) {\n                error = true;\n                log_error(\"conf: '%s' has a depth greater than %d\", cf->fname,\n                          CONF_MAX_DEPTH);\n            }\n            depth++;\n            break;\n\n        case YAML_MAPPING_END_EVENT:\n            if (depth == CONF_MAX_DEPTH) {\n                if (seq) {\n                    seq = false;\n                } else {\n                    error = true;\n                    log_error(\"conf: '%s' missing sequence directive at depth \"\n                              \"%d\", cf->fname, depth);\n                }\n            }\n            depth--;\n            count[depth] = 0;\n            break;\n\n        case YAML_SEQUENCE_START_EVENT:\n            if (seq) {\n                error = true;\n                log_error(\"conf: '%s' has more than one sequence directive\",\n                          cf->fname);\n            } else if (depth != CONF_MAX_DEPTH) {\n                error = true;\n                log_error(\"conf: '%s' has sequence at depth %d instead of %d\",\n                          cf->fname, depth, CONF_MAX_DEPTH);\n            } else if (count[depth] != 1) {\n                error = true;\n                log_error(\"conf: '%s' has invalid \\\"key:value\\\" at depth %d\",\n                          cf->fname, depth);\n            }\n            seq = true;\n            break;\n\n        case YAML_SEQUENCE_END_EVENT:\n            ASSERT(depth == CONF_MAX_DEPTH);\n            count[depth] = 0;\n            break;\n\n        case YAML_SCALAR_EVENT:\n            if (depth == 0) {\n                error = true;\n                log_error(\"conf: '%s' has invalid empty \\\"key:\\\" at depth %d\",\n                          cf->fname, depth);\n            } else if (depth == CONF_ROOT_DEPTH && count[depth] != 0) {\n                error = true;\n                log_error(\"conf: '%s' has invalid mapping \\\"key:\\\" at depth %d\",\n                          cf->fname, depth);\n            } else if (depth == CONF_MAX_DEPTH && count[depth] == 2) {\n                /* found a \"key: value\", resetting! */\n                count[depth] = 0;\n            }\n            count[depth]++;\n            break;\n\n        default:\n            NOT_REACHED();\n        }\n\n        conf_event_done(cf);\n    } while (!done && !error);\n\n    conf_yaml_deinit(cf);\n\n    return !error ? NC_OK : NC_ERROR;\n}\n\nstatic rstatus_t\nconf_pre_validate(struct conf *cf)\n{\n    rstatus_t status;\n\n    status = conf_validate_document(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = conf_validate_tokens(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = conf_validate_structure(cf);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    cf->sound = 1;\n\n    return NC_OK;\n}\n\nstatic int\nconf_server_name_cmp(const void *t1, const void *t2)\n{\n    const struct conf_server *s1 = t1, *s2 = t2;\n\n    return string_compare(&s1->name, &s2->name);\n}\n\nstatic int\nconf_pool_name_cmp(const void *t1, const void *t2)\n{\n    const struct conf_pool *p1 = t1, *p2 = t2;\n\n    return string_compare(&p1->name, &p2->name);\n}\n\nstatic int\nconf_pool_listen_cmp(const void *t1, const void *t2)\n{\n    const struct conf_pool *p1 = t1, *p2 = t2;\n\n    return string_compare(&p1->listen.pname, &p2->listen.pname);\n}\n\nstatic rstatus_t\nconf_validate_server(struct conf *cf, struct conf_pool *cp)\n{\n    uint32_t i, nserver;\n    bool valid;\n\n    nserver = array_n(&cp->server);\n    if (nserver == 0) {\n        log_error(\"conf: pool '%.*s' has no servers\", cp->name.len,\n                  cp->name.data);\n        return NC_ERROR;\n    }\n\n    /*\n     * Disallow duplicate servers - servers with identical \"host:port:weight\"\n     * or \"name\" combination are considered as duplicates. When server name\n     * is configured, we only check for duplicate \"name\" and not for duplicate\n     * \"host:port:weight\"\n     */\n    array_sort(&cp->server, conf_server_name_cmp);\n    for (valid = true, i = 0; i < nserver - 1; i++) {\n        struct conf_server *cs1, *cs2;\n\n        cs1 = array_get(&cp->server, i);\n        cs2 = array_get(&cp->server, i + 1);\n\n        if (string_compare(&cs1->name, &cs2->name) == 0) {\n            log_error(\"conf: pool '%.*s' has servers with same name '%.*s'\",\n                      cp->name.len, cp->name.data, cs1->name.len,\n                      cs1->name.data);\n            valid = false;\n            break;\n        }\n    }\n    if (!valid) {\n        return NC_ERROR;\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nconf_validate_pool(struct conf *cf, struct conf_pool *cp)\n{\n    rstatus_t status;\n\n    ASSERT(!cp->valid);\n    ASSERT(!string_empty(&cp->name));\n\n    if (!cp->listen.valid) {\n        log_error(\"conf: directive \\\"listen:\\\" is missing\");\n        return NC_ERROR;\n    }\n\n    /* set default values for unset directives */\n\n    if (cp->distribution == CONF_UNSET_DIST) {\n        cp->distribution = CONF_DEFAULT_DIST;\n    }\n\n    if (cp->hash == CONF_UNSET_HASH) {\n        cp->hash = CONF_DEFAULT_HASH;\n    }\n\n    if (cp->timeout == CONF_UNSET_NUM) {\n        cp->timeout = CONF_DEFAULT_TIMEOUT;\n    }\n\n    if (cp->backlog == CONF_UNSET_NUM) {\n        cp->backlog = CONF_DEFAULT_LISTEN_BACKLOG;\n    }\n\n    cp->client_connections = CONF_DEFAULT_CLIENT_CONNECTIONS;\n\n    if (cp->redis == CONF_UNSET_NUM) {\n        cp->redis = CONF_DEFAULT_REDIS;\n    }\n\n    if (cp->tcpkeepalive == CONF_UNSET_NUM) {\n        cp->tcpkeepalive = CONF_DEFAULT_TCPKEEPALIVE;\n    }\n\n    if (cp->reuseport == CONF_UNSET_NUM) {\n\tcp->reuseport = CONF_DEFAULT_REUSEPORT;\n    }\n\n    if (cp->redis_db == CONF_UNSET_NUM) {\n        cp->redis_db = CONF_DEFAULT_REDIS_DB;\n    }\n\n    if (cp->preconnect == CONF_UNSET_NUM) {\n        cp->preconnect = CONF_DEFAULT_PRECONNECT;\n    }\n\n    if (cp->auto_eject_hosts == CONF_UNSET_NUM) {\n        cp->auto_eject_hosts = CONF_DEFAULT_AUTO_EJECT_HOSTS;\n    }\n\n    if (cp->server_connections == CONF_UNSET_NUM) {\n        cp->server_connections = CONF_DEFAULT_SERVER_CONNECTIONS;\n    } else if (cp->server_connections == 0) {\n        log_error(\"conf: directive \\\"server_connections:\\\" cannot be 0\");\n        return NC_ERROR;\n    }\n\n    if (cp->server_retry_timeout == CONF_UNSET_NUM) {\n        cp->server_retry_timeout = CONF_DEFAULT_SERVER_RETRY_TIMEOUT;\n    }\n\n    if (cp->server_failure_limit == CONF_UNSET_NUM) {\n        cp->server_failure_limit = CONF_DEFAULT_SERVER_FAILURE_LIMIT;\n    }\n\n    if (!cp->redis && cp->redis_auth.len > 0) {\n        log_error(\"conf: directive \\\"redis_auth:\\\" is only valid for a redis pool\");\n        return NC_ERROR;\n    }\n\n    status = conf_validate_server(cf, cp);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    cp->valid = 1;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nconf_post_validate(struct conf *cf)\n{\n    rstatus_t status;\n    uint32_t i, npool;\n    bool valid;\n\n    ASSERT(cf->sound && cf->parsed);\n    ASSERT(!cf->valid);\n\n    npool = array_n(&cf->pool);\n    if (npool == 0) {\n        log_error(\"conf: '%s' has no pools\", cf->fname);\n        return NC_ERROR;\n    }\n\n    /* validate pool */\n    for (i = 0; i < npool; i++) {\n        struct conf_pool *cp = array_get(&cf->pool, i);\n\n        status = conf_validate_pool(cf, cp);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    /* disallow pools with duplicate listen: key values */\n    array_sort(&cf->pool, conf_pool_listen_cmp);\n    for (valid = true, i = 0; i < npool - 1; i++) {\n        struct conf_pool *p1, *p2;\n\n        p1 = array_get(&cf->pool, i);\n        p2 = array_get(&cf->pool, i + 1);\n\n        if (string_compare(&p1->listen.pname, &p2->listen.pname) == 0) {\n            log_error(\"conf: pools '%.*s' and '%.*s' have the same listen \"\n                      \"address '%.*s'\", p1->name.len, p1->name.data,\n                      p2->name.len, p2->name.data, p1->listen.pname.len,\n                      p1->listen.pname.data);\n            valid = false;\n            break;\n        }\n    }\n    if (!valid) {\n        return NC_ERROR;\n    }\n\n    /* disallow pools with duplicate names */\n    array_sort(&cf->pool, conf_pool_name_cmp);\n    for (valid = true, i = 0; i < npool - 1; i++) {\n        struct conf_pool *p1, *p2;\n\n        p1 = array_get(&cf->pool, i);\n        p2 = array_get(&cf->pool, i + 1);\n\n        if (string_compare(&p1->name, &p2->name) == 0) {\n            log_error(\"conf: '%s' has pools with same name %.*s'\", cf->fname,\n                      p1->name.len, p1->name.data);\n            valid = false;\n            break;\n        }\n    }\n    if (!valid) {\n        return NC_ERROR;\n    }\n\n    return NC_OK;\n}\n\nstruct conf *\nconf_create(const char *filename)\n{\n    rstatus_t status;\n    struct conf *cf;\n\n    cf = conf_open(filename);\n    if (cf == NULL) {\n        return NULL;\n    }\n\n    /* validate configuration file before parsing */\n    status = conf_pre_validate(cf);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    /* parse the configuration file */\n    status = conf_parse(cf);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    /* validate parsed configuration */\n    status = conf_post_validate(cf);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    conf_dump(cf);\n\n    fclose(cf->fh);\n    cf->fh = NULL;\n\n    return cf;\n\nerror:\n    log_stderr(\"nutcracker: configuration file '%s' syntax is invalid\",\n               filename);\n    fclose(cf->fh);\n    cf->fh = NULL;\n    conf_destroy(cf);\n    return NULL;\n}\n\nvoid\nconf_destroy(struct conf *cf)\n{\n    while (array_n(&cf->arg) != 0) {\n        conf_pop_scalar(cf);\n    }\n    array_deinit(&cf->arg);\n\n    while (array_n(&cf->pool) != 0) {\n        conf_pool_deinit(array_pop(&cf->pool));\n    }\n    array_deinit(&cf->pool);\n\n    nc_free(cf);\n}\n\nconst char *\nconf_set_string(struct conf *cf, const struct command *cmd, void *conf)\n{\n    rstatus_t status;\n    uint8_t *p;\n    struct string *field;\n    const struct string *value;\n\n    p = conf;\n    field = (struct string *)(p + cmd->offset);\n\n    if (field->data != CONF_UNSET_PTR) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    status = string_duplicate(field, value);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    return CONF_OK;\n}\n\nconst char *\nconf_set_listen(struct conf *cf, const struct command *cmd, void *conf)\n{\n    rstatus_t status;\n    struct string *value;\n    struct conf_listen *field;\n    uint8_t *p, *name;\n    uint32_t namelen;\n\n    p = conf;\n    field = (struct conf_listen *)(p + cmd->offset);\n\n    if (field->valid == 1) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    status = string_duplicate(&field->pname, value);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    if (value->data[0] == '/') {\n        uint8_t *q, *start, *perm;\n\n        /* parse \"socket_path permissions\" from the end */\n        p = value->data + value->len -1;\n        start = value->data;\n        q = nc_strrchr(p, start, ' ');\n        if (q == NULL) {\n            /* no permissions field, so use defaults */\n            name = value->data;\n            namelen = value->len;\n            field->perm = (mode_t)0;\n        } else {\n            perm = q + 1;\n\n            p = q - 1;\n            name = start;\n            namelen = (uint32_t)(p - start + 1);\n\n            errno = 0;\n            field->perm = (mode_t)strtol((char *)perm, NULL, 8);\n            if (errno || field->perm > 0777) {\n                return \"has an invalid file permission in \\\"socket_path permission\\\" format string\";\n            }\n        }\n    } else {\n        uint8_t *q, *start, *port;\n        uint32_t portlen;\n\n        /* parse \"hostname:port\" from the end */\n        p = value->data + value->len - 1;\n        start = value->data;\n        q = nc_strrchr(p, start, ':');\n        if (q == NULL) {\n            return \"has an invalid \\\"hostname:port\\\" format string\";\n        }\n\n        port = q + 1;\n        portlen = (uint32_t)(p - port + 1);\n\n        p = q - 1;\n\n        name = start;\n        namelen = (uint32_t)(p - start + 1);\n\n        field->port = nc_atoi(port, portlen);\n        if (field->port < 0 || !nc_valid_port(field->port)) {\n            return \"has an invalid port in \\\"hostname:port\\\" format string\";\n        }\n    }\n\n    status = string_copy(&field->name, name, namelen);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    status = nc_resolve(&field->name, field->port, &field->info);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    field->valid = 1;\n\n    return CONF_OK;\n}\n\nconst char *\nconf_add_server(struct conf *cf, const struct command *cmd, void *conf)\n{\n    rstatus_t status;\n    struct array *a;\n    struct string *value;\n    struct conf_server *field;\n    uint8_t *p, *q, *start;\n    uint8_t *pname, *addr, *port, *weight, *name;\n    uint32_t k, delimlen, pnamelen, addrlen, portlen, weightlen, namelen;\n    const char *const delim = \" ::\";\n\n    p = conf;\n    a = (struct array *)(p + cmd->offset);\n\n    field = array_push(a);\n    if (field == NULL) {\n        return CONF_ERROR;\n    }\n\n    conf_server_init(field);\n\n    value = array_top(&cf->arg);\n\n    /* parse \"hostname:port:weight [name]\" or \"/path/unix_socket:weight [name]\" from the end */\n    p = value->data + value->len - 1;\n    start = value->data;\n    addr = NULL;\n    addrlen = 0;\n    weight = NULL;\n    weightlen = 0;\n    port = NULL;\n    portlen = 0;\n    name = NULL;\n    namelen = 0;\n\n    delimlen = value->data[0] == '/' ? 2 : 3;\n\n    for (k = 0; k < sizeof(delim); k++) {\n        q = nc_strrchr(p, start, delim[k]);\n        if (q == NULL) {\n            if (k == 0) {\n                /*\n                 * name in \"hostname:port:weight [name]\" format string is\n                 * optional\n                 */\n                continue;\n            }\n            break;\n        }\n\n        switch (k) {\n        case 0:\n            name = q + 1;\n            namelen = (uint32_t)(p - name + 1);\n            break;\n\n        case 1:\n            weight = q + 1;\n            weightlen = (uint32_t)(p - weight + 1);\n            break;\n\n        case 2:\n            port = q + 1;\n            portlen = (uint32_t)(p - port + 1);\n            break;\n\n        default:\n            NOT_REACHED();\n        }\n\n        p = q - 1;\n    }\n\n    if (k != delimlen) {\n        return \"has an invalid \\\"hostname:port:weight [name]\\\"or \\\"/path/unix_socket:weight [name]\\\" format string\";\n    }\n\n    pname = value->data;\n    pnamelen = namelen > 0 ? value->len - (namelen + 1) : value->len;\n    status = string_copy(&field->pname, pname, pnamelen);\n    if (status != NC_OK) {\n        array_pop(a);\n        return CONF_ERROR;\n    }\n\n    addr = start;\n    addrlen = (uint32_t)(p - start + 1);\n\n    field->weight = nc_atoi(weight, weightlen);\n    if (field->weight < 0) {\n        return \"has an invalid weight in \\\"hostname:port:weight [name]\\\" format string\";\n    } else if (field->weight == 0) {\n        return \"has a zero weight in \\\"hostname:port:weight [name]\\\" format string\";\n    }\n\n    if (value->data[0] != '/') {\n        field->port = nc_atoi(port, portlen);\n        if (field->port < 0 || !nc_valid_port(field->port)) {\n            return \"has an invalid port in \\\"hostname:port:weight [name]\\\" format string\";\n        }\n    }\n\n    if (name == NULL) {\n        /*\n         * To maintain backward compatibility with libmemcached, we don't\n         * include the port as the part of the input string to the consistent\n         * hashing algorithm, when it is equal to 11211.\n         */\n        if (field->port == CONF_DEFAULT_KETAMA_PORT) {\n            name = addr;\n            namelen = addrlen;\n        } else {\n            name = addr;\n            namelen = addrlen + 1 + portlen;\n        }\n    }\n\n    status = string_copy(&field->name, name, namelen);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    status = string_copy(&field->addrstr, addr, addrlen);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    /*\n     * The address resolution of the backend server hostname is lazy.\n     * The resolution occurs when a new connection to the server is\n     * created, which could either be the first time or every time\n     * the server gets re-added to the pool after an auto ejection\n     */\n\n    field->valid = 1;\n\n    return CONF_OK;\n}\n\nconst char *\nconf_set_num(struct conf *cf, const struct command *cmd, void *conf)\n{\n    uint8_t *p;\n    int num, *np;\n    const struct string *value;\n\n    p = conf;\n    np = (int *)(p + cmd->offset);\n\n    if (*np != CONF_UNSET_NUM) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    num = nc_atoi(value->data, value->len);\n    if (num < 0) {\n        return \"is not a number\";\n    }\n\n    *np = num;\n\n    return CONF_OK;\n}\n\nconst char *\nconf_set_bool(struct conf *cf, const struct command *cmd, void *conf)\n{\n    uint8_t *p;\n    int *bp;\n    const struct string *value;\n\n    p = conf;\n    bp = (int *)(p + cmd->offset);\n\n    if (*bp != CONF_UNSET_NUM) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    if (string_compare(value, &true_str) == 0) {\n        *bp = 1;\n    } else if (string_compare(value, &false_str) == 0) {\n        *bp = 0;\n    } else {\n        return \"is not \\\"true\\\" or \\\"false\\\"\";\n    }\n\n    return CONF_OK;\n}\n\nconst char *\nconf_set_hash(struct conf *cf, const struct command *cmd, void *conf)\n{\n    uint8_t *p;\n    hash_type_t *hp;\n    const struct string *value, *hash;\n\n    p = conf;\n    hp = (hash_type_t *)(p + cmd->offset);\n\n    if (*hp != CONF_UNSET_HASH) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    for (hash = hash_strings; hash->len != 0; hash++) {\n        if (string_compare(value, hash) != 0) {\n            continue;\n        }\n\n        *hp = (hash_type_t)(hash - hash_strings);\n\n        return CONF_OK;\n    }\n\n    return \"is not a valid hash\";\n}\n\nconst char *\nconf_set_distribution(struct conf *cf, const struct command *cmd, void *conf)\n{\n    uint8_t *p;\n    dist_type_t *dp;\n    const struct string *value, *dist;\n\n    p = conf;\n    dp = (dist_type_t *)(p + cmd->offset);\n\n    if (*dp != CONF_UNSET_DIST) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    for (dist = dist_strings; dist->len != 0; dist++) {\n        if (string_compare(value, dist) != 0) {\n            continue;\n        }\n\n        *dp = (dist_type_t)(dist - dist_strings);\n\n        return CONF_OK;\n    }\n\n    return \"is not a valid distribution\";\n}\n\nconst char *\nconf_set_hashtag(struct conf *cf, const struct command *cmd, void *conf)\n{\n    rstatus_t status;\n    uint8_t *p;\n    struct string *field;\n    const struct string *value;\n\n    p = conf;\n    field = (struct string *)(p + cmd->offset);\n\n    if (field->data != CONF_UNSET_PTR) {\n        return \"is a duplicate\";\n    }\n\n    value = array_top(&cf->arg);\n\n    if (value->len != 2) {\n        return \"is not a valid hash tag string with two characters\";\n    }\n\n    status = string_duplicate(field, value);\n    if (status != NC_OK) {\n        return CONF_ERROR;\n    }\n\n    return CONF_OK;\n}\n"
  },
  {
    "path": "src/nc_conf.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_CONF_H_\n#define _NC_CONF_H_\n\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <yaml.h>\n\n#include <nc_core.h>\n#include <hashkit/nc_hashkit.h>\n\n#define CONF_OK             (void *) NULL\n#define CONF_ERROR          (void *) \"has an invalid value\"\n\n#define CONF_ROOT_DEPTH     1\n#define CONF_MAX_DEPTH      CONF_ROOT_DEPTH + 1\n\n#define CONF_DEFAULT_ARGS       3\n#define CONF_DEFAULT_POOL       8\n#define CONF_DEFAULT_SERVERS    8\n\n#define CONF_UNSET_NUM  -1\n#define CONF_UNSET_PTR  NULL\n#define CONF_UNSET_HASH (hash_type_t) -1\n#define CONF_UNSET_DIST (dist_type_t) -1\n\n#define CONF_DEFAULT_HASH                    HASH_FNV1A_64\n#define CONF_DEFAULT_DIST                    DIST_KETAMA\n#define CONF_DEFAULT_TIMEOUT                 -1\n#define CONF_DEFAULT_LISTEN_BACKLOG          512\n#define CONF_DEFAULT_CLIENT_CONNECTIONS      0\n#define CONF_DEFAULT_REDIS                   false\n#define CONF_DEFAULT_REDIS_DB                0\n#define CONF_DEFAULT_PRECONNECT              false\n#define CONF_DEFAULT_AUTO_EJECT_HOSTS        false\n#define CONF_DEFAULT_SERVER_RETRY_TIMEOUT    30 * 1000      /* in msec */\n#define CONF_DEFAULT_SERVER_FAILURE_LIMIT    2\n#define CONF_DEFAULT_SERVER_CONNECTIONS      1\n#define CONF_DEFAULT_KETAMA_PORT             11211\n#define CONF_DEFAULT_TCPKEEPALIVE            false\n#define CONF_DEFAULT_REUSEPORT\t\t      false\n\nstruct conf_listen {\n    struct string   pname;   /* listen: as \"hostname:port\" */\n    struct string   name;    /* hostname:port */\n    int             port;    /* port */\n    mode_t          perm;    /* socket permissions */\n    struct sockinfo info;    /* listen socket info */\n    unsigned        valid:1; /* valid? */\n};\n\nstruct conf_server {\n    struct string   pname;      /* server: as \"hostname:port:weight\" */\n    struct string   name;       /* hostname:port or [name] */\n    struct string   addrstr;    /* hostname */\n    int             port;       /* port */\n    int             weight;     /* weight */\n    struct sockinfo info;       /* connect socket info */\n    unsigned        valid:1;    /* valid? */\n};\n\nstruct conf_pool {\n    struct string      name;                  /* pool name (root node) */\n    struct conf_listen listen;                /* listen: */\n    hash_type_t        hash;                  /* hash: */\n    struct string      hash_tag;              /* hash_tag: */\n    dist_type_t        distribution;          /* distribution: */\n    int                timeout;               /* timeout: */\n    int                backlog;               /* backlog: */\n    int                client_connections;    /* client_connections: */\n    int                tcpkeepalive;          /* tcpkeepalive: */\n    int                redis;                 /* redis: */\n    struct string      redis_auth;            /* redis_auth: redis auth password (matches requirepass on redis) */\n    int                redis_db;              /* redis_db: redis db */\n    int                preconnect;            /* preconnect: */\n    int                auto_eject_hosts;      /* auto_eject_hosts: */\n    int                server_connections;    /* server_connections: */\n    int                server_retry_timeout;  /* server_retry_timeout: in msec */\n    int                server_failure_limit;  /* server_failure_limit: */\n    struct array       server;                /* servers: conf_server[] */\n    unsigned           valid:1;               /* valid? */\n    int                reuseport;             /* set SO_REUSEPORT to socket */\n};\n\nstruct conf {\n    const char    *fname;           /* file name (ref in argv[]) */\n    FILE          *fh;              /* file handle */\n    struct array  arg;              /* string[] (parsed {key, value} pairs) */\n    struct array  pool;             /* conf_pool[] (parsed pools) */\n    uint32_t      depth;            /* parsed tree depth */\n    yaml_parser_t parser;           /* yaml parser */\n    yaml_event_t  event;            /* yaml event */\n    yaml_token_t  token;            /* yaml token */\n    unsigned      seq:1;            /* sequence? */\n    unsigned      valid_parser:1;   /* valid parser? */\n    unsigned      valid_event:1;    /* valid event? */\n    unsigned      valid_token:1;    /* valid token? */\n    unsigned      sound:1;          /* sound? */\n    unsigned      parsed:1;         /* parsed? */\n    unsigned      valid:1;          /* valid? */\n};\n\nstruct command {\n    struct string name;\n    const char    *(*set)(struct conf *cf, const struct command *cmd, void *data);\n    int           offset;\n};\n\n#define null_command { null_string, NULL, 0 }\n\nconst char *conf_set_string(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_set_listen(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_add_server(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_set_num(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_set_bool(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_set_hash(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_set_distribution(struct conf *cf, const struct command *cmd, void *conf);\nconst char *conf_set_hashtag(struct conf *cf, const struct command *cmd, void *conf);\n\nrstatus_t conf_server_each_transform(void *elem, void *data);\nrstatus_t conf_pool_each_transform(void *elem, void *data);\n\nstruct conf *conf_create(const char *filename);\nvoid conf_destroy(struct conf *cf);\n\n#endif\n"
  },
  {
    "path": "src/nc_connection.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <sys/uio.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_client.h>\n#include <nc_proxy.h>\n#include <proto/nc_proto.h>\n\n/*\n *                   nc_connection.[ch]\n *                Connection (struct conn)\n *                 +         +          +\n *                 |         |          |\n *                 |       Proxy        |\n *                 |     nc_proxy.[ch]  |\n *                 /                    \\\n *              Client                Server\n *           nc_client.[ch]         nc_server.[ch]\n *\n * Nutcracker essentially multiplexes m client connections over n server\n * connections. Usually m >> n, so that nutcracker can pipeline requests\n * from several clients over a server connection and hence use the connection\n * bandwidth to the server efficiently\n *\n * Client and server connection maintain two fifo queues for requests:\n *\n * 1). in_q (imsg_q):  queue of incoming requests\n * 2). out_q (omsg_q): queue of outstanding (outgoing) requests\n *\n * Request received over the client connection are forwarded to the server by\n * enqueuing the request in the chosen server's in_q. From the client's\n * perspective once the request is forwarded, it is outstanding and is tracked\n * in the client's out_q (unless the request was tagged as noreply). The server\n * in turn picks up requests from its own in_q in fifo order and puts them on\n * the wire. Once the request is outstanding on the wire, and a response is\n * expected for it, the server keeps track of outstanding requests it in its\n * own out_q.\n *\n * The server's out_q enables us to pair a request with a response while the\n * client's out_q enables us to pair request and response in the order in\n * which they are received from the client.\n *\n *\n *      Clients                             Servers\n *                                    .\n *    in_q: <empty>                   .\n *    out_q: req11 -> req12           .   in_q:  req22\n *    (client1)                       .   out_q: req11 -> req21 -> req12\n *                                    .   (server1)\n *    in_q: <empty>                   .\n *    out_q: req21 -> req22 -> req23  .\n *    (client2)                       .\n *                                    .   in_q:  req23\n *                                    .   out_q: <empty>\n *                                    .   (server2)\n *\n * In the above example, client1 has two pipelined requests req11 and req12\n * both of which are outstanding on the server connection server1. On the\n * other hand, client2 has three requests req21, req22 and req23, of which\n * only req21 is outstanding on the server connection while req22 and\n * req23 are still waiting to be put on the wire. The fifo of client's\n * out_q ensures that we always send back the response of request at the head\n * of the queue, before sending out responses of other completed requests in\n * the queue.\n */\n\nstatic uint32_t nfree_connq;       /* # free conn q */\nstatic struct conn_tqh free_connq; /* free conn q */\nstatic uint64_t ntotal_conn;       /* total # connections counter from start */\nstatic uint32_t ncurr_conn;        /* current # connections */\nstatic uint32_t ncurr_cconn;       /* current # client connections */\n\n/*\n * Return the context associated with this connection.\n */\nstruct context *\nconn_to_ctx(const struct conn *conn)\n{\n    struct server_pool *pool;\n\n    if (conn->proxy || conn->client) {\n        pool = conn->owner;\n    } else {\n        struct server *server = conn->owner;\n        pool = server->owner;\n    }\n\n    return pool->ctx;\n}\n\nstatic struct conn *\n_conn_get(void)\n{\n    struct conn *conn;\n\n    if (!TAILQ_EMPTY(&free_connq)) {\n        ASSERT(nfree_connq > 0);\n\n        conn = TAILQ_FIRST(&free_connq);\n        nfree_connq--;\n        TAILQ_REMOVE(&free_connq, conn, conn_tqe);\n    } else {\n        conn = nc_alloc(sizeof(*conn));\n        if (conn == NULL) {\n            return NULL;\n        }\n    }\n\n    conn->owner = NULL;\n\n    conn->sd = -1;\n    /* {family, addrlen, addr} are initialized in enqueue handler */\n\n    TAILQ_INIT(&conn->imsg_q);\n    TAILQ_INIT(&conn->omsg_q);\n    conn->rmsg = NULL;\n    conn->smsg = NULL;\n\n    /*\n     * Callbacks {recv, recv_next, recv_done}, {send, send_next, send_done},\n     * {close, active}, parse, {ref, unref}, {enqueue_inq, dequeue_inq} and\n     * {enqueue_outq, dequeue_outq} are initialized by the wrapper.\n     */\n\n    conn->send_bytes = 0;\n    conn->recv_bytes = 0;\n\n    conn->events = 0;\n    conn->err = 0;\n    conn->recv_active = 0;\n    conn->recv_ready = 0;\n    conn->send_active = 0;\n    conn->send_ready = 0;\n\n    conn->client = 0;\n    conn->proxy = 0;\n    conn->connecting = 0;\n    conn->connected = 0;\n    conn->eof = 0;\n    conn->done = 0;\n    conn->redis = 0;\n    conn->authenticated = 0;\n\n    ntotal_conn++;\n    ncurr_conn++;\n\n    return conn;\n}\n\nstruct conn *\nconn_get(void *owner, bool client, bool redis)\n{\n    struct conn *conn;\n\n    conn = _conn_get();\n    if (conn == NULL) {\n        return NULL;\n    }\n\n    /* connection either handles redis or memcache messages */\n    conn->redis = redis ? 1 : 0;\n\n    conn->client = client ? 1 : 0;\n\n    if (conn->client) {\n        /*\n         * client receives a request, possibly parsing it, and sends a\n         * response downstream.\n         */\n        conn->recv = msg_recv;\n        conn->recv_next = req_recv_next;\n        conn->recv_done = req_recv_done;\n\n        conn->send = msg_send;\n        conn->send_next = rsp_send_next;\n        conn->send_done = rsp_send_done;\n\n        conn->close = client_close;\n        conn->active = client_active;\n\n        conn->ref = client_ref;\n        conn->unref = client_unref;\n\n        conn->enqueue_inq = NULL;\n        conn->dequeue_inq = NULL;\n        conn->enqueue_outq = req_client_enqueue_omsgq;\n        conn->dequeue_outq = req_client_dequeue_omsgq;\n        conn->post_connect = NULL;\n        conn->swallow_msg = NULL;\n\n        ncurr_cconn++;\n    } else {\n        /*\n         * server receives a response, possibly parsing it, and sends a\n         * request upstream.\n         */\n        conn->recv = msg_recv;\n        conn->recv_next = rsp_recv_next;\n        conn->recv_done = rsp_recv_done;\n\n        conn->send = msg_send;\n        conn->send_next = req_send_next;\n        conn->send_done = req_send_done;\n\n        conn->close = server_close;\n        conn->active = server_active;\n\n        conn->ref = server_ref;\n        conn->unref = server_unref;\n\n        conn->enqueue_inq = req_server_enqueue_imsgq;\n        conn->dequeue_inq = req_server_dequeue_imsgq;\n        conn->enqueue_outq = req_server_enqueue_omsgq;\n        conn->dequeue_outq = req_server_dequeue_omsgq;\n        if (redis) {\n          conn->post_connect = redis_post_connect;\n          conn->swallow_msg = redis_swallow_msg;\n        } else {\n          conn->post_connect = memcache_post_connect;\n          conn->swallow_msg = memcache_swallow_msg;\n        }\n    }\n\n    conn->ref(conn, owner);\n    log_debug(LOG_VVERB, \"get conn %p client %d\", conn, conn->client);\n\n    return conn;\n}\n\nstruct conn *\nconn_get_proxy(struct server_pool *pool)\n{\n    struct conn *conn;\n\n    conn = _conn_get();\n    if (conn == NULL) {\n        return NULL;\n    }\n\n    conn->redis = pool->redis;\n\n    conn->proxy = 1;\n\n    conn->recv = proxy_recv;\n    conn->recv_next = NULL;\n    conn->recv_done = NULL;\n\n    conn->send = NULL;\n    conn->send_next = NULL;\n    conn->send_done = NULL;\n\n    conn->close = proxy_close;\n    conn->active = NULL;\n\n    conn->ref = proxy_ref;\n    conn->unref = proxy_unref;\n\n    conn->enqueue_inq = NULL;\n    conn->dequeue_inq = NULL;\n    conn->enqueue_outq = NULL;\n    conn->dequeue_outq = NULL;\n\n    conn->ref(conn, pool);\n\n    log_debug(LOG_VVERB, \"get conn %p proxy %d\", conn, conn->proxy);\n\n    return conn;\n}\n\nstatic void\nconn_free(struct conn *conn)\n{\n    log_debug(LOG_VVERB, \"free conn %p\", conn);\n    nc_free(conn);\n}\n\nvoid\nconn_put(struct conn *conn)\n{\n    ASSERT(conn->sd < 0);\n    ASSERT(conn->owner == NULL);\n\n    log_debug(LOG_VVERB, \"put conn %p\", conn);\n\n    nfree_connq++;\n    TAILQ_INSERT_HEAD(&free_connq, conn, conn_tqe);\n\n    if (conn->client) {\n        ncurr_cconn--;\n    }\n    ncurr_conn--;\n}\n\nvoid\nconn_init(void)\n{\n    log_debug(LOG_DEBUG, \"conn size %d\", (int)sizeof(struct conn));\n    nfree_connq = 0;\n    TAILQ_INIT(&free_connq);\n}\n\nvoid\nconn_deinit(void)\n{\n    struct conn *conn, *nconn; /* current and next connection */\n\n    for (conn = TAILQ_FIRST(&free_connq); conn != NULL;\n         conn = nconn, nfree_connq--) {\n        ASSERT(nfree_connq > 0);\n        nconn = TAILQ_NEXT(conn, conn_tqe);\n        conn_free(conn);\n    }\n    ASSERT(nfree_connq == 0);\n}\n\nssize_t\nconn_recv(struct conn *conn, void *buf, size_t size)\n{\n    ssize_t n;\n\n    ASSERT(buf != NULL);\n    ASSERT(size > 0);\n    ASSERT(conn->recv_ready);\n\n    for (;;) {\n        n = nc_read(conn->sd, buf, size);\n\n        log_debug(LOG_VERB, \"recv on sd %d %zd of %zu\", conn->sd, n, size);\n\n        if (n > 0) {\n            if (n < (ssize_t) size) {\n                conn->recv_ready = 0;\n            }\n            conn->recv_bytes += (size_t)n;\n            return n;\n        }\n\n        if (n == 0) {\n            conn->recv_ready = 0;\n            conn->eof = 1;\n            log_debug(LOG_INFO, \"recv on sd %d eof rb %zu sb %zu\", conn->sd,\n                      conn->recv_bytes, conn->send_bytes);\n            return n;\n        }\n\n        if (errno == EINTR) {\n            log_debug(LOG_VERB, \"recv on sd %d not ready - eintr\", conn->sd);\n            continue;\n        } else if (errno == EAGAIN || errno == EWOULDBLOCK) {\n            conn->recv_ready = 0;\n            log_debug(LOG_VERB, \"recv on sd %d not ready - eagain\", conn->sd);\n            return NC_EAGAIN;\n        } else {\n            conn->recv_ready = 0;\n            conn->err = errno;\n            log_error(\"recv on sd %d failed: %s\", conn->sd, strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    NOT_REACHED();\n\n    return NC_ERROR;\n}\n\nssize_t\nconn_sendv(struct conn *conn, const struct array *sendv, size_t nsend)\n{\n    ssize_t n;\n\n    ASSERT(array_n(sendv) > 0);\n    ASSERT(nsend != 0);\n    ASSERT(conn->send_ready);\n\n    for (;;) {\n        n = nc_writev(conn->sd, sendv->elem, sendv->nelem);\n\n        log_debug(LOG_VERB, \"sendv on sd %d %zd of %zu in %\"PRIu32\" buffers\",\n                  conn->sd, n, nsend, sendv->nelem);\n\n        if (n > 0) {\n            if (n < (ssize_t) nsend) {\n                conn->send_ready = 0;\n            }\n            conn->send_bytes += (size_t)n;\n            return n;\n        }\n\n        if (n == 0) {\n            log_warn(\"sendv on sd %d returned zero\", conn->sd);\n            conn->send_ready = 0;\n            return 0;\n        }\n\n        if (errno == EINTR) {\n            log_debug(LOG_VERB, \"sendv on sd %d not ready - eintr\", conn->sd);\n            continue;\n        } else if (errno == EAGAIN || errno == EWOULDBLOCK) {\n            conn->send_ready = 0;\n            log_debug(LOG_VERB, \"sendv on sd %d not ready - eagain\", conn->sd);\n            return NC_EAGAIN;\n        } else {\n            conn->send_ready = 0;\n            conn->err = errno;\n            log_error(\"sendv on sd %d failed: %s\", conn->sd, strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    NOT_REACHED();\n\n    return NC_ERROR;\n}\n\nuint32_t\nconn_ncurr_conn(void)\n{\n    return ncurr_conn;\n}\n\nuint64_t\nconn_ntotal_conn(void)\n{\n    return ntotal_conn;\n}\n\nuint32_t\nconn_ncurr_cconn(void)\n{\n    return ncurr_cconn;\n}\n\n/*\n * Returns true if the connection is authenticated or doesn't require\n * authentication, otherwise return false\n */\nbool\nconn_authenticated(const struct conn *conn)\n{\n    struct server_pool *pool;\n\n    ASSERT(!conn->proxy);\n\n    pool = conn->client ? conn->owner : ((struct server *)conn->owner)->owner;\n\n    if (!pool->require_auth) {\n        return true;\n    }\n\n    if (!conn->authenticated) {\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/nc_connection.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_CONNECTION_H_\n#define _NC_CONNECTION_H_\n\n#include <nc_core.h>\n\ntypedef rstatus_t (*conn_recv_t)(struct context *, struct conn*);\ntypedef struct msg* (*conn_recv_next_t)(struct context *, struct conn *, bool);\ntypedef void (*conn_recv_done_t)(struct context *, struct conn *, struct msg *, struct msg *);\n\ntypedef rstatus_t (*conn_send_t)(struct context *, struct conn*);\ntypedef struct msg* (*conn_send_next_t)(struct context *, struct conn *);\ntypedef void (*conn_send_done_t)(struct context *, struct conn *, struct msg *);\n\ntypedef void (*conn_close_t)(struct context *, struct conn *);\ntypedef bool (*conn_active_t)(const struct conn *);\n\ntypedef void (*conn_ref_t)(struct conn *, void *);\ntypedef void (*conn_unref_t)(struct conn *);\n\ntypedef void (*conn_msgq_t)(struct context *, struct conn *, struct msg *);\ntypedef void (*conn_post_connect_t)(struct context *ctx, struct conn *, struct server *server);\ntypedef void (*conn_swallow_msg_t)(struct conn *, struct msg *, struct msg *);\n\nstruct conn {\n    TAILQ_ENTRY(conn)   conn_tqe;        /* link in server_pool / server / free q */\n    void                *owner;          /* connection owner - server_pool / server */\n\n    int                 sd;              /* socket descriptor */\n    int                 family;          /* socket address family */\n    socklen_t           addrlen;         /* socket length */\n    struct sockaddr     *addr;           /* socket address (ref in server or server_pool) */\n\n    struct msg_tqh      imsg_q;          /* incoming request Q */\n    struct msg_tqh      omsg_q;          /* outstanding request Q */\n    struct msg          *rmsg;           /* current message being rcvd */\n    struct msg          *smsg;           /* current message being sent */\n\n    conn_recv_t         recv;            /* recv (read) handler */\n    conn_recv_next_t    recv_next;       /* recv next message handler */\n    conn_recv_done_t    recv_done;       /* read done handler */\n    conn_send_t         send;            /* send (write) handler */\n    conn_send_next_t    send_next;       /* write next message handler */\n    conn_send_done_t    send_done;       /* write done handler */\n    conn_close_t        close;           /* close handler */\n    conn_active_t       active;          /* active? handler */\n    conn_post_connect_t post_connect;    /* post connect handler */\n    conn_swallow_msg_t  swallow_msg;     /* react on messages to be swallowed */\n\n    conn_ref_t          ref;             /* connection reference handler */\n    conn_unref_t        unref;           /* connection unreference handler */\n\n    conn_msgq_t         enqueue_inq;     /* connection inq msg enqueue handler */\n    conn_msgq_t         dequeue_inq;     /* connection inq msg dequeue handler */\n    conn_msgq_t         enqueue_outq;    /* connection outq msg enqueue handler */\n    conn_msgq_t         dequeue_outq;    /* connection outq msg dequeue handler */\n\n    size_t              recv_bytes;      /* received (read) bytes */\n    size_t              send_bytes;      /* sent (written) bytes */\n\n    uint32_t            events;          /* connection io events */\n    err_t               err;             /* connection errno */\n    unsigned            recv_active:1;   /* recv active? */\n    unsigned            recv_ready:1;    /* recv ready? */\n    unsigned            send_active:1;   /* send active? */\n    unsigned            send_ready:1;    /* send ready? */\n\n    unsigned            client:1;        /* client? or server? */\n    unsigned            proxy:1;         /* proxy? */\n    unsigned            connecting:1;    /* connecting? */\n    unsigned            connected:1;     /* connected? */\n    unsigned            eof:1;           /* eof? aka passive close? */\n    unsigned            done:1;          /* done? aka close? */\n    unsigned            redis:1;         /* redis? */\n    unsigned            authenticated:1; /* authenticated? */\n};\n\nTAILQ_HEAD(conn_tqh, conn);\n\nstruct context *conn_to_ctx(const struct conn *conn);\nstruct conn *conn_get(void *owner, bool client, bool redis);\nstruct conn *conn_get_proxy(struct server_pool *pool);\nvoid conn_put(struct conn *conn);\nssize_t conn_recv(struct conn *conn, void *buf, size_t size);\nssize_t conn_sendv(struct conn *conn, const struct array *sendv, size_t nsend);\nvoid conn_init(void);\nvoid conn_deinit(void);\nuint32_t conn_ncurr_conn(void);\nuint64_t conn_ntotal_conn(void);\nuint32_t conn_ncurr_cconn(void);\nbool conn_authenticated(const struct conn *conn);\n\n#endif\n"
  },
  {
    "path": "src/nc_core.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <unistd.h>\n#include <nc_core.h>\n#include <nc_conf.h>\n#include <nc_server.h>\n#include <nc_proxy.h>\n\nstatic uint32_t ctx_id; /* context generation */\n\nstatic rstatus_t\ncore_calc_connections(struct context *ctx)\n{\n    int status;\n    struct rlimit limit;\n\n    status = getrlimit(RLIMIT_NOFILE, &limit);\n    if (status < 0) {\n        log_error(\"getrlimit failed: %s\", strerror(errno));\n        return NC_ERROR;\n    }\n\n    ctx->max_nfd = (uint32_t)limit.rlim_cur;\n    ctx->max_ncconn = ctx->max_nfd - ctx->max_nsconn - RESERVED_FDS;\n    log_debug(LOG_NOTICE, \"max fds %\"PRIu32\" max client conns %\"PRIu32\" \"\n              \"max server conns %\"PRIu32\"\", ctx->max_nfd, ctx->max_ncconn,\n              ctx->max_nsconn);\n\n    return NC_OK;\n}\n\nstatic struct context *\ncore_ctx_create(struct instance *nci)\n{\n    rstatus_t status;\n    struct context *ctx;\n\n    ctx = nc_alloc(sizeof(*ctx));\n    if (ctx == NULL) {\n        return NULL;\n    }\n    ctx->id = ++ctx_id;\n    ctx->cf = NULL;\n    ctx->stats = NULL;\n    ctx->evb = NULL;\n    array_null(&ctx->pool);\n    ctx->max_timeout = nci->stats_interval;\n    ctx->timeout = ctx->max_timeout;\n    ctx->max_nfd = 0;\n    ctx->max_ncconn = 0;\n    ctx->max_nsconn = 0;\n\n    /* parse and create configuration */\n    ctx->cf = conf_create(nci->conf_filename);\n    if (ctx->cf == NULL) {\n        nc_free(ctx);\n        return NULL;\n    }\n\n    /* initialize server pool from configuration */\n    status = server_pool_init(&ctx->pool, &ctx->cf->pool, ctx);\n    if (status != NC_OK) {\n        conf_destroy(ctx->cf);\n        nc_free(ctx);\n        return NULL;\n    }\n\n    /*\n     * Get rlimit and calculate max client connections after we have\n     * calculated max server connections\n     */\n    status = core_calc_connections(ctx);\n    if (status != NC_OK) {\n        server_pool_deinit(&ctx->pool);\n        conf_destroy(ctx->cf);\n        nc_free(ctx);\n        return NULL;\n    }\n\n    /* create stats per server pool */\n    ctx->stats = stats_create(nci->stats_port, nci->stats_addr, nci->stats_interval,\n                              nci->hostname, &ctx->pool);\n    if (ctx->stats == NULL) {\n        server_pool_deinit(&ctx->pool);\n        conf_destroy(ctx->cf);\n        nc_free(ctx);\n        return NULL;\n    }\n\n    /* initialize event handling for client, proxy and server */\n    ctx->evb = event_base_create(EVENT_SIZE, &core_core);\n    if (ctx->evb == NULL) {\n        stats_destroy(ctx->stats);\n        server_pool_deinit(&ctx->pool);\n        conf_destroy(ctx->cf);\n        nc_free(ctx);\n        return NULL;\n    }\n\n    /* preconnect? servers in server pool */\n    status = server_pool_preconnect(ctx);\n    if (status != NC_OK) {\n        server_pool_disconnect(ctx);\n        event_base_destroy(ctx->evb);\n        stats_destroy(ctx->stats);\n        server_pool_deinit(&ctx->pool);\n        conf_destroy(ctx->cf);\n        nc_free(ctx);\n        return NULL;\n    }\n\n    /* initialize proxy per server pool */\n    status = proxy_init(ctx);\n    if (status != NC_OK) {\n        server_pool_disconnect(ctx);\n        event_base_destroy(ctx->evb);\n        stats_destroy(ctx->stats);\n        server_pool_deinit(&ctx->pool);\n        conf_destroy(ctx->cf);\n        nc_free(ctx);\n        return NULL;\n    }\n\n    log_debug(LOG_VVERB, \"created ctx %p id %\"PRIu32\"\", ctx, ctx->id);\n\n    return ctx;\n}\n\nstatic void\ncore_ctx_destroy(struct context *ctx)\n{\n    log_debug(LOG_VVERB, \"destroy ctx %p id %\"PRIu32\"\", ctx, ctx->id);\n    proxy_deinit(ctx);\n    server_pool_disconnect(ctx);\n    event_base_destroy(ctx->evb);\n    stats_destroy(ctx->stats);\n    server_pool_deinit(&ctx->pool);\n    conf_destroy(ctx->cf);\n    nc_free(ctx);\n}\n\nstruct context *\ncore_start(struct instance *nci)\n{\n    struct context *ctx;\n\n    mbuf_init(nci);\n    msg_init();\n    conn_init();\n\n    ctx = core_ctx_create(nci);\n    if (ctx != NULL) {\n        nci->ctx = ctx;\n        return ctx;\n    }\n\n    conn_deinit();\n    msg_deinit();\n    mbuf_deinit();\n\n    return NULL;\n}\n\nvoid\ncore_stop(struct context *ctx)\n{\n    conn_deinit();\n    msg_deinit();\n    mbuf_deinit();\n    core_ctx_destroy(ctx);\n}\n\nstatic rstatus_t\ncore_recv(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n\n    status = conn->recv(ctx, conn);\n    if (status != NC_OK) {\n        log_debug(LOG_INFO, \"recv on %c %d failed: %s\",\n                  conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd,\n                  strerror(errno));\n    }\n\n    return status;\n}\n\nstatic rstatus_t\ncore_send(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n\n    status = conn->send(ctx, conn);\n    if (status != NC_OK) {\n        log_debug(LOG_INFO, \"send on %c %d failed: status: %d errno: %d %s\",\n                  conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd,\n                  status, errno, strerror(errno));\n    }\n\n    return status;\n}\n\nstatic void\ncore_close(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    char type;\n    const char *addrstr;\n\n    ASSERT(conn->sd > 0);\n\n    if (conn->client) {\n        type = 'c';\n        addrstr = nc_unresolve_peer_desc(conn->sd);\n    } else {\n        type = conn->proxy ? 'p' : 's';\n        addrstr = nc_unresolve_addr(conn->addr, conn->addrlen);\n    }\n    log_debug(LOG_NOTICE, \"close %c %d '%s' on event %04\"PRIX32\" eof %d done \"\n              \"%d rb %zu sb %zu%c %s\", type, conn->sd, addrstr, conn->events,\n              conn->eof, conn->done, conn->recv_bytes, conn->send_bytes,\n              conn->err ? ':' : ' ', conn->err ? strerror(conn->err) : \"\");\n\n    status = event_del_conn(ctx->evb, conn);\n    if (status < 0) {\n        log_warn(\"event del conn %c %d failed, ignored: %s\",\n                 type, conn->sd, strerror(errno));\n    }\n\n    conn->close(ctx, conn);\n}\n\nstatic void\ncore_error(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    char type = conn->client ? 'c' : (conn->proxy ? 'p' : 's');\n\n    status = nc_get_soerror(conn->sd);\n    if (status < 0) {\n        log_warn(\"get soerr on %c %d failed, ignored: %s\", type, conn->sd,\n                  strerror(errno));\n    }\n    conn->err = errno;\n\n    core_close(ctx, conn);\n}\n\nstatic void\ncore_timeout(struct context *ctx)\n{\n    for (;;) {\n        struct msg *msg;\n        struct conn *conn;\n        int64_t now, then;\n\n        msg = msg_tmo_min();\n        if (msg == NULL) {\n            ctx->timeout = ctx->max_timeout;\n            return;\n        }\n\n        /* skip over req that are in-error or done */\n\n        if (msg->error || msg->done) {\n            msg_tmo_delete(msg);\n            continue;\n        }\n\n        /*\n         * timeout expired req and all the outstanding req on the timing\n         * out server\n         */\n\n        conn = msg->tmo_rbe.data;\n        then = msg->tmo_rbe.key;\n\n        now = nc_msec_now();\n        if (now < then) {\n            int delta = (int)(then - now);\n            ctx->timeout = MIN(delta, ctx->max_timeout);\n            return;\n        }\n\n        log_debug(LOG_INFO, \"req %\"PRIu64\" on s %d timedout\", msg->id, conn->sd);\n\n        msg_tmo_delete(msg);\n        conn->err = ETIMEDOUT;\n\n        core_close(ctx, conn);\n    }\n}\n\nrstatus_t\ncore_core(void *arg, uint32_t events)\n{\n    rstatus_t status;\n    struct conn *conn = arg;\n    struct context *ctx;\n\n    if (conn->owner == NULL) {\n        log_warn(\"conn is already unrefed!\");\n        return NC_OK;\n    }\n\n    ctx = conn_to_ctx(conn);\n\n    log_debug(LOG_VVERB, \"event %04\"PRIX32\" on %c %d\", events,\n              conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd);\n\n    conn->events = events;\n\n    /* error takes precedence over read | write */\n    if (events & EVENT_ERR) {\n        core_error(ctx, conn);\n        return NC_ERROR;\n    }\n\n    /* read takes precedence over write */\n    if (events & EVENT_READ) {\n        status = core_recv(ctx, conn);\n        if (status != NC_OK || conn->done || conn->err) {\n            core_close(ctx, conn);\n            return NC_ERROR;\n        }\n    }\n\n    if (events & EVENT_WRITE) {\n        status = core_send(ctx, conn);\n        if (status != NC_OK || conn->done || conn->err) {\n            core_close(ctx, conn);\n            return NC_ERROR;\n        }\n    }\n\n    return NC_OK;\n}\n\nrstatus_t\ncore_loop(struct context *ctx)\n{\n    int nsd;\n\n    nsd = event_wait(ctx->evb, ctx->timeout);\n    if (nsd < 0) {\n        return nsd;\n    }\n\n    core_timeout(ctx);\n\n    stats_swap(ctx->stats);\n\n    return NC_OK;\n}\n"
  },
  {
    "path": "src/nc_core.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_CORE_H_\n#define _NC_CORE_H_\n\n#ifdef HAVE_CONFIG_H\n# include <config.h>\n#endif\n\n#ifdef HAVE_DEBUG_LOG\n# define NC_DEBUG_LOG 1\n#endif\n\n#ifdef HAVE_ASSERT_PANIC\n# define NC_ASSERT_PANIC 1\n#endif\n\n#ifdef HAVE_ASSERT_LOG\n# define NC_ASSERT_LOG 1\n#endif\n\n#ifdef HAVE_STATS\n# define NC_STATS 1\n#else\n# define NC_STATS 0\n#endif\n\n#ifdef HAVE_EPOLL\n# define NC_HAVE_EPOLL 1\n#elif HAVE_KQUEUE\n# define NC_HAVE_KQUEUE 1\n#elif HAVE_EVENT_PORTS\n# define NC_HAVE_EVENT_PORTS 1\n#else\n# error missing scalable I/O event notification mechanism\n#endif\n\n#ifdef HAVE_LITTLE_ENDIAN\n# define NC_LITTLE_ENDIAN 1\n#endif\n\n#ifdef HAVE_BACKTRACE\n# define NC_HAVE_BACKTRACE 1\n#endif\n\n#define NC_OK        0\n#define NC_ERROR    -1\n#define NC_EAGAIN   -2\n#define NC_ENOMEM   -3\n\n/* reserved fds for std streams, log, stats fd, epoll etc. */\n#define RESERVED_FDS 32\n\ntypedef int rstatus_t; /* return type */\ntypedef int err_t;     /* error type */\n\nstruct array;\nstruct string;\nstruct context;\nstruct conn;\nstruct conn_tqh;\nstruct msg;\nstruct msg_tqh;\nstruct server;\nstruct server_pool;\nstruct mbuf;\nstruct mhdr;\nstruct conf;\nstruct stats;\nstruct instance;\nstruct event_base;\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <inttypes.h>\n#include <string.h>\n#include <stdio.h>\n#include <ctype.h>\n#include <errno.h>\n#include <limits.h>\n#include <time.h>\n#include <unistd.h>\n#include <pthread.h>\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <sys/time.h>\n#include <sys/resource.h>\n#include <netinet/in.h>\n\n#include <nc_array.h>\n#include <nc_string.h>\n#include <nc_queue.h>\n#include <nc_rbtree.h>\n#include <nc_log.h>\n#include <nc_util.h>\n#include <event/nc_event.h>\n#include <nc_stats.h>\n#include <nc_mbuf.h>\n#include <nc_message.h>\n#include <nc_connection.h>\n#include <nc_server.h>\n\nstruct context {\n    uint32_t           id;          /* unique context id */\n    struct conf        *cf;         /* configuration */\n    struct stats       *stats;      /* stats */\n\n    struct array       pool;        /* server_pool[] */\n    struct event_base  *evb;        /* event base */\n    int                max_timeout; /* max timeout in msec */\n    int                timeout;     /* timeout in msec */\n\n    uint32_t           max_nfd;     /* max # files */\n    uint32_t           max_ncconn;  /* max # client connections */\n    uint32_t           max_nsconn;  /* max # server connections */\n};\n\n\nstruct instance {\n    struct context  *ctx;                        /* active context */\n    int             log_level;                   /* log level */\n    const char      *log_filename;               /* log filename */\n    const char      *conf_filename;              /* configuration filename */\n    uint16_t        stats_port;                  /* stats monitoring port */\n    int             stats_interval;              /* stats aggregation interval */\n    const char      *stats_addr;                 /* stats monitoring addr */\n    char            hostname[NC_MAXHOSTNAMELEN]; /* hostname */\n    size_t          mbuf_chunk_size;             /* mbuf chunk size */\n    pid_t           pid;                         /* process id */\n    const char      *pid_filename;               /* pid filename */\n    unsigned        pidfile:1;                   /* pid file created? */\n};\n\nstruct context *core_start(struct instance *nci);\nvoid core_stop(struct context *ctx);\nrstatus_t core_core(void *arg, uint32_t events);\nrstatus_t core_loop(struct context *ctx);\n\n#endif\n"
  },
  {
    "path": "src/nc_log.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <stdarg.h>\n#include <ctype.h>\n#include <time.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n#include <nc_core.h>\n\nstatic struct logger logger;\n\nint\nlog_init(int level, const char *name)\n{\n    struct logger *l = &logger;\n\n    l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB));\n    l->name = name;\n    if (name == NULL || !strlen(name)) {\n        l->fd = STDERR_FILENO;\n    } else {\n        l->fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644);\n        if (l->fd < 0) {\n            log_stderr(\"opening log file '%s' failed: %s\", name,\n                       strerror(errno));\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nvoid\nlog_deinit(void)\n{\n    struct logger *l = &logger;\n\n    if (l->fd < 0 || l->fd == STDERR_FILENO) {\n        return;\n    }\n\n    close(l->fd);\n}\n\nvoid\nlog_reopen(void)\n{\n    struct logger *l = &logger;\n\n    if (l->fd != STDERR_FILENO) {\n        close(l->fd);\n        l->fd = open(l->name, O_WRONLY | O_APPEND | O_CREAT, 0644);\n        if (l->fd < 0) {\n            log_stderr_safe(\"reopening log file '%s' failed, ignored: %s\", l->name,\n                       strerror(errno));\n        }\n    }\n}\n\nvoid\nlog_level_up(void)\n{\n    struct logger *l = &logger;\n\n    if (l->level < LOG_PVERB) {\n        l->level++;\n        log_safe(\"up log level to %d\", l->level);\n    }\n}\n\nvoid\nlog_level_down(void)\n{\n    struct logger *l = &logger;\n\n    if (l->level > LOG_EMERG) {\n        l->level--;\n        log_safe(\"down log level to %d\", l->level);\n    }\n}\n\nvoid\nlog_level_set(int level)\n{\n    struct logger *l = &logger;\n\n    l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB));\n    loga(\"set log level to %d\", l->level);\n}\n\nvoid\nlog_stacktrace(void)\n{\n    struct logger *l = &logger;\n\n    if (l->fd < 0) {\n        return;\n    }\n    nc_stacktrace_fd(l->fd);\n}\n\nint\nlog_loggable(int level)\n{\n    struct logger *l = &logger;\n\n    if (level > l->level) {\n        return 0;\n    }\n\n    return 1;\n}\n\nvoid\n_log(const char *file, int line, int panic, const char *fmt, ...)\n{\n    struct logger *l = &logger;\n    int len, size, errno_save;\n    char buf[LOG_MAX_LEN];\n    va_list args;\n    ssize_t n;\n    struct timeval tv;\n\n    if (l->fd < 0) {\n        return;\n    }\n\n    errno_save = errno;\n    len = 0;            /* length of output buffer */\n    size = LOG_MAX_LEN; /* size of output buffer */\n\n    gettimeofday(&tv, NULL);\n    buf[len++] = '[';\n    len += nc_strftime(buf + len, size - len, \"%Y-%m-%d %H:%M:%S.\", localtime(&tv.tv_sec));\n    len += nc_scnprintf(buf + len, size - len, \"%03ld\", tv.tv_usec/1000);\n    len += nc_scnprintf(buf + len, size - len, \"] %s:%d \", file, line);\n\n    va_start(args, fmt);\n    len += nc_vscnprintf(buf + len, size - len, fmt, args);\n    va_end(args);\n\n    buf[len++] = '\\n';\n\n    n = nc_write(l->fd, buf, len);\n    if (n < 0) {\n        l->nerror++;\n    }\n\n    errno = errno_save;\n\n    if (panic) {\n        abort();\n    }\n}\n\nvoid\n_log_stderr(const char *fmt, ...)\n{\n    struct logger *l = &logger;\n    int len, size, errno_save;\n    char buf[4 * LOG_MAX_LEN];\n    va_list args;\n    ssize_t n;\n\n    errno_save = errno;\n    len = 0;                /* length of output buffer */\n    size = 4 * LOG_MAX_LEN; /* size of output buffer */\n\n    va_start(args, fmt);\n    len += nc_vscnprintf(buf, size, fmt, args);\n    va_end(args);\n\n    buf[len++] = '\\n';\n\n    n = nc_write(STDERR_FILENO, buf, len);\n    if (n < 0) {\n        l->nerror++;\n    }\n\n    errno = errno_save;\n}\n\n/*\n * Hexadecimal dump in the canonical hex + ascii display\n * See -C option in man hexdump\n */\nvoid\n_log_hexdump(const char *file, int line, const char *data, int datalen,\n             const char *fmt, ...)\n{\n    struct logger *l = &logger;\n    char buf[8 * LOG_MAX_LEN];\n    int i, off, len, size, errno_save;\n    ssize_t n;\n\n    if (l->fd < 0) {\n        return;\n    }\n\n    /* log hexdump */\n    errno_save = errno;\n    off = 0;                  /* data offset */\n    len = 0;                  /* length of output buffer */\n    size = 8 * LOG_MAX_LEN;   /* size of output buffer */\n\n    while (datalen != 0 && (len < size - 1)) {\n        const char *save, *str;\n        unsigned char c;\n        int savelen;\n\n        len += nc_scnprintf(buf + len, size - len, \"%08x  \", off);\n\n        save = data;\n        savelen = datalen;\n\n        for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) {\n            c = (unsigned char)(*data);\n            str = (i == 7) ? \"  \" : \" \";\n            len += nc_scnprintf(buf + len, size - len, \"%02x%s\", c, str);\n        }\n        for ( ; i < 16; i++) {\n            str = (i == 7) ? \"  \" : \" \";\n            len += nc_scnprintf(buf + len, size - len, \"  %s\", str);\n        }\n\n        data = save;\n        datalen = savelen;\n\n        len += nc_scnprintf(buf + len, size - len, \"  |\");\n\n        for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) {\n            c = (unsigned char)(isprint(*data) ? *data : '.');\n            len += nc_scnprintf(buf + len, size - len, \"%c\", c);\n        }\n        len += nc_scnprintf(buf + len, size - len, \"|\\n\");\n\n        off += 16;\n    }\n\n    n = nc_write(l->fd, buf, len);\n    if (n < 0) {\n        l->nerror++;\n    }\n\n    if (len >= size - 1) {\n        n = nc_write(l->fd, \"\\n\", 1);\n        if (n < 0) {\n            l->nerror++;\n        }\n    }\n\n    errno = errno_save;\n}\n\nvoid\n_log_safe(const char *fmt, ...)\n{\n    struct logger *l = &logger;\n    int len, size, errno_save;\n    char buf[LOG_MAX_LEN];\n    va_list args;\n    ssize_t n;\n\n    if (l->fd < 0) {\n        return;\n    }\n\n    errno_save = errno;\n    len = 0;            /* length of output buffer */\n    size = LOG_MAX_LEN; /* size of output buffer */\n\n    len += nc_safe_snprintf(buf + len, size - len, \"[.......................] \");\n\n    va_start(args, fmt);\n    len += nc_safe_vsnprintf(buf + len, size - len, fmt, args);\n    va_end(args);\n\n    buf[len++] = '\\n';\n\n    n = nc_write(l->fd, buf, len);\n    if (n < 0) {\n        l->nerror++;\n    }\n\n    errno = errno_save;\n}\n\nvoid\n_log_stderr_safe(const char *fmt, ...)\n{\n    struct logger *l = &logger;\n    int len, size, errno_save;\n    char buf[LOG_MAX_LEN];\n    va_list args;\n    ssize_t n;\n\n    errno_save = errno;\n    len = 0;            /* length of output buffer */\n    size = LOG_MAX_LEN; /* size of output buffer */\n\n    len += nc_safe_snprintf(buf + len, size - len, \"[.......................] \");\n\n    va_start(args, fmt);\n    len += nc_safe_vsnprintf(buf + len, size - len, fmt, args);\n    va_end(args);\n\n    buf[len++] = '\\n';\n\n    n = nc_write(STDERR_FILENO, buf, len);\n    if (n < 0) {\n        l->nerror++;\n    }\n\n    errno = errno_save;\n}\n"
  },
  {
    "path": "src/nc_log.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_LOG_H_\n#define _NC_LOG_H_\n\n#include <nc_util.h>\n\nstruct logger {\n    const char *name; /* log file name */\n    int  level;       /* log level */\n    int  fd;          /* log file descriptor */\n    int  nerror;      /* # log error */\n};\n\n#define LOG_EMERG   0   /* system in unusable */\n#define LOG_ALERT   1   /* action must be taken immediately */\n#define LOG_CRIT    2   /* critical conditions */\n#define LOG_ERR     3   /* error conditions */\n#define LOG_WARN    4   /* warning conditions */\n#define LOG_NOTICE  5   /* normal but significant condition (default) */\n#define LOG_INFO    6   /* informational */\n#define LOG_DEBUG   7   /* debug messages */\n#define LOG_VERB    8   /* verbose messages */\n#define LOG_VVERB   9   /* verbose messages on crack */\n#define LOG_VVVERB  10  /* verbose messages on ganga */\n#define LOG_PVERB   11  /* periodic verbose messages on crack */\n\n#define LOG_MAX_LEN 256 /* max length of log message */\n\n/*\n * log_stderr   - log to stderr\n * loga         - log always\n * loga_hexdump - log hexdump always\n * log_error    - error log messages\n * log_warn     - warning log messages\n * log_panic    - log messages followed by a panic\n * ...\n * log_debug    - debug log messages based on a log level\n * log_hexdump  - hexadump -C of a log buffer\n */\n#ifdef NC_DEBUG_LOG\n\n#define log_debug(_level, ...) do {                                         \\\n    if (log_loggable(_level) != 0) {                                        \\\n        _log(__FILE__, __LINE__, 0, __VA_ARGS__);                           \\\n    }                                                                       \\\n} while (0)\n\n#define log_hexdump(_level, _data, _datalen, ...) do {                      \\\n    if (log_loggable(_level) != 0) {                                        \\\n        _log(__FILE__, __LINE__, 0, __VA_ARGS__);                           \\\n        _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen),  \\\n                     __VA_ARGS__);                                          \\\n    }                                                                       \\\n} while (0)\n\n#else\n\n#define log_debug(_level, ...)\n#define log_hexdump(_level, _data, _datalen, ...)\n\n#endif\n\n#define log_stderr(...) do {                                                \\\n    _log_stderr(__VA_ARGS__);                                               \\\n} while (0)\n\n#define log_safe(...) do {                                                  \\\n    _log_safe(__VA_ARGS__);                                                 \\\n} while (0)\n\n#define log_stderr_safe(...) do {                                           \\\n    _log_stderr_safe(__VA_ARGS__);                                          \\\n} while (0)\n\n#define loga(...) do {                                                      \\\n    _log(__FILE__, __LINE__, 0, __VA_ARGS__);                               \\\n} while (0)\n\n#define loga_hexdump(_data, _datalen, ...) do {                             \\\n    _log(__FILE__, __LINE__, 0, __VA_ARGS__);                               \\\n    _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen),      \\\n                 __VA_ARGS__);                                              \\\n} while (0)                                                                 \\\n\n#define log_error(...) do {                                                 \\\n    if (log_loggable(LOG_ALERT) != 0) {                                     \\\n        _log(__FILE__, __LINE__, 0, __VA_ARGS__);                           \\\n    }                                                                       \\\n} while (0)\n\n#define log_warn(...) do {                                                  \\\n    if (log_loggable(LOG_WARN) != 0) {                                      \\\n        _log(__FILE__, __LINE__, 0, __VA_ARGS__);                           \\\n    }                                                                       \\\n} while (0)\n\n#define log_panic(...) do {                                                 \\\n    if (log_loggable(LOG_EMERG) != 0) {                                     \\\n        _log(__FILE__, __LINE__, 1, __VA_ARGS__);                           \\\n    }                                                                       \\\n} while (0)\n\n\nint log_init(int level, const char *filename);\nvoid log_deinit(void);\nvoid log_level_up(void);\nvoid log_level_down(void);\nvoid log_level_set(int level);\nvoid log_stacktrace(void);\nvoid log_reopen(void);\nint log_loggable(int level);\nvoid _log(const char *file, int line, int panic, const char *fmt, ...) NC_ATTRIBUTE_FORMAT(printf, 4, 5);\nvoid _log_stderr(const char *fmt, ...) NC_ATTRIBUTE_FORMAT(printf, 1, 2);\nvoid _log_safe(const char *fmt, ...) NC_ATTRIBUTE_FORMAT(printf, 1, 2);\nvoid _log_stderr_safe(const char *fmt, ...) NC_ATTRIBUTE_FORMAT(printf, 1, 2);\nvoid _log_hexdump(const char *file, int line, const char *data, int datalen, const char *fmt, ...);\n\n#endif\n"
  },
  {
    "path": "src/nc_mbuf.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include <nc_core.h>\n\nstatic uint32_t nfree_mbufq;   /* # free mbuf */\nstatic struct mhdr free_mbufq; /* free mbuf q */\n\nstatic size_t mbuf_chunk_size; /* mbuf chunk size - header + data (const) */\nstatic size_t mbuf_offset;     /* mbuf offset in chunk (const) */\n\nstatic struct mbuf *\n_mbuf_get(void)\n{\n    struct mbuf *mbuf;\n    uint8_t *buf;\n\n    if (!STAILQ_EMPTY(&free_mbufq)) {\n        ASSERT(nfree_mbufq > 0);\n\n        mbuf = STAILQ_FIRST(&free_mbufq);\n        nfree_mbufq--;\n        STAILQ_REMOVE_HEAD(&free_mbufq, next);\n\n        ASSERT(mbuf->magic == MBUF_MAGIC);\n        goto done;\n    }\n\n    buf = nc_alloc(mbuf_chunk_size);\n    if (buf == NULL) {\n        return NULL;\n    }\n\n    /*\n     * mbuf header is at the tail end of the mbuf. This enables us to catch\n     * buffer overrun early by asserting on the magic value during get or\n     * put operations\n     *\n     *   <------------- mbuf_chunk_size ------------->\n     *   +-------------------------------------------+\n     *   |       mbuf data          |  mbuf header   |\n     *   |     (mbuf_offset)        | (struct mbuf)  |\n     *   +-------------------------------------------+\n     *   ^           ^        ^     ^^\n     *   |           |        |     ||\n     *   \\           |        |     |\\\n     *   mbuf->start \\        |     | mbuf->end (one byte past valid bound)\n     *                mbuf->pos     \\\n     *                        \\      mbuf\n     *                        mbuf->last (one byte past valid byte)\n     *\n     */\n    mbuf = (struct mbuf *)(buf + mbuf_offset);\n    mbuf->magic = MBUF_MAGIC;\n\ndone:\n    STAILQ_NEXT(mbuf, next) = NULL;\n    return mbuf;\n}\n\nstruct mbuf *\nmbuf_get(void)\n{\n    struct mbuf *mbuf;\n    uint8_t *buf;\n\n    mbuf = _mbuf_get();\n    if (mbuf == NULL) {\n        return NULL;\n    }\n\n    buf = (uint8_t *)mbuf - mbuf_offset;\n    mbuf->start = buf;\n    mbuf->end = buf + mbuf_offset;\n\n    ASSERT(mbuf->end - mbuf->start == (int)mbuf_offset);\n    ASSERT(mbuf->start < mbuf->end);\n\n    mbuf->pos = mbuf->start;\n    mbuf->last = mbuf->start;\n\n    log_debug(LOG_VVERB, \"get mbuf %p\", mbuf);\n\n    return mbuf;\n}\n\nstatic void\nmbuf_free(struct mbuf *mbuf)\n{\n    uint8_t *buf;\n\n    log_debug(LOG_VVERB, \"put mbuf %p len %d\", mbuf, (int)(mbuf->last - mbuf->pos));\n\n    ASSERT(STAILQ_NEXT(mbuf, next) == NULL);\n    ASSERT(mbuf->magic == MBUF_MAGIC);\n\n    buf = (uint8_t *)mbuf - mbuf_offset;\n    nc_free(buf);\n}\n\nvoid\nmbuf_put(struct mbuf *mbuf)\n{\n    log_debug(LOG_VVERB, \"put mbuf %p len %d\", mbuf, (int)(mbuf->last - mbuf->pos));\n\n    ASSERT(STAILQ_NEXT(mbuf, next) == NULL);\n    ASSERT(mbuf->magic == MBUF_MAGIC);\n\n    nfree_mbufq++;\n    STAILQ_INSERT_HEAD(&free_mbufq, mbuf, next);\n}\n\n/*\n * Rewind the mbuf by discarding any of the read or unread data that it\n * might hold.\n */\nvoid\nmbuf_rewind(struct mbuf *mbuf)\n{\n    mbuf->pos = mbuf->start;\n    mbuf->last = mbuf->start;\n}\n\n/*\n * Return the length of data in mbuf. Mbuf cannot contain more than\n * 2^32 bytes (4G).\n */\nuint32_t\nmbuf_length(const struct mbuf *mbuf)\n{\n    ASSERT(mbuf->last >= mbuf->pos);\n\n    return (uint32_t)(mbuf->last - mbuf->pos);\n}\n\n/*\n * Return the remaining space size for any new data in mbuf. Mbuf cannot\n * contain more than 2^32 bytes (4G).\n */\nuint32_t\nmbuf_size(const struct mbuf *mbuf)\n{\n    ASSERT(mbuf->end >= mbuf->last);\n\n    return (uint32_t)(mbuf->end - mbuf->last);\n}\n\n/*\n * Return the maximum available space size for data in any mbuf. Mbuf cannot\n * contain more than 2^32 bytes (4G).\n */\nsize_t\nmbuf_data_size(void)\n{\n    return mbuf_offset;\n}\n\n/*\n * Insert mbuf at the tail of the mhdr Q\n */\nvoid\nmbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf)\n{\n    STAILQ_INSERT_TAIL(mhdr, mbuf, next);\n    log_debug(LOG_VVERB, \"insert mbuf %p len %d\", mbuf,\n            (int)(mbuf->last - mbuf->pos));\n}\n\n/*\n * Remove mbuf from the mhdr Q\n */\nvoid\nmbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf)\n{\n    log_debug(LOG_VVERB, \"remove mbuf %p len %d\", mbuf,\n            (int)(mbuf->last - mbuf->pos));\n\n    STAILQ_REMOVE(mhdr, mbuf, mbuf, next);\n    STAILQ_NEXT(mbuf, next) = NULL;\n}\n\n/*\n * Copy n bytes from memory area pos to mbuf.\n *\n * The memory areas should not overlap and the mbuf should have\n * enough space for n bytes.\n */\nvoid\nmbuf_copy(struct mbuf *mbuf, const uint8_t *pos, size_t n)\n{\n    if (n == 0) {\n        return;\n    }\n\n    /* mbuf has space for n bytes */\n    ASSERT(!mbuf_full(mbuf) && n <= mbuf_size(mbuf));\n\n    /* no overlapping copy */\n    ASSERT(pos < mbuf->start || pos >= mbuf->end);\n\n    nc_memcpy(mbuf->last, pos, n);\n    mbuf->last += n;\n}\n\n/*\n * Split mbuf h into h and t by copying data from h to t. Before\n * the copy, we invoke a precopy handler cb that will copy a predefined\n * string to the head of t.\n *\n * Return new mbuf t, if the split was successful.\n */\nstruct mbuf *\nmbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg)\n{\n    struct mbuf *mbuf, *nbuf;\n    size_t size;\n\n    ASSERT(!STAILQ_EMPTY(h));\n\n    mbuf = STAILQ_LAST(h, mbuf, next);\n    ASSERT(pos >= mbuf->pos && pos <= mbuf->last);\n\n    nbuf = mbuf_get();\n    if (nbuf == NULL) {\n        return NULL;\n    }\n\n    if (cb != NULL) {\n        /* precopy nbuf */\n        cb(nbuf, cbarg);\n    }\n\n    /* copy data from mbuf to nbuf */\n    size = (size_t)(mbuf->last - pos);\n    mbuf_copy(nbuf, pos, size);\n\n    /* adjust mbuf */\n    mbuf->last = pos;\n\n    log_debug(LOG_VVERB, \"split into mbuf %p len %\"PRIu32\" and nbuf %p len \"\n              \"%\"PRIu32\" copied %zu bytes\", mbuf, mbuf_length(mbuf), nbuf,\n              mbuf_length(nbuf), size);\n\n    return nbuf;\n}\n\nvoid\nmbuf_init(const struct instance *nci)\n{\n    nfree_mbufq = 0;\n    STAILQ_INIT(&free_mbufq);\n\n    mbuf_chunk_size = nci->mbuf_chunk_size;\n    mbuf_offset = mbuf_chunk_size - MBUF_HSIZE;\n\n    log_debug(LOG_DEBUG, \"mbuf hsize %d chunk size %zu offset %zu length %zu\",\n              (int)MBUF_HSIZE, mbuf_chunk_size, mbuf_offset, mbuf_offset);\n}\n\nvoid\nmbuf_deinit(void)\n{\n    while (!STAILQ_EMPTY(&free_mbufq)) {\n        struct mbuf *mbuf = STAILQ_FIRST(&free_mbufq);\n        mbuf_remove(&free_mbufq, mbuf);\n        mbuf_free(mbuf);\n        nfree_mbufq--;\n    }\n    ASSERT(nfree_mbufq == 0);\n}\n"
  },
  {
    "path": "src/nc_mbuf.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_MBUF_H_\n#define _NC_MBUF_H_\n\n#include <nc_core.h>\n\ntypedef void (*mbuf_copy_t)(struct mbuf *, void *);\n\nstruct mbuf {\n    uint32_t           magic;   /* mbuf magic (const) */\n    STAILQ_ENTRY(mbuf) next;    /* next mbuf */\n    uint8_t            *pos;    /* read marker */\n    uint8_t            *last;   /* write marker */\n    uint8_t            *start;  /* start of buffer (const) */\n    uint8_t            *end;    /* end of buffer (const) */\n};\n\nSTAILQ_HEAD(mhdr, mbuf);\n\n#define MBUF_MAGIC      0xdeadbeef\n#define MBUF_MIN_SIZE   512\n#define MBUF_MAX_SIZE   16777216\n#define MBUF_SIZE       16384\n#define MBUF_HSIZE      sizeof(struct mbuf)\n\nstatic inline bool\nmbuf_empty(const struct mbuf *mbuf)\n{\n    return mbuf->pos == mbuf->last;\n}\n\nstatic inline bool\nmbuf_full(const struct mbuf *mbuf)\n{\n    return mbuf->last == mbuf->end;\n}\n\nvoid mbuf_init(const struct instance *nci);\nvoid mbuf_deinit(void);\nstruct mbuf *mbuf_get(void);\nvoid mbuf_put(struct mbuf *mbuf);\nvoid mbuf_rewind(struct mbuf *mbuf);\nuint32_t mbuf_length(const struct mbuf *mbuf);\nuint32_t mbuf_size(const struct mbuf *mbuf);\nsize_t mbuf_data_size(void);\nvoid mbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf);\nvoid mbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf);\nvoid mbuf_copy(struct mbuf *mbuf, const uint8_t *pos, size_t n);\nstruct mbuf *mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg);\n\n#endif\n"
  },
  {
    "path": "src/nc_message.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#include <sys/uio.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <proto/nc_proto.h>\n\n#if (IOV_MAX > 128)\n#define NC_IOV_MAX 128\n#else\n#define NC_IOV_MAX IOV_MAX\n#endif\n\n/*\n *            nc_message.[ch]\n *         message (struct msg)\n *            +        +            .\n *            |        |            .\n *            /        \\            .\n *         Request    Response      .../ nc_mbuf.[ch]  (mesage buffers)\n *      nc_request.c  nc_response.c .../ nc_memcache.c; nc_redis.c (message parser)\n *\n * Messages in nutcracker are manipulated by a chain of processing handlers,\n * where each handler is responsible for taking the input and producing an\n * output for the next handler in the chain. This mechanism of processing\n * loosely conforms to the standard chain-of-responsibility design pattern\n *\n * At the high level, each handler takes in a message: request or response\n * and produces the message for the next handler in the chain. The input\n * for a handler is either a request or response, but never both and\n * similarly the output of an handler is either a request or response or\n * nothing.\n *\n * Each handler itself is composed of two processing units:\n *\n * 1). filter: manipulates output produced by the handler, usually based\n *     on a policy. If needed, multiple filters can be hooked into each\n *     location.\n * 2). forwarder: chooses one of the backend servers to send the request\n *     to, usually based on the configured distribution and key hasher.\n *\n * Handlers are registered either with Client or Server or Proxy\n * connections. A Proxy connection only has a read handler as it is only\n * responsible for accepting new connections from client. Read handler\n * (conn_recv_t) registered with client is responsible for reading requests,\n * while that registered with server is responsible for reading responses.\n * Write handler (conn_send_t) registered with client is responsible for\n * writing response, while that registered with server is responsible for\n * writing requests.\n *\n * Note that in the above discussion, the terminology send is used\n * synonymously with write or OUT event. Similarly recv is used synonymously\n * with read or IN event\n *\n *             Client+             Proxy           Server+\n *                              (nutcracker)\n *                                   .\n *       msg_recv {read event}       .       msg_recv {read event}\n *         +                         .                         +\n *         |                         .                         |\n *         \\                         .                         /\n *         req_recv_next             .             rsp_recv_next\n *           +                       .                       +\n *           |                       .                       |       Rsp\n *           req_recv_done           .           rsp_recv_done      <===\n *             +                     .                     +\n *             |                     .                     |\n *    Req      \\                     .                     /\n *    ===>     req_filter*           .           *rsp_filter\n *               +                   .                   +\n *               |                   .                   |\n *               \\                   .                   /\n *               req_forward-//  (a) . (c)  \\\\-rsp_forward\n *                                   .\n *                                   .\n *       msg_send {write event}      .      msg_send {write event}\n *         +                         .                         +\n *         |                         .                         |\n *    Rsp' \\                         .                         /     Req'\n *   <===  rsp_send_next             .             req_send_next     ===>\n *           +                       .                       +\n *           |                       .                       |\n *           \\                       .                       /\n *           rsp_send_done-//    (d) . (b)    //-req_send_done\n *\n *\n * (a) -> (b) -> (c) -> (d) is the normal flow of transaction consisting\n * of a single request response, where (a) and (b) handle request from\n * client, while (c) and (d) handle the corresponding response from the\n * server.\n */\n\nstatic uint64_t msg_id;          /* message id counter */\nstatic uint64_t frag_id;         /* fragment id counter */\nstatic uint32_t nfree_msgq;      /* # free msg q */\nstatic struct msg_tqh free_msgq; /* free msg q */\nstatic struct rbtree tmo_rbt;    /* timeout rbtree */\nstatic struct rbnode tmo_rbs;    /* timeout rbtree sentinel */\n\n#define DEFINE_ACTION(_name) string(#_name),\nstatic const struct string msg_type_strings[] = {\n    MSG_TYPE_CODEC( DEFINE_ACTION )\n    null_string\n};\n#undef DEFINE_ACTION\n\nstatic struct msg *\nmsg_from_rbe(struct rbnode *node)\n{\n    struct msg *msg;\n    int offset;\n\n    offset = offsetof(struct msg, tmo_rbe);\n    msg = (struct msg *)((char *)node - offset);\n\n    return msg;\n}\n\nstruct msg *\nmsg_tmo_min(void)\n{\n    struct rbnode *node;\n\n    node = rbtree_min(&tmo_rbt);\n    if (node == NULL) {\n        return NULL;\n    }\n\n    return msg_from_rbe(node);\n}\n\nvoid\nmsg_tmo_insert(struct msg *msg, struct conn *conn)\n{\n    struct rbnode *node;\n    int timeout;\n\n    ASSERT(msg->request);\n    ASSERT(!msg->quit && !msg->noreply);\n\n    timeout = server_timeout(conn);\n    if (timeout <= 0) {\n        return;\n    }\n\n    node = &msg->tmo_rbe;\n    node->key = nc_msec_now() + timeout;\n    node->data = conn;\n\n    rbtree_insert(&tmo_rbt, node);\n\n    log_debug(LOG_VERB, \"insert msg %\"PRIu64\" into tmo rbt with expiry of \"\n              \"%d msec\", msg->id, timeout);\n}\n\nvoid\nmsg_tmo_delete(struct msg *msg)\n{\n    struct rbnode *node;\n\n    node = &msg->tmo_rbe;\n\n    /* already deleted */\n\n    if (node->data == NULL) {\n        return;\n    }\n\n    rbtree_delete(&tmo_rbt, node);\n\n    log_debug(LOG_VERB, \"delete msg %\"PRIu64\" from tmo rbt\", msg->id);\n}\n\nstatic struct msg *\n_msg_get(void)\n{\n    struct msg *msg;\n\n    if (!TAILQ_EMPTY(&free_msgq)) {\n        ASSERT(nfree_msgq > 0);\n\n        msg = TAILQ_FIRST(&free_msgq);\n        nfree_msgq--;\n        TAILQ_REMOVE(&free_msgq, msg, m_tqe);\n        goto done;\n    }\n\n    msg = nc_alloc(sizeof(*msg));\n    if (msg == NULL) {\n        return NULL;\n    }\n\ndone:\n    /* c_tqe, s_tqe, and m_tqe are left uninitialized */\n    msg->id = ++msg_id;\n    msg->peer = NULL;\n    msg->owner = NULL;\n\n    rbtree_node_init(&msg->tmo_rbe);\n\n    STAILQ_INIT(&msg->mhdr);\n    msg->mlen = 0;\n    msg->start_ts = 0;\n\n    msg->state = 0;\n    msg->pos = NULL;\n    msg->token = NULL;\n\n    msg->parser = NULL;\n    msg->add_auth = NULL;\n    msg->result = MSG_PARSE_OK;\n\n    msg->fragment = NULL;\n    msg->reply = NULL;\n    msg->pre_coalesce = NULL;\n    msg->post_coalesce = NULL;\n\n    msg->type = MSG_UNKNOWN;\n\n    msg->keys = array_create(1, sizeof(struct keypos));\n    if (msg->keys == NULL) {\n        nc_free(msg);\n        return NULL;\n    }\n\n    msg->vlen = 0;\n    msg->end = NULL;\n\n    msg->frag_owner = NULL;\n    msg->frag_seq = NULL;\n    msg->nfrag = 0;\n    msg->nfrag_done = 0;\n    msg->frag_id = 0;\n\n    msg->narg_start = NULL;\n    msg->narg_end = NULL;\n    msg->narg = 0;\n    msg->rnarg = 0;\n    msg->rlen = 0;\n    /*\n     * This is used for both parsing redis responses\n     * and as a counter for coalescing responses such as DEL\n     */\n    msg->integer = 0;\n\n    msg->err = 0;\n    msg->error = 0;\n    msg->ferror = 0;\n    msg->request = 0;\n    msg->quit = 0;\n    msg->noreply = 0;\n    msg->noforward = 0;\n    msg->done = 0;\n    msg->fdone = 0;\n    msg->swallow = 0;\n    msg->redis = 0;\n\n    return msg;\n}\n\nstruct msg *\nmsg_get(struct conn *conn, bool request, bool redis)\n{\n    struct msg *msg;\n\n    msg = _msg_get();\n    if (msg == NULL) {\n        return NULL;\n    }\n\n    msg->owner = conn;\n    msg->request = request ? 1 : 0;\n    msg->redis = redis ? 1 : 0;\n\n    if (redis) {\n        if (request) {\n            msg->parser = redis_parse_req;\n        } else {\n            msg->parser = redis_parse_rsp;\n        }\n        msg->add_auth = redis_add_auth;\n        msg->fragment = redis_fragment;\n        msg->reply = redis_reply;\n        msg->failure = redis_failure;\n        msg->pre_coalesce = redis_pre_coalesce;\n        msg->post_coalesce = redis_post_coalesce;\n    } else {\n        if (request) {\n            msg->parser = memcache_parse_req;\n        } else {\n            msg->parser = memcache_parse_rsp;\n        }\n        msg->add_auth = memcache_add_auth;\n        msg->fragment = memcache_fragment;\n        msg->failure = memcache_failure;\n        msg->pre_coalesce = memcache_pre_coalesce;\n        msg->post_coalesce = memcache_post_coalesce;\n    }\n\n    if (log_loggable(LOG_NOTICE) != 0) {\n        msg->start_ts = nc_usec_now();\n    }\n\n    log_debug(LOG_VVERB, \"get msg %p id %\"PRIu64\" request %d owner sd %d\",\n              msg, msg->id, msg->request, conn->sd);\n\n    return msg;\n}\n\nstruct msg *\nmsg_get_error(bool redis, err_t err)\n{\n    struct msg *msg;\n    struct mbuf *mbuf;\n    int n;\n    const char *errstr = err ? strerror(err) : \"unknown\";\n    const char *protstr = redis ? \"-ERR\" : \"SERVER_ERROR\";\n\n    msg = _msg_get();\n    if (msg == NULL) {\n        return NULL;\n    }\n\n    msg->state = 0;\n    msg->type = MSG_RSP_MC_SERVER_ERROR;\n\n    mbuf = mbuf_get();\n    if (mbuf == NULL) {\n        msg_put(msg);\n        return NULL;\n    }\n    mbuf_insert(&msg->mhdr, mbuf);\n\n    n = nc_scnprintf(mbuf->last, mbuf_size(mbuf), \"%s %s\"CRLF, protstr, errstr);\n    mbuf->last += n;\n    msg->mlen = (uint32_t)n;\n\n    log_debug(LOG_VVERB, \"get msg %p id %\"PRIu64\" len %\"PRIu32\" error '%s'\",\n              msg, msg->id, msg->mlen, errstr);\n\n    return msg;\n}\n\nstatic void\nmsg_free(struct msg *msg)\n{\n    ASSERT(STAILQ_EMPTY(&msg->mhdr));\n\n    log_debug(LOG_VVERB, \"free msg %p id %\"PRIu64\"\", msg, msg->id);\n    nc_free(msg);\n}\n\nvoid\nmsg_put(struct msg *msg)\n{\n    log_debug(LOG_VVERB, \"put msg %p id %\"PRIu64\"\", msg, msg->id);\n\n    while (!STAILQ_EMPTY(&msg->mhdr)) {\n        struct mbuf *mbuf = STAILQ_FIRST(&msg->mhdr);\n        mbuf_remove(&msg->mhdr, mbuf);\n        mbuf_put(mbuf);\n    }\n\n    if (msg->frag_seq) {\n        nc_free(msg->frag_seq);\n        msg->frag_seq = NULL;\n    }\n\n    if (msg->keys) {\n        msg->keys->nelem = 0; /* a hack here */\n        array_destroy(msg->keys);\n        msg->keys = NULL;\n    }\n\n    nfree_msgq++;\n    TAILQ_INSERT_HEAD(&free_msgq, msg, m_tqe);\n}\n\nvoid\nmsg_dump(const struct msg *msg, int level)\n{\n    const struct mbuf *mbuf;\n\n    if (log_loggable(level) == 0) {\n        return;\n    }\n\n    loga(\"msg dump id %\"PRIu64\" request %d len %\"PRIu32\" type %d done %d \"\n         \"error %d (err %d)\", msg->id, msg->request, msg->mlen, msg->type,\n         msg->done, msg->error, msg->err);\n\n    STAILQ_FOREACH(mbuf, &msg->mhdr, next) {\n        uint8_t *p, *q;\n        long int len;\n\n        p = mbuf->start;\n        q = mbuf->last;\n        len = q - p;\n\n        loga_hexdump(p, len, \"mbuf [%p] with %ld bytes of data\", p, len);\n    }\n}\n\nvoid\nmsg_init(void)\n{\n    log_debug(LOG_DEBUG, \"msg size %d\", (int)sizeof(struct msg));\n    msg_id = 0;\n    frag_id = 0;\n    nfree_msgq = 0;\n    TAILQ_INIT(&free_msgq);\n    rbtree_init(&tmo_rbt, &tmo_rbs);\n}\n\nvoid\nmsg_deinit(void)\n{\n    struct msg *msg, *nmsg;\n\n    for (msg = TAILQ_FIRST(&free_msgq); msg != NULL;\n         msg = nmsg, nfree_msgq--) {\n        ASSERT(nfree_msgq > 0);\n        nmsg = TAILQ_NEXT(msg, m_tqe);\n        msg_free(msg);\n    }\n    ASSERT(nfree_msgq == 0);\n}\n\nconst struct string *\nmsg_type_string(msg_type_t type)\n{\n    return &msg_type_strings[type];\n}\n\nbool\nmsg_empty(const struct msg *msg)\n{\n    return msg->mlen == 0;\n}\n\nuint32_t\nmsg_backend_idx(const struct msg *msg, const uint8_t *key, uint32_t keylen)\n{\n    struct conn *conn = msg->owner;\n    struct server_pool *pool = conn->owner;\n\n    return server_pool_idx(pool, key, keylen);\n}\n\nstruct mbuf *\nmsg_ensure_mbuf(struct msg *msg, size_t len)\n{\n    struct mbuf *mbuf;\n\n    if (STAILQ_EMPTY(&msg->mhdr) ||\n        mbuf_size(STAILQ_LAST(&msg->mhdr, mbuf, next)) < len) {\n        mbuf = mbuf_get();\n        if (mbuf == NULL) {\n            return NULL;\n        }\n        mbuf_insert(&msg->mhdr, mbuf);\n    } else {\n        mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next);\n    }\n\n    return mbuf;\n}\n\n/*\n * Append n bytes of data, with n <= mbuf_size(mbuf)\n * into mbuf\n */\nrstatus_t\nmsg_append(struct msg *msg, const uint8_t *pos, size_t n)\n{\n    struct mbuf *mbuf;\n\n    ASSERT(n <= mbuf_data_size());\n\n    mbuf = msg_ensure_mbuf(msg, n);\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n\n    ASSERT(n <= mbuf_size(mbuf));\n\n    mbuf_copy(mbuf, pos, n);\n    msg->mlen += (uint32_t)n;\n\n    return NC_OK;\n}\n\n/*\n * Prepend n bytes of data, with n <= mbuf_size(mbuf)\n * into mbuf\n */\nrstatus_t\nmsg_prepend(struct msg *msg, const uint8_t *pos, size_t n)\n{\n    struct mbuf *mbuf;\n\n    mbuf = mbuf_get();\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n\n    ASSERT(n <= mbuf_size(mbuf));\n\n    mbuf_copy(mbuf, pos, n);\n    msg->mlen += (uint32_t)n;\n\n    STAILQ_INSERT_HEAD(&msg->mhdr, mbuf, next);\n\n    return NC_OK;\n}\n\n/*\n * Prepend a formatted string into msg. Returns an error if the formatted\n * string does not fit in a single mbuf.\n */\nrstatus_t\nmsg_prepend_format(struct msg *msg, const char *fmt, ...)\n{\n    struct mbuf *mbuf;\n    int n;\n    uint32_t size;\n    va_list args;\n\n    mbuf = mbuf_get();\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n\n    size = mbuf_size(mbuf);\n\n    va_start(args, fmt);\n    n = nc_vsnprintf(mbuf->last, size, fmt, args);\n    va_end(args);\n    if (n <= 0 || n >= (int)size) {\n        return NC_ERROR;\n    }\n\n    mbuf->last += n;\n    msg->mlen += (uint32_t)n;\n    STAILQ_INSERT_HEAD(&msg->mhdr, mbuf, next);\n\n    return NC_OK;\n}\n\ninline uint64_t\nmsg_gen_frag_id(void)\n{\n    return ++frag_id;\n}\n\nstatic rstatus_t\nmsg_parsed(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    struct msg *nmsg;\n    struct mbuf *mbuf, *nbuf;\n\n    mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next);\n    if (msg->pos == mbuf->last) {\n        /* no more data to parse */\n        conn->recv_done(ctx, conn, msg, NULL);\n        return NC_OK;\n    }\n\n    /*\n     * Input mbuf has un-parsed data. Split mbuf of the current message msg\n     * into (mbuf, nbuf), where mbuf is the portion of the message that has\n     * been parsed and nbuf is the portion of the message that is un-parsed.\n     * Parse nbuf as a new message nmsg in the next iteration.\n     */\n    nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL);\n    if (nbuf == NULL) {\n        return NC_ENOMEM;\n    }\n\n    nmsg = msg_get(msg->owner, msg->request, conn->redis);\n    if (nmsg == NULL) {\n        mbuf_put(nbuf);\n        return NC_ENOMEM;\n    }\n    mbuf_insert(&nmsg->mhdr, nbuf);\n    nmsg->pos = nbuf->pos;\n\n    /* update length of current (msg) and new message (nmsg) */\n    nmsg->mlen = mbuf_length(nbuf);\n    msg->mlen -= nmsg->mlen;\n\n    conn->recv_done(ctx, conn, msg, nmsg);\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nmsg_repair(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    struct mbuf *nbuf;\n\n    nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL);\n    if (nbuf == NULL) {\n        return NC_ENOMEM;\n    }\n    mbuf_insert(&msg->mhdr, nbuf);\n    msg->pos = nbuf->pos;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nmsg_parse(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    rstatus_t status;\n\n    if (msg_empty(msg)) {\n        /* no data to parse */\n        conn->recv_done(ctx, conn, msg, NULL);\n        return NC_OK;\n    }\n\n    msg->parser(msg);\n\n    switch (msg->result) {\n    case MSG_PARSE_OK:\n        status = msg_parsed(ctx, conn, msg);\n        break;\n\n    case MSG_PARSE_REPAIR:\n        status = msg_repair(ctx, conn, msg);\n        break;\n\n    case MSG_PARSE_AGAIN:\n        status = NC_OK;\n        break;\n\n    default:\n        status = NC_ERROR;\n        conn->err = errno;\n        break;\n    }\n\n    return conn->err != 0 ? NC_ERROR : status;\n}\n\nstatic rstatus_t\nmsg_recv_chain(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    rstatus_t status;\n    struct msg *nmsg;\n    struct mbuf *mbuf;\n    size_t msize;\n    ssize_t n;\n\n    mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next);\n    if (mbuf == NULL || mbuf_full(mbuf)) {\n        mbuf = mbuf_get();\n        if (mbuf == NULL) {\n            return NC_ENOMEM;\n        }\n        mbuf_insert(&msg->mhdr, mbuf);\n        msg->pos = mbuf->pos;\n    }\n    ASSERT(mbuf->end - mbuf->last > 0);\n\n    msize = mbuf_size(mbuf);\n\n    n = conn_recv(conn, mbuf->last, msize);\n    if (n < 0) {\n        if (n == NC_EAGAIN) {\n            return NC_OK;\n        }\n        return NC_ERROR;\n    }\n\n    ASSERT((mbuf->last + n) <= mbuf->end);\n    mbuf->last += n;\n    msg->mlen += (uint32_t)n;\n\n    for (;;) {\n        status = msg_parse(ctx, conn, msg);\n        if (status != NC_OK) {\n            return status;\n        }\n\n        /* get next message to parse */\n        nmsg = conn->recv_next(ctx, conn, false);\n        if (nmsg == NULL || nmsg == msg) {\n            /* no more data to parse */\n            break;\n        }\n\n        msg = nmsg;\n    }\n\n    return NC_OK;\n}\n\nrstatus_t\nmsg_recv(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    struct msg *msg;\n\n    ASSERT(conn->recv_active);\n\n    conn->recv_ready = 1;\n    do {\n        msg = conn->recv_next(ctx, conn, true);\n        if (msg == NULL) {\n            return NC_OK;\n        }\n\n        status = msg_recv_chain(ctx, conn, msg);\n        if (status != NC_OK) {\n            return status;\n        }\n    } while (conn->recv_ready);\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nmsg_send_chain(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    struct msg_tqh send_msgq;            /* send msg q */\n    struct msg *nmsg;                    /* next msg */\n    struct mbuf *mbuf, *nbuf;            /* current and next mbuf */\n    size_t mlen;                         /* current mbuf data length */\n    struct iovec *ciov, iov[NC_IOV_MAX]; /* current iovec */\n    struct array sendv;                  /* send iovec */\n    size_t nsend, nsent;                 /* bytes to send; bytes sent */\n    size_t limit;                        /* bytes to send limit */\n    ssize_t n;                           /* bytes sent by sendv */\n\n    TAILQ_INIT(&send_msgq);\n\n    array_set(&sendv, iov, sizeof(iov[0]), NC_IOV_MAX);\n\n    /* preprocess - build iovec */\n\n    nsend = 0;\n    /*\n     * readv() and writev() returns EINVAL if the sum of the iov_len values\n     * overflows an ssize_t value Or, the vector count iovcnt is less than\n     * zero or greater than the permitted maximum.\n     */\n    limit = SSIZE_MAX;\n\n    for (;;) {\n        ASSERT(conn->smsg == msg);\n\n        TAILQ_INSERT_TAIL(&send_msgq, msg, m_tqe);\n\n        for (mbuf = STAILQ_FIRST(&msg->mhdr);\n             mbuf != NULL && array_n(&sendv) < NC_IOV_MAX && nsend < limit;\n             mbuf = nbuf) {\n            nbuf = STAILQ_NEXT(mbuf, next);\n\n            if (mbuf_empty(mbuf)) {\n                continue;\n            }\n\n            mlen = mbuf_length(mbuf);\n            if ((nsend + mlen) > limit) {\n                mlen = limit - nsend;\n            }\n\n            ciov = array_push(&sendv);\n            ciov->iov_base = mbuf->pos;\n            ciov->iov_len = mlen;\n\n            nsend += mlen;\n        }\n\n        if (array_n(&sendv) >= NC_IOV_MAX || nsend >= limit) {\n            break;\n        }\n\n        msg = conn->send_next(ctx, conn);\n        if (msg == NULL) {\n            break;\n        }\n    }\n\n    /*\n     * (nsend == 0) is possible in redis multi-del\n     * see PR: https://github.com/twitter/twemproxy/pull/225\n     */\n    conn->smsg = NULL;\n    if (!TAILQ_EMPTY(&send_msgq) && nsend != 0) {\n        n = conn_sendv(conn, &sendv, nsend);\n    } else {\n        n = 0;\n    }\n\n    nsent = n > 0 ? (size_t)n : 0;\n\n    /* postprocess - process sent messages in send_msgq */\n\n    for (msg = TAILQ_FIRST(&send_msgq); msg != NULL; msg = nmsg) {\n        nmsg = TAILQ_NEXT(msg, m_tqe);\n\n        TAILQ_REMOVE(&send_msgq, msg, m_tqe);\n\n        if (nsent == 0) {\n            if (msg->mlen == 0) {\n                conn->send_done(ctx, conn, msg);\n            }\n            continue;\n        }\n\n        /* adjust mbufs of the sent message */\n        for (mbuf = STAILQ_FIRST(&msg->mhdr); mbuf != NULL; mbuf = nbuf) {\n            nbuf = STAILQ_NEXT(mbuf, next);\n\n            if (mbuf_empty(mbuf)) {\n                continue;\n            }\n\n            mlen = mbuf_length(mbuf);\n            if (nsent < mlen) {\n                /* mbuf was sent partially; process remaining bytes later */\n                mbuf->pos += nsent;\n                ASSERT(mbuf->pos < mbuf->last);\n                nsent = 0;\n                break;\n            }\n\n            /* mbuf was sent completely; mark it empty */\n            mbuf->pos = mbuf->last;\n            nsent -= mlen;\n        }\n\n        /* message has been sent completely, finalize it */\n        if (mbuf == NULL) {\n            conn->send_done(ctx, conn, msg);\n        }\n    }\n\n    ASSERT(TAILQ_EMPTY(&send_msgq));\n\n    if (n >= 0) {\n        return NC_OK;\n    }\n\n    return (n == NC_EAGAIN) ? NC_OK : NC_ERROR;\n}\n\nrstatus_t\nmsg_send(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    struct msg *msg;\n\n    ASSERT(conn->send_active);\n\n    conn->send_ready = 1;\n    do {\n        msg = conn->send_next(ctx, conn);\n        if (msg == NULL) {\n            /* nothing to send */\n            return NC_OK;\n        }\n\n        status = msg_send_chain(ctx, conn, msg);\n        if (status != NC_OK) {\n            return status;\n        }\n\n    } while (conn->send_ready);\n\n    return NC_OK;\n}\n\n/*\n * Set a placeholder key for a command with no key that is forwarded to an\n * arbitrary backend.\n */\nbool msg_set_placeholder_key(struct msg *r)\n{\n    struct keypos *kpos;\n    ASSERT(array_n(r->keys) == 0);\n    kpos = array_push(r->keys);\n    if (kpos == NULL) {\n        return false;\n    }\n    kpos->start = (uint8_t *)\"placeholder\";\n    kpos->end = kpos->start + sizeof(\"placeholder\") - 1;\n    return true;\n}\n\n"
  },
  {
    "path": "src/nc_message.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_MESSAGE_H_\n#define _NC_MESSAGE_H_\n\n#include <nc_core.h>\n#include <proto/nc_proto.h>\n\ntypedef void (*msg_parse_t)(struct msg *);\ntypedef rstatus_t (*msg_add_auth_t)(struct context *ctx, struct conn *c_conn, struct conn *s_conn);\ntypedef rstatus_t (*msg_fragment_t)(struct msg *, uint32_t, struct msg_tqh *);\ntypedef void (*msg_coalesce_t)(struct msg *r);\ntypedef rstatus_t (*msg_reply_t)(struct msg *r);\ntypedef bool (*msg_failure_t)(const struct msg *r);\n\ntypedef enum msg_parse_result {\n    MSG_PARSE_OK,                         /* parsing ok */\n    MSG_PARSE_ERROR,                      /* parsing error */\n    MSG_PARSE_REPAIR,                     /* more to parse -> repair parsed & unparsed data */\n    MSG_PARSE_AGAIN,                      /* incomplete -> parse again */\n} msg_parse_result_t;\n\n#define MSG_TYPE_CODEC(ACTION)                                                                      \\\n    ACTION( UNKNOWN )                                                                               \\\n    ACTION( REQ_MC_GET )                       /* memcache retrieval requests */                    \\\n    ACTION( REQ_MC_GETS )                                                                           \\\n    ACTION( REQ_MC_DELETE )                    /* memcache delete request */                        \\\n    ACTION( REQ_MC_CAS )                       /* memcache cas request and storage request */       \\\n    ACTION( REQ_MC_SET )                       /* memcache storage request */                       \\\n    ACTION( REQ_MC_ADD )                                                                            \\\n    ACTION( REQ_MC_REPLACE )                                                                        \\\n    ACTION( REQ_MC_APPEND )                                                                         \\\n    ACTION( REQ_MC_PREPEND )                                                                        \\\n    ACTION( REQ_MC_INCR )                      /* memcache arithmetic request */                    \\\n    ACTION( REQ_MC_DECR )                                                                           \\\n    ACTION( REQ_MC_TOUCH )                     /* memcache touch request */                         \\\n    ACTION( REQ_MC_QUIT )                      /* memcache quit request */                          \\\n    ACTION( REQ_MC_VERSION )                   /* memcache version request */                       \\\n    ACTION( RSP_MC_NUM )                       /* memcache arithmetic response */                   \\\n    ACTION( RSP_MC_STORED )                    /* memcache cas and storage response */              \\\n    ACTION( RSP_MC_NOT_STORED )                                                                     \\\n    ACTION( RSP_MC_EXISTS )                                                                         \\\n    ACTION( RSP_MC_NOT_FOUND )                                                                      \\\n    ACTION( RSP_MC_END )                                                                            \\\n    ACTION( RSP_MC_VALUE )                                                                          \\\n    ACTION( RSP_MC_DELETED )                   /* memcache delete response */                       \\\n    ACTION( RSP_MC_TOUCHED )                   /* memcache touch response */                        \\\n    ACTION( RSP_MC_VERSION )                   /* memcache version response */                      \\\n    ACTION( RSP_MC_ERROR )                     /* memcache error responses */                       \\\n    ACTION( RSP_MC_CLIENT_ERROR )                                                                   \\\n    ACTION( RSP_MC_SERVER_ERROR )                                                                   \\\n    ACTION( REQ_REDIS_COPY )                   /* redis commands - keys */                          \\\n    ACTION( REQ_REDIS_DEL )                                                                         \\\n    ACTION( REQ_REDIS_EXISTS )                                                                      \\\n    ACTION( REQ_REDIS_EXPIRE )                                                                      \\\n    ACTION( REQ_REDIS_EXPIREAT )                                                                    \\\n    ACTION( REQ_REDIS_MOVE )                                                                        \\\n    ACTION( REQ_REDIS_PEXPIRE )                                                                     \\\n    ACTION( REQ_REDIS_PEXPIREAT )                                                                   \\\n    ACTION( REQ_REDIS_PERSIST )                                                                     \\\n    ACTION( REQ_REDIS_PTTL )                                                                        \\\n    ACTION( REQ_REDIS_SORT )                                                                        \\\n    ACTION( REQ_REDIS_TOUCH )                                                                       \\\n    ACTION( REQ_REDIS_TTL )                                                                         \\\n    ACTION( REQ_REDIS_TYPE )                                                                        \\\n    ACTION( REQ_REDIS_UNLINK )                                                                      \\\n    ACTION( REQ_REDIS_APPEND )                 /* redis requests - string */                        \\\n    ACTION( REQ_REDIS_BITCOUNT )                                                                    \\\n    ACTION( REQ_REDIS_BITFIELD )                                                                    \\\n    ACTION( REQ_REDIS_BITPOS )                                                                      \\\n    ACTION( REQ_REDIS_DECR )                                                                        \\\n    ACTION( REQ_REDIS_DECRBY )                                                                      \\\n    ACTION( REQ_REDIS_DUMP )                                                                        \\\n    ACTION( REQ_REDIS_GET )                                                                         \\\n    ACTION( REQ_REDIS_GETBIT )                                                                      \\\n    ACTION( REQ_REDIS_GETDEL )                                                                      \\\n    ACTION( REQ_REDIS_GETEX )                                                                       \\\n    ACTION( REQ_REDIS_GETRANGE )                                                                    \\\n    ACTION( REQ_REDIS_GETSET )                                                                      \\\n    ACTION( REQ_REDIS_INCR )                                                                        \\\n    ACTION( REQ_REDIS_INCRBY )                                                                      \\\n    ACTION( REQ_REDIS_INCRBYFLOAT )                                                                 \\\n    ACTION( REQ_REDIS_MGET )                                                                        \\\n    ACTION( REQ_REDIS_MSET )                                                                        \\\n    ACTION( REQ_REDIS_PSETEX )                                                                      \\\n    ACTION( REQ_REDIS_RESTORE )                                                                     \\\n    ACTION( REQ_REDIS_SET )                                                                         \\\n    ACTION( REQ_REDIS_SETBIT )                                                                      \\\n    ACTION( REQ_REDIS_SETEX )                                                                       \\\n    ACTION( REQ_REDIS_SETNX )                                                                       \\\n    ACTION( REQ_REDIS_SETRANGE )                                                                    \\\n    ACTION( REQ_REDIS_STRLEN )                                                                      \\\n    ACTION( REQ_REDIS_HDEL )                   /* redis requests - hashes */                        \\\n    ACTION( REQ_REDIS_HEXISTS )                                                                     \\\n    ACTION( REQ_REDIS_HGET )                                                                        \\\n    ACTION( REQ_REDIS_HGETALL )                                                                     \\\n    ACTION( REQ_REDIS_HINCRBY )                                                                     \\\n    ACTION( REQ_REDIS_HINCRBYFLOAT )                                                                \\\n    ACTION( REQ_REDIS_HKEYS )                                                                       \\\n    ACTION( REQ_REDIS_HLEN )                                                                        \\\n    ACTION( REQ_REDIS_HMGET )                                                                       \\\n    ACTION( REQ_REDIS_HMSET )                                                                       \\\n    ACTION( REQ_REDIS_HRANDFIELD )                                                                  \\\n    ACTION( REQ_REDIS_HSET )                                                                        \\\n    ACTION( REQ_REDIS_HSETNX )                                                                      \\\n    ACTION( REQ_REDIS_HSCAN)                                                                        \\\n    ACTION( REQ_REDIS_HSTRLEN)                                                                      \\\n    ACTION( REQ_REDIS_HVALS )                                                                       \\\n    ACTION( REQ_REDIS_LINDEX )                 /* redis requests - lists */                         \\\n    ACTION( REQ_REDIS_LINSERT )                                                                     \\\n    ACTION( REQ_REDIS_LLEN )                                                                        \\\n    ACTION( REQ_REDIS_LMOVE )                                                                       \\\n    ACTION( REQ_REDIS_LPOP )                                                                        \\\n    ACTION( REQ_REDIS_LPOS )                                                                        \\\n    ACTION( REQ_REDIS_LPUSH )                                                                       \\\n    ACTION( REQ_REDIS_LPUSHX )                                                                      \\\n    ACTION( REQ_REDIS_LRANGE )                                                                      \\\n    ACTION( REQ_REDIS_LREM )                                                                        \\\n    ACTION( REQ_REDIS_LSET )                                                                        \\\n    ACTION( REQ_REDIS_LTRIM )                                                                       \\\n    ACTION( REQ_REDIS_PFADD )                  /* redis requests - hyperloglog */                   \\\n    ACTION( REQ_REDIS_PFCOUNT )                                                                     \\\n    ACTION( REQ_REDIS_PFMERGE )                                                                     \\\n    ACTION( REQ_REDIS_RPOP )                                                                        \\\n    ACTION( REQ_REDIS_RPOPLPUSH )                                                                   \\\n    ACTION( REQ_REDIS_RPUSH )                                                                       \\\n    ACTION( REQ_REDIS_RPUSHX )                                                                      \\\n    ACTION( REQ_REDIS_SADD )                   /* redis requests - sets */                          \\\n    ACTION( REQ_REDIS_SCARD )                                                                       \\\n    ACTION( REQ_REDIS_SDIFF )                                                                       \\\n    ACTION( REQ_REDIS_SDIFFSTORE )                                                                  \\\n    ACTION( REQ_REDIS_SINTER )                                                                      \\\n    ACTION( REQ_REDIS_SINTERSTORE )                                                                 \\\n    ACTION( REQ_REDIS_SISMEMBER )                                                                   \\\n    ACTION( REQ_REDIS_SMISMEMBER )                                                                  \\\n    ACTION( REQ_REDIS_SMEMBERS )                                                                    \\\n    ACTION( REQ_REDIS_SMOVE )                                                                       \\\n    ACTION( REQ_REDIS_SPOP )                                                                        \\\n    ACTION( REQ_REDIS_SRANDMEMBER )                                                                 \\\n    ACTION( REQ_REDIS_SREM )                                                                        \\\n    ACTION( REQ_REDIS_SUNION )                                                                      \\\n    ACTION( REQ_REDIS_SUNIONSTORE )                                                                 \\\n    ACTION( REQ_REDIS_SSCAN)                                                                        \\\n    ACTION( REQ_REDIS_ZADD )                   /* redis requests - sorted sets */                   \\\n    ACTION( REQ_REDIS_ZCARD )                                                                       \\\n    ACTION( REQ_REDIS_ZCOUNT )                                                                      \\\n    ACTION( REQ_REDIS_ZDIFF )                                                                       \\\n    ACTION( REQ_REDIS_ZDIFFSTORE )                                                                  \\\n    ACTION( REQ_REDIS_ZINCRBY )                                                                     \\\n    ACTION( REQ_REDIS_ZINTER )                                                                      \\\n    ACTION( REQ_REDIS_ZINTERSTORE )                                                                 \\\n    ACTION( REQ_REDIS_ZLEXCOUNT )                                                                   \\\n    ACTION( REQ_REDIS_ZMSCORE )                                                                     \\\n    ACTION( REQ_REDIS_ZPOPMIN )                                                                     \\\n    ACTION( REQ_REDIS_ZPOPMAX )                                                                     \\\n    ACTION( REQ_REDIS_ZRANDMEMBER )                                                                 \\\n    ACTION( REQ_REDIS_ZRANGE )                                                                      \\\n    ACTION( REQ_REDIS_ZRANGEBYLEX )                                                                 \\\n    ACTION( REQ_REDIS_ZRANGEBYSCORE )                                                               \\\n    ACTION( REQ_REDIS_ZRANGESTORE )                                                                 \\\n    ACTION( REQ_REDIS_ZRANK )                                                                       \\\n    ACTION( REQ_REDIS_ZREM )                                                                        \\\n    ACTION( REQ_REDIS_ZREMRANGEBYRANK )                                                             \\\n    ACTION( REQ_REDIS_ZREMRANGEBYLEX )                                                              \\\n    ACTION( REQ_REDIS_ZREMRANGEBYSCORE )                                                            \\\n    ACTION( REQ_REDIS_ZREVRANGE )                                                                   \\\n    ACTION( REQ_REDIS_ZREVRANGEBYLEX )                                                              \\\n    ACTION( REQ_REDIS_ZREVRANGEBYSCORE )                                                            \\\n    ACTION( REQ_REDIS_ZREVRANK )                                                                    \\\n    ACTION( REQ_REDIS_ZUNION )                                                                      \\\n    ACTION( REQ_REDIS_ZSCAN )                                                                       \\\n    ACTION( REQ_REDIS_ZSCORE )                                                                      \\\n    ACTION( REQ_REDIS_ZUNIONSTORE )                                                                 \\\n    ACTION( REQ_REDIS_GEOADD )                   /* redis requests - geo */                         \\\n    ACTION( REQ_REDIS_GEODIST )                                                                     \\\n    ACTION( REQ_REDIS_GEOHASH )                                                                     \\\n    ACTION( REQ_REDIS_GEORADIUS )                                                                   \\\n    ACTION( REQ_REDIS_GEORADIUSBYMEMBER )                                                           \\\n    ACTION( REQ_REDIS_GEOPOS )                                                                      \\\n    ACTION( REQ_REDIS_GEOSEARCH)                                                                    \\\n    ACTION( REQ_REDIS_GEOSEARCHSTORE)                                                               \\\n    ACTION( REQ_REDIS_EVAL )                   /* redis requests - eval */                          \\\n    ACTION( REQ_REDIS_EVALSHA )                                                                     \\\n    ACTION( REQ_REDIS_PING )                   /* redis requests - ping/quit */                     \\\n    ACTION( REQ_REDIS_QUIT)                                                                         \\\n    ACTION( REQ_REDIS_AUTH)                                                                         \\\n    ACTION( REQ_REDIS_SELECT)                  /* only during init */                               \\\n    ACTION( REQ_REDIS_COMMAND)                 /* Sent to random server for redis-cli completions*/ \\\n    ACTION( REQ_REDIS_LOLWUT)                  /* Vitally important */                              \\\n    ACTION( RSP_REDIS_STATUS )                 /* redis response */                                 \\\n    ACTION( RSP_REDIS_ERROR )                                                                       \\\n    ACTION( RSP_REDIS_ERROR_ERR )                                                                   \\\n    ACTION( RSP_REDIS_ERROR_OOM )                                                                   \\\n    ACTION( RSP_REDIS_ERROR_BUSY )                                                                  \\\n    ACTION( RSP_REDIS_ERROR_NOAUTH )                                                                \\\n    ACTION( RSP_REDIS_ERROR_LOADING )                                                               \\\n    ACTION( RSP_REDIS_ERROR_BUSYKEY )                                                               \\\n    ACTION( RSP_REDIS_ERROR_MISCONF )                                                               \\\n    ACTION( RSP_REDIS_ERROR_NOSCRIPT )                                                              \\\n    ACTION( RSP_REDIS_ERROR_READONLY )                                                              \\\n    ACTION( RSP_REDIS_ERROR_WRONGTYPE )                                                             \\\n    ACTION( RSP_REDIS_ERROR_EXECABORT )                                                             \\\n    ACTION( RSP_REDIS_ERROR_MASTERDOWN )                                                            \\\n    ACTION( RSP_REDIS_ERROR_NOREPLICAS )                                                            \\\n    ACTION( RSP_REDIS_INTEGER )                                                                     \\\n    ACTION( RSP_REDIS_BULK )                                                                        \\\n    ACTION( RSP_REDIS_MULTIBULK )                                                                   \\\n    ACTION( SENTINEL )                                                                              \\\n\n\n#define DEFINE_ACTION(_name) MSG_##_name,\ntypedef enum msg_type {\n    MSG_TYPE_CODEC(DEFINE_ACTION)\n} msg_type_t;\n#undef DEFINE_ACTION\n\nstruct keypos {\n    uint8_t              *start;           /* key start pos */\n    uint8_t              *end;             /* key end pos */\n};\n\n/*\n * This represents a message with a list of mbufs\n * that can be a redis/memcache request/response/error response.\n */\nstruct msg {\n    TAILQ_ENTRY(msg)     c_tqe;           /* link in client q */\n    TAILQ_ENTRY(msg)     s_tqe;           /* link in server q */\n    TAILQ_ENTRY(msg)     m_tqe;           /* link in send q / free q */\n\n    uint64_t             id;              /* message id */\n    struct msg           *peer;           /* message peer */\n    struct conn          *owner;          /* message owner - client | server */\n\n    struct rbnode        tmo_rbe;         /* entry in rbtree */\n\n    struct mhdr          mhdr;            /* message mbuf header */\n    uint32_t             mlen;            /* message length */\n    int64_t              start_ts;        /* request start timestamp in usec */\n\n    int                  state;           /* current parser state */\n    uint8_t              *pos;            /* parser position marker */\n    uint8_t              *token;          /* token marker */\n\n    msg_parse_t          parser;          /* message parser */\n    msg_parse_result_t   result;          /* message parsing result */\n\n    msg_fragment_t       fragment;        /* message fragment */\n    msg_reply_t          reply;           /* generate message reply (example: ping) */\n    msg_add_auth_t       add_auth;        /* add auth message when we forward msg */\n    msg_failure_t        failure;         /* transient failure response? */\n\n    msg_coalesce_t       pre_coalesce;    /* message pre-coalesce */\n    msg_coalesce_t       post_coalesce;   /* message post-coalesce */\n\n    msg_type_t           type;            /* message type */\n\n    struct array         *keys;           /* array of keypos, for req */\n\n    uint32_t             vlen;            /* value length (memcache) */\n    uint8_t              *end;            /* end marker (memcache) */\n\n    uint8_t              *narg_start;     /* narg start (redis) */\n    uint8_t              *narg_end;       /* narg end (redis) */\n    uint32_t             narg;            /* # arguments (redis, memcache) */\n    uint32_t             rnarg;           /* running # arg used by parsing fsa (redis) */\n    uint32_t             rlen;            /* running length in parsing fsa (redis) */\n    uint32_t             integer;         /* integer reply value (redis) */\n    uint8_t              is_top_level;     /* is this top level (redis) */\n\n    struct msg           *frag_owner;     /* owner of fragment message */\n    uint32_t             nfrag;           /* # fragment */\n    uint32_t             nfrag_done;      /* # fragment done */\n    uint64_t             frag_id;         /* id of fragmented message */\n    struct msg           **frag_seq;      /* sequence of fragment message, map from keys to fragments*/\n\n    err_t                err;             /* errno on error? */\n    unsigned             error:1;         /* error? */\n    unsigned             ferror:1;        /* one or more fragments are in error? */\n    unsigned             request:1;       /* request? or response? */\n    unsigned             quit:1;          /* quit request? */\n    unsigned             noreply:1;       /* noreply? */\n    unsigned             noforward:1;     /* not need forward (example: ping) */\n    unsigned             done:1;          /* done? */\n    unsigned             fdone:1;         /* all fragments are done? */\n    unsigned             swallow:1;       /* swallow response? */\n    unsigned             redis:1;         /* redis? */\n};\n\nTAILQ_HEAD(msg_tqh, msg);\n\nstruct msg *msg_tmo_min(void);\nvoid msg_tmo_insert(struct msg *msg, struct conn *conn);\nvoid msg_tmo_delete(struct msg *msg);\n\nvoid msg_init(void);\nvoid msg_deinit(void);\nconst struct string *msg_type_string(msg_type_t type);\nstruct msg *msg_get(struct conn *conn, bool request, bool redis);\nvoid msg_put(struct msg *msg);\nstruct msg *msg_get_error(bool redis, err_t err);\nvoid msg_dump(const struct msg *msg, int level);\nbool msg_empty(const struct msg *msg);\nrstatus_t msg_recv(struct context *ctx, struct conn *conn);\nrstatus_t msg_send(struct context *ctx, struct conn *conn);\nuint64_t msg_gen_frag_id(void);\nuint32_t msg_backend_idx(const struct msg *msg, const uint8_t *key, uint32_t keylen);\nstruct mbuf *msg_ensure_mbuf(struct msg *msg, size_t len);\nrstatus_t msg_append(struct msg *msg, const uint8_t *pos, size_t n);\nrstatus_t msg_prepend(struct msg *msg, const uint8_t *pos, size_t n);\nrstatus_t msg_prepend_format(struct msg *msg, const char *fmt, ...);\nbool msg_set_placeholder_key(struct msg *r);\n\nstruct msg *req_get(struct conn *conn);\nvoid req_put(struct msg *msg);\nbool req_done(const struct conn *conn, struct msg *msg);\nbool req_error(const struct conn *conn, struct msg *msg);\nvoid req_server_enqueue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg);\nvoid req_server_enqueue_imsgq_head(struct context *ctx, struct conn *conn, struct msg *msg);\nvoid req_server_dequeue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg);\nvoid req_client_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg);\nvoid req_server_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg);\nvoid req_client_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg);\nvoid req_server_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg);\nstruct msg *req_recv_next(struct context *ctx, struct conn *conn, bool alloc);\nvoid req_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg);\nstruct msg *req_fake(struct context *ctx, struct conn *conn);\nstruct msg *req_send_next(struct context *ctx, struct conn *conn);\nvoid req_send_done(struct context *ctx, struct conn *conn, struct msg *msg);\n\nstruct msg *rsp_get(struct conn *conn);\nvoid rsp_put(struct msg *msg);\nstruct msg *rsp_recv_next(struct context *ctx, struct conn *conn, bool alloc);\nvoid rsp_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg);\nstruct msg *rsp_send_next(struct context *ctx, struct conn *conn);\nvoid rsp_send_done(struct context *ctx, struct conn *conn, struct msg *msg);\n\n#endif\n"
  },
  {
    "path": "src/nc_proxy.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <sys/stat.h>\n#include <sys/un.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_proxy.h>\n\nvoid\nproxy_ref(struct conn *conn, void *owner)\n{\n    struct server_pool *pool = owner;\n\n    ASSERT(!conn->client && conn->proxy);\n    ASSERT(conn->owner == NULL);\n\n    conn->family = pool->info.family;\n    conn->addrlen = pool->info.addrlen;\n    conn->addr = (struct sockaddr *)&pool->info.addr;\n\n    pool->p_conn = conn;\n\n    /* owner of the proxy connection is the server pool */\n    conn->owner = owner;\n\n    log_debug(LOG_VVERB, \"ref conn %p owner %p into pool %\"PRIu32\"\", conn,\n              pool, pool->idx);\n}\n\nvoid\nproxy_unref(struct conn *conn)\n{\n    struct server_pool *pool;\n\n    ASSERT(!conn->client && conn->proxy);\n    ASSERT(conn->owner != NULL);\n\n    pool = conn->owner;\n    conn->owner = NULL;\n\n    pool->p_conn = NULL;\n\n    log_debug(LOG_VVERB, \"unref conn %p owner %p from pool %\"PRIu32\"\", conn,\n              pool, pool->idx);\n}\n\nvoid\nproxy_close(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n\n    ASSERT(!conn->client && conn->proxy);\n\n    if (conn->sd < 0) {\n        conn->unref(conn);\n        conn_put(conn);\n        return;\n    }\n\n    ASSERT(conn->rmsg == NULL);\n    ASSERT(conn->smsg == NULL);\n    ASSERT(TAILQ_EMPTY(&conn->imsg_q));\n    ASSERT(TAILQ_EMPTY(&conn->omsg_q));\n\n    conn->unref(conn);\n\n    status = close(conn->sd);\n    if (status < 0) {\n        log_error(\"close p %d failed, ignored: %s\", conn->sd, strerror(errno));\n    }\n    conn->sd = -1;\n\n    conn_put(conn);\n}\n\nstatic rstatus_t\nproxy_reuse(struct conn *p)\n{\n    rstatus_t status;\n    struct sockaddr_un *un;\n\n    switch (p->family) {\n    case AF_INET:\n    case AF_INET6:\n        status = nc_set_reuseaddr(p->sd);\n        break;\n\n    case AF_UNIX:\n        /*\n         * bind() will fail if the pathname already exist. So, we call unlink()\n         * to delete the pathname, in case it already exists. If it does not\n         * exist, unlink() returns error, which we ignore\n         */\n        un = (struct sockaddr_un *) p->addr;\n        unlink(un->sun_path);\n        status = NC_OK;\n        break;\n\n    default:\n        NOT_REACHED();\n        status = NC_ERROR;\n    }\n\n    return status;\n}\n\nstatic rstatus_t\nproxy_listen(struct context *ctx, struct conn *p)\n{\n    rstatus_t status;\n    struct server_pool *pool = p->owner;\n\n    ASSERT(p->proxy);\n\n    p->sd = socket(p->family, SOCK_STREAM, 0);\n    if (p->sd < 0) {\n        log_error(\"socket failed: %s\", strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = proxy_reuse(p);\n    if (status < 0) {\n        log_error(\"reuse of addr '%.*s' for listening on p %d failed: %s\",\n                  pool->addrstr.len, pool->addrstr.data, p->sd,\n                  strerror(errno));\n        return NC_ERROR;\n    }\n\n    if (pool->reuseport) {\n        status = nc_set_reuseport(p->sd);\n        if (status < 0) {\n            log_error(\"reuse of port '%.*s' for listening on p %d failed: %s\",\n                      pool->addrstr.len, pool->addrstr.data, p->sd,\n                      strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    status = bind(p->sd, p->addr, p->addrlen);\n    if (status < 0) {\n        log_error(\"bind on p %d to addr '%.*s' failed: %s\", p->sd,\n                  pool->addrstr.len, pool->addrstr.data, strerror(errno));\n        return NC_ERROR;\n    }\n\n    if (p->family == AF_UNIX && pool->perm) {\n        struct sockaddr_un *un = (struct sockaddr_un *)p->addr;\n        status = chmod(un->sun_path, pool->perm);\n        if (status < 0) {\n            log_error(\"chmod on p %d on addr '%.*s' failed: %s\", p->sd,\n                      pool->addrstr.len, pool->addrstr.data, strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    status = listen(p->sd, pool->backlog);\n    if (status < 0) {\n        log_error(\"listen on p %d on addr '%.*s' failed: %s\", p->sd,\n                  pool->addrstr.len, pool->addrstr.data, strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = nc_set_nonblocking(p->sd);\n    if (status < 0) {\n        log_error(\"set nonblock on p %d on addr '%.*s' failed: %s\", p->sd,\n                  pool->addrstr.len, pool->addrstr.data, strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = event_add_conn(ctx->evb, p);\n    if (status < 0) {\n        log_error(\"event add conn p %d on addr '%.*s' failed: %s\",\n                  p->sd, pool->addrstr.len, pool->addrstr.data,\n                  strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = event_del_out(ctx->evb, p);\n    if (status < 0) {\n        log_error(\"event del out p %d on addr '%.*s' failed: %s\",\n                  p->sd, pool->addrstr.len, pool->addrstr.data,\n                  strerror(errno));\n        return NC_ERROR;\n    }\n\n    return NC_OK;\n}\n\nrstatus_t\nproxy_each_init(void *elem, void *data)\n{\n    rstatus_t status;\n    struct server_pool *pool = elem;\n    struct conn *p;\n\n    p = conn_get_proxy(pool);\n    if (p == NULL) {\n        return NC_ENOMEM;\n    }\n\n    status = proxy_listen(pool->ctx, p);\n    if (status != NC_OK) {\n        p->close(pool->ctx, p);\n        return status;\n    }\n\n    log_debug(LOG_NOTICE, \"p %d listening on '%.*s' in %s pool %\"PRIu32\" '%.*s'\"\n              \" with %\"PRIu32\" servers\", p->sd, pool->addrstr.len,\n              pool->addrstr.data, pool->redis ? \"redis\" : \"memcache\",\n              pool->idx, pool->name.len, pool->name.data,\n              array_n(&pool->server));\n\n    return NC_OK;\n}\n\nrstatus_t\nproxy_init(struct context *ctx)\n{\n    rstatus_t status;\n\n    ASSERT(array_n(&ctx->pool) != 0);\n\n    status = array_each(&ctx->pool, proxy_each_init, NULL);\n    if (status != NC_OK) {\n        proxy_deinit(ctx);\n        return status;\n    }\n\n    log_debug(LOG_VVERB, \"init proxy with %\"PRIu32\" pools\",\n              array_n(&ctx->pool));\n\n    return NC_OK;\n}\n\nrstatus_t\nproxy_each_deinit(void *elem, void *data)\n{\n    struct server_pool *pool = elem;\n    struct conn *p;\n\n    p = pool->p_conn;\n    if (p != NULL) {\n        p->close(pool->ctx, p);\n    }\n\n    return NC_OK;\n}\n\nvoid\nproxy_deinit(struct context *ctx)\n{\n    rstatus_t status;\n\n    ASSERT(array_n(&ctx->pool) != 0);\n\n    status = array_each(&ctx->pool, proxy_each_deinit, NULL);\n    if (status != NC_OK) {\n        return;\n    }\n\n    log_debug(LOG_VVERB, \"deinit proxy with %\"PRIu32\" pools\",\n              array_n(&ctx->pool));\n}\n\nstatic rstatus_t\nproxy_accept(struct context *ctx, struct conn *p)\n{\n    rstatus_t status;\n    struct conn *c;\n    int sd;\n    struct server_pool *pool = p->owner;\n\n    ASSERT(p->proxy && !p->client);\n    ASSERT(p->sd > 0);\n    ASSERT(p->recv_active && p->recv_ready);\n\n    for (;;) {\n        sd = accept(p->sd, NULL, NULL);\n        if (sd < 0) {\n            if (errno == EINTR) {\n                log_debug(LOG_VERB, \"accept on p %d not ready - eintr\", p->sd);\n                continue;\n            }\n\n            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED) {\n                log_debug(LOG_VERB, \"accept on p %d not ready - eagain\", p->sd);\n                p->recv_ready = 0;\n                return NC_OK;\n            }\n\n            /*\n             * Workaround of https://github.com/twitter/twemproxy/issues/97\n             *\n             * We should never reach here because the check for conn_ncurr_cconn()\n             * against ctx->max_ncconn should catch this earlier in the cycle.\n             * If we reach here ignore EMFILE/ENFILE, return NC_OK will enable\n             * the server continue to run instead of close the server socket\n             *\n             * The right solution however, is on EMFILE/ENFILE to mask out IN\n             * event on the proxy and mask it back in when some existing\n             * connections gets closed\n             */\n            if (errno == EMFILE || errno == ENFILE) {\n                log_debug(LOG_CRIT, \"accept on p %d with max fds %\"PRIu32\" \"\n                          \"used connections %\"PRIu32\" max client connections %\"PRIu32\" \"\n                          \"curr client connections %\"PRIu32\" failed: %s\",\n                          p->sd, ctx->max_nfd, conn_ncurr_conn(),\n                          ctx->max_ncconn, conn_ncurr_cconn(), strerror(errno));\n\n                p->recv_ready = 0;\n\n                return NC_OK;\n            }\n\n            log_error(\"accept on p %d failed: %s\", p->sd, strerror(errno));\n\n            return NC_ERROR;\n        }\n\n        break;\n    }\n\n    if (conn_ncurr_cconn() >= ctx->max_ncconn) {\n        log_debug(LOG_CRIT, \"client connections %\"PRIu32\" exceed limit %\"PRIu32,\n                  conn_ncurr_cconn(), ctx->max_ncconn);\n        status = close(sd);\n        if (status < 0) {\n            log_error(\"close c %d failed, ignored: %s\", sd, strerror(errno));\n        }\n        return NC_OK;\n    }\n\n    c = conn_get(p->owner, true, p->redis);\n    if (c == NULL) {\n        log_error(\"get conn for c %d from p %d failed: %s\", sd, p->sd,\n                  strerror(errno));\n        status = close(sd);\n        if (status < 0) {\n            log_error(\"close c %d failed, ignored: %s\", sd, strerror(errno));\n        }\n        return NC_ENOMEM;\n    }\n    c->sd = sd;\n\n    stats_pool_incr(ctx, c->owner, client_connections);\n\n    status = nc_set_nonblocking(c->sd);\n    if (status < 0) {\n        log_error(\"set nonblock on c %d from p %d failed: %s\", c->sd, p->sd,\n                  strerror(errno));\n        c->close(ctx, c);\n        return status;\n    }\n\n    if (pool->tcpkeepalive) {\n        status = nc_set_tcpkeepalive(c->sd);\n        if (status < 0) {\n            log_warn(\"set tcpkeepalive on c %d from p %d failed, ignored: %s\",\n                     c->sd, p->sd, strerror(errno));\n        }\n    }\n\n    if (p->family == AF_INET || p->family == AF_INET6) {\n        status = nc_set_tcpnodelay(c->sd);\n        if (status < 0) {\n            log_warn(\"set tcpnodelay on c %d from p %d failed, ignored: %s\",\n                     c->sd, p->sd, strerror(errno));\n        }\n    }\n\n    status = event_add_conn(ctx->evb, c);\n    if (status < 0) {\n        log_error(\"event add conn from p %d failed: %s\", p->sd,\n                  strerror(errno));\n        c->close(ctx, c);\n        return status;\n    }\n\n    log_debug(LOG_NOTICE, \"accepted c %d on p %d from '%s'\", c->sd, p->sd,\n              nc_unresolve_peer_desc(c->sd));\n\n    return NC_OK;\n}\n\nrstatus_t\nproxy_recv(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n\n    ASSERT(conn->proxy && !conn->client);\n    ASSERT(conn->recv_active);\n\n    conn->recv_ready = 1;\n    do {\n        status = proxy_accept(ctx, conn);\n        if (status != NC_OK) {\n            return status;\n        }\n    } while (conn->recv_ready);\n\n    return NC_OK;\n}\n"
  },
  {
    "path": "src/nc_proxy.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_PROXY_H_\n#define _NC_PROXY_H_\n\n#include <nc_core.h>\n\nvoid proxy_ref(struct conn *conn, void *owner);\nvoid proxy_unref(struct conn *conn);\nvoid proxy_close(struct context *ctx, struct conn *conn);\n\nrstatus_t proxy_each_init(void *elem, void *data);\nrstatus_t proxy_each_deinit(void *elem, void *data);\n\nrstatus_t proxy_init(struct context *ctx);\nvoid proxy_deinit(struct context *ctx);\nrstatus_t proxy_recv(struct context *ctx, struct conn *conn);\n\n#endif\n"
  },
  {
    "path": "src/nc_queue.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * Copyright (c) 1991, 1993\n *    The Regents of the University of California.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 4. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *    @(#)queue.h    8.5 (Berkeley) 8/20/94\n * $FreeBSD: src/sys/sys/queue.h,v 1.73 2010/02/20 01:05:30 emaste Exp $\n */\n\n#ifndef _NC_QUEUE_H_\n#define _NC_QUEUE_H_\n\n#include <nc_log.h>\n\n#ifndef __offsetof\n#define __offsetof(type, field) ((size_t)(&((type *)NULL)->field))\n#endif\n\n/*\n * This file defines five types of data structures: singly-linked lists,\n * singly-linked tail queues, lists, tail queues, and circular queues.\n *\n * A singly-linked list is headed by a single forward pointer. The elements\n * are singly linked for minimum space and pointer manipulation overhead at\n * the expense of O(n) removal for arbitrary elements. New elements can be\n * added to the list after an existing element or at the head of the list.\n * Elements being removed from the head of the list should use the explicit\n * macro for this purpose for optimum efficiency. A singly-linked list may\n * only be traversed in the forward direction.  Singly-linked lists are ideal\n * for applications with large datasets and few or no removals or for\n * implementing a LIFO queue.\n *\n * A singly-linked tail queue is headed by a pair of pointers, one to the\n * head of the list and the other to the tail of the list. The elements are\n * singly linked for minimum space and pointer manipulation overhead at the\n * expense of O(n) removal for arbitrary elements. New elements can be added\n * to the list after an existing element, at the head of the list, or at the\n * end of the list. Elements being removed from the head of the tail queue\n * should use the explicit macro for this purpose for optimum efficiency.\n * A singly-linked tail queue may only be traversed in the forward direction.\n * Singly-linked tail queues are ideal for applications with large datasets\n * and few or no removals or for implementing a FIFO queue.\n *\n * A list is headed by a single forward pointer (or an array of forward\n * pointers for a hash table header). The elements are doubly linked\n * so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before\n * or after an existing element or at the head of the list. A list\n * may only be traversed in the forward direction.\n *\n * A tail queue is headed by a pair of pointers, one to the head of the\n * list and the other to the tail of the list. The elements are doubly\n * linked so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before or\n * after an existing element, at the head of the list, or at the end of\n * the list. A tail queue may be traversed in either direction.\n *\n * A circle queue is headed by a pair of pointers, one to the head of the\n * list and the other to the tail of the list. The elements are doubly\n * linked so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before or after\n * an existing element, at the head of the list, or at the end of the list.\n * A circle queue may be traversed in either direction, but has a more\n * complex end of list detection.\n *\n * For details on the use of these macros, see the queue(3) manual page.\n *\n *\n *                      SLIST   LIST    STAILQ  TAILQ   CIRCLEQ\n * _HEAD                +       +       +       +       +\n * _HEAD_INITIALIZER    +       +       +       +       +\n * _ENTRY               +       +       +       +       +\n * _INIT                +       +       +       +       +\n * _EMPTY               +       +       +       +       +\n * _FIRST               +       +       +       +       +\n * _NEXT                +       +       +       +       +\n * _PREV                -       -       -       +       +\n * _LAST                -       -       +       +       +\n * _FOREACH             +       +       +       +       +\n * _FOREACH_REVERSE     -       -       -       +       +\n * _INSERT_HEAD         +       +       +       +       +\n * _INSERT_BEFORE       -       +       -       +       +\n * _INSERT_AFTER        +       +       +       +       +\n * _INSERT_TAIL         -       -       +       +       +\n * _REMOVE_HEAD         +       -       +       -       -\n * _REMOVE              +       +       +       +       +\n *\n */\n\n#define QUEUE_MACRO_SCRUB 1\n\n#ifdef NC_ASSERT_PANIC\n# define QUEUE_MACRO_TRACE  1\n# define QUEUE_MACRO_ASSERT 1\n#endif\n\n#ifdef QUEUE_MACRO_SCRUB\n\n#define QMD_SAVELINK(name, link)    void **name = (void *)&(link)\n\n#define TRASHIT(x) do {                                                 \\\n    (x) = (void *) NULL;                                                \\\n} while (0)\n\n#else\n\n#define QMD_SAVELINK(name, link)\n#define TRASHIT(x)\n\n#endif /* QUEUE_MACRO_SCRUB */\n\n#ifdef QUEUE_MACRO_TRACE\n\n/* Store the last 2 places the queue element or head was altered */\nstruct qm_trace {\n    char *lastfile;\n    int  lastline;\n    char *prevfile;\n    int  prevline;\n};\n\n#define TRACEBUF    struct qm_trace trace;\n\n#define QMD_TRACE_HEAD(head) do {                                       \\\n    (head)->trace.prevline = (head)->trace.lastline;                    \\\n    (head)->trace.prevfile = (head)->trace.lastfile;                    \\\n    (head)->trace.lastline = __LINE__;                                  \\\n    (head)->trace.lastfile = __FILE__;                                  \\\n} while (0)\n\n#define QMD_TRACE_HEAD_INIT(head) do {                                  \\\n    (head)->trace.prevline = __LINE__;                                  \\\n    (head)->trace.prevfile = __FILE__;                                  \\\n    (head)->trace.lastline = __LINE__;                                  \\\n    (head)->trace.lastfile = __FILE__;                                  \\\n} while (0)\n\n#define QMD_TRACE_ELEM(elem) do {                                       \\\n    (elem)->trace.prevline = (elem)->trace.lastline;                    \\\n    (elem)->trace.prevfile = (elem)->trace.lastfile;                    \\\n    (elem)->trace.lastline = __LINE__;                                  \\\n    (elem)->trace.lastfile = __FILE__;                                  \\\n} while (0)\n\n#else\n\n#define QMD_TRACE_ELEM(elem)\n#define QMD_TRACE_HEAD(head)\n#define QMD_TRACE_HEAD_INIT(head)\n#define TRACEBUF\n\n#endif /* QUEUE_MACRO_TRACE */\n\n/*\n * Singly-linked List declarations.\n */\n#define SLIST_HEAD(name, type)                                          \\\nstruct name {                                                           \\\n    struct type *slh_first; /* first element */                         \\\n}\n\n#define SLIST_HEAD_INITIALIZER(head)                                    \\\n    { NULL }\n\n#define SLIST_ENTRY(type)                                               \\\nstruct {                                                                \\\n    struct type *sle_next;  /* next element */                          \\\n}\n\n/*\n * Singly-linked List functions.\n */\n#define SLIST_EMPTY(head)    ((head)->slh_first == NULL)\n\n#define SLIST_FIRST(head)    ((head)->slh_first)\n\n#define SLIST_FOREACH(var, head, field)                                 \\\n    for ((var) = SLIST_FIRST((head));                                   \\\n        (var);                                                          \\\n        (var) = SLIST_NEXT((var), field))\n\n#define SLIST_FOREACH_SAFE(var, head, field, tvar)                      \\\n    for ((var) = SLIST_FIRST((head));                                   \\\n        (var) && ((tvar) = SLIST_NEXT((var), field), 1);                \\\n        (var) = (tvar))\n\n#define SLIST_FOREACH_PREVPTR(var, varp, head, field)                   \\\n    for ((varp) = &SLIST_FIRST((head));                                 \\\n        ((var) = *(varp)) != NULL;                                      \\\n        (varp) = &SLIST_NEXT((var), field))\n\n#define SLIST_INIT(head) do {                                           \\\n    SLIST_FIRST((head)) = NULL;                                         \\\n} while (0)\n\n#define SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \\\n    SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field);           \\\n    SLIST_NEXT((slistelm), field) = (elm);                              \\\n} while (0)\n\n#define SLIST_INSERT_HEAD(head, elm, field) do {                        \\\n    SLIST_NEXT((elm), field) = SLIST_FIRST((head));                     \\\n    SLIST_FIRST((head)) = (elm);                                        \\\n} while (0)\n\n#define SLIST_NEXT(elm, field)    ((elm)->field.sle_next)\n\n#define SLIST_REMOVE(head, elm, type, field) do {                       \\\n    if (SLIST_FIRST((head)) == (elm)) {                                 \\\n        SLIST_REMOVE_HEAD((head), field);                               \\\n    } else {                                                            \\\n        struct type *curelm = SLIST_FIRST((head));                      \\\n        while (SLIST_NEXT(curelm, field) != (elm)) {                    \\\n            curelm = SLIST_NEXT(curelm, field);                         \\\n        }                                                               \\\n        SLIST_REMOVE_AFTER(curelm, field);                              \\\n    }                                                                   \\\n} while (0)\n\n#define SLIST_REMOVE_AFTER(elm, field) do {                             \\\n    QMD_SAVELINK(oldnext, SLIST_NEXT(SLIST_NEXT(elm, field), field));   \\\n    SLIST_NEXT(elm, field) = SLIST_NEXT(SLIST_NEXT(elm, field), field); \\\n    TRASHIT(*oldnext);                                                  \\\n} while (0)\n\n#define SLIST_REMOVE_HEAD(head, field) do {                             \\\n    QMD_SAVELINK(oldnext, SLIST_NEXT(SLIST_FIRST((head)), field));      \\\n    SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field);       \\\n    TRASHIT(*oldnext);                                                  \\\n} while (0)\n\n/*\n * Singly-linked Tail queue declarations.\n */\n#define STAILQ_HEAD(name, type)                                         \\\nstruct name {                                                           \\\n    struct type *stqh_first; /* first element */                        \\\n    struct type **stqh_last; /* addr of last next element */            \\\n}\n\n#define STAILQ_HEAD_INITIALIZER(head)                                   \\\n    { NULL, &(head).stqh_first }\n\n#define STAILQ_ENTRY(type)                                              \\\nstruct {                                                                \\\n    struct type *stqe_next;    /* next element */                       \\\n}\n\n/*\n * Singly-linked Tail queue functions.\n */\n#define STAILQ_CONCAT(head1, head2) do {                                \\\n    if (!STAILQ_EMPTY((head2))) {                                       \\\n        *(head1)->stqh_last = (head2)->stqh_first;                      \\\n        (head1)->stqh_last = (head2)->stqh_last;                        \\\n        STAILQ_INIT((head2));                                           \\\n    }                                                                   \\\n} while (0)\n\n#define STAILQ_EMPTY(head)    ((head)->stqh_first == NULL)\n\n#define STAILQ_FIRST(head)    ((head)->stqh_first)\n\n#define STAILQ_FOREACH(var, head, field)                                \\\n    for ((var) = STAILQ_FIRST((head));                                  \\\n         (var);                                                         \\\n         (var) = STAILQ_NEXT((var), field))\n\n#define STAILQ_FOREACH_SAFE(var, head, field, tvar)                     \\\n    for ((var) = STAILQ_FIRST((head));                                  \\\n        (var) && ((tvar) = STAILQ_NEXT((var), field), 1);               \\\n        (var) = (tvar))\n\n#define STAILQ_INIT(head) do {                                          \\\n    STAILQ_FIRST((head)) = NULL;                                        \\\n    (head)->stqh_last = &STAILQ_FIRST((head));                          \\\n} while (0)\n\n#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do {               \\\n    if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\\\n        (head)->stqh_last = &STAILQ_NEXT((elm), field);                 \\\n    STAILQ_NEXT((tqelm), field) = (elm);                                \\\n} while (0)\n\n#define STAILQ_INSERT_HEAD(head, elm, field) do {                       \\\n    if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL)     \\\n        (head)->stqh_last = &STAILQ_NEXT((elm), field);                 \\\n    STAILQ_FIRST((head)) = (elm);                                       \\\n} while (0)\n\n#define STAILQ_INSERT_TAIL(head, elm, field) do {                       \\\n    STAILQ_NEXT((elm), field) = NULL;                                   \\\n    *(head)->stqh_last = (elm);                                         \\\n    (head)->stqh_last = &STAILQ_NEXT((elm), field);                     \\\n} while (0)\n\n#define STAILQ_LAST(head, type, field)                                  \\\n    (STAILQ_EMPTY((head)) ?                                             \\\n        NULL :                                                          \\\n            ((struct type *)(void *)                                    \\\n        ((char *)((head)->stqh_last) - __offsetof(struct type, field))))\n\n#define STAILQ_NEXT(elm, field)    ((elm)->field.stqe_next)\n\n#define STAILQ_REMOVE(head, elm, type, field) do {                      \\\n    if (STAILQ_FIRST((head)) == (elm)) {                                \\\n        STAILQ_REMOVE_HEAD((head), field);                              \\\n    }                                                                   \\\n    else {                                                              \\\n        struct type *curelm = STAILQ_FIRST((head));                     \\\n        while (STAILQ_NEXT(curelm, field) != (elm))                     \\\n            curelm = STAILQ_NEXT(curelm, field);                        \\\n        STAILQ_REMOVE_AFTER(head, curelm, field);                       \\\n    }                                                                   \\\n} while (0)\n\n#define STAILQ_REMOVE_HEAD(head, field) do {                            \\\n    QMD_SAVELINK(oldnext, STAILQ_NEXT(STAILQ_FIRST((head)), field));    \\\n    if ((STAILQ_FIRST((head)) =                                         \\\n         STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) {           \\\n        (head)->stqh_last = &STAILQ_FIRST((head));                      \\\n    }                                                                   \\\n    TRASHIT(*oldnext);                                                  \\\n} while (0)\n\n#define STAILQ_REMOVE_AFTER(head, elm, field) do {                      \\\n    QMD_SAVELINK(oldnext, STAILQ_NEXT(STAILQ_NEXT(elm, field), field)); \\\n    if ((STAILQ_NEXT(elm, field) =                                      \\\n         STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) {        \\\n        (head)->stqh_last = &STAILQ_NEXT((elm), field);                 \\\n    }                                                                   \\\n    TRASHIT(*oldnext);                                                  \\\n} while (0)\n\n#define STAILQ_SWAP(head1, head2, type) do {                            \\\n    struct type *swap_first = STAILQ_FIRST(head1);                      \\\n    struct type **swap_last = (head1)->stqh_last;                       \\\n    STAILQ_FIRST(head1) = STAILQ_FIRST(head2);                          \\\n    (head1)->stqh_last = (head2)->stqh_last;                            \\\n    STAILQ_FIRST(head2) = swap_first;                                   \\\n    (head2)->stqh_last = swap_last;                                     \\\n    if (STAILQ_EMPTY(head1))                                            \\\n        (head1)->stqh_last = &STAILQ_FIRST(head1);                      \\\n    if (STAILQ_EMPTY(head2))                                            \\\n        (head2)->stqh_last = &STAILQ_FIRST(head2);                      \\\n} while (0)\n\n\n/*\n * List declarations.\n */\n#define LIST_HEAD(name, type)                                           \\\nstruct name {                                                           \\\n    struct type *lh_first; /* first element */                          \\\n}\n\n#define LIST_HEAD_INITIALIZER(head)                                     \\\n    { NULL }\n\n#define LIST_ENTRY(type)                                                \\\nstruct {                                                                \\\n    struct type *le_next;  /* next element */                           \\\n    struct type **le_prev; /* address of previous next element */       \\\n}\n\n/*\n * List functions.\n */\n\n#ifdef QUEUE_MACRO_ASSERT\n\n#define QMD_LIST_CHECK_HEAD(head, field) do {                               \\\n    if (LIST_FIRST((head)) != NULL &&                                       \\\n        LIST_FIRST((head))->field.le_prev != &LIST_FIRST((head))) {         \\\n        log_panic(\"Bad list head %p first->prev != head\", (void *)(head));  \\\n    }                                                                       \\\n} while (0)\n\n#define QMD_LIST_CHECK_NEXT(elm, field) do {                                \\\n    if (LIST_NEXT((elm), field) != NULL &&                                  \\\n        LIST_NEXT((elm), field)->field.le_prev != &((elm)->field.le_next)) {\\\n        log_panic(\"Bad link elm %p next->prev != elm\",(void *)(elm));       \\\n    }                                                                       \\\n} while (0)\n\n#define QMD_LIST_CHECK_PREV(elm, field) do {                            \\\n    if (*(elm)->field.le_prev != (elm)) {                               \\\n        log_panic(\"Bad link elm %p prev->next != elm\",(void *)(elm));   \\\n    }                                                                   \\\n} while (0)\n\n#else\n\n#define QMD_LIST_CHECK_HEAD(head, field)\n#define QMD_LIST_CHECK_NEXT(elm, field)\n#define QMD_LIST_CHECK_PREV(elm, field)\n\n#endif /* QUEUE_MACRO_ASSERT */\n\n#define LIST_EMPTY(head)    ((head)->lh_first == NULL)\n\n#define LIST_FIRST(head)    ((head)->lh_first)\n\n#define LIST_FOREACH(var, head, field)                                  \\\n    for ((var) = LIST_FIRST((head));                                    \\\n        (var);                                                          \\\n        (var) = LIST_NEXT((var), field))\n\n#define LIST_FOREACH_SAFE(var, head, field, tvar)                       \\\n    for ((var) = LIST_FIRST((head));                                    \\\n        (var) && ((tvar) = LIST_NEXT((var), field), 1);                 \\\n        (var) = (tvar))\n\n#define LIST_INIT(head) do {                                            \\\n    LIST_FIRST((head)) = NULL;                                          \\\n} while (0)\n\n#define LIST_INSERT_AFTER(listelm, elm, field) do {                     \\\n    QMD_LIST_CHECK_NEXT(listelm, field);                                \\\n    if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\\\n        LIST_NEXT((listelm), field)->field.le_prev =                    \\\n            &LIST_NEXT((elm), field);                                   \\\n    LIST_NEXT((listelm), field) = (elm);                                \\\n    (elm)->field.le_prev = &LIST_NEXT((listelm), field);                \\\n} while (0)\n\n#define LIST_INSERT_BEFORE(listelm, elm, field) do {                    \\\n    QMD_LIST_CHECK_PREV(listelm, field);                                \\\n    (elm)->field.le_prev = (listelm)->field.le_prev;                    \\\n    LIST_NEXT((elm), field) = (listelm);                                \\\n    *(listelm)->field.le_prev = (elm);                                  \\\n    (listelm)->field.le_prev = &LIST_NEXT((elm), field);                \\\n} while (0)\n\n#define LIST_INSERT_HEAD(head, elm, field) do {                         \\\n    QMD_LIST_CHECK_HEAD((head), field);                                 \\\n    if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL)         \\\n        LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);   \\\n    LIST_FIRST((head)) = (elm);                                         \\\n    (elm)->field.le_prev = &LIST_FIRST((head));                         \\\n} while (0)\n\n#define LIST_NEXT(elm, field)    ((elm)->field.le_next)\n\n#define LIST_REMOVE(elm, field) do {                                    \\\n    QMD_SAVELINK(oldnext, (elm)->field.le_next);                        \\\n    QMD_SAVELINK(oldprev, (elm)->field.le_prev);                        \\\n    QMD_LIST_CHECK_NEXT(elm, field);                                    \\\n    QMD_LIST_CHECK_PREV(elm, field);                                    \\\n    if (LIST_NEXT((elm), field) != NULL)                                \\\n        LIST_NEXT((elm), field)->field.le_prev =                        \\\n            (elm)->field.le_prev;                                       \\\n    *(elm)->field.le_prev = LIST_NEXT((elm), field);                    \\\n    TRASHIT(*oldnext);                                                  \\\n    TRASHIT(*oldprev);                                                  \\\n} while (0)\n\n#define LIST_SWAP(head1, head2, type, field) do {                       \\\n    struct type *swap_tmp = LIST_FIRST((head1));                        \\\n    LIST_FIRST((head1)) = LIST_FIRST((head2));                          \\\n    LIST_FIRST((head2)) = swap_tmp;                                     \\\n    if ((swap_tmp = LIST_FIRST((head1))) != NULL)                       \\\n        swap_tmp->field.le_prev = &LIST_FIRST((head1));                 \\\n    if ((swap_tmp = LIST_FIRST((head2))) != NULL)                       \\\n        swap_tmp->field.le_prev = &LIST_FIRST((head2));                 \\\n} while (0)\n\n/*\n * Tail queue declarations.\n */\n#define TAILQ_HEAD(name, type)                                          \\\nstruct name {                                                           \\\n    struct type *tqh_first; /* first element */                         \\\n    struct type **tqh_last; /* addr of last next element */             \\\n    TRACEBUF                                                            \\\n}\n\n#define TAILQ_HEAD_INITIALIZER(head)                                    \\\n    { NULL, &(head).tqh_first }\n\n#define TAILQ_ENTRY(type)                                               \\\nstruct {                                                                \\\n    struct type *tqe_next;  /* next element */                          \\\n    struct type **tqe_prev; /* address of previous next element */      \\\n    TRACEBUF                                                            \\\n}\n\n/*\n * Tail queue functions.\n */\n#ifdef QUEUE_MACRO_ASSERT\n\n#define QMD_TAILQ_CHECK_HEAD(head, field) do {                              \\\n    if (!TAILQ_EMPTY(head) &&                                               \\\n        TAILQ_FIRST((head))->field.tqe_prev != &TAILQ_FIRST((head))) {      \\\n        log_panic(\"Bad tailq head %p first->prev != head\", (void *)(head)); \\\n    }                                                                       \\\n} while (0)\n\n#define QMD_TAILQ_CHECK_TAIL(head, field) do {                              \\\n    if (*(head)->tqh_last != NULL) {                                        \\\n        log_panic(\"Bad tailq NEXT(%p->tqh_last) != NULL\",(void *)(head));   \\\n    }                                                                       \\\n} while (0)\n\n#define QMD_TAILQ_CHECK_NEXT(elm, field) do {                               \\\n    if (TAILQ_NEXT((elm), field) != NULL &&                                 \\\n        TAILQ_NEXT((elm), field)->field.tqe_prev != &((elm)->field.tqe_next)) {\\\n        log_panic(\"Bad link elm %p next->prev != elm\",(void *)(elm));       \\\n    }                                                                       \\\n} while (0)\n\n#define QMD_TAILQ_CHECK_PREV(elm, field) do {                           \\\n    if (*(elm)->field.tqe_prev != (elm)) {                              \\\n        log_panic(\"Bad link elm %p prev->next != elm\",(void *)(elm));   \\\n    }                                                                   \\\n} while (0)\n\n#else\n\n#define QMD_TAILQ_CHECK_HEAD(head, field)\n#define QMD_TAILQ_CHECK_TAIL(head, headname)\n#define QMD_TAILQ_CHECK_NEXT(elm, field)\n#define QMD_TAILQ_CHECK_PREV(elm, field)\n\n#endif /* QUEUE_MACRO_ASSERT */\n\n#define TAILQ_CONCAT(head1, head2, field) do {                          \\\n    if (!TAILQ_EMPTY(head2)) {                                          \\\n        *(head1)->tqh_last = (head2)->tqh_first;                        \\\n        (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last;         \\\n        (head1)->tqh_last = (head2)->tqh_last;                          \\\n        TAILQ_INIT((head2));                                            \\\n        QMD_TRACE_HEAD(head1);                                          \\\n        QMD_TRACE_HEAD(head2);                                          \\\n    }                                                                   \\\n} while (0)\n\n#define TAILQ_EMPTY(head)    ((head)->tqh_first == NULL)\n\n#define TAILQ_FIRST(head)    ((head)->tqh_first)\n\n#define TAILQ_FOREACH(var, head, field)                                 \\\n    for ((var) = TAILQ_FIRST((head));                                   \\\n        (var);                                                          \\\n        (var) = TAILQ_NEXT((var), field))\n\n#define TAILQ_FOREACH_SAFE(var, head, field, tvar)                      \\\n    for ((var) = TAILQ_FIRST((head));                                   \\\n        (var) && ((tvar) = TAILQ_NEXT((var), field), 1);                \\\n        (var) = (tvar))\n\n#define TAILQ_FOREACH_REVERSE(var, head, headname, field)               \\\n    for ((var) = TAILQ_LAST((head), headname);                          \\\n        (var);                                                          \\\n        (var) = TAILQ_PREV((var), headname, field))\n\n#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)    \\\n    for ((var) = TAILQ_LAST((head), headname);                          \\\n        (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1);      \\\n        (var) = (tvar))\n\n#define TAILQ_INIT(head) do {                                           \\\n    TAILQ_FIRST((head)) = NULL;                                         \\\n    (head)->tqh_last = &TAILQ_FIRST((head));                            \\\n    QMD_TRACE_HEAD_INIT(head);                                               \\\n} while (0)\n\n#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {              \\\n    QMD_TAILQ_CHECK_NEXT(listelm, field);                               \\\n    if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) {  \\\n        TAILQ_NEXT((elm), field)->field.tqe_prev =  &TAILQ_NEXT((elm), field);\\\n    } else {                                                            \\\n        (head)->tqh_last = &TAILQ_NEXT((elm), field);                   \\\n        QMD_TRACE_HEAD(head);                                           \\\n    }                                                                   \\\n    TAILQ_NEXT((listelm), field) = (elm);                               \\\n    (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field);              \\\n    QMD_TRACE_ELEM(&(elm)->field);                                      \\\n    QMD_TRACE_ELEM(&listelm->field);                                    \\\n} while (0)\n\n#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \\\n    QMD_TAILQ_CHECK_PREV(listelm, field);                               \\\n    (elm)->field.tqe_prev = (listelm)->field.tqe_prev;                  \\\n    TAILQ_NEXT((elm), field) = (listelm);                               \\\n    *(listelm)->field.tqe_prev = (elm);                                 \\\n    (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field);              \\\n    QMD_TRACE_ELEM(&(elm)->field);                                      \\\n    QMD_TRACE_ELEM(&listelm->field);                                    \\\n} while (0)\n\n#define TAILQ_INSERT_HEAD(head, elm, field) do {                        \\\n    QMD_TAILQ_CHECK_HEAD(head, field);                                  \\\n    if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL)       \\\n        TAILQ_FIRST((head))->field.tqe_prev =                           \\\n            &TAILQ_NEXT((elm), field);                                  \\\n    else                                                                \\\n        (head)->tqh_last = &TAILQ_NEXT((elm), field);                   \\\n    TAILQ_FIRST((head)) = (elm);                                        \\\n    (elm)->field.tqe_prev = &TAILQ_FIRST((head));                       \\\n    QMD_TRACE_HEAD(head);                                               \\\n    QMD_TRACE_ELEM(&(elm)->field);                                      \\\n} while (0)\n\n#define TAILQ_INSERT_TAIL(head, elm, field) do {                        \\\n    QMD_TAILQ_CHECK_TAIL(head, field);                                  \\\n    TAILQ_NEXT((elm), field) = NULL;                                    \\\n    (elm)->field.tqe_prev = (head)->tqh_last;                           \\\n    *(head)->tqh_last = (elm);                                          \\\n    (head)->tqh_last = &TAILQ_NEXT((elm), field);                       \\\n    QMD_TRACE_HEAD(head);                                               \\\n    QMD_TRACE_ELEM(&(elm)->field);                                      \\\n} while (0)\n\n#define TAILQ_LAST(head, headname)                                      \\\n    (*(((struct headname *)((head)->tqh_last))->tqh_last))\n\n#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)\n\n#define TAILQ_PREV(elm, headname, field)                                \\\n    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))\n\n#define TAILQ_REMOVE(head, elm, field) do {                             \\\n    QMD_SAVELINK(oldnext, (elm)->field.tqe_next);                       \\\n    QMD_SAVELINK(oldprev, (elm)->field.tqe_prev);                       \\\n    QMD_TAILQ_CHECK_NEXT(elm, field);                                   \\\n    QMD_TAILQ_CHECK_PREV(elm, field);                                   \\\n    if ((TAILQ_NEXT((elm), field)) != NULL) {                           \\\n        TAILQ_NEXT((elm), field)->field.tqe_prev =                      \\\n            (elm)->field.tqe_prev;                                      \\\n    } else {                                                            \\\n        (head)->tqh_last = (elm)->field.tqe_prev;                       \\\n        QMD_TRACE_HEAD(head);                                           \\\n    }                                                                   \\\n    *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);                  \\\n    TRASHIT(*oldnext);                                                  \\\n    TRASHIT(*oldprev);                                                  \\\n    QMD_TRACE_ELEM(&(elm)->field);                                      \\\n} while (0)\n\n#define TAILQ_SWAP(head1, head2, type, field) do {                      \\\n    struct type *swap_first = (head1)->tqh_first;                       \\\n    struct type **swap_last = (head1)->tqh_last;                        \\\n    (head1)->tqh_first = (head2)->tqh_first;                            \\\n    (head1)->tqh_last = (head2)->tqh_last;                              \\\n    (head2)->tqh_first = swap_first;                                    \\\n    (head2)->tqh_last = swap_last;                                      \\\n    if ((swap_first = (head1)->tqh_first) != NULL)                      \\\n        swap_first->field.tqe_prev = &(head1)->tqh_first;               \\\n    else                                                                \\\n        (head1)->tqh_last = &(head1)->tqh_first;                        \\\n    if ((swap_first = (head2)->tqh_first) != NULL)                      \\\n        swap_first->field.tqe_prev = &(head2)->tqh_first;               \\\n    else                                                                \\\n        (head2)->tqh_last = &(head2)->tqh_first;                        \\\n} while (0)\n\n/*\n * Circular queue declarations.\n */\n#define CIRCLEQ_HEAD(name, type)                                        \\\nstruct name {                                                           \\\n    struct type *cqh_first; /* first element */                         \\\n    struct type *cqh_last;  /* last element */                          \\\n}\n\n#define CIRCLEQ_HEAD_INITIALIZER(head)                                  \\\n    { (void *)&(head), (void *)&(head) }\n\n#define CIRCLEQ_ENTRY(type)                                             \\\nstruct {                                                                \\\n    struct type *cqe_next; /* next element */                           \\\n    struct type *cqe_prev; /* previous element */                       \\\n}\n\n/*\n * Circular queue functions.\n */\n#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head))\n\n#define CIRCLEQ_FIRST(head) ((head)->cqh_first)\n\n#define CIRCLEQ_FOREACH(var, head, field)                               \\\n    for ((var) = CIRCLEQ_FIRST((head));                                 \\\n        (var) != (void *)(head) || ((var) = NULL);                      \\\n        (var) = CIRCLEQ_NEXT((var), field))\n\n#define CIRCLEQ_FOREACH_REVERSE(var, head, field)                       \\\n    for ((var) = CIRCLEQ_LAST((head));                                  \\\n        (var) != (void *)(head) || ((var) = NULL);                      \\\n        (var) = CIRCLEQ_PREV((var), field))\n\n#define CIRCLEQ_INIT(head) do {                                         \\\n    CIRCLEQ_FIRST((head)) = (void *)(head);                             \\\n    CIRCLEQ_LAST((head)) = (void *)(head);                              \\\n} while (0)\n\n#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {            \\\n    CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field);        \\\n    CIRCLEQ_PREV((elm), field) = (listelm);                             \\\n    if (CIRCLEQ_NEXT((listelm), field) == (void *)(head))               \\\n        CIRCLEQ_LAST((head)) = (elm);                                   \\\n    else                                                                \\\n        CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm);    \\\n    CIRCLEQ_NEXT((listelm), field) = (elm);                             \\\n} while (0)\n\n#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {           \\\n    CIRCLEQ_NEXT((elm), field) = (listelm);                             \\\n    CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field);        \\\n    if (CIRCLEQ_PREV((listelm), field) == (void *)(head))               \\\n        CIRCLEQ_FIRST((head)) = (elm);                                  \\\n    else                                                                \\\n        CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm);    \\\n    CIRCLEQ_PREV((listelm), field) = (elm);                             \\\n} while (0)\n\n#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {                      \\\n    CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head));                 \\\n    CIRCLEQ_PREV((elm), field) = (void *)(head);                        \\\n    if (CIRCLEQ_LAST((head)) == (void *)(head))                         \\\n        CIRCLEQ_LAST((head)) = (elm);                                   \\\n    else                                                                \\\n        CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm);             \\\n    CIRCLEQ_FIRST((head)) = (elm);                                      \\\n} while (0)\n\n#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {                      \\\n    CIRCLEQ_NEXT((elm), field) = (void *)(head);                        \\\n    CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head));                  \\\n    if (CIRCLEQ_FIRST((head)) == (void *)(head))                        \\\n        CIRCLEQ_FIRST((head)) = (elm);                                  \\\n    else                                                                \\\n        CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm);              \\\n    CIRCLEQ_LAST((head)) = (elm);                                       \\\n} while (0)\n\n#define CIRCLEQ_LAST(head) ((head)->cqh_last)\n\n#define CIRCLEQ_NEXT(elm,field) ((elm)->field.cqe_next)\n\n#define CIRCLEQ_PREV(elm,field) ((elm)->field.cqe_prev)\n\n#define CIRCLEQ_REMOVE(head, elm, field) do {                           \\\n    if (CIRCLEQ_NEXT((elm), field) == (void *)(head))                   \\\n        CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field);              \\\n    else                                                                \\\n        CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) =               \\\n            CIRCLEQ_PREV((elm), field);                                 \\\n    if (CIRCLEQ_PREV((elm), field) == (void *)(head))                   \\\n        CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field);             \\\n    else                                                                \\\n        CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) =               \\\n            CIRCLEQ_NEXT((elm), field);                                 \\\n} while (0)\n\n#endif\n"
  },
  {
    "path": "src/nc_rbtree.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n\nvoid\nrbtree_node_init(struct rbnode *node)\n{\n    node->left = NULL;\n    node->right = NULL;\n    node->parent = NULL;\n    node->key = 0ULL;\n    node->data = NULL;\n    /* color is left uninitialized */\n}\n\nvoid\nrbtree_init(struct rbtree *tree, struct rbnode *node)\n{\n    rbtree_node_init(node);\n    rbtree_black(node);\n    tree->root = node;\n    tree->sentinel = node;\n}\n\nstatic struct rbnode *\nrbtree_node_min(struct rbnode *node, const struct rbnode *sentinel)\n{\n    /* traverse left links */\n\n    while (node->left != sentinel) {\n        node = node->left;\n    }\n\n    return node;\n}\n\nstruct rbnode *\nrbtree_min(const struct rbtree *tree)\n{\n    struct rbnode *node = tree->root;\n    const struct rbnode *sentinel = tree->sentinel;\n\n    /* empty tree */\n\n    if (node == sentinel) {\n        return NULL;\n    }\n\n    return rbtree_node_min(node, sentinel);\n}\n\nstatic void\nrbtree_left_rotate(struct rbnode **root, struct rbnode *sentinel,\n                   struct rbnode *node)\n{\n    struct rbnode *temp;\n\n    temp = node->right;\n    node->right = temp->left;\n\n    if (temp->left != sentinel) {\n        temp->left->parent = node;\n    }\n\n    temp->parent = node->parent;\n\n    if (node == *root) {\n        *root = temp;\n    } else if (node == node->parent->left) {\n        node->parent->left = temp;\n    } else {\n        node->parent->right = temp;\n    }\n\n    temp->left = node;\n    node->parent = temp;\n}\n\nstatic void\nrbtree_right_rotate(struct rbnode **root, struct rbnode *sentinel,\n                    struct rbnode *node)\n{\n    struct rbnode *temp;\n\n    temp = node->left;\n    node->left = temp->right;\n\n    if (temp->right != sentinel) {\n        temp->right->parent = node;\n    }\n\n    temp->parent = node->parent;\n\n    if (node == *root) {\n        *root = temp;\n    } else if (node == node->parent->right) {\n        node->parent->right = temp;\n    } else {\n        node->parent->left = temp;\n    }\n\n    temp->right = node;\n    node->parent = temp;\n}\n\nvoid\nrbtree_insert(struct rbtree *tree, struct rbnode *node)\n{\n    struct rbnode **root = &tree->root;\n    struct rbnode *sentinel = tree->sentinel;\n    struct rbnode *temp, **p;\n\n    /* empty tree */\n\n    if (*root == sentinel) {\n        node->parent = NULL;\n        node->left = sentinel;\n        node->right = sentinel;\n        rbtree_black(node);\n        *root = node;\n        return;\n    }\n\n    /* a binary tree insert */\n\n    temp = *root;\n    for (;;) {\n\n        p = (node->key < temp->key) ? &temp->left : &temp->right;\n        if (*p == sentinel) {\n            break;\n        }\n        temp = *p;\n    }\n\n    *p = node;\n    node->parent = temp;\n    node->left = sentinel;\n    node->right = sentinel;\n    rbtree_red(node);\n\n    /* re-balance tree */\n\n    while (node != *root && rbtree_is_red(node->parent)) {\n\n        if (node->parent == node->parent->parent->left) {\n            temp = node->parent->parent->right;\n\n            if (rbtree_is_red(temp)) {\n                rbtree_black(node->parent);\n                rbtree_black(temp);\n                rbtree_red(node->parent->parent);\n                node = node->parent->parent;\n            } else {\n                if (node == node->parent->right) {\n                    node = node->parent;\n                    rbtree_left_rotate(root, sentinel, node);\n                }\n\n                rbtree_black(node->parent);\n                rbtree_red(node->parent->parent);\n                rbtree_right_rotate(root, sentinel, node->parent->parent);\n            }\n        } else {\n            temp = node->parent->parent->left;\n\n            if (rbtree_is_red(temp)) {\n                rbtree_black(node->parent);\n                rbtree_black(temp);\n                rbtree_red(node->parent->parent);\n                node = node->parent->parent;\n            } else {\n                if (node == node->parent->left) {\n                    node = node->parent;\n                    rbtree_right_rotate(root, sentinel, node);\n                }\n\n                rbtree_black(node->parent);\n                rbtree_red(node->parent->parent);\n                rbtree_left_rotate(root, sentinel, node->parent->parent);\n            }\n        }\n    }\n\n    rbtree_black(*root);\n}\n\nvoid\nrbtree_delete(struct rbtree *tree, struct rbnode *node)\n{\n    struct rbnode **root = &tree->root;\n    struct rbnode *sentinel = tree->sentinel;\n    struct rbnode *subst, *temp, *w;\n    uint8_t red;\n\n    /* a binary tree delete */\n\n    if (node->left == sentinel) {\n        temp = node->right;\n        subst = node;\n    } else if (node->right == sentinel) {\n        temp = node->left;\n        subst = node;\n    } else {\n        subst = rbtree_node_min(node->right, sentinel);\n        temp = subst->right;\n    }\n\n    if (subst == *root) {\n        *root = temp;\n        rbtree_black(temp);\n\n        rbtree_node_init(node);\n\n        return;\n    }\n\n    red = rbtree_is_red(subst);\n\n    if (subst == subst->parent->left) {\n        subst->parent->left = temp;\n    } else {\n        subst->parent->right = temp;\n    }\n\n    if (subst == node) {\n        temp->parent = subst->parent;\n    } else {\n\n        if (subst->parent == node) {\n            temp->parent = subst;\n        } else {\n            temp->parent = subst->parent;\n        }\n\n        subst->left = node->left;\n        subst->right = node->right;\n        subst->parent = node->parent;\n        rbtree_copy_color(subst, node);\n\n        if (node == *root) {\n            *root = subst;\n        } else {\n            if (node == node->parent->left) {\n                node->parent->left = subst;\n            } else {\n                node->parent->right = subst;\n            }\n        }\n\n        if (subst->left != sentinel) {\n            subst->left->parent = subst;\n        }\n\n        if (subst->right != sentinel) {\n            subst->right->parent = subst;\n        }\n    }\n\n    rbtree_node_init(node);\n\n    if (red) {\n        return;\n    }\n\n    /* a delete fixup */\n\n    while (temp != *root && rbtree_is_black(temp)) {\n\n        if (temp == temp->parent->left) {\n            w = temp->parent->right;\n\n            if (rbtree_is_red(w)) {\n                rbtree_black(w);\n                rbtree_red(temp->parent);\n                rbtree_left_rotate(root, sentinel, temp->parent);\n                w = temp->parent->right;\n            }\n\n            if (rbtree_is_black(w->left) && rbtree_is_black(w->right)) {\n                rbtree_red(w);\n                temp = temp->parent;\n            } else {\n                if (rbtree_is_black(w->right)) {\n                    rbtree_black(w->left);\n                    rbtree_red(w);\n                    rbtree_right_rotate(root, sentinel, w);\n                    w = temp->parent->right;\n                }\n\n                rbtree_copy_color(w, temp->parent);\n                rbtree_black(temp->parent);\n                rbtree_black(w->right);\n                rbtree_left_rotate(root, sentinel, temp->parent);\n                temp = *root;\n            }\n\n        } else {\n            w = temp->parent->left;\n\n            if (rbtree_is_red(w)) {\n                rbtree_black(w);\n                rbtree_red(temp->parent);\n                rbtree_right_rotate(root, sentinel, temp->parent);\n                w = temp->parent->left;\n            }\n\n            if (rbtree_is_black(w->left) && rbtree_is_black(w->right)) {\n                rbtree_red(w);\n                temp = temp->parent;\n            } else {\n                if (rbtree_is_black(w->left)) {\n                    rbtree_black(w->right);\n                    rbtree_red(w);\n                    rbtree_left_rotate(root, sentinel, w);\n                    w = temp->parent->left;\n                }\n\n                rbtree_copy_color(w, temp->parent);\n                rbtree_black(temp->parent);\n                rbtree_black(w->left);\n                rbtree_right_rotate(root, sentinel, temp->parent);\n                temp = *root;\n            }\n        }\n    }\n\n    rbtree_black(temp);\n}\n"
  },
  {
    "path": "src/nc_rbtree.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_RBTREE_\n#define _NC_RBTREE_\n\n#define rbtree_red(_node)           ((_node)->color = 1)\n#define rbtree_black(_node)         ((_node)->color = 0)\n#define rbtree_is_red(_node)        ((_node)->color)\n#define rbtree_is_black(_node)      (!rbtree_is_red(_node))\n#define rbtree_copy_color(_n1, _n2) ((_n1)->color = (_n2)->color)\n\nstruct rbnode {\n    struct rbnode *left;     /* left link */\n    struct rbnode *right;    /* right link */\n    struct rbnode *parent;   /* parent link */\n    int64_t       key;       /* key for ordering */\n    void          *data;     /* opaque data */\n    uint8_t       color;     /* red | black */\n};\n\nstruct rbtree {\n    struct rbnode *root;     /* root node */\n    struct rbnode *sentinel; /* nil node */\n};\n\nvoid rbtree_node_init(struct rbnode *node);\nvoid rbtree_init(struct rbtree *tree, struct rbnode *node);\nstruct rbnode *rbtree_min(const struct rbtree *tree);\nvoid rbtree_insert(struct rbtree *tree, struct rbnode *node);\nvoid rbtree_delete(struct rbtree *tree, struct rbnode *node);\n\n#endif\n"
  },
  {
    "path": "src/nc_request.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n#include <nc_server.h>\n\nstruct msg *\nreq_get(struct conn *conn)\n{\n    struct msg *msg;\n\n    ASSERT(conn->client && !conn->proxy);\n\n    msg = msg_get(conn, true, conn->redis);\n    if (msg == NULL) {\n        conn->err = errno;\n    }\n    return msg;\n}\n\nstatic void\nreq_log(const struct msg *req)\n{\n    const struct msg *rsp;           /* peer message (response) */\n    int64_t req_time;          /* time cost for this request */\n    const char *peer_str;      /* peer client ip:port */\n    uint32_t req_len, rsp_len; /* request and response length */\n    const struct string *req_type; /* request type string */\n    const struct keypos *kpos;\n\n    if (log_loggable(LOG_NOTICE) == 0) {\n        return;\n    }\n\n    /* a fragment? */\n    if (req->frag_id != 0 && req->frag_owner != req) {\n        return;\n    }\n\n    /* conn close normally? */\n    if (req->mlen == 0) {\n        return;\n    }\n    /*\n     * there is a race scenario where a requests comes in, the log level is not LOG_NOTICE,\n     * and before the response arrives you modify the log level to LOG_NOTICE\n     * using SIGTTIN OR SIGTTOU, then req_log() wouldn't have msg->start_ts set\n     */\n    if (req->start_ts == 0) {\n        return;\n    }\n\n    req_time = nc_usec_now() - req->start_ts;\n\n    rsp = req->peer;\n    req_len = req->mlen;\n    rsp_len = (rsp != NULL) ? rsp->mlen : 0;\n\n    if (array_n(req->keys) < 1) {\n        return;\n    }\n\n    kpos = array_get(req->keys, 0);\n\n    /*\n     * FIXME: add backend addr here\n     * Maybe we can store addrstr just like server_pool in conn struct\n     * when connections are resolved\n     */\n    peer_str = nc_unresolve_peer_desc(req->owner->sd);\n\n    req_type = msg_type_string(req->type);\n\n    log_debug(LOG_NOTICE, \"req %\"PRIu64\" done on c %d req_time %\"PRIi64\".%03\"PRIi64\n              \" msec type %.*s narg %\"PRIu32\" req_len %\"PRIu32\" rsp_len %\"PRIu32\n              \" key0 '%.*s' peer '%s' done %d error %d\",\n              req->id, req->owner->sd, req_time / 1000, req_time % 1000,\n              req_type->len, req_type->data, req->narg, req_len, rsp_len,\n              (int)(kpos->end ? kpos->end - kpos->start : 0), kpos->start,\n              peer_str, req->done, req->error);\n}\n\nvoid\nreq_put(struct msg *msg)\n{\n    struct msg *pmsg; /* peer message (response) */\n\n    ASSERT(msg->request);\n\n    req_log(msg);\n\n    pmsg = msg->peer;\n    if (pmsg != NULL) {\n        ASSERT(!pmsg->request && pmsg->peer == msg);\n        msg->peer = NULL;\n        pmsg->peer = NULL;\n        rsp_put(pmsg);\n    }\n\n    msg_tmo_delete(msg);\n\n    msg_put(msg);\n}\n\n/*\n * Return true if request is done, false otherwise\n *\n * A request is done, if we received response for the given request.\n * A request vector is done if we received responses for all its\n * fragments.\n *\n * msg->fdone is modified to cache whether this request was done.\n */\nbool\nreq_done(const struct conn *conn, struct msg *msg)\n{\n    struct msg *cmsg;        /* current message */\n    uint64_t id;             /* fragment id */\n    uint32_t nfragment;      /* # fragment */\n\n    ASSERT(conn->client && !conn->proxy);\n    ASSERT(msg->request);\n\n    if (!msg->done) {\n        return false;\n    }\n\n    id = msg->frag_id;\n    if (id == 0) {\n        return true;\n    }\n\n    if (msg->fdone) {\n        /* request has already been marked as done */\n        return true;\n    }\n\n    if (msg->nfrag_done < msg->nfrag) {\n        return false;\n    }\n\n    /* check all fragments of the given request vector are done */\n\n    for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) {\n\n        if (!cmsg->done) {\n            return false;\n        }\n    }\n\n    for (cmsg = TAILQ_NEXT(msg, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_NEXT(cmsg, c_tqe)) {\n\n        if (!cmsg->done) {\n            return false;\n        }\n    }\n\n    /*\n     * At this point, all the fragments including the last fragment have\n     * been received.\n     *\n     * Mark all fragments of the given request vector to be done to speed up\n     * future req_done calls for any of fragments of this request\n     */\n\n    msg->fdone = 1;\n    nfragment = 0;\n\n    for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) {\n        cmsg->fdone = 1;\n        nfragment++;\n    }\n\n    for (cmsg = TAILQ_NEXT(msg, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_NEXT(cmsg, c_tqe)) {\n        cmsg->fdone = 1;\n        nfragment++;\n    }\n\n    ASSERT(msg->frag_owner->nfrag == nfragment);\n\n    msg->post_coalesce(msg->frag_owner);\n\n    log_debug(LOG_DEBUG, \"req from c %d with fid %\"PRIu64\" and %\"PRIu32\" \"\n              \"fragments is done\", conn->sd, id, nfragment);\n\n    return true;\n}\n\n\n/*\n * Return true if request is in error, false otherwise\n *\n * A request is in error, if there was an error in receiving response for the\n * given request. A multiget request is in error if there was an error in\n * receiving response for any its fragments.\n */\nbool\nreq_error(const struct conn *conn, struct msg *msg)\n{\n    struct msg *cmsg; /* current message */\n    uint64_t id;\n    uint32_t nfragment;\n\n    ASSERT(msg->request && req_done(conn, msg));\n\n    if (msg->error) {\n        return true;\n    }\n\n    id = msg->frag_id;\n    if (id == 0) {\n        return false;\n    }\n\n    if (msg->ferror) {\n        /* request has already been marked to be in error */\n        return true;\n    }\n\n    /* check if any of the fragments of the given request are in error */\n\n    for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) {\n\n        if (cmsg->error) {\n            goto ferror;\n        }\n    }\n\n    for (cmsg = TAILQ_NEXT(msg, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_NEXT(cmsg, c_tqe)) {\n\n        if (cmsg->error) {\n            goto ferror;\n        }\n    }\n\n    return false;\n\nferror:\n\n    /*\n     * Mark all fragments of the given request to be in error to speed up\n     * future req_error calls for any of fragments of this request\n     */\n\n    msg->ferror = 1;\n    nfragment = 1;\n\n    for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) {\n        cmsg->ferror = 1;\n        nfragment++;\n    }\n\n    for (cmsg = TAILQ_NEXT(msg, c_tqe);\n         cmsg != NULL && cmsg->frag_id == id;\n         cmsg = TAILQ_NEXT(cmsg, c_tqe)) {\n        cmsg->ferror = 1;\n        nfragment++;\n    }\n\n    log_debug(LOG_DEBUG, \"req from c %d with fid %\"PRIu64\" and %\"PRIu32\" \"\n              \"fragments is in error\", conn->sd, id, nfragment);\n\n    return true;\n}\n\nvoid\nreq_server_enqueue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(!conn->client && !conn->proxy);\n\n    /*\n     * timeout clock starts ticking the instant the message is enqueued into\n     * the server in_q; the clock continues to tick until it either expires\n     * or the message is dequeued from the server out_q\n     *\n     * noreply request are free from timeouts because client is not intrested\n     * in the response anyway!\n     */\n    if (!msg->noreply) {\n        msg_tmo_insert(msg, conn);\n    }\n\n    TAILQ_INSERT_TAIL(&conn->imsg_q, msg, s_tqe);\n\n    stats_server_incr(ctx, conn->owner, in_queue);\n    stats_server_incr_by(ctx, conn->owner, in_queue_bytes, msg->mlen);\n}\n\nvoid\nreq_server_enqueue_imsgq_head(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(!conn->client && !conn->proxy);\n\n    /*\n     * timeout clock starts ticking the instant the message is enqueued into\n     * the server in_q; the clock continues to tick until it either expires\n     * or the message is dequeued from the server out_q\n     *\n     * noreply request are free from timeouts because client is not intrested\n     * in the response anyway!\n     */\n    if (!msg->noreply) {\n        msg_tmo_insert(msg, conn);\n    }\n\n    TAILQ_INSERT_HEAD(&conn->imsg_q, msg, s_tqe);\n\n    stats_server_incr(ctx, conn->owner, in_queue);\n    stats_server_incr_by(ctx, conn->owner, in_queue_bytes, msg->mlen);\n}\n\nvoid\nreq_server_dequeue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(!conn->client && !conn->proxy);\n\n    TAILQ_REMOVE(&conn->imsg_q, msg, s_tqe);\n\n    stats_server_decr(ctx, conn->owner, in_queue);\n    stats_server_decr_by(ctx, conn->owner, in_queue_bytes, msg->mlen);\n}\n\nvoid\nreq_client_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(conn->client && !conn->proxy);\n\n    TAILQ_INSERT_TAIL(&conn->omsg_q, msg, c_tqe);\n}\n\nvoid\nreq_server_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(!conn->client && !conn->proxy);\n\n    TAILQ_INSERT_TAIL(&conn->omsg_q, msg, s_tqe);\n\n    stats_server_incr(ctx, conn->owner, out_queue);\n    stats_server_incr_by(ctx, conn->owner, out_queue_bytes, msg->mlen);\n}\n\nvoid\nreq_client_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(conn->client && !conn->proxy);\n\n    TAILQ_REMOVE(&conn->omsg_q, msg, c_tqe);\n}\n\nvoid\nreq_server_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(msg->request);\n    ASSERT(!conn->client && !conn->proxy);\n\n    msg_tmo_delete(msg);\n\n    TAILQ_REMOVE(&conn->omsg_q, msg, s_tqe);\n\n    stats_server_decr(ctx, conn->owner, out_queue);\n    stats_server_decr_by(ctx, conn->owner, out_queue_bytes, msg->mlen);\n}\n\nstruct msg *\nreq_recv_next(struct context *ctx, struct conn *conn, bool alloc)\n{\n    struct msg *msg;\n\n    ASSERT(conn->client && !conn->proxy);\n\n    if (conn->eof) {\n        msg = conn->rmsg;\n\n        /* client sent eof before sending the entire request */\n        if (msg != NULL) {\n            conn->rmsg = NULL;\n\n            ASSERT(msg->peer == NULL);\n            ASSERT(msg->request && !msg->done);\n\n            log_error(\"eof c %d discarding incomplete req %\"PRIu64\" len \"\n                      \"%\"PRIu32\"\", conn->sd, msg->id, msg->mlen);\n\n            req_put(msg);\n        }\n\n        /*\n         * TCP half-close enables the client to terminate its half of the\n         * connection (i.e. the client no longer sends data), but it still\n         * is able to receive data from the proxy. The proxy closes its\n         * half (by sending the second FIN) when the client has no\n         * outstanding requests\n         */\n        if (!conn->active(conn)) {\n            conn->done = 1;\n            log_debug(LOG_INFO, \"c %d is done\", conn->sd);\n        }\n        return NULL;\n    }\n\n    msg = conn->rmsg;\n    if (msg != NULL) {\n        ASSERT(msg->request);\n        return msg;\n    }\n\n    if (!alloc) {\n        return NULL;\n    }\n\n    msg = req_get(conn);\n    if (msg != NULL) {\n        conn->rmsg = msg;\n    }\n\n    return msg;\n}\n\nstatic rstatus_t\nreq_make_reply(struct context *ctx, struct conn *conn, struct msg *req)\n{\n    struct msg *rsp;\n\n    rsp = msg_get(conn, false, conn->redis); /* replay */\n    if (rsp == NULL) {\n        conn->err = errno;\n        return NC_ENOMEM;\n    }\n\n    req->peer = rsp;\n    rsp->peer = req;\n    rsp->request = 0;\n\n    req->done = 1;\n    conn->enqueue_outq(ctx, conn, req);\n\n    return NC_OK;\n}\n\nstatic bool\nreq_filter(struct conn *conn, struct msg *msg)\n{\n    ASSERT(conn->client && !conn->proxy);\n\n    if (msg_empty(msg)) {\n        ASSERT(conn->rmsg == NULL);\n        log_debug(LOG_VERB, \"filter empty req %\"PRIu64\" from c %d\", msg->id,\n                  conn->sd);\n        req_put(msg);\n        return true;\n    }\n\n    /*\n     * Handle \"quit\\r\\n\" (memcache) or \"*1\\r\\n$4\\r\\nquit\\r\\n\" (redis), which\n     * is the protocol way of doing a passive close. The connection is closed\n     * as soon as all pending replies have been written to the client.\n     */\n    if (msg->quit) {\n        log_debug(LOG_INFO, \"filter quit req %\"PRIu64\" from c %d\", msg->id,\n                  conn->sd);\n        if (conn->rmsg != NULL) {\n            log_debug(LOG_INFO, \"discard invalid req %\"PRIu64\" len %\"PRIu32\" \"\n                      \"from c %d sent after quit req\", conn->rmsg->id,\n                      conn->rmsg->mlen, conn->sd);\n        }\n        conn->eof = 1;\n        conn->recv_ready = 0;\n        req_put(msg);\n        return true;\n    }\n\n    /*\n     * If this conn is not authenticated, we will mark it as noforward,\n     * and handle it in the redis_reply handler.\n     */\n    if (!conn_authenticated(conn)) {\n        msg->noforward = 1;\n    }\n\n    return false;\n}\n\nstatic void\nreq_forward_error(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    rstatus_t status;\n\n    ASSERT(conn->client && !conn->proxy);\n\n    log_debug(LOG_INFO, \"forward req %\"PRIu64\" len %\"PRIu32\" type %d from \"\n              \"c %d failed: %s\", msg->id, msg->mlen, msg->type, conn->sd,\n              strerror(errno));\n\n    msg->done = 1;\n    msg->error = 1;\n    msg->err = errno;\n\n    /* noreply request don't expect any response */\n    if (msg->noreply) {\n        req_put(msg);\n        return;\n    }\n\n    if (req_done(conn, TAILQ_FIRST(&conn->omsg_q))) {\n        status = event_add_out(ctx->evb, conn);\n        if (status != NC_OK) {\n            conn->err = errno;\n        }\n    }\n}\n\nstatic void\nreq_forward_stats(struct context *ctx, struct server *server, struct msg *msg)\n{\n    ASSERT(msg->request);\n\n    stats_server_incr(ctx, server, requests);\n    stats_server_incr_by(ctx, server, request_bytes, msg->mlen);\n}\n\nstatic void\nreq_forward(struct context *ctx, struct conn *c_conn, struct msg *msg)\n{\n    rstatus_t status;\n    struct conn *s_conn;\n    uint8_t *key;\n    uint32_t keylen;\n    struct keypos *kpos;\n\n    ASSERT(c_conn->client && !c_conn->proxy);\n\n    /* enqueue message (request) into client outq, if response is expected */\n    if (!msg->noreply) {\n        c_conn->enqueue_outq(ctx, c_conn, msg);\n    }\n\n    ASSERT(array_n(msg->keys) > 0);\n    kpos = array_get(msg->keys, 0);\n    key = kpos->start;\n    keylen = (uint32_t)(kpos->end - kpos->start);\n\n    s_conn = server_pool_conn(ctx, c_conn->owner, key, keylen);\n    if (s_conn == NULL) {\n        /*\n         * Handle a failure to establish a new connection to a server,\n         * e.g. due to dns resolution errors.\n         *\n         * If this is a fragmented request sent to multiple servers such as\n         * a memcache get(multiget),\n         * mark the fragment for this request to the server as done.\n         *\n         * Normally, this would be done when the request was forwarded to the\n         * server, but due to failing to connect to the server this check is\n         * repeated here.\n         */\n        if (msg->frag_owner != NULL) {\n            msg->frag_owner->nfrag_done++;\n        }\n        req_forward_error(ctx, c_conn, msg);\n        return;\n    }\n    ASSERT(!s_conn->client && !s_conn->proxy);\n\n    /* enqueue the message (request) into server inq */\n    if (TAILQ_EMPTY(&s_conn->imsg_q)) {\n        status = event_add_out(ctx->evb, s_conn);\n        if (status != NC_OK) {\n            req_forward_error(ctx, c_conn, msg);\n            s_conn->err = errno;\n            return;\n        }\n    }\n\n    if (!conn_authenticated(s_conn)) {\n        status = msg->add_auth(ctx, c_conn, s_conn);\n        if (status != NC_OK) {\n            req_forward_error(ctx, c_conn, msg);\n            s_conn->err = errno;\n            return;\n        }\n    }\n\n    s_conn->enqueue_inq(ctx, s_conn, msg);\n\n    req_forward_stats(ctx, s_conn->owner, msg);\n\n    log_debug(LOG_VERB, \"forward from c %d to s %d req %\"PRIu64\" len %\"PRIu32\n              \" type %d with key '%.*s'\", c_conn->sd, s_conn->sd, msg->id,\n              msg->mlen, msg->type, keylen, key);\n}\n\nvoid\nreq_recv_done(struct context *ctx, struct conn *conn, struct msg *msg,\n              struct msg *nmsg)\n{\n    rstatus_t status;\n    struct server_pool *pool;\n    struct msg_tqh frag_msgq;\n    struct msg *sub_msg;\n    struct msg *tmsg; \t\t\t/* tmp next message */\n\n    ASSERT(conn->client && !conn->proxy);\n    ASSERT(msg->request);\n    ASSERT(msg->owner == conn);\n    ASSERT(conn->rmsg == msg);\n    ASSERT(nmsg == NULL || nmsg->request);\n\n    /* enqueue next message (request), if any */\n    conn->rmsg = nmsg;\n\n    if (req_filter(conn, msg)) {\n        return;\n    }\n\n    if (msg->noforward) {\n        status = req_make_reply(ctx, conn, msg);\n        if (status != NC_OK) {\n            conn->err = errno;\n            return;\n        }\n\n        status = msg->reply(msg);\n        if (status != NC_OK) {\n            conn->err = errno;\n            return;\n        }\n\n        status = event_add_out(ctx->evb, conn);\n        if (status != NC_OK) {\n            conn->err = errno;\n        }\n\n        return;\n    }\n\n    /* do fragment */\n    pool = conn->owner;\n    TAILQ_INIT(&frag_msgq);\n    status = msg->fragment(msg, array_n(&pool->server), &frag_msgq);\n    if (status != NC_OK) {\n        if (!msg->noreply) {\n            conn->enqueue_outq(ctx, conn, msg);\n        }\n        req_forward_error(ctx, conn, msg);\n    }\n\n    /* if no fragment happened */\n    if (TAILQ_EMPTY(&frag_msgq)) {\n        req_forward(ctx, conn, msg);\n        return;\n    }\n\n    status = req_make_reply(ctx, conn, msg);\n    if (status != NC_OK) {\n        if (!msg->noreply) {\n            conn->enqueue_outq(ctx, conn, msg);\n        }\n        req_forward_error(ctx, conn, msg);\n    }\n\n    for (sub_msg = TAILQ_FIRST(&frag_msgq); sub_msg != NULL; sub_msg = tmsg) {\n        tmsg = TAILQ_NEXT(sub_msg, m_tqe);\n\n        TAILQ_REMOVE(&frag_msgq, sub_msg, m_tqe);\n        req_forward(ctx, conn, sub_msg);\n    }\n\n    ASSERT(TAILQ_EMPTY(&frag_msgq));\n    return;\n}\n\nstruct msg *\nreq_send_next(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    struct msg *msg, *nmsg; /* current and next message */\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    if (conn->connecting) {\n        server_connected(ctx, conn);\n    }\n\n    nmsg = TAILQ_FIRST(&conn->imsg_q);\n    if (nmsg == NULL) {\n        /* nothing to send as the server inq is empty */\n        status = event_del_out(ctx->evb, conn);\n        if (status != NC_OK) {\n            conn->err = errno;\n        }\n\n        return NULL;\n    }\n\n    msg = conn->smsg;\n    if (msg != NULL) {\n        ASSERT(msg->request && !msg->done);\n        nmsg = TAILQ_NEXT(msg, s_tqe);\n    }\n\n    conn->smsg = nmsg;\n\n    if (nmsg == NULL) {\n        return NULL;\n    }\n\n    ASSERT(nmsg->request && !nmsg->done);\n\n    log_debug(LOG_VVERB, \"send next req %\"PRIu64\" len %\"PRIu32\" type %d on \"\n              \"s %d\", nmsg->id, nmsg->mlen, nmsg->type, conn->sd);\n\n    return nmsg;\n}\n\nvoid\nreq_send_done(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    ASSERT(!conn->client && !conn->proxy);\n    ASSERT(msg != NULL && conn->smsg == NULL);\n    ASSERT(msg->request && !msg->done);\n    ASSERT(msg->owner != conn);\n\n    log_debug(LOG_VVERB, \"send done req %\"PRIu64\" len %\"PRIu32\" type %d on \"\n              \"s %d\", msg->id, msg->mlen, msg->type, conn->sd);\n\n    /* dequeue the message (request) from server inq */\n    conn->dequeue_inq(ctx, conn, msg);\n\n    /*\n     * noreply request instructs the server not to send any response. So,\n     * enqueue message (request) in server outq, if response is expected.\n     * Otherwise, free the noreply request\n     */\n    if (!msg->noreply) {\n        conn->enqueue_outq(ctx, conn, msg);\n    } else {\n        req_put(msg);\n    }\n}\n"
  },
  {
    "path": "src/nc_response.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <nc_core.h>\n#include <nc_server.h>\n\nstruct msg *\nrsp_get(struct conn *conn)\n{\n    struct msg *msg;\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    msg = msg_get(conn, false, conn->redis);\n    if (msg == NULL) {\n        conn->err = errno;\n    }\n\n    return msg;\n}\n\nvoid\nrsp_put(struct msg *msg)\n{\n    ASSERT(!msg->request);\n    ASSERT(msg->peer == NULL);\n    msg_put(msg);\n}\n\nstatic struct msg *\nrsp_make_error(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    struct msg *pmsg;        /* peer message (response) */\n    struct msg *cmsg, *nmsg; /* current and next message (request) */\n    uint64_t id;\n    err_t err;\n\n    ASSERT(conn->client && !conn->proxy);\n    ASSERT(msg->request && req_error(conn, msg));\n    ASSERT(msg->owner == conn);\n\n    id = msg->frag_id;\n    if (id != 0) {\n        for (err = 0, cmsg = TAILQ_NEXT(msg, c_tqe);\n             cmsg != NULL && cmsg->frag_id == id;\n             cmsg = nmsg) {\n            nmsg = TAILQ_NEXT(cmsg, c_tqe);\n\n            /* dequeue request (error fragment) from client outq */\n            conn->dequeue_outq(ctx, conn, cmsg);\n            if (err == 0 && cmsg->err != 0) {\n                err = cmsg->err;\n            }\n\n            req_put(cmsg);\n        }\n    } else {\n        err = msg->err;\n    }\n\n    pmsg = msg->peer;\n    if (pmsg != NULL) {\n        ASSERT(!pmsg->request && pmsg->peer == msg);\n        msg->peer = NULL;\n        pmsg->peer = NULL;\n        rsp_put(pmsg);\n    }\n\n    return msg_get_error(conn->redis, err);\n}\n\nstruct msg *\nrsp_recv_next(struct context *ctx, struct conn *conn, bool alloc)\n{\n    struct msg *msg;\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    if (conn->eof) {\n        msg = conn->rmsg;\n\n        /* server sent eof before sending the entire request */\n        if (msg != NULL) {\n            conn->rmsg = NULL;\n\n            ASSERT(msg->peer == NULL);\n            ASSERT(!msg->request);\n\n            log_error(\"eof s %d discarding incomplete rsp %\"PRIu64\" len \"\n                      \"%\"PRIu32\"\", conn->sd, msg->id, msg->mlen);\n\n            rsp_put(msg);\n        }\n\n        /*\n         * We treat TCP half-close from a server different from how we treat\n         * those from a client. On a FIN from a server, we close the connection\n         * immediately by sending the second FIN even if there were outstanding\n         * or pending requests. This is actually a tricky part in the FA, as\n         * we don't expect this to happen unless the server is misbehaving or\n         * it crashes\n         */\n        conn->done = 1;\n        log_error(\"s %d active %d is done\", conn->sd, conn->active(conn));\n\n        return NULL;\n    }\n\n    msg = conn->rmsg;\n    if (msg != NULL) {\n        ASSERT(!msg->request);\n        return msg;\n    }\n\n    if (!alloc) {\n        return NULL;\n    }\n\n    msg = rsp_get(conn);\n    if (msg != NULL) {\n        conn->rmsg = msg;\n    }\n\n    return msg;\n}\n\nstatic bool\nrsp_filter(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    struct msg *pmsg;\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    if (msg_empty(msg)) {\n        ASSERT(conn->rmsg == NULL);\n        log_debug(LOG_VERB, \"filter empty rsp %\"PRIu64\" on s %d\", msg->id,\n                  conn->sd);\n        rsp_put(msg);\n        return true;\n    }\n\n    pmsg = TAILQ_FIRST(&conn->omsg_q);\n    if (pmsg == NULL) {\n        log_debug(LOG_ERR, \"filter stray rsp %\"PRIu64\" len %\"PRIu32\" on s %d\",\n                  msg->id, msg->mlen, conn->sd);\n        rsp_put(msg);\n\n        /*\n         * Memcached server can respond with an error response before it has\n         * received the entire request. This is most commonly seen for set\n         * requests that exceed item_size_max. IMO, this behavior of memcached\n         * is incorrect. The right behavior for update requests that are over\n         * item_size_max would be to either:\n         * - close the connection Or,\n         * - read the entire item_size_max data and then send CLIENT_ERROR\n         *\n         * We handle this stray packet scenario in nutcracker by closing the\n         * server connection which would end up sending SERVER_ERROR to all\n         * clients that have requests pending on this server connection. The\n         * fix is aggressive, but not doing so would lead to clients getting\n         * out of sync with the server and as a result clients end up getting\n         * responses that don't correspond to the right request.\n         *\n         * See: https://github.com/twitter/twemproxy/issues/149\n         */\n        conn->err = EINVAL;\n        conn->done = 1;\n        return true;\n    }\n    ASSERT(pmsg->peer == NULL);\n    ASSERT(pmsg->request && !pmsg->done);\n\n    /*\n     * If the response from a server suggests a protocol level transient\n     * failure, close the server connection and send back a generic error\n     * response to the client.\n     *\n     * If auto_eject_host is enabled, this will also update the failure_count\n     * and eject the server if it exceeds the failure_limit\n     */\n    if (msg->failure(msg)) {\n        log_debug(LOG_INFO, \"server failure rsp %\"PRIu64\" len %\"PRIu32\" \"\n                  \"type %d on s %d\", msg->id, msg->mlen, msg->type, conn->sd);\n        rsp_put(msg);\n\n        conn->err = EINVAL;\n        conn->done = 1;\n\n        return true;\n    }\n\n    if (pmsg->swallow) {\n        conn->swallow_msg(conn, pmsg, msg);\n\n        conn->dequeue_outq(ctx, conn, pmsg);\n        pmsg->done = 1;\n\n        log_debug(LOG_INFO, \"swallow rsp %\"PRIu64\" len %\"PRIu32\" of req \"\n                  \"%\"PRIu64\" on s %d\", msg->id, msg->mlen, pmsg->id,\n                  conn->sd);\n\n        rsp_put(msg);\n        req_put(pmsg);\n        return true;\n    }\n\n    return false;\n}\n\nstatic void\nrsp_forward_stats(struct context *ctx, struct server *server, struct msg *msg, uint32_t msgsize)\n{\n    ASSERT(!msg->request);\n\n    stats_server_incr(ctx, server, responses);\n    stats_server_incr_by(ctx, server, response_bytes, msgsize);\n}\n\nstatic void\nrsp_forward(struct context *ctx, struct conn *s_conn, struct msg *msg)\n{\n    rstatus_t status;\n    struct msg *pmsg;\n    struct conn *c_conn;\n    uint32_t msgsize;\n\n    ASSERT(!s_conn->client && !s_conn->proxy);\n    msgsize = msg->mlen;\n\n    /* response from server implies that server is ok and heartbeating */\n    server_ok(ctx, s_conn);\n\n    /* dequeue peer message (request) from server */\n    pmsg = TAILQ_FIRST(&s_conn->omsg_q);\n    ASSERT(pmsg != NULL && pmsg->peer == NULL);\n    ASSERT(pmsg->request && !pmsg->done);\n\n    s_conn->dequeue_outq(ctx, s_conn, pmsg);\n    pmsg->done = 1;\n\n    /* establish msg <-> pmsg (response <-> request) link */\n    pmsg->peer = msg;\n    msg->peer = pmsg;\n\n    msg->pre_coalesce(msg);\n\n    c_conn = pmsg->owner;\n    ASSERT(c_conn->client && !c_conn->proxy);\n\n    if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) {\n        status = event_add_out(ctx->evb, c_conn);\n        if (status != NC_OK) {\n            c_conn->err = errno;\n        }\n    }\n\n    rsp_forward_stats(ctx, s_conn->owner, msg, msgsize);\n}\n\nvoid\nrsp_recv_done(struct context *ctx, struct conn *conn, struct msg *msg,\n              struct msg *nmsg)\n{\n    ASSERT(!conn->client && !conn->proxy);\n    ASSERT(msg != NULL && conn->rmsg == msg);\n    ASSERT(!msg->request);\n    ASSERT(msg->owner == conn);\n    ASSERT(nmsg == NULL || !nmsg->request);\n\n    /* enqueue next message (response), if any */\n    conn->rmsg = nmsg;\n\n    if (rsp_filter(ctx, conn, msg)) {\n        return;\n    }\n\n    rsp_forward(ctx, conn, msg);\n}\n\nstruct msg *\nrsp_send_next(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    struct msg *msg, *pmsg; /* response and it's peer request */\n\n    ASSERT(conn->client && !conn->proxy);\n\n    pmsg = TAILQ_FIRST(&conn->omsg_q);\n    if (pmsg == NULL || !req_done(conn, pmsg)) {\n        /* nothing is outstanding, initiate close? */\n        if (pmsg == NULL && conn->eof) {\n            conn->done = 1;\n            log_debug(LOG_INFO, \"c %d is done\", conn->sd);\n        }\n\n        status = event_del_out(ctx->evb, conn);\n        if (status != NC_OK) {\n            conn->err = errno;\n        }\n\n        return NULL;\n    }\n\n    msg = conn->smsg;\n    if (msg != NULL) {\n        ASSERT(!msg->request && msg->peer != NULL);\n        ASSERT(req_done(conn, msg->peer));\n        pmsg = TAILQ_NEXT(msg->peer, c_tqe);\n    }\n\n    if (pmsg == NULL || !req_done(conn, pmsg)) {\n        conn->smsg = NULL;\n        return NULL;\n    }\n    ASSERT(pmsg->request && !pmsg->swallow);\n\n    if (req_error(conn, pmsg)) {\n        msg = rsp_make_error(ctx, conn, pmsg);\n        if (msg == NULL) {\n            conn->err = errno;\n            return NULL;\n        }\n        msg->peer = pmsg;\n        pmsg->peer = msg;\n        stats_pool_incr(ctx, conn->owner, forward_error);\n    } else {\n        msg = pmsg->peer;\n    }\n    ASSERT(!msg->request);\n\n    conn->smsg = msg;\n\n    log_debug(LOG_VVERB, \"send next rsp %\"PRIu64\" on c %d\", msg->id, conn->sd);\n\n    return msg;\n}\n\nvoid\nrsp_send_done(struct context *ctx, struct conn *conn, struct msg *msg)\n{\n    struct msg *pmsg; /* peer message (request) */\n\n    ASSERT(conn->client && !conn->proxy);\n    ASSERT(conn->smsg == NULL);\n\n    log_debug(LOG_VVERB, \"send done rsp %\"PRIu64\" on c %d\", msg->id, conn->sd);\n\n    pmsg = msg->peer;\n\n    ASSERT(!msg->request && pmsg->request);\n    ASSERT(pmsg->peer == msg);\n    ASSERT(pmsg->done && !pmsg->swallow);\n\n    /* dequeue request from client outq */\n    conn->dequeue_outq(ctx, conn, pmsg);\n\n    req_put(pmsg);\n}\n"
  },
  {
    "path": "src/nc_server.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <unistd.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n#include <nc_conf.h>\n\nstatic void\nserver_resolve(struct server *server, struct conn *conn)\n{\n    rstatus_t status;\n\n    status = nc_resolve(&server->addrstr, server->port, &server->info);\n    if (status != NC_OK) {\n        conn->err = EHOSTDOWN;\n        conn->done = 1;\n        return;\n    }\n\n    conn->family = server->info.family;\n    conn->addrlen = server->info.addrlen;\n    conn->addr = (struct sockaddr *)&server->info.addr;\n}\n\nvoid\nserver_ref(struct conn *conn, void *owner)\n{\n    struct server *server = owner;\n\n    ASSERT(!conn->client && !conn->proxy);\n    ASSERT(conn->owner == NULL);\n\n    server_resolve(server, conn);\n\n    server->ns_conn_q++;\n    TAILQ_INSERT_TAIL(&server->s_conn_q, conn, conn_tqe);\n\n    conn->owner = owner;\n\n    log_debug(LOG_VVERB, \"ref conn %p owner %p into '%.*s\", conn, server,\n              server->pname.len, server->pname.data);\n}\n\nvoid\nserver_unref(struct conn *conn)\n{\n    struct server *server;\n\n    ASSERT(!conn->client && !conn->proxy);\n    ASSERT(conn->owner != NULL);\n\n    server = conn->owner;\n    conn->owner = NULL;\n\n    ASSERT(server->ns_conn_q != 0);\n    server->ns_conn_q--;\n    TAILQ_REMOVE(&server->s_conn_q, conn, conn_tqe);\n\n    log_debug(LOG_VVERB, \"unref conn %p owner %p from '%.*s'\", conn, server,\n              server->pname.len, server->pname.data);\n}\n\nint\nserver_timeout(struct conn *conn)\n{\n    struct server *server;\n    struct server_pool *pool;\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    server = conn->owner;\n    pool = server->owner;\n\n    return pool->timeout;\n}\n\nbool\nserver_active(const struct conn *conn)\n{\n    ASSERT(!conn->client && !conn->proxy);\n\n    if (!TAILQ_EMPTY(&conn->imsg_q)) {\n        log_debug(LOG_VVERB, \"s %d is active\", conn->sd);\n        return true;\n    }\n\n    if (!TAILQ_EMPTY(&conn->omsg_q)) {\n        log_debug(LOG_VVERB, \"s %d is active\", conn->sd);\n        return true;\n    }\n\n    if (conn->rmsg != NULL) {\n        log_debug(LOG_VVERB, \"s %d is active\", conn->sd);\n        return true;\n    }\n\n    if (conn->smsg != NULL) {\n        log_debug(LOG_VVERB, \"s %d is active\", conn->sd);\n        return true;\n    }\n\n    log_debug(LOG_VVERB, \"s %d is inactive\", conn->sd);\n\n    return false;\n}\n\nstatic rstatus_t\nserver_each_set_owner(void *elem, void *data)\n{\n    struct server *s = elem;\n    struct server_pool *sp = data;\n\n    s->owner = sp;\n\n    return NC_OK;\n}\n\nrstatus_t\nserver_init(struct array *server, struct array *conf_server,\n            struct server_pool *sp)\n{\n    rstatus_t status;\n    uint32_t nserver;\n\n    nserver = array_n(conf_server);\n    ASSERT(nserver != 0);\n    ASSERT(array_n(server) == 0);\n\n    status = array_init(server, nserver, sizeof(struct server));\n    if (status != NC_OK) {\n        return status;\n    }\n\n    /* transform conf server to server */\n    status = array_each(conf_server, conf_server_each_transform, server);\n    if (status != NC_OK) {\n        server_deinit(server);\n        return status;\n    }\n    ASSERT(array_n(server) == nserver);\n\n    /* set server owner */\n    status = array_each(server, server_each_set_owner, sp);\n    if (status != NC_OK) {\n        server_deinit(server);\n        return status;\n    }\n\n    log_debug(LOG_DEBUG, \"init %\"PRIu32\" servers in pool %\"PRIu32\" '%.*s'\",\n              nserver, sp->idx, sp->name.len, sp->name.data);\n\n    return NC_OK;\n}\n\nvoid\nserver_deinit(struct array *server)\n{\n    uint32_t i, nserver;\n\n    for (i = 0, nserver = array_n(server); i < nserver; i++) {\n        struct server *s;\n\n        s = array_pop(server);\n        ASSERT(TAILQ_EMPTY(&s->s_conn_q) && s->ns_conn_q == 0);\n    }\n    array_deinit(server);\n}\n\nstruct conn *\nserver_conn(struct server *server)\n{\n    struct server_pool *pool;\n    struct conn *conn;\n\n    pool = server->owner;\n\n    /*\n     * FIXME: handle multiple server connections per server and do load\n     * balancing on it. Support multiple algorithms for\n     * 'server_connections:' > 0 key\n     */\n\n    if (server->ns_conn_q < pool->server_connections) {\n        return conn_get(server, false, pool->redis);\n    }\n    ASSERT(server->ns_conn_q == pool->server_connections);\n\n    /*\n     * Pick a server connection from the head of the queue and insert\n     * it back into the tail of queue to maintain the lru order\n     */\n    conn = TAILQ_FIRST(&server->s_conn_q);\n    ASSERT(!conn->client && !conn->proxy);\n\n    TAILQ_REMOVE(&server->s_conn_q, conn, conn_tqe);\n    TAILQ_INSERT_TAIL(&server->s_conn_q, conn, conn_tqe);\n\n    return conn;\n}\n\nstatic rstatus_t\nserver_each_preconnect(void *elem, void *data)\n{\n    rstatus_t status;\n    struct server *server;\n    struct server_pool *pool;\n    struct conn *conn;\n\n    server = elem;\n    pool = server->owner;\n\n    conn = server_conn(server);\n    if (conn == NULL) {\n        return NC_ENOMEM;\n    }\n\n    status = server_connect(pool->ctx, server, conn);\n    if (status != NC_OK) {\n        log_warn(\"connect to server '%.*s' failed, ignored: %s\",\n                 server->pname.len, server->pname.data, strerror(errno));\n        server_close(pool->ctx, conn);\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nserver_each_disconnect(void *elem, void *data)\n{\n    struct server *server;\n    struct server_pool *pool;\n\n    server = elem;\n    pool = server->owner;\n\n    while (!TAILQ_EMPTY(&server->s_conn_q)) {\n        struct conn *conn;\n\n        ASSERT(server->ns_conn_q > 0);\n\n        conn = TAILQ_FIRST(&server->s_conn_q);\n        conn->close(pool->ctx, conn);\n    }\n\n    return NC_OK;\n}\n\nstatic void\nserver_failure(struct context *ctx, struct server *server)\n{\n    struct server_pool *pool = server->owner;\n    int64_t now, next;\n    rstatus_t status;\n\n    if (!pool->auto_eject_hosts) {\n        return;\n    }\n\n    server->failure_count++;\n\n    log_debug(LOG_VERB, \"server '%.*s' failure count %\"PRIu32\" limit %\"PRIu32,\n              server->pname.len, server->pname.data, server->failure_count,\n              pool->server_failure_limit);\n\n    if (server->failure_count < pool->server_failure_limit) {\n        return;\n    }\n\n    now = nc_usec_now();\n    if (now < 0) {\n        return;\n    }\n\n    stats_server_set_ts(ctx, server, server_ejected_at, now);\n\n    next = now + pool->server_retry_timeout;\n\n    log_debug(LOG_INFO, \"update pool %\"PRIu32\" '%.*s' to delete server '%.*s' \"\n              \"for next %\"PRId64\" secs\", pool->idx, pool->name.len,\n              pool->name.data, server->pname.len, server->pname.data,\n              pool->server_retry_timeout / 1000 / 1000);\n\n    stats_pool_incr(ctx, pool, server_ejects);\n\n    server->failure_count = 0;\n    server->next_retry = next;\n\n    status = server_pool_run(pool);\n    if (status != NC_OK) {\n        log_error(\"updating pool %\"PRIu32\" '%.*s' failed: %s\", pool->idx,\n                  pool->name.len, pool->name.data, strerror(errno));\n    }\n}\n\nstatic void\nserver_close_stats(struct context *ctx, struct server *server, err_t err,\n                   unsigned eof, unsigned connected)\n{\n    if (connected) {\n        stats_server_decr(ctx, server, server_connections);\n    }\n\n    if (eof) {\n        stats_server_incr(ctx, server, server_eof);\n        return;\n    }\n\n    switch (err) {\n    case ETIMEDOUT:\n        stats_server_incr(ctx, server, server_timedout);\n        break;\n    case EPIPE:\n    case ECONNRESET:\n    case ECONNABORTED:\n    case ECONNREFUSED:\n    case ENOTCONN:\n    case ENETDOWN:\n    case ENETUNREACH:\n    case EHOSTDOWN:\n    case EHOSTUNREACH:\n    default:\n        stats_server_incr(ctx, server, server_err);\n        break;\n    }\n}\n\nvoid\nserver_close(struct context *ctx, struct conn *conn)\n{\n    rstatus_t status;\n    struct msg *msg, *nmsg; /* current and next message */\n    struct conn *c_conn;    /* peer client connection */\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    server_close_stats(ctx, conn->owner, conn->err, conn->eof,\n                       conn->connected);\n\n    conn->connected = false;\n\n    if (conn->sd < 0) {\n        server_failure(ctx, conn->owner);\n        conn->unref(conn);\n        conn_put(conn);\n        return;\n    }\n\n    for (msg = TAILQ_FIRST(&conn->imsg_q); msg != NULL; msg = nmsg) {\n        nmsg = TAILQ_NEXT(msg, s_tqe);\n\n        /* dequeue the message (request) from server inq */\n        conn->dequeue_inq(ctx, conn, msg);\n\n        /*\n         * Don't send any error response, if\n         * 1. request is tagged as noreply or,\n         * 2. client has already closed its connection\n         */\n        if (msg->swallow || msg->noreply) {\n            log_debug(LOG_INFO, \"close s %d swallow req %\"PRIu64\" len %\"PRIu32\n                      \" type %d\", conn->sd, msg->id, msg->mlen, msg->type);\n            req_put(msg);\n        } else {\n            c_conn = msg->owner;\n            ASSERT(c_conn->client && !c_conn->proxy);\n\n            msg->done = 1;\n            msg->error = 1;\n            msg->err = conn->err;\n\n            if (msg->frag_owner != NULL) {\n                msg->frag_owner->nfrag_done++;\n            }\n\n            if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) {\n                event_add_out(ctx->evb, msg->owner);\n            }\n\n            log_debug(LOG_INFO, \"close s %d schedule error for req %\"PRIu64\" \"\n                      \"len %\"PRIu32\" type %d from c %d%c %s\", conn->sd, msg->id,\n                      msg->mlen, msg->type, c_conn->sd, conn->err ? ':' : ' ',\n                      conn->err ? strerror(conn->err): \" \");\n        }\n    }\n    ASSERT(TAILQ_EMPTY(&conn->imsg_q));\n\n    for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) {\n        nmsg = TAILQ_NEXT(msg, s_tqe);\n\n        /* dequeue the message (request) from server outq */\n        conn->dequeue_outq(ctx, conn, msg);\n\n        if (msg->swallow) {\n            log_debug(LOG_INFO, \"close s %d swallow req %\"PRIu64\" len %\"PRIu32\n                      \" type %d\", conn->sd, msg->id, msg->mlen, msg->type);\n            req_put(msg);\n        } else {\n            c_conn = msg->owner;\n            ASSERT(c_conn->client && !c_conn->proxy);\n\n            msg->done = 1;\n            msg->error = 1;\n            msg->err = conn->err;\n            if (msg->frag_owner != NULL) {\n                msg->frag_owner->nfrag_done++;\n            }\n\n            if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) {\n                event_add_out(ctx->evb, msg->owner);\n            }\n\n            log_debug(LOG_INFO, \"close s %d schedule error for req %\"PRIu64\" \"\n                      \"len %\"PRIu32\" type %d from c %d%c %s\", conn->sd, msg->id,\n                      msg->mlen, msg->type, c_conn->sd, conn->err ? ':' : ' ',\n                      conn->err ? strerror(conn->err): \" \");\n        }\n    }\n    ASSERT(TAILQ_EMPTY(&conn->omsg_q));\n\n    msg = conn->rmsg;\n    if (msg != NULL) {\n        conn->rmsg = NULL;\n\n        ASSERT(!msg->request);\n        ASSERT(msg->peer == NULL);\n\n        rsp_put(msg);\n\n        log_debug(LOG_INFO, \"close s %d discarding rsp %\"PRIu64\" len %\"PRIu32\" \"\n                  \"in error\", conn->sd, msg->id, msg->mlen);\n    }\n\n    ASSERT(conn->smsg == NULL);\n\n    server_failure(ctx, conn->owner);\n\n    conn->unref(conn);\n\n    status = close(conn->sd);\n    if (status < 0) {\n        log_error(\"close s %d failed, ignored: %s\", conn->sd, strerror(errno));\n    }\n    conn->sd = -1;\n\n    conn_put(conn);\n}\n\nrstatus_t\nserver_connect(struct context *ctx, struct server *server, struct conn *conn)\n{\n    rstatus_t status;\n\n    ASSERT(!conn->client && !conn->proxy);\n\n    if (conn->err) {\n      ASSERT(conn->done && conn->sd < 0);\n      errno = conn->err;\n      return NC_ERROR;\n    }\n\n    if (conn->sd > 0) {\n        /* already connected on server connection */\n        return NC_OK;\n    }\n\n    log_debug(LOG_VVERB, \"connect to server '%.*s'\", server->pname.len,\n              server->pname.data);\n\n    conn->sd = socket(conn->family, SOCK_STREAM, 0);\n    if (conn->sd < 0) {\n        log_error(\"socket for server '%.*s' failed: %s\", server->pname.len,\n                  server->pname.data, strerror(errno));\n        status = NC_ERROR;\n        goto error;\n    }\n\n    status = nc_set_nonblocking(conn->sd);\n    if (status != NC_OK) {\n        log_error(\"set nonblock on s %d for server '%.*s' failed: %s\",\n                  conn->sd, server->pname.len, server->pname.data,\n                  strerror(errno));\n        goto error;\n    }\n\n    if (server->pname.data[0] != '/') {\n        status = nc_set_tcpnodelay(conn->sd);\n        if (status != NC_OK) {\n            log_warn(\"set tcpnodelay on s %d for server '%.*s' failed, ignored: %s\",\n                     conn->sd, server->pname.len, server->pname.data,\n                     strerror(errno));\n        }\n    }\n\n    status = event_add_conn(ctx->evb, conn);\n    if (status != NC_OK) {\n        log_error(\"event add conn s %d for server '%.*s' failed: %s\",\n                  conn->sd, server->pname.len, server->pname.data,\n                  strerror(errno));\n        goto error;\n    }\n\n    ASSERT(!conn->connecting && !conn->connected);\n\n    status = connect(conn->sd, conn->addr, conn->addrlen);\n    if (status != NC_OK) {\n        if (errno == EINPROGRESS) {\n            conn->connecting = 1;\n            log_debug(LOG_DEBUG, \"connecting on s %d to server '%.*s'\",\n                      conn->sd, server->pname.len, server->pname.data);\n            return NC_OK;\n        }\n\n        log_error(\"connect on s %d to server '%.*s' failed: %s\", conn->sd,\n                  server->pname.len, server->pname.data, strerror(errno));\n\n        goto error;\n    }\n\n    ASSERT(!conn->connecting);\n    conn->connected = 1;\n    log_debug(LOG_INFO, \"connected on s %d to server '%.*s'\", conn->sd,\n              server->pname.len, server->pname.data);\n\n    return NC_OK;\n\nerror:\n    conn->err = errno;\n    return status;\n}\n\nvoid\nserver_connected(struct context *ctx, struct conn *conn)\n{\n    struct server *server = conn->owner;\n\n    ASSERT(!conn->client && !conn->proxy);\n    ASSERT(conn->connecting && !conn->connected);\n\n    stats_server_incr(ctx, server, server_connections);\n\n    conn->connecting = 0;\n    conn->connected = 1;\n\n    conn->post_connect(ctx, conn, server);\n\n    log_debug(LOG_INFO, \"connected on s %d to server '%.*s'\", conn->sd,\n              server->pname.len, server->pname.data);\n}\n\nvoid\nserver_ok(struct context *ctx, struct conn *conn)\n{\n    struct server *server = conn->owner;\n\n    ASSERT(!conn->client && !conn->proxy);\n    ASSERT(conn->connected);\n\n    if (server->failure_count != 0) {\n        log_debug(LOG_VERB, \"reset server '%.*s' failure count from %\"PRIu32\n                  \" to 0\", server->pname.len, server->pname.data,\n                  server->failure_count);\n        server->failure_count = 0;\n        server->next_retry = 0LL;\n    }\n}\n\nstatic rstatus_t\nserver_pool_update(struct server_pool *pool)\n{\n    rstatus_t status;\n    int64_t now;\n    uint32_t pnlive_server; /* prev # live server */\n\n    if (!pool->auto_eject_hosts) {\n        return NC_OK;\n    }\n\n    if (pool->next_rebuild == 0LL) {\n        return NC_OK;\n    }\n\n    now = nc_usec_now();\n    if (now < 0) {\n        return NC_ERROR;\n    }\n\n    if (now <= pool->next_rebuild) {\n        if (pool->nlive_server == 0) {\n            errno = ECONNREFUSED;\n            return NC_ERROR;\n        }\n        return NC_OK;\n    }\n\n    pnlive_server = pool->nlive_server;\n\n    status = server_pool_run(pool);\n    if (status != NC_OK) {\n        log_error(\"updating pool %\"PRIu32\" with dist %d failed: %s\", pool->idx,\n                  pool->dist_type, strerror(errno));\n        return status;\n    }\n\n    log_debug(LOG_INFO, \"update pool %\"PRIu32\" '%.*s' to add %\"PRIu32\" servers\",\n              pool->idx, pool->name.len, pool->name.data,\n              pool->nlive_server - pnlive_server);\n\n\n    return NC_OK;\n}\n\nstatic uint32_t\nserver_pool_hash(const struct server_pool *pool, const uint8_t *key, uint32_t keylen)\n{\n    ASSERT(array_n(&pool->server) != 0);\n    ASSERT(key != NULL);\n\n    if (array_n(&pool->server) == 1) {\n        return 0;\n    }\n\n    if (keylen == 0) {\n        return 0;\n    }\n\n    return pool->key_hash((const char *)key, keylen);\n}\n\nuint32_t\nserver_pool_idx(const struct server_pool *pool, const uint8_t *key, uint32_t keylen)\n{\n    uint32_t hash, idx;\n    uint32_t nserver = array_n(&pool->server);\n\n    ASSERT(nserver != 0);\n    ASSERT(key != NULL);\n\n    if (nserver == 1) {\n        /* Optimization: Skip hashing and dispatching for pools with only one server */\n        return 0;\n    }\n\n    /*\n     * If hash_tag: is configured for this server pool, we use the part of\n     * the key within the hash tag as an input to the distributor. Otherwise\n     * we use the full key\n     */\n    if (!string_empty(&pool->hash_tag)) {\n        const struct string *tag = &pool->hash_tag;\n        const uint8_t *tag_start, *tag_end;\n\n        tag_start = nc_strchr(key, key + keylen, tag->data[0]);\n        if (tag_start != NULL) {\n            tag_end = nc_strchr(tag_start + 1, key + keylen, tag->data[1]);\n            if ((tag_end != NULL) && (tag_end - tag_start > 1)) {\n                key = tag_start + 1;\n                keylen = (uint32_t)(tag_end - key);\n            }\n        }\n    }\n\n    switch (pool->dist_type) {\n    case DIST_KETAMA:\n        hash = server_pool_hash(pool, key, keylen);\n        idx = ketama_dispatch(pool->continuum, pool->ncontinuum, hash);\n        break;\n\n    case DIST_MODULA:\n        hash = server_pool_hash(pool, key, keylen);\n        idx = modula_dispatch(pool->continuum, pool->ncontinuum, hash);\n        break;\n\n    case DIST_RANDOM:\n        idx = random_dispatch(pool->continuum, pool->ncontinuum, 0);\n        break;\n\n    default:\n        NOT_REACHED();\n        return 0;\n    }\n    ASSERT(idx < array_n(&pool->server));\n    return idx;\n}\n\nstatic struct server *\nserver_pool_server(struct server_pool *pool, const uint8_t *key, uint32_t keylen)\n{\n    struct server *server;\n    uint32_t idx;\n\n    idx = server_pool_idx(pool, key, keylen);\n    server = array_get(&pool->server, idx);\n\n    log_debug(LOG_VERB, \"key '%.*s' on dist %d maps to server '%.*s'\", keylen,\n              key, pool->dist_type, server->pname.len, server->pname.data);\n\n    return server;\n}\n\nstruct conn *\nserver_pool_conn(struct context *ctx, struct server_pool *pool, const uint8_t *key,\n                 uint32_t keylen)\n{\n    rstatus_t status;\n    struct server *server;\n    struct conn *conn;\n\n    status = server_pool_update(pool);\n    if (status != NC_OK) {\n        return NULL;\n    }\n\n    /* from a given {key, keylen} pick a server from pool */\n    server = server_pool_server(pool, key, keylen);\n    if (server == NULL) {\n        return NULL;\n    }\n\n    /* pick a connection to a given server */\n    conn = server_conn(server);\n    if (conn == NULL) {\n        return NULL;\n    }\n\n    status = server_connect(ctx, server, conn);\n    if (status != NC_OK) {\n        server_close(ctx, conn);\n        return NULL;\n    }\n\n    return conn;\n}\n\nstatic rstatus_t\nserver_pool_each_preconnect(void *elem, void *data)\n{\n    rstatus_t status;\n    struct server_pool *sp = elem;\n\n    if (!sp->preconnect) {\n        return NC_OK;\n    }\n\n    status = array_each(&sp->server, server_each_preconnect, NULL);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    return NC_OK;\n}\n\nrstatus_t\nserver_pool_preconnect(struct context *ctx)\n{\n    rstatus_t status;\n\n    status = array_each(&ctx->pool, server_pool_each_preconnect, NULL);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nserver_pool_each_disconnect(void *elem, void *data)\n{\n    rstatus_t status;\n    struct server_pool *sp = elem;\n\n    status = array_each(&sp->server, server_each_disconnect, NULL);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    return NC_OK;\n}\n\nvoid\nserver_pool_disconnect(struct context *ctx)\n{\n    array_each(&ctx->pool, server_pool_each_disconnect, NULL);\n}\n\nstatic rstatus_t\nserver_pool_each_set_owner(void *elem, void *data)\n{\n    struct server_pool *sp = elem;\n    struct context *ctx = data;\n\n    sp->ctx = ctx;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nserver_pool_each_calc_connections(void *elem, void *data)\n{\n    struct server_pool *sp = elem;\n    struct context *ctx = data;\n\n    ctx->max_nsconn += sp->server_connections * array_n(&sp->server);\n    ctx->max_nsconn += 1; /* pool listening socket */\n\n    return NC_OK;\n}\n\nrstatus_t\nserver_pool_run(struct server_pool *pool)\n{\n    ASSERT(array_n(&pool->server) != 0);\n\n    switch (pool->dist_type) {\n    case DIST_KETAMA:\n        return ketama_update(pool);\n\n    case DIST_MODULA:\n        return modula_update(pool);\n\n    case DIST_RANDOM:\n        return random_update(pool);\n\n    default:\n        NOT_REACHED();\n        return NC_ERROR;\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nserver_pool_each_run(void *elem, void *data)\n{\n    return server_pool_run(elem);\n}\n\nrstatus_t\nserver_pool_init(struct array *server_pool, struct array *conf_pool,\n                 struct context *ctx)\n{\n    rstatus_t status;\n    uint32_t npool;\n\n    npool = array_n(conf_pool);\n    ASSERT(npool != 0);\n    ASSERT(array_n(server_pool) == 0);\n\n    status = array_init(server_pool, npool, sizeof(struct server_pool));\n    if (status != NC_OK) {\n        return status;\n    }\n\n    /* transform conf pool to server pool */\n    status = array_each(conf_pool, conf_pool_each_transform, server_pool);\n    if (status != NC_OK) {\n        server_pool_deinit(server_pool);\n        return status;\n    }\n    ASSERT(array_n(server_pool) == npool);\n\n    /* set ctx as the server pool owner */\n    status = array_each(server_pool, server_pool_each_set_owner, ctx);\n    if (status != NC_OK) {\n        server_pool_deinit(server_pool);\n        return status;\n    }\n\n    /* compute max server connections */\n    ctx->max_nsconn = 0;\n    status = array_each(server_pool, server_pool_each_calc_connections, ctx);\n    if (status != NC_OK) {\n        server_pool_deinit(server_pool);\n        return status;\n    }\n\n    /* update server pool continuum */\n    status = array_each(server_pool, server_pool_each_run, NULL);\n    if (status != NC_OK) {\n        server_pool_deinit(server_pool);\n        return status;\n    }\n\n    log_debug(LOG_DEBUG, \"init %\"PRIu32\" pools\", npool);\n\n    return NC_OK;\n}\n\nvoid\nserver_pool_deinit(struct array *server_pool)\n{\n    uint32_t i, npool;\n\n    for (i = 0, npool = array_n(server_pool); i < npool; i++) {\n        struct server_pool *sp;\n\n        sp = array_pop(server_pool);\n        ASSERT(sp->p_conn == NULL);\n        ASSERT(TAILQ_EMPTY(&sp->c_conn_q) && sp->nc_conn_q == 0);\n\n        if (sp->continuum != NULL) {\n            nc_free(sp->continuum);\n            sp->ncontinuum = 0;\n            sp->nserver_continuum = 0;\n            sp->nlive_server = 0;\n        }\n\n        server_deinit(&sp->server);\n\n        log_debug(LOG_DEBUG, \"deinit pool %\"PRIu32\" '%.*s'\", sp->idx,\n                  sp->name.len, sp->name.data);\n    }\n\n    array_deinit(server_pool);\n\n    log_debug(LOG_DEBUG, \"deinit %\"PRIu32\" pools\", npool);\n}\n"
  },
  {
    "path": "src/nc_server.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_SERVER_H_\n#define _NC_SERVER_H_\n\n#include <nc_core.h>\n\n/*\n * server_pool is a collection of servers and their continuum. Each\n * server_pool is the owner of a single proxy connection and one or\n * more client connections. server_pool itself is owned by the current\n * context.\n *\n * Each server is the owner of one or more server connections. server\n * itself is owned by the server_pool.\n *\n *  +-------------+\n *  |             |<---------------------+\n *  |             |<------------+        |\n *  |             |     +-------+--+-----+----+--------------+\n *  |   pool 0    |+--->|          |          |              |\n *  |             |     | server 0 | server 1 | ...     ...  |\n *  |             |     |          |          |              |--+\n *  |             |     +----------+----------+--------------+  |\n *  +-------------+                                             //\n *  |             |\n *  |             |\n *  |             |\n *  |   pool 1    |\n *  |             |\n *  |             |\n *  |             |\n *  +-------------+\n *  |             |\n *  |             |\n *  .             .\n *  .    ...      .\n *  .             .\n *  |             |\n *  |             |\n *  +-------------+\n *            |\n *            |\n *            //\n */\n\ntypedef uint32_t (*hash_t)(const char *, size_t);\n\nstruct continuum {\n    uint32_t index;  /* server index */\n    uint32_t value;  /* hash value */\n};\n\nstruct server {\n    uint32_t           idx;           /* server index */\n    struct server_pool *owner;        /* owner pool */\n\n    struct string      pname;         /* hostname:port:weight (ref in conf_server) */\n    struct string      name;          /* hostname:port or [name] (ref in conf_server) */\n    struct string      addrstr;       /* hostname (ref in conf_server) */\n    uint16_t           port;          /* port */\n    uint32_t           weight;        /* weight */\n    struct sockinfo    info;          /* server socket info */\n\n    uint32_t           ns_conn_q;     /* # server connection */\n    struct conn_tqh    s_conn_q;      /* server connection q */\n\n    int64_t            next_retry;    /* next retry time in usec */\n    uint32_t           failure_count; /* # consecutive failures */\n};\n\nstruct server_pool {\n    uint32_t           idx;                  /* pool index */\n    struct context     *ctx;                 /* owner context */\n\n    struct conn        *p_conn;              /* proxy connection (listener) */\n    uint32_t           nc_conn_q;            /* # client connection */\n    struct conn_tqh    c_conn_q;             /* client connection q */\n\n    struct array       server;               /* server[] */\n    uint32_t           ncontinuum;           /* # continuum points */\n    uint32_t           nserver_continuum;    /* # servers - live and dead on continuum (const) */\n    struct continuum   *continuum;           /* continuum */\n    uint32_t           nlive_server;         /* # live server */\n    int64_t            next_rebuild;         /* next distribution rebuild time in usec */\n\n    struct string      name;                 /* pool name (ref in conf_pool) */\n    struct string      addrstr;              /* pool address - hostname:port (ref in conf_pool) */\n    uint16_t           port;                 /* port */\n    struct sockinfo    info;                 /* listen socket info */\n    mode_t             perm;                 /* socket permission */\n    int                dist_type;            /* distribution type (dist_type_t) */\n    int                key_hash_type;        /* key hash type (hash_type_t) */\n    hash_t             key_hash;             /* key hasher */\n    struct string      hash_tag;             /* key hash tag (ref in conf_pool) */\n    int                timeout;              /* timeout in msec */\n    int                backlog;              /* listen backlog */\n    int                redis_db;             /* redis database to connect to */\n    uint32_t           client_connections;   /* maximum # client connection */\n    uint32_t           server_connections;   /* maximum # server connection */\n    int64_t            server_retry_timeout; /* server retry timeout in usec */\n    uint32_t           server_failure_limit; /* server failure limit */\n    struct string      redis_auth;           /* redis_auth password (matches requirepass on redis) */\n    unsigned           require_auth;         /* require_auth? */\n    unsigned           auto_eject_hosts:1;   /* auto_eject_hosts? */\n    unsigned           preconnect:1;         /* preconnect? */\n    unsigned           redis:1;              /* redis? */\n    unsigned           tcpkeepalive:1;       /* tcpkeepalive? */\n    unsigned           reuseport:1;          /* set SO_REUSEPORT to socket */\n};\n\nvoid server_ref(struct conn *conn, void *owner);\nvoid server_unref(struct conn *conn);\nint server_timeout(struct conn *conn);\nbool server_active(const struct conn *conn);\nrstatus_t server_init(struct array *server, struct array *conf_server, struct server_pool *sp);\nvoid server_deinit(struct array *server);\nstruct conn *server_conn(struct server *server);\nrstatus_t server_connect(struct context *ctx, struct server *server, struct conn *conn);\nvoid server_close(struct context *ctx, struct conn *conn);\nvoid server_connected(struct context *ctx, struct conn *conn);\nvoid server_ok(struct context *ctx, struct conn *conn);\n\nuint32_t server_pool_idx(const struct server_pool *pool, const uint8_t *key, uint32_t keylen);\nstruct conn *server_pool_conn(struct context *ctx, struct server_pool *pool, const uint8_t *key, uint32_t keylen);\nrstatus_t server_pool_run(struct server_pool *pool);\nrstatus_t server_pool_preconnect(struct context *ctx);\nvoid server_pool_disconnect(struct context *ctx);\nrstatus_t server_pool_init(struct array *server_pool, struct array *conf_pool, struct context *ctx);\nvoid server_pool_deinit(struct array *server_pool);\n\n#endif\n"
  },
  {
    "path": "src/nc_signal.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <signal.h>\n\n#include <nc_core.h>\n#include <nc_signal.h>\n\nstatic const struct signal signals[] = {\n    { SIGUSR1, \"SIGUSR1\", 0,                 signal_handler },\n    { SIGUSR2, \"SIGUSR2\", 0,                 signal_handler },\n    { SIGTTIN, \"SIGTTIN\", 0,                 signal_handler },\n    { SIGTTOU, \"SIGTTOU\", 0,                 signal_handler },\n    { SIGHUP,  \"SIGHUP\",  0,                 signal_handler },\n    { SIGINT,  \"SIGINT\",  0,                 signal_handler },\n    { SIGSEGV, \"SIGSEGV\", (int)SA_RESETHAND, signal_handler },\n    { SIGPIPE, \"SIGPIPE\", 0,                 SIG_IGN },\n    { 0,        NULL,     0,                 NULL }\n};\n\nrstatus_t\nsignal_init(void)\n{\n    const struct signal *sig;\n\n    for (sig = signals; sig->signo != 0; sig++) {\n        rstatus_t status;\n        struct sigaction sa;\n\n        memset(&sa, 0, sizeof(sa));\n        sa.sa_handler = sig->handler;\n        sa.sa_flags = sig->flags;\n        sigemptyset(&sa.sa_mask);\n\n        status = sigaction(sig->signo, &sa, NULL);\n        if (status < 0) {\n            log_error(\"sigaction(%s) failed: %s\", sig->signame,\n                      strerror(errno));\n            return NC_ERROR;\n        }\n    }\n\n    return NC_OK;\n}\n\nvoid\nsignal_deinit(void)\n{\n}\n\nvoid\nsignal_handler(int signo)\n{\n    const struct signal *sig;\n    void (*action)(void);\n    char *actionstr;\n    bool done;\n\n    for (sig = signals; sig->signo != 0; sig++) {\n        if (sig->signo == signo) {\n            break;\n        }\n    }\n    ASSERT(sig->signo != 0);\n\n    actionstr = \"\";\n    action = NULL;\n    done = false;\n\n    switch (signo) {\n    case SIGUSR1:\n        break;\n\n    case SIGUSR2:\n        break;\n\n    case SIGTTIN:\n        actionstr = \", up logging level\";\n        action = log_level_up;\n        break;\n\n    case SIGTTOU:\n        actionstr = \", down logging level\";\n        action = log_level_down;\n        break;\n\n    case SIGHUP:\n        actionstr = \", reopening log file\";\n        action = log_reopen;\n        break;\n\n    case SIGINT:\n        done = true;\n        actionstr = \", exiting\";\n        break;\n\n    case SIGSEGV:\n        log_stacktrace();\n        actionstr = \", core dumping\";\n        raise(SIGSEGV);\n        break;\n\n    default:\n        NOT_REACHED();\n    }\n\n    log_safe(\"signal %d (%s) received%s\", signo, sig->signame, actionstr);\n\n    if (action != NULL) {\n        action();\n    }\n\n    if (done) {\n        exit(1);\n    }\n}\n"
  },
  {
    "path": "src/nc_signal.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_SIGNAL_H_\n#define _NC_SIGNAL_H_\n\n#include <nc_core.h>\n\nstruct signal {\n    int  signo;\n    char *signame;\n    int  flags;\n    void (*handler)(int signo);\n};\n\nrstatus_t signal_init(void);\nvoid signal_deinit(void);\nvoid signal_handler(int signo);\n\n#endif\n"
  },
  {
    "path": "src/nc_stats.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n\n#include <nc_core.h>\n#include <nc_server.h>\n\nstruct stats_desc {\n    char *name; /* stats name */\n    char *desc; /* stats description */\n};\n\n#define DEFINE_ACTION(_name, _type, _desc) { .type = _type, .name = string(#_name) },\nstatic struct stats_metric stats_pool_codec[] = {\n    STATS_POOL_CODEC( DEFINE_ACTION )\n};\n\nstatic struct stats_metric stats_server_codec[] = {\n    STATS_SERVER_CODEC( DEFINE_ACTION )\n};\n#undef DEFINE_ACTION\n\n#define DEFINE_ACTION(_name, _type, _desc) { .name = #_name, .desc = _desc },\nstatic const struct stats_desc stats_pool_desc[] = {\n    STATS_POOL_CODEC( DEFINE_ACTION )\n};\n\nstatic const struct stats_desc stats_server_desc[] = {\n    STATS_SERVER_CODEC( DEFINE_ACTION )\n};\n#undef DEFINE_ACTION\n\nvoid\nstats_describe(void)\n{\n    uint32_t i;\n\n    log_stderr(\"pool stats:\");\n    for (i = 0; i < NELEMS(stats_pool_desc); i++) {\n        log_stderr(\"  %-20s\\\"%s\\\"\", stats_pool_desc[i].name,\n                   stats_pool_desc[i].desc);\n    }\n\n    log_stderr(\"\");\n\n    log_stderr(\"server stats:\");\n    for (i = 0; i < NELEMS(stats_server_desc); i++) {\n        log_stderr(\"  %-20s\\\"%s\\\"\", stats_server_desc[i].name,\n                   stats_server_desc[i].desc);\n    }\n}\n\nstatic void\nstats_metric_init(struct stats_metric *stm)\n{\n    switch (stm->type) {\n    case STATS_COUNTER:\n        stm->value.counter = 0LL;\n        break;\n\n    case STATS_GAUGE:\n        stm->value.counter = 0LL;\n        break;\n\n    case STATS_TIMESTAMP:\n        stm->value.timestamp = 0LL;\n        break;\n\n    default:\n        NOT_REACHED();\n    }\n}\n\nstatic void\nstats_metric_reset(struct array *stats_metric)\n{\n    uint32_t i, nmetric;\n\n    nmetric = array_n(stats_metric);\n    ASSERT(nmetric == STATS_POOL_NFIELD || nmetric == STATS_SERVER_NFIELD);\n\n    for (i = 0; i < nmetric; i++) {\n        struct stats_metric *stm = array_get(stats_metric, i);\n\n        stats_metric_init(stm);\n    }\n}\n\nstatic rstatus_t\nstats_pool_metric_init(struct array *stats_metric)\n{\n    rstatus_t status;\n    uint32_t i, nfield = STATS_POOL_NFIELD;\n\n    status = array_init(stats_metric, nfield, sizeof(struct stats_metric));\n    if (status != NC_OK) {\n        return status;\n    }\n\n    for (i = 0; i < nfield; i++) {\n        struct stats_metric *stm = array_push(stats_metric);\n\n        /* initialize from pool codec first */\n        *stm = stats_pool_codec[i];\n\n        /* initialize individual metric */\n        stats_metric_init(stm);\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_server_metric_init(struct stats_server *sts)\n{\n    rstatus_t status;\n    uint32_t i, nfield = STATS_SERVER_NFIELD;\n\n    status = array_init(&sts->metric, nfield, sizeof(struct stats_metric));\n    if (status != NC_OK) {\n        return status;\n    }\n\n    for (i = 0; i < nfield; i++) {\n        struct stats_metric *stm = array_push(&sts->metric);\n\n        /* initialize from server codec first */\n        *stm = stats_server_codec[i];\n\n        /* initialize individual metric */\n        stats_metric_init(stm);\n    }\n\n    return NC_OK;\n}\n\nstatic void\nstats_metric_deinit(struct array *metric)\n{\n    uint32_t i, nmetric;\n\n    nmetric = array_n(metric);\n    for (i = 0; i < nmetric; i++) {\n        array_pop(metric);\n    }\n    array_deinit(metric);\n}\n\nstatic rstatus_t\nstats_server_init(struct stats_server *sts, struct server *s)\n{\n    rstatus_t status;\n\n    sts->name = s->name;\n    array_null(&sts->metric);\n\n    status = stats_server_metric_init(sts);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    log_debug(LOG_VVVERB, \"init stats server '%.*s' with %\"PRIu32\" metric\",\n              sts->name.len, sts->name.data, array_n(&sts->metric));\n\n    return NC_OK;\n\n}\n\nstatic rstatus_t\nstats_server_map(struct array *stats_server, const struct array *server)\n{\n    rstatus_t status;\n    uint32_t i, nserver;\n\n    nserver = array_n(server);\n    ASSERT(nserver != 0);\n\n    status = array_init(stats_server, nserver, sizeof(struct stats_server));\n    if (status != NC_OK) {\n        return status;\n    }\n\n    for (i = 0; i < nserver; i++) {\n        struct server *s = array_get(server, i);\n        struct stats_server *sts = array_push(stats_server);\n\n        status = stats_server_init(sts, s);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    log_debug(LOG_VVVERB, \"map %\"PRIu32\" stats servers\", nserver);\n\n    return NC_OK;\n}\n\nstatic void\nstats_server_unmap(struct array *stats_server)\n{\n    uint32_t i, nserver;\n\n    nserver = array_n(stats_server);\n\n    for (i = 0; i < nserver; i++) {\n        struct stats_server *sts = array_pop(stats_server);\n        stats_metric_deinit(&sts->metric);\n    }\n    array_deinit(stats_server);\n\n    log_debug(LOG_VVVERB, \"unmap %\"PRIu32\" stats servers\", nserver);\n}\n\nstatic rstatus_t\nstats_pool_init(struct stats_pool *stp, const struct server_pool *sp)\n{\n    rstatus_t status;\n\n    stp->name = sp->name;\n    array_null(&stp->metric);\n    array_null(&stp->server);\n\n    status = stats_pool_metric_init(&stp->metric);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_server_map(&stp->server, &sp->server);\n    if (status != NC_OK) {\n        stats_metric_deinit(&stp->metric);\n        return status;\n    }\n\n    log_debug(LOG_VVVERB, \"init stats pool '%.*s' with %\"PRIu32\" metric and \"\n              \"%\"PRIu32\" server\", stp->name.len, stp->name.data,\n              array_n(&stp->metric), array_n(&stp->metric));\n\n    return NC_OK;\n}\n\nstatic void\nstats_pool_reset(struct array *stats_pool)\n{\n    uint32_t i, npool;\n\n    npool = array_n(stats_pool);\n\n    for (i = 0; i < npool; i++) {\n        struct stats_pool *stp = array_get(stats_pool, i);\n        uint32_t j, nserver;\n\n        stats_metric_reset(&stp->metric);\n\n        nserver = array_n(&stp->server);\n        for (j = 0; j < nserver; j++) {\n            struct stats_server *sts = array_get(&stp->server, j);\n            stats_metric_reset(&sts->metric);\n        }\n    }\n}\n\nstatic rstatus_t\nstats_pool_map(struct array *stats_pool, const struct array *server_pool)\n{\n    rstatus_t status;\n    uint32_t i, npool;\n\n    npool = array_n(server_pool);\n    ASSERT(npool != 0);\n\n    status = array_init(stats_pool, npool, sizeof(struct stats_pool));\n    if (status != NC_OK) {\n        return status;\n    }\n\n    for (i = 0; i < npool; i++) {\n        const struct server_pool *sp = array_get(server_pool, i);\n        struct stats_pool *stp = array_push(stats_pool);\n\n        status = stats_pool_init(stp, sp);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    log_debug(LOG_VVVERB, \"map %\"PRIu32\" stats pools\", npool);\n\n    return NC_OK;\n}\n\nstatic void\nstats_pool_unmap(struct array *stats_pool)\n{\n    uint32_t i, npool;\n\n    npool = array_n(stats_pool);\n\n    for (i = 0; i < npool; i++) {\n        struct stats_pool *stp = array_pop(stats_pool);\n        stats_metric_deinit(&stp->metric);\n        stats_server_unmap(&stp->server);\n    }\n    array_deinit(stats_pool);\n\n    log_debug(LOG_VVVERB, \"unmap %\"PRIu32\" stats pool\", npool);\n}\n\nstatic rstatus_t\nstats_create_buf(struct stats *st)\n{\n    uint32_t int64_max_digits = 20; /* INT64_MAX = 9223372036854775807 */\n    uint32_t key_value_extra = 8;   /* \"key\": \"value\", */\n    uint32_t pool_extra = 8;        /* '\"pool_name\": { ' + ' }' */\n    uint32_t server_extra = 8;      /* '\"server_name\": { ' + ' }' */\n    size_t size = 0;\n    uint32_t i;\n\n    ASSERT(st->buf.data == NULL && st->buf.size == 0);\n\n    /* header */\n    size += 1;\n\n    size += st->service_str.len;\n    size += st->service.len;\n    size += key_value_extra;\n\n    size += st->source_str.len;\n    size += st->source.len;\n    size += key_value_extra;\n\n    size += st->version_str.len;\n    size += st->version.len;\n    size += key_value_extra;\n\n    size += st->uptime_str.len;\n    size += int64_max_digits;\n    size += key_value_extra;\n\n    size += st->timestamp_str.len;\n    size += int64_max_digits;\n    size += key_value_extra;\n\n    size += st->ntotal_conn_str.len;\n    size += int64_max_digits;\n    size += key_value_extra;\n\n    size += st->ncurr_conn_str.len;\n    size += int64_max_digits;\n    size += key_value_extra;\n\n    /* server pools */\n    for (i = 0; i < array_n(&st->sum); i++) {\n        struct stats_pool *stp = array_get(&st->sum, i);\n        uint32_t j;\n\n        size += stp->name.len;\n        size += pool_extra;\n\n        for (j = 0; j < array_n(&stp->metric); j++) {\n            struct stats_metric *stm = array_get(&stp->metric, j);\n\n            size += stm->name.len;\n            size += int64_max_digits;\n            size += key_value_extra;\n        }\n\n        /* servers per pool */\n        for (j = 0; j < array_n(&stp->server); j++) {\n            struct stats_server *sts = array_get(&stp->server, j);\n            uint32_t k;\n\n            size += sts->name.len;\n            size += server_extra;\n\n            for (k = 0; k < array_n(&sts->metric); k++) {\n                struct stats_metric *stm = array_get(&sts->metric, k);\n\n                size += stm->name.len;\n                size += int64_max_digits;\n                size += key_value_extra;\n            }\n        }\n    }\n\n    /* footer */\n    size += 2;\n\n    size = NC_ALIGN(size, NC_ALIGNMENT);\n\n    st->buf.data = nc_alloc(size);\n    if (st->buf.data == NULL) {\n        log_error(\"create stats buffer of size %zu failed: %s\", size,\n                   strerror(errno));\n        return NC_ENOMEM;\n    }\n    st->buf.size = size;\n\n    log_debug(LOG_DEBUG, \"stats buffer size %zu\", size);\n\n    return NC_OK;\n}\n\nstatic void\nstats_destroy_buf(struct stats *st)\n{\n    if (st->buf.size != 0) {\n        ASSERT(st->buf.data != NULL);\n        nc_free(st->buf.data);\n        st->buf.size = 0;\n    }\n}\n\nstatic rstatus_t\nstats_add_string(struct stats *st, const struct string *key, const struct string *val)\n{\n    struct stats_buffer *buf;\n    uint8_t *pos;\n    size_t room;\n    int n;\n\n    buf = &st->buf;\n    pos = buf->data + buf->len;\n    room = buf->size - buf->len - 1;\n\n    n = nc_snprintf(pos, room, \"\\\"%.*s\\\":\\\"%.*s\\\", \", key->len, key->data,\n                    val->len, val->data);\n    if (n < 0 || n >= (int)room) {\n        return NC_ERROR;\n    }\n\n    buf->len += (size_t)n;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_add_num(struct stats *st, const struct string *key, int64_t val)\n{\n    struct stats_buffer *buf;\n    uint8_t *pos;\n    size_t room;\n    int n;\n\n    buf = &st->buf;\n    pos = buf->data + buf->len;\n    room = buf->size - buf->len - 1;\n\n    n = nc_snprintf(pos, room, \"\\\"%.*s\\\":%\"PRId64\", \", key->len, key->data,\n                    val);\n    if (n < 0 || n >= (int)room) {\n        return NC_ERROR;\n    }\n\n    buf->len += (size_t)n;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_add_header(struct stats *st)\n{\n    rstatus_t status;\n    struct stats_buffer *buf;\n    int64_t cur_ts, uptime;\n\n    buf = &st->buf;\n    buf->data[0] = '{';\n    buf->len = 1;\n\n    cur_ts = (int64_t)time(NULL);\n    uptime = cur_ts - st->start_ts;\n\n    status = stats_add_string(st, &st->service_str, &st->service);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_add_string(st, &st->source_str, &st->source);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_add_string(st, &st->version_str, &st->version);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_add_num(st, &st->uptime_str, uptime);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_add_num(st, &st->timestamp_str, cur_ts);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_add_num(st, &st->ntotal_conn_str, conn_ntotal_conn());\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = stats_add_num(st, &st->ncurr_conn_str, conn_ncurr_conn());\n    if (status != NC_OK) {\n        return status;\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_add_footer(struct stats *st)\n{\n    struct stats_buffer *buf;\n    uint8_t *pos;\n\n    buf = &st->buf;\n\n    if (buf->len == buf->size) {\n        return NC_ERROR;\n    }\n\n    /* overwrite the last byte and add a new byte */\n    pos = buf->data + buf->len - 1;\n    pos[0] = '}';\n    pos[1] = '\\n';\n    buf->len += 1;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_begin_nesting(struct stats *st, const struct string *key)\n{\n    struct stats_buffer *buf;\n    uint8_t *pos;\n    size_t room;\n    int n;\n\n    buf = &st->buf;\n    pos = buf->data + buf->len;\n    room = buf->size - buf->len - 1;\n\n    n = nc_snprintf(pos, room, \"\\\"%.*s\\\": {\", key->len, key->data);\n    if (n < 0 || n >= (int)room) {\n        return NC_ERROR;\n    }\n\n    buf->len += (size_t)n;\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_end_nesting(struct stats *st)\n{\n    struct stats_buffer *buf;\n    uint8_t *pos;\n\n    buf = &st->buf;\n    pos = buf->data + buf->len;\n\n    pos -= 2; /* go back by 2 bytes */\n\n    switch (pos[0]) {\n    case ',':\n        /* overwrite last two bytes; len remains unchanged */\n        ASSERT(pos[1] == ' ');\n        pos[0] = '}';\n        pos[1] = ',';\n        break;\n\n    case '}':\n        if (buf->len == buf->size) {\n            return NC_ERROR;\n        }\n        /* overwrite the last byte and add a new byte */\n        ASSERT(pos[1] == ',');\n        pos[1] = '}';\n        pos[2] = ',';\n        buf->len += 1;\n        break;\n\n    default:\n        NOT_REACHED();\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_copy_metric(struct stats *st, struct array *metric)\n{\n    rstatus_t status;\n    uint32_t i;\n\n    for (i = 0; i < array_n(metric); i++) {\n        struct stats_metric *stm = array_get(metric, i);\n\n        status = stats_add_num(st, &stm->name, stm->value.counter);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    return NC_OK;\n}\n\nstatic void\nstats_aggregate_metric(struct array *dst, const struct array *src)\n{\n    uint32_t i;\n\n    for (i = 0; i < array_n(src); i++) {\n        const struct stats_metric *stm1;\n        struct stats_metric *stm2;\n\n        stm1 = array_get(src, i);\n        stm2 = array_get(dst, i);\n\n        ASSERT(stm1->type == stm2->type);\n\n        switch (stm1->type) {\n        case STATS_COUNTER:\n            stm2->value.counter += stm1->value.counter;\n            break;\n\n        case STATS_GAUGE:\n            stm2->value.counter += stm1->value.counter;\n            break;\n\n        case STATS_TIMESTAMP:\n            if (stm1->value.timestamp) {\n                stm2->value.timestamp = stm1->value.timestamp;\n            }\n            break;\n\n        default:\n            NOT_REACHED();\n        }\n    }\n}\n\nstatic void\nstats_aggregate(struct stats *st)\n{\n    uint32_t i;\n\n    if (st->aggregate == 0) {\n        log_debug(LOG_PVERB, \"skip aggregate of shadow %p to sum %p as \"\n                  \"generator is slow\", st->shadow.elem, st->sum.elem);\n        return;\n    }\n\n    log_debug(LOG_PVERB, \"aggregate stats shadow %p to sum %p\", st->shadow.elem,\n              st->sum.elem);\n\n    for (i = 0; i < array_n(&st->shadow); i++) {\n        struct stats_pool *stp1, *stp2;\n        uint32_t j;\n\n        stp1 = array_get(&st->shadow, i);\n        stp2 = array_get(&st->sum, i);\n        stats_aggregate_metric(&stp2->metric, &stp1->metric);\n\n        for (j = 0; j < array_n(&stp1->server); j++) {\n            struct stats_server *sts1, *sts2;\n\n            sts1 = array_get(&stp1->server, j);\n            sts2 = array_get(&stp2->server, j);\n            stats_aggregate_metric(&sts2->metric, &sts1->metric);\n        }\n    }\n\n    st->aggregate = 0;\n}\n\nstatic rstatus_t\nstats_make_rsp(struct stats *st)\n{\n    rstatus_t status;\n    uint32_t i;\n\n    status = stats_add_header(st);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    for (i = 0; i < array_n(&st->sum); i++) {\n        struct stats_pool *stp = array_get(&st->sum, i);\n        uint32_t j;\n\n        status = stats_begin_nesting(st, &stp->name);\n        if (status != NC_OK) {\n            return status;\n        }\n\n        /* copy pool metric from sum(c) to buffer */\n        status = stats_copy_metric(st, &stp->metric);\n        if (status != NC_OK) {\n            return status;\n        }\n\n        for (j = 0; j < array_n(&stp->server); j++) {\n            struct stats_server *sts = array_get(&stp->server, j);\n\n            status = stats_begin_nesting(st, &sts->name);\n            if (status != NC_OK) {\n                return status;\n            }\n\n            /* copy server metric from sum(c) to buffer */\n            status = stats_copy_metric(st, &sts->metric);\n            if (status != NC_OK) {\n                return status;\n            }\n\n            status = stats_end_nesting(st);\n            if (status != NC_OK) {\n                return status;\n            }\n        }\n\n        status = stats_end_nesting(st);\n        if (status != NC_OK) {\n            return status;\n        }\n    }\n\n    status = stats_add_footer(st);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_send_rsp(struct stats *st)\n{\n    rstatus_t status;\n    ssize_t n;\n    int sd;\n\n    status = stats_make_rsp(st);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    sd = accept(st->sd, NULL, NULL);\n    if (sd < 0) {\n        log_error(\"accept on m %d failed: %s\", st->sd, strerror(errno));\n        return NC_ERROR;\n    }\n\n    log_debug(LOG_VERB, \"send stats on sd %d %zu bytes\", sd, st->buf.len);\n\n    n = nc_sendn(sd, st->buf.data, st->buf.len);\n    if (n < 0) {\n        log_error(\"send stats on sd %d failed: %s\", sd, strerror(errno));\n        close(sd);\n        return NC_ERROR;\n    }\n\n    close(sd);\n\n    return NC_OK;\n}\n\nstatic void\nstats_loop_callback(void *arg1, void *arg2)\n{\n    struct stats *st = arg1;\n    int n = *((int *)arg2);\n\n    /* aggregate stats from shadow (b) -> sum (c) */\n    stats_aggregate(st);\n\n    if (n == 0) {\n        return;\n    }\n\n    /* send aggregate stats sum (c) to collector */\n    stats_send_rsp(st);\n}\n\nstatic void *\nstats_loop(void *arg)\n{\n    event_loop_stats(stats_loop_callback, arg);\n    return NULL;\n}\n\nstatic rstatus_t\nstats_listen(struct stats *st)\n{\n    rstatus_t status;\n    struct sockinfo si;\n\n    status = nc_resolve(&st->addr, st->port, &si);\n    if (status < 0) {\n        return status;\n    }\n\n    st->sd = socket(si.family, SOCK_STREAM, 0);\n    if (st->sd < 0) {\n        log_error(\"socket failed: %s\", strerror(errno));\n        return NC_ERROR;\n    }\n    nc_set_reuseport(st->sd);\n    status = nc_set_reuseaddr(st->sd);\n    if (status < 0) {\n        log_error(\"set reuseaddr on m %d failed for stats server: %s\", st->sd, strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = bind(st->sd, (struct sockaddr *)&si.addr, si.addrlen);\n    if (status < 0) {\n        log_error(\"bind on m %d to stats server addr '%.*s:%u' failed: %s\", st->sd,\n                  st->addr.len, st->addr.data, st->port, strerror(errno));\n        return NC_ERROR;\n    }\n\n    status = listen(st->sd, SOMAXCONN);\n    if (status < 0) {\n        log_error(\"listen on m %d for stats server '%.*s:%u' failed: %s\", st->sd,\n                  st->addr.len, st->addr.data, st->port, strerror(errno));\n        return NC_ERROR;\n    }\n\n    log_debug(LOG_NOTICE, \"m %d listening on stats server '%.*s:%u'\", st->sd,\n              st->addr.len, st->addr.data, st->port);\n\n    return NC_OK;\n}\n\nstatic rstatus_t\nstats_start_aggregator(struct stats *st)\n{\n    rstatus_t status;\n\n    if (!stats_enabled) {\n        return NC_OK;\n    }\n\n    status = stats_listen(st);\n    if (status != NC_OK) {\n        return status;\n    }\n\n    status = pthread_create(&st->tid, NULL, stats_loop, st);\n    if (status < 0) {\n        log_error(\"stats aggregator create failed: %s\", strerror(status));\n        return NC_ERROR;\n    }\n\n    return NC_OK;\n}\n\nstatic void\nstats_stop_aggregator(struct stats *st)\n{\n    if (!stats_enabled) {\n        return;\n    }\n\n    close(st->sd);\n}\n\nstruct stats *\nstats_create(uint16_t stats_port, const char *stats_ip, int stats_interval,\n             const char *source, const struct array *server_pool)\n{\n    rstatus_t status;\n    struct stats *st;\n\n    st = nc_alloc(sizeof(*st));\n    if (st == NULL) {\n        return NULL;\n    }\n\n    st->port = stats_port;\n    st->interval = stats_interval;\n    string_set_raw(&st->addr, stats_ip);\n\n    st->start_ts = (int64_t)time(NULL);\n\n    st->buf.len = 0;\n    st->buf.data = NULL;\n    st->buf.size = 0;\n\n    array_null(&st->current);\n    array_null(&st->shadow);\n    array_null(&st->sum);\n\n    st->tid = (pthread_t) -1;\n    st->sd = -1;\n\n    string_set_text(&st->service_str, \"service\");\n    string_set_text(&st->service, \"nutcracker\");\n\n    string_set_text(&st->source_str, \"source\");\n    string_set_raw(&st->source, source);\n\n    string_set_text(&st->version_str, \"version\");\n    string_set_text(&st->version, NC_VERSION_STRING);\n\n    string_set_text(&st->uptime_str, \"uptime\");\n    string_set_text(&st->timestamp_str, \"timestamp\");\n\n    string_set_text(&st->ntotal_conn_str, \"total_connections\");\n    string_set_text(&st->ncurr_conn_str, \"curr_connections\");\n\n    st->updated = 0;\n    st->aggregate = 0;\n\n    /* map server pool to current (a), shadow (b) and sum (c) */\n\n    status = stats_pool_map(&st->current, server_pool);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    status = stats_pool_map(&st->shadow, server_pool);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    status = stats_pool_map(&st->sum, server_pool);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    status = stats_create_buf(st);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    status = stats_start_aggregator(st);\n    if (status != NC_OK) {\n        goto error;\n    }\n\n    return st;\n\nerror:\n    stats_destroy(st);\n    return NULL;\n}\n\nvoid\nstats_destroy(struct stats *st)\n{\n    stats_stop_aggregator(st);\n    stats_pool_unmap(&st->sum);\n    stats_pool_unmap(&st->shadow);\n    stats_pool_unmap(&st->current);\n    stats_destroy_buf(st);\n    nc_free(st);\n}\n\nvoid\nstats_swap(struct stats *st)\n{\n    if (!stats_enabled) {\n        return;\n    }\n\n    if (st->aggregate == 1) {\n        log_debug(LOG_PVERB, \"skip swap of current %p shadow %p as aggregator \"\n                  \"is busy\", st->current.elem, st->shadow.elem);\n        return;\n    }\n\n    if (st->updated == 0) {\n        log_debug(LOG_PVERB, \"skip swap of current %p shadow %p as there is \"\n                  \"nothing new\", st->current.elem, st->shadow.elem);\n        return;\n    }\n\n    log_debug(LOG_PVERB, \"swap stats current %p shadow %p\", st->current.elem,\n              st->shadow.elem);\n\n    array_swap(&st->current, &st->shadow);\n\n    /*\n     * Reset current (a) stats before giving it back to generator to keep\n     * stats addition idempotent\n     */\n    stats_pool_reset(&st->current);\n    st->updated = 0;\n\n    st->aggregate = 1;\n}\n\nstatic struct stats_metric *\nstats_pool_to_metric(struct context *ctx, const struct server_pool *pool,\n                     stats_pool_field_t fidx)\n{\n    struct stats *st;\n    struct stats_pool *stp;\n    struct stats_metric *stm;\n    uint32_t pidx;\n\n    pidx = pool->idx;\n\n    st = ctx->stats;\n    stp = array_get(&st->current, pidx);\n    stm = array_get(&stp->metric, fidx);\n\n    st->updated = 1;\n\n    log_debug(LOG_VVVERB, \"metric '%.*s' in pool %\"PRIu32\"\", stm->name.len,\n              stm->name.data, pidx);\n\n    return stm;\n}\n\nvoid\n_stats_pool_incr(struct context *ctx, const struct server_pool *pool,\n                 stats_pool_field_t fidx)\n{\n    struct stats_metric *stm;\n\n    stm = stats_pool_to_metric(ctx, pool, fidx);\n\n    ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE);\n    stm->value.counter++;\n\n    log_debug(LOG_VVVERB, \"incr field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_pool_decr(struct context *ctx, const struct server_pool *pool,\n                 stats_pool_field_t fidx)\n{\n    struct stats_metric *stm;\n\n    stm = stats_pool_to_metric(ctx, pool, fidx);\n\n    ASSERT(stm->type == STATS_GAUGE);\n    stm->value.counter--;\n\n    log_debug(LOG_VVVERB, \"decr field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_pool_incr_by(struct context *ctx, const struct server_pool *pool,\n                    stats_pool_field_t fidx, int64_t val)\n{\n    struct stats_metric *stm;\n\n    stm = stats_pool_to_metric(ctx, pool, fidx);\n\n    ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE);\n    stm->value.counter += val;\n\n    log_debug(LOG_VVVERB, \"incr by field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_pool_decr_by(struct context *ctx, const struct server_pool *pool,\n                    stats_pool_field_t fidx, int64_t val)\n{\n    struct stats_metric *stm;\n\n    stm = stats_pool_to_metric(ctx, pool, fidx);\n\n    ASSERT(stm->type == STATS_GAUGE);\n    stm->value.counter -= val;\n\n    log_debug(LOG_VVVERB, \"decr by field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_pool_set_ts(struct context *ctx, const struct server_pool *pool,\n                   stats_pool_field_t fidx, int64_t val)\n{\n    struct stats_metric *stm;\n\n    stm = stats_pool_to_metric(ctx, pool, fidx);\n\n    ASSERT(stm->type == STATS_TIMESTAMP);\n    stm->value.timestamp = val;\n\n    log_debug(LOG_VVVERB, \"set ts field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.timestamp);\n}\n\nstatic struct stats_metric *\nstats_server_to_metric(struct context *ctx, const struct server *server,\n                       stats_server_field_t fidx)\n{\n    struct stats *st;\n    struct stats_pool *stp;\n    struct stats_server *sts;\n    struct stats_metric *stm;\n    uint32_t pidx, sidx;\n\n    sidx = server->idx;\n    pidx = server->owner->idx;\n\n    st = ctx->stats;\n    stp = array_get(&st->current, pidx);\n    sts = array_get(&stp->server, sidx);\n    stm = array_get(&sts->metric, fidx);\n\n    st->updated = 1;\n\n    log_debug(LOG_VVVERB, \"metric '%.*s' in pool %\"PRIu32\" server %\"PRIu32\"\",\n              stm->name.len, stm->name.data, pidx, sidx);\n\n    return stm;\n}\n\nvoid\n_stats_server_incr(struct context *ctx, const struct server *server,\n                   stats_server_field_t fidx)\n{\n    struct stats_metric *stm;\n\n    stm = stats_server_to_metric(ctx, server, fidx);\n\n    ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE);\n    stm->value.counter++;\n\n    log_debug(LOG_VVVERB, \"incr field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_server_decr(struct context *ctx, const struct server *server,\n                   stats_server_field_t fidx)\n{\n    struct stats_metric *stm;\n\n    stm = stats_server_to_metric(ctx, server, fidx);\n\n    ASSERT(stm->type == STATS_GAUGE);\n    stm->value.counter--;\n\n    log_debug(LOG_VVVERB, \"decr field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_server_incr_by(struct context *ctx, const struct server *server,\n                      stats_server_field_t fidx, int64_t val)\n{\n    struct stats_metric *stm;\n\n    stm = stats_server_to_metric(ctx, server, fidx);\n\n    ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE);\n    stm->value.counter += val;\n\n    log_debug(LOG_VVVERB, \"incr by field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_server_decr_by(struct context *ctx, const struct server *server,\n                      stats_server_field_t fidx, int64_t val)\n{\n    struct stats_metric *stm;\n\n    stm = stats_server_to_metric(ctx, server, fidx);\n\n    ASSERT(stm->type == STATS_GAUGE);\n    stm->value.counter -= val;\n\n    log_debug(LOG_VVVERB, \"decr by field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.counter);\n}\n\nvoid\n_stats_server_set_ts(struct context *ctx, const struct server *server,\n                     stats_server_field_t fidx, int64_t val)\n{\n    struct stats_metric *stm;\n\n    stm = stats_server_to_metric(ctx, server, fidx);\n\n    ASSERT(stm->type == STATS_TIMESTAMP);\n    stm->value.timestamp = val;\n\n    log_debug(LOG_VVVERB, \"set ts field '%.*s' to %\"PRId64\"\", stm->name.len,\n              stm->name.data, stm->value.timestamp);\n}\n"
  },
  {
    "path": "src/nc_stats.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_STATS_H_\n#define _NC_STATS_H_\n\n#include <nc_core.h>\n\n#define STATS_POOL_CODEC(ACTION)                                                                                    \\\n    /* client behavior */                                                                                           \\\n    ACTION( client_eof,             STATS_COUNTER,      \"# eof on client connections\")                              \\\n    ACTION( client_err,             STATS_COUNTER,      \"# errors on client connections\")                           \\\n    ACTION( client_connections,     STATS_GAUGE,        \"# active client connections\")                              \\\n    /* pool behavior */                                                                                             \\\n    ACTION( server_ejects,          STATS_COUNTER,      \"# times backend server was ejected\")                       \\\n    /* forwarder behavior */                                                                                        \\\n    ACTION( forward_error,          STATS_COUNTER,      \"# times we encountered a forwarding error\")                \\\n    ACTION( fragments,              STATS_COUNTER,      \"# fragments created from a multi-vector request\")          \\\n\n#define STATS_SERVER_CODEC(ACTION)                                                                                  \\\n    /* server behavior */                                                                                           \\\n    ACTION( server_eof,             STATS_COUNTER,      \"# eof on server connections\")                              \\\n    ACTION( server_err,             STATS_COUNTER,      \"# errors on server connections\")                           \\\n    ACTION( server_timedout,        STATS_COUNTER,      \"# timeouts on server connections\")                         \\\n    ACTION( server_connections,     STATS_GAUGE,        \"# active server connections\")                              \\\n    ACTION( server_ejected_at,      STATS_TIMESTAMP,    \"timestamp when server was ejected in usec since epoch\")    \\\n    /* data behavior */                                                                                             \\\n    ACTION( requests,               STATS_COUNTER,      \"# requests\")                                               \\\n    ACTION( request_bytes,          STATS_COUNTER,      \"total request bytes\")                                      \\\n    ACTION( responses,              STATS_COUNTER,      \"# responses\")                                              \\\n    ACTION( response_bytes,         STATS_COUNTER,      \"total response bytes\")                                     \\\n    ACTION( in_queue,               STATS_GAUGE,        \"# requests in incoming queue\")                             \\\n    ACTION( in_queue_bytes,         STATS_GAUGE,        \"current request bytes in incoming queue\")                  \\\n    ACTION( out_queue,              STATS_GAUGE,        \"# requests in outgoing queue\")                             \\\n    ACTION( out_queue_bytes,        STATS_GAUGE,        \"current request bytes in outgoing queue\")                  \\\n\n#define STATS_ADDR      \"0.0.0.0\"\n#define STATS_PORT      22222\n#define STATS_INTERVAL  (30 * 1000) /* in msec */\n\ntypedef enum stats_type {\n    STATS_INVALID,\n    STATS_COUNTER,    /* monotonic accumulator */\n    STATS_GAUGE,      /* non-monotonic accumulator */\n    STATS_TIMESTAMP,  /* monotonic timestamp (in nsec) */\n    STATS_SENTINEL\n} stats_type_t;\n\nstruct stats_metric {\n    stats_type_t  type;         /* type */\n    struct string name;         /* name (ref) */\n    union {\n        int64_t   counter;      /* accumulating counter */\n        int64_t   timestamp;    /* monotonic timestamp */\n    } value;\n};\n\nstruct stats_server {\n    struct string name;   /* server name (ref) */\n    struct array  metric; /* stats_metric[] for server codec */\n};\n\nstruct stats_pool {\n    struct string name;   /* pool name (ref) */\n    struct array  metric; /* stats_metric[] for pool codec */\n    struct array  server; /* stats_server[] */\n};\n\nstruct stats_buffer {\n    size_t   len;   /* buffer length */\n    uint8_t  *data; /* buffer data */\n    size_t   size;  /* buffer alloc size */\n};\n\nstruct stats {\n    uint16_t            port;            /* stats monitoring port */\n    int                 interval;        /* stats aggregation interval */\n    struct string       addr;            /* stats monitoring address */\n\n    int64_t             start_ts;        /* start timestamp of nutcracker */\n    struct stats_buffer buf;             /* output buffer */\n\n    struct array        current;         /* stats_pool[] (a) */\n    struct array        shadow;          /* stats_pool[] (b) */\n    struct array        sum;             /* stats_pool[] (c = a + b) */\n\n    pthread_t           tid;             /* stats aggregator thread */\n    int                 sd;              /* stats descriptor */\n\n    struct string       service_str;     /* service string */\n    struct string       service;         /* service */\n    struct string       source_str;      /* source string */\n    struct string       source;          /* source */\n    struct string       version_str;     /* version string */\n    struct string       version;         /* version */\n    struct string       uptime_str;      /* uptime string */\n    struct string       timestamp_str;   /* timestamp string */\n    struct string       ntotal_conn_str; /* total connections string */\n    struct string       ncurr_conn_str;  /* curr connections string */\n\n    volatile int        aggregate;       /* shadow (b) aggregate? */\n    volatile int        updated;         /* current (a) updated? */\n};\n\n#define DEFINE_ACTION(_name, _type, _desc) STATS_POOL_##_name,\ntypedef enum stats_pool_field {\n    STATS_POOL_CODEC(DEFINE_ACTION)\n    STATS_POOL_NFIELD\n} stats_pool_field_t;\n#undef DEFINE_ACTION\n\n#define DEFINE_ACTION(_name, _type, _desc) STATS_SERVER_##_name,\ntypedef enum stats_server_field {\n    STATS_SERVER_CODEC(DEFINE_ACTION)\n    STATS_SERVER_NFIELD\n} stats_server_field_t;\n#undef DEFINE_ACTION\n\n#if defined NC_STATS && NC_STATS == 1\n\n#define stats_pool_incr(_ctx, _pool, _name) do {                        \\\n    _stats_pool_incr(_ctx, _pool, STATS_POOL_##_name);                  \\\n} while (0)\n\n#define stats_pool_decr(_ctx, _pool, _name) do {                        \\\n    _stats_pool_decr(_ctx, _pool, STATS_POOL_##_name);                  \\\n} while (0)\n\n#define stats_pool_incr_by(_ctx, _pool, _name, _val) do {               \\\n    _stats_pool_incr_by(_ctx, _pool, STATS_POOL_##_name, _val);         \\\n} while (0)\n\n#define stats_pool_decr_by(_ctx, _pool, _name, _val) do {               \\\n    _stats_pool_decr_by(_ctx, _pool, STATS_POOL_##_name, _val);         \\\n} while (0)\n\n#define stats_pool_set_ts(_ctx, _pool, _name, _val) do {                \\\n    _stats_pool_set_ts(_ctx, _pool, STATS_POOL_##_name, _val);          \\\n} while (0)\n\n#define stats_server_incr(_ctx, _server, _name) do {                    \\\n    _stats_server_incr(_ctx, _server, STATS_SERVER_##_name);            \\\n} while (0)\n\n#define stats_server_decr(_ctx, _server, _name) do {                    \\\n    _stats_server_decr(_ctx, _server, STATS_SERVER_##_name);            \\\n} while (0)\n\n#define stats_server_incr_by(_ctx, _server, _name, _val) do {           \\\n    _stats_server_incr_by(_ctx, _server, STATS_SERVER_##_name, _val);   \\\n} while (0)\n\n#define stats_server_decr_by(_ctx, _server, _name, _val) do {           \\\n    _stats_server_decr_by(_ctx, _server, STATS_SERVER_##_name, _val);   \\\n} while (0)\n\n#define stats_server_set_ts(_ctx, _server, _name, _val) do {            \\\n     _stats_server_set_ts(_ctx, _server, STATS_SERVER_##_name, _val);   \\\n} while (0)\n\n#else\n\n#define stats_pool_incr(_ctx, _pool, _name)\n\n#define stats_pool_decr(_ctx, _pool, _name)\n\n#define stats_pool_incr_by(_ctx, _pool, _name, _val)\n\n#define stats_pool_decr_by(_ctx, _pool, _name, _val)\n\n#define stats_server_incr(_ctx, _server, _name)\n\n#define stats_server_decr(_ctx, _server, _name)\n\n#define stats_server_incr_by(_ctx, _server, _name, _val)\n\n#define stats_server_decr_by(_ctx, _server, _name, _val)\n\n#endif\n\n#define stats_enabled   NC_STATS\n\nvoid stats_describe(void);\n\nvoid _stats_pool_incr(struct context *ctx, const struct server_pool *pool, stats_pool_field_t fidx);\nvoid _stats_pool_decr(struct context *ctx, const struct server_pool *pool, stats_pool_field_t fidx);\nvoid _stats_pool_incr_by(struct context *ctx, const struct server_pool *pool, stats_pool_field_t fidx, int64_t val);\nvoid _stats_pool_decr_by(struct context *ctx, const struct server_pool *pool, stats_pool_field_t fidx, int64_t val);\nvoid _stats_pool_set_ts(struct context *ctx, const struct server_pool *pool, stats_pool_field_t fidx, int64_t val);\n\nvoid _stats_server_incr(struct context *ctx, const struct server *server, stats_server_field_t fidx);\nvoid _stats_server_decr(struct context *ctx, const struct server *server, stats_server_field_t fidx);\nvoid _stats_server_incr_by(struct context *ctx, const struct server *server, stats_server_field_t fidx, int64_t val);\nvoid _stats_server_decr_by(struct context *ctx, const struct server *server, stats_server_field_t fidx, int64_t val);\nvoid _stats_server_set_ts(struct context *ctx, const struct server *server, stats_server_field_t fidx, int64_t val);\n\nstruct stats *stats_create(uint16_t stats_port, const char *stats_ip, int stats_interval, const char *source, const struct array *server_pool);\nvoid stats_destroy(struct stats *stats);\nvoid stats_swap(struct stats *stats);\n\n#endif\n"
  },
  {
    "path": "src/nc_string.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include <nc_core.h>\n\n/*\n * String (struct string) is a sequence of unsigned char objects terminated\n * by the null character '\\0'. The length of the string is pre-computed and\n * made available explicitly as an additional field. This means that we don't\n * have to walk the entire character sequence until the null terminating\n * character everytime that the length of the String is requested\n *\n * The only way to create a String is to initialize it using, string_init()\n * and duplicate an existing String - string_duplicate() or copy an existing\n * raw sequence of character bytes - string_copy(). Such String's must be\n * freed using string_deinit()\n *\n * We can also create String as reference to raw string - string_set_raw()\n * or to text string - string_set_text() or string(). Such String don't have\n * to be freed.\n */\n\nvoid\nstring_init(struct string *str)\n{\n    str->len = 0;\n    str->data = NULL;\n}\n\nvoid\nstring_deinit(struct string *str)\n{\n    ASSERT((str->len == 0 && str->data == NULL) ||\n           (str->len != 0 && str->data != NULL));\n\n    if (str->data != NULL) {\n        nc_free(str->data);\n        string_init(str);\n    }\n}\n\nbool\nstring_empty(const struct string *str)\n{\n    ASSERT((str->len == 0 && str->data == NULL) ||\n           (str->len != 0 && str->data != NULL));\n    return str->len == 0;\n}\n\nrstatus_t\nstring_duplicate(struct string *dst, const struct string *src)\n{\n    ASSERT(dst->len == 0 && dst->data == NULL);\n    ASSERT(src->len != 0 && src->data != NULL);\n\n    dst->data = nc_strndup(src->data, src->len + 1);\n    if (dst->data == NULL) {\n        return NC_ENOMEM;\n    }\n\n    dst->len = src->len;\n    dst->data[dst->len] = '\\0';\n\n    return NC_OK;\n}\n\nrstatus_t\nstring_copy(struct string *dst, const uint8_t *src, uint32_t srclen)\n{\n    ASSERT(dst->len == 0 && dst->data == NULL);\n    ASSERT(src != NULL && srclen != 0);\n\n    dst->data = nc_strndup(src, srclen + 1);\n    if (dst->data == NULL) {\n        return NC_ENOMEM;\n    }\n\n    dst->len = srclen;\n    dst->data[dst->len] = '\\0';\n\n    return NC_OK;\n}\n\nint\nstring_compare(const struct string *s1, const struct string *s2)\n{\n    if (s1->len != s2->len) {\n        return s1->len > s2->len ? 1 : -1;\n    }\n\n    return nc_strncmp(s1->data, s2->data, s1->len);\n}\n\nstatic const char *const hex = \"0123456789abcdef\";\n\nstatic char *\n_safe_utoa(int _base, uint64_t val, char *buf)\n{\n    uint32_t base = (uint32_t) _base;\n    *buf-- = 0;\n    do {\n        *buf-- = hex[val % base];\n    } while ((val /= base) != 0);\n    return buf + 1;\n}\n\nstatic char *\n_safe_itoa(int base, int64_t val, char *buf)\n{\n    char *orig_buf = buf;\n    const int32_t is_neg = (val < 0);\n    *buf-- = 0;\n\n    if (is_neg) {\n        val = -val;\n    }\n    if (is_neg && base == 16) {\n        int ix;\n        val -= 1;\n        for (ix = 0; ix < 16; ++ix)\n            buf[-ix] = '0';\n    }\n\n    do {\n        *buf-- = hex[val % base];\n    } while ((val /= base) != 0);\n\n    if (is_neg && base == 10) {\n        *buf-- = '-';\n    }\n\n    if (is_neg && base == 16) {\n        int ix;\n        buf = orig_buf - 1;\n        for (ix = 0; ix < 16; ++ix, --buf) {\n            /* *INDENT-OFF* */\n            switch (*buf) {\n            case '0': *buf = 'f'; break;\n            case '1': *buf = 'e'; break;\n            case '2': *buf = 'd'; break;\n            case '3': *buf = 'c'; break;\n            case '4': *buf = 'b'; break;\n            case '5': *buf = 'a'; break;\n            case '6': *buf = '9'; break;\n            case '7': *buf = '8'; break;\n            case '8': *buf = '7'; break;\n            case '9': *buf = '6'; break;\n            case 'a': *buf = '5'; break;\n            case 'b': *buf = '4'; break;\n            case 'c': *buf = '3'; break;\n            case 'd': *buf = '2'; break;\n            case 'e': *buf = '1'; break;\n            case 'f': *buf = '0'; break;\n            }\n            /* *INDENT-ON* */\n        }\n    }\n    return buf + 1;\n}\n\nstatic const char *\n_safe_check_longlong(const char *fmt, int32_t * have_longlong)\n{\n    *have_longlong = false;\n    if (*fmt == 'l') {\n        fmt++;\n        if (*fmt != 'l') {\n            *have_longlong = (sizeof(long) == sizeof(int64_t));\n        } else {\n            fmt++;\n            *have_longlong = true;\n        }\n    }\n    return fmt;\n}\n\nint\n_safe_vsnprintf(char *to, size_t size, const char *format, va_list ap)\n{\n    char *start = to;\n    char *end = start + size - 1;\n    for (; *format; ++format) {\n        int32_t have_longlong = false;\n        if (*format != '%') {\n            if (to == end) {    /* end of buffer */\n                break;\n            }\n            *to++ = *format;    /* copy ordinary char */\n            continue;\n        }\n        ++format;               /* skip '%' */\n\n        format = _safe_check_longlong(format, &have_longlong);\n\n        switch (*format) {\n        case 'd':\n        case 'i':\n        case 'u':\n        case 'x':\n        case 'p':\n            {\n                int64_t ival = 0;\n                uint64_t uval = 0;\n                if (*format == 'p')\n                    have_longlong = (sizeof(void *) == sizeof(uint64_t));\n                if (have_longlong) {\n                    if (*format == 'u') {\n                        uval = va_arg(ap, uint64_t);\n                    } else {\n                        ival = va_arg(ap, int64_t);\n                    }\n                } else {\n                    if (*format == 'u') {\n                        uval = va_arg(ap, uint32_t);\n                    } else {\n                        ival = va_arg(ap, int32_t);\n                    }\n                }\n\n                {\n                    char buff[22];\n                    const int base = (*format == 'x' || *format == 'p') ? 16 : 10;\n\n\t\t            /* *INDENT-OFF* */\n                    char *val_as_str = (*format == 'u') ?\n                        _safe_utoa(base, uval, &buff[sizeof(buff) - 1]) :\n                        _safe_itoa(base, ival, &buff[sizeof(buff) - 1]);\n\t\t            /* *INDENT-ON* */\n\n                    /* Strip off \"ffffffff\" if we have 'x' format without 'll' */\n                    if (*format == 'x' && !have_longlong && ival < 0) {\n                        val_as_str += 8;\n                    }\n\n                    while (*val_as_str && to < end) {\n                        *to++ = *val_as_str++;\n                    }\n                    continue;\n                }\n            }\n        case 's':\n            {\n                const char *val = va_arg(ap, char *);\n                if (!val) {\n                    val = \"(null)\";\n                }\n                while (*val && to < end) {\n                    *to++ = *val++;\n                }\n                continue;\n            }\n        }\n    }\n    *to = 0;\n    return (int)(to - start);\n}\n\nint\n_safe_snprintf(char *to, size_t n, const char *fmt, ...)\n{\n    int result;\n    va_list args;\n    va_start(args, fmt);\n    result = _safe_vsnprintf(to, n, fmt, args);\n    va_end(args);\n    return result;\n}\n"
  },
  {
    "path": "src/nc_string.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_STRING_H_\n#define _NC_STRING_H_\n\n#include <string.h>\n#include <sys/types.h>\n#include <stdarg.h>\n\n#include <nc_core.h>\n\nstruct string {\n    uint32_t len;   /* string length */\n    uint8_t  *data; /* string data */\n};\n\n#define string(_str)   { sizeof(_str) - 1, (uint8_t *)(_str) }\n#define null_string    { 0, NULL }\n\n#define string_set_text(_str, _text) do {       \\\n    (_str)->len = (uint32_t)(sizeof(_text) - 1);\\\n    (_str)->data = (uint8_t *)(_text);          \\\n} while (0);\n\n#define string_set_raw(_str, _raw) do {         \\\n    (_str)->len = (uint32_t)(nc_strlen(_raw));  \\\n    (_str)->data = (uint8_t *)(_raw);           \\\n} while (0);\n\nvoid string_init(struct string *str);\nvoid string_deinit(struct string *str);\nbool string_empty(const struct string *str);\nrstatus_t string_duplicate(struct string *dst, const struct string *src);\nrstatus_t string_copy(struct string *dst, const uint8_t *src, uint32_t srclen);\nint string_compare(const struct string *s1, const struct string *s2);\n\n/*\n * Wrapper around common routines for manipulating C character\n * strings\n */\n#define nc_memcpy(_d, _c, _n)           \\\n    memcpy(_d, _c, (size_t)(_n))\n\n#define nc_memmove(_d, _c, _n)          \\\n    memmove(_d, _c, (size_t)(_n))\n\n#define nc_memchr(_d, _c, _n)           \\\n    memchr(_d, _c, (size_t)(_n))\n\n#define nc_strlen(_s)                   \\\n    strlen((char *)(_s))\n\n#define nc_strncmp(_s1, _s2, _n)        \\\n    strncmp((char *)(_s1), (char *)(_s2), (size_t)(_n))\n\n#define nc_strchr(_p, _l, _c)           \\\n    _nc_strchr((uint8_t *)(_p), (uint8_t *)(_l), (uint8_t)(_c))\n\n#define nc_strrchr(_p, _s, _c)          \\\n    _nc_strrchr((uint8_t *)(_p),(uint8_t *)(_s), (uint8_t)(_c))\n\n#define nc_strndup(_s, _n)              \\\n    (uint8_t *)strndup((char *)(_s), (size_t)(_n));\n\n/*\n * snprintf(s, n, ...) will write at most n - 1 of the characters printed into\n * the output string; the nth character then gets the terminating `\\0'; if\n * the return value is greater than or equal to the n argument, the string\n * was too short and some of the printed characters were discarded; the output\n * is always null-terminated.\n *\n * Note that, the return value of snprintf() is always the number of characters\n * that would be printed into the output string, assuming n were limited not\n * including the trailing `\\0' used to end output.\n *\n * scnprintf(s, n, ...) is same as snprintf() except, it returns the number\n * of characters printed into the output string not including the trailing '\\0'\n */\n#define nc_snprintf(_s, _n, ...)        \\\n    snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__)\n\n#define nc_scnprintf(_s, _n, ...)       \\\n    _scnprintf((char *)(_s), (size_t)(_n), __VA_ARGS__)\n\n#define nc_vsnprintf(_s, _n, _f, _a)    \\\n    vsnprintf((char *)(_s), (size_t)(_n), _f, _a)\n\n#define nc_vscnprintf(_s, _n, _f, _a)   \\\n    _vscnprintf((char *)(_s), (size_t)(_n), _f, _a)\n\n#define nc_strftime(_s, _n, fmt, tm)        \\\n    (int)strftime((char *)(_s), (size_t)(_n), fmt, tm)\n\n/*\n * A (very) limited version of snprintf\n * @param   to   Destination buffer\n * @param   n    Size of destination buffer\n * @param   fmt  printf() style format string\n * @returns Number of bytes written, including terminating '\\0'\n * Supports 'd' 'i' 'u' 'x' 'p' 's' conversion\n * Supports 'l' and 'll' modifiers for integral types\n * Does not support any width/precision\n * Implemented with simplicity, and async-signal-safety in mind\n */\nint _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap);\nint _safe_snprintf(char *to, size_t n, const char *fmt, ...);\n\n#define nc_safe_snprintf(_s, _n, ...)       \\\n    _safe_snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__)\n\n#define nc_safe_vsnprintf(_s, _n, _f, _a)   \\\n    _safe_vsnprintf((char *)(_s), (size_t)(_n), _f, _a)\n\nstatic inline uint8_t *\n_nc_strchr(uint8_t *p, uint8_t *last, uint8_t c)\n{\n    while (p < last) {\n        if (*p == c) {\n            return p;\n        }\n        p++;\n    }\n\n    return NULL;\n}\n\nstatic inline uint8_t *\n_nc_strrchr(uint8_t *p, uint8_t *start, uint8_t c)\n{\n    while (p >= start) {\n        if (*p == c) {\n            return p;\n        }\n        p--;\n    }\n\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/nc_util.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <netdb.h>\n\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/ioctl.h>\n\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n\n#include <nc_core.h>\n\n#ifdef NC_HAVE_BACKTRACE\n# include <execinfo.h>\n#endif\n\nint\nnc_set_blocking(int sd)\n{\n    int flags;\n\n    flags = fcntl(sd, F_GETFL, 0);\n    if (flags < 0) {\n        return flags;\n    }\n\n    return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK);\n}\n\nint\nnc_set_nonblocking(int sd)\n{\n    int flags;\n\n    flags = fcntl(sd, F_GETFL, 0);\n    if (flags < 0) {\n        return flags;\n    }\n\n    return fcntl(sd, F_SETFL, flags | O_NONBLOCK);\n}\n\nint\nnc_set_reuseaddr(int sd)\n{\n    int reuse;\n    socklen_t len;\n\n    reuse = 1;\n    len = sizeof(reuse);\n\n    return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len);\n}\n\nint\nnc_set_reuseport(int sd)\n{\n    int reuse;\n    socklen_t len;\n\n    reuse = 1;\n    len = sizeof(reuse);\n#ifdef SO_REUSEPORT\n    return setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &reuse, len);\n#endif\n    return -1;\n}\n\n/*\n * Disable Nagle algorithm on TCP socket.\n *\n * This option helps to minimize transmit latency by disabling coalescing\n * of data to fill up a TCP segment inside the kernel. Sockets with this\n * option must use readv() or writev() to do data transfer in bulk and\n * hence avoid the overhead of small packets.\n */\nint\nnc_set_tcpnodelay(int sd)\n{\n    int nodelay;\n    socklen_t len;\n\n    nodelay = 1;\n    len = sizeof(nodelay);\n\n    return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len);\n}\n\nint\nnc_set_linger(int sd, int timeout)\n{\n    struct linger linger;\n    socklen_t len;\n\n    linger.l_onoff = 1;\n    linger.l_linger = timeout;\n\n    len = sizeof(linger);\n\n    return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len);\n}\n\nint\nnc_set_tcpkeepalive(int sd)\n{\n    int val = 1;\n    return setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val));\n}\n\nint\nnc_set_sndbuf(int sd, int size)\n{\n    socklen_t len;\n\n    len = sizeof(size);\n\n    return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len);\n}\n\nint\nnc_set_rcvbuf(int sd, int size)\n{\n    socklen_t len;\n\n    len = sizeof(size);\n\n    return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len);\n}\n\nint\nnc_get_soerror(int sd)\n{\n    int status, err;\n    socklen_t len;\n\n    err = 0;\n    len = sizeof(err);\n\n    status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len);\n    if (status == 0) {\n        errno = err;\n    }\n\n    return status;\n}\n\nint\nnc_get_sndbuf(int sd)\n{\n    int status, size;\n    socklen_t len;\n\n    size = 0;\n    len = sizeof(size);\n\n    status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len);\n    if (status < 0) {\n        return status;\n    }\n\n    return size;\n}\n\nint\nnc_get_rcvbuf(int sd)\n{\n    int status, size;\n    socklen_t len;\n\n    size = 0;\n    len = sizeof(size);\n\n    status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len);\n    if (status < 0) {\n        return status;\n    }\n\n    return size;\n}\n\nint\n_nc_atoi(const uint8_t *line, size_t n)\n{\n    int value;\n\n    if (n == 0) {\n        return -1;\n    }\n\n    for (value = 0; n--; line++) {\n        if (*line < '0' || *line > '9') {\n            return -1;\n        }\n\n        value = value * 10 + (*line - '0');\n    }\n\n    if (value < 0) {\n        return -1;\n    }\n\n    return value;\n}\n\nbool\nnc_valid_port(int n)\n{\n    if (n < 1 || n > UINT16_MAX) {\n        return false;\n    }\n\n    return true;\n}\n\nvoid *\n_nc_alloc(size_t size, const char *name, int line)\n{\n    void *p;\n\n    ASSERT(size != 0);\n\n    p = malloc(size);\n    if (p == NULL) {\n        log_error(\"malloc(%zu) failed @ %s:%d\", size, name, line);\n    } else {\n        log_debug(LOG_VVERB, \"malloc(%zu) at %p @ %s:%d\", size, p, name, line);\n    }\n\n    return p;\n}\n\nvoid *\n_nc_zalloc(size_t size, const char *name, int line)\n{\n    void *p;\n\n    p = _nc_alloc(size, name, line);\n    if (p != NULL) {\n        memset(p, 0, size);\n    }\n\n    return p;\n}\n\nvoid *\n_nc_calloc(size_t nmemb, size_t size, const char *name, int line)\n{\n    return _nc_zalloc(nmemb * size, name, line);\n}\n\nvoid *\n_nc_realloc(void *ptr, size_t size, const char *name, int line)\n{\n    void *p;\n\n    ASSERT(size != 0);\n\n    p = realloc(ptr, size);\n    if (p == NULL) {\n        log_error(\"realloc(%zu) failed @ %s:%d\", size, name, line);\n    } else {\n        log_debug(LOG_VVERB, \"realloc(%zu) at %p @ %s:%d\", size, p, name, line);\n    }\n\n    return p;\n}\n\nvoid\n_nc_free(void *ptr, const char *name, int line)\n{\n    ASSERT(ptr != NULL);\n    log_debug(LOG_VVERB, \"free(%p) @ %s:%d\", ptr, name, line);\n    free(ptr);\n}\n\nvoid\nnc_stacktrace(int skip_count)\n{\n#ifdef NC_HAVE_BACKTRACE\n    void *stack[64];\n    char **symbols;\n    int size, i, j;\n\n    size = backtrace(stack, 64);\n    symbols = backtrace_symbols(stack, size);\n    if (symbols == NULL) {\n        return;\n    }\n\n    skip_count++; /* skip the current frame also */\n\n    for (i = skip_count, j = 0; i < size; i++, j++) {\n        loga(\"[%d] %s\", j, symbols[i]);\n    }\n\n    free(symbols);\n#endif\n}\n\nvoid\nnc_stacktrace_fd(int fd)\n{\n#ifdef NC_HAVE_BACKTRACE\n    void *stack[64];\n    int size;\n\n    size = backtrace(stack, 64);\n    backtrace_symbols_fd(stack, size, fd);\n#endif\n}\n\nvoid\nnc_assert(const char *cond, const char *file, int line, int panic)\n{\n    log_error(\"assert '%s' failed @ (%s, %d)\", cond, file, line);\n    if (panic) {\n        nc_stacktrace(1);\n        abort();\n    }\n}\n\nint\n_vscnprintf(char *buf, size_t size, const char *fmt, va_list args)\n{\n    int n;\n\n    n = vsnprintf(buf, size, fmt, args);\n\n    /*\n     * The return value is the number of characters which would be written\n     * into buf not including the trailing '\\0'. If size is == 0 the\n     * function returns 0.\n     *\n     * On error, the function also returns 0. This is to allow idiom such\n     * as len += _vscnprintf(...)\n     *\n     * See: http://lwn.net/Articles/69419/\n     */\n    if (n <= 0) {\n        return 0;\n    }\n\n    if (n < (int) size) {\n        return n;\n    }\n\n    return (int)(size - 1);\n}\n\nint\n_scnprintf(char *buf, size_t size, const char *fmt, ...)\n{\n    va_list args;\n    int n;\n\n    va_start(args, fmt);\n    n = _vscnprintf(buf, size, fmt, args);\n    va_end(args);\n\n    return n;\n}\n\n/*\n * Send n bytes on a blocking descriptor\n */\nssize_t\n_nc_sendn(int sd, const void *vptr, size_t n)\n{\n    size_t nleft;\n    ssize_t\tnsend;\n    const char *ptr;\n\n    ptr = vptr;\n    nleft = n;\n    while (nleft > 0) {\n        nsend = send(sd, ptr, nleft, 0);\n        if (nsend < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            return nsend;\n        }\n        if (nsend == 0) {\n            return -1;\n        }\n\n        nleft -= (size_t)nsend;\n        ptr += nsend;\n    }\n\n    return (ssize_t)n;\n}\n\n/*\n * Recv n bytes from a blocking descriptor\n */\nssize_t\n_nc_recvn(int sd, void *vptr, size_t n)\n{\n\tsize_t nleft;\n\tssize_t\tnrecv;\n\tchar *ptr;\n\n\tptr = vptr;\n\tnleft = n;\n\twhile (nleft > 0) {\n        nrecv = recv(sd, ptr, nleft, 0);\n        if (nrecv < 0) {\n            if (errno == EINTR) {\n                continue;\n            }\n            return nrecv;\n        }\n        if (nrecv == 0) {\n            break;\n        }\n\n        nleft -= (size_t)nrecv;\n        ptr += nrecv;\n    }\n\n    return (ssize_t)(n - nleft);\n}\n\n/*\n * Return the current time in microseconds since Epoch\n */\nint64_t\nnc_usec_now(void)\n{\n    struct timeval now;\n    int64_t usec;\n    int status;\n\n    status = gettimeofday(&now, NULL);\n    if (status < 0) {\n        log_error(\"gettimeofday failed: %s\", strerror(errno));\n        return -1;\n    }\n\n    usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec;\n\n    return usec;\n}\n\n/*\n * Return the current time in milliseconds since Epoch\n */\nint64_t\nnc_msec_now(void)\n{\n    return nc_usec_now() / 1000LL;\n}\n\nstatic int\nnc_resolve_inet(const struct string *name, int port, struct sockinfo *si)\n{\n    int status;\n    struct addrinfo *ai, *cai; /* head and current addrinfo */\n    struct addrinfo hints;\n    char *node, service[NC_UINTMAX_MAXLEN];\n    bool found;\n\n    ASSERT(nc_valid_port(port));\n\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_flags = AI_NUMERICSERV;\n    hints.ai_family = AF_UNSPEC;     /* AF_INET or AF_INET6 */\n    hints.ai_socktype = SOCK_STREAM;\n    hints.ai_protocol = 0;\n    hints.ai_addrlen = 0;\n    hints.ai_addr = NULL;\n    hints.ai_canonname = NULL;\n\n    if (name != NULL) {\n        node = (char *)name->data;\n    } else {\n        /*\n         * If AI_PASSIVE flag is specified in hints.ai_flags, and node is\n         * NULL, then the returned socket addresses will be suitable for\n         * bind(2)ing a socket that will accept(2) connections. The returned\n         * socket address will contain the wildcard IP address.\n         */\n        node = NULL;\n        hints.ai_flags |= AI_PASSIVE;\n    }\n\n    nc_snprintf(service, NC_UINTMAX_MAXLEN, \"%d\", port);\n\n    /*\n     * getaddrinfo() returns zero on success or one of the error codes listed\n     * in gai_strerror(3) if an error occurs\n     */\n    status = getaddrinfo(node, service, &hints, &ai);\n    if (status != 0) {\n        log_error(\"address resolution of node '%s' service '%s' failed: %s\",\n                  node, service, gai_strerror(status));\n        return -1;\n    }\n\n    /*\n     * getaddrinfo() can return a linked list of more than one addrinfo,\n     * since we requested for both AF_INET and AF_INET6 addresses and the\n     * host itself can be multi-homed. Since we don't care whether we are\n     * using ipv4 or ipv6, we just use the first address from this collection\n     * in the order in which it was returned.\n     *\n     * The sorting function used within getaddrinfo() is defined in RFC 3484;\n     * the order can be tweaked for a particular system by editing\n     * /etc/gai.conf\n     */\n    for (cai = ai, found = false; cai != NULL; cai = cai->ai_next) {\n        si->family = cai->ai_family;\n        si->addrlen = cai->ai_addrlen;\n        nc_memcpy(&si->addr, cai->ai_addr, si->addrlen);\n        found = true;\n        break;\n    }\n\n    freeaddrinfo(ai);\n\n    return !found ? -1 : 0;\n}\n\nstatic int\nnc_resolve_unix(const struct string *name, struct sockinfo *si)\n{\n    struct sockaddr_un *un;\n\n    if (name->len >= NC_UNIX_ADDRSTRLEN) {\n        return -1;\n    }\n\n    un = &si->addr.un;\n\n    un->sun_family = AF_UNIX;\n    nc_memcpy(un->sun_path, name->data, name->len);\n    un->sun_path[name->len] = '\\0';\n\n    si->family = AF_UNIX;\n    si->addrlen = sizeof(*un);\n    /* si->addr is an alias of un */\n\n    return 0;\n}\n\n/*\n * Resolve a hostname and service by translating it to socket address and\n * return it in si\n *\n * This routine is reentrant\n */\nint\nnc_resolve(const struct string *name, int port, struct sockinfo *si)\n{\n    if (name != NULL && name->data[0] == '/') {\n        return nc_resolve_unix(name, si);\n    }\n\n    return nc_resolve_inet(name, port, si);\n}\n\n/*\n * Unresolve the socket address by translating it to a character string\n * describing the host and service\n *\n * This routine is not reentrant\n */\nconst char *\nnc_unresolve_addr(struct sockaddr *addr, socklen_t addrlen)\n{\n    static char unresolve[NI_MAXHOST + NI_MAXSERV];\n    static char host[NI_MAXHOST], service[NI_MAXSERV];\n    int status;\n\n    status = getnameinfo(addr, addrlen, host, sizeof(host),\n                         service, sizeof(service),\n                         NI_NUMERICHOST | NI_NUMERICSERV);\n    if (status < 0) {\n        return \"unknown\";\n    }\n\n    nc_snprintf(unresolve, sizeof(unresolve), \"%s:%s\", host, service);\n\n    return unresolve;\n}\n\n/*\n * Unresolve the socket descriptor peer address by translating it to a\n * character string describing the host and service\n *\n * This routine is not reentrant\n */\nconst char *\nnc_unresolve_peer_desc(int sd)\n{\n    static struct sockinfo si;\n    struct sockaddr *addr;\n    socklen_t addrlen;\n    int status;\n\n    memset(&si, 0, sizeof(si));\n    addr = (struct sockaddr *)&si.addr;\n    addrlen = sizeof(si.addr);\n\n    status = getpeername(sd, addr, &addrlen);\n    if (status < 0) {\n        return \"unknown\";\n    }\n\n    return nc_unresolve_addr(addr, addrlen);\n}\n\n/*\n * Unresolve the socket descriptor address by translating it to a\n * character string describing the host and service\n *\n * This routine is not reentrant\n */\nconst char *\nnc_unresolve_desc(int sd)\n{\n    static struct sockinfo si;\n    struct sockaddr *addr;\n    socklen_t addrlen;\n    int status;\n\n    memset(&si, 0, sizeof(si));\n    addr = (struct sockaddr *)&si.addr;\n    addrlen = sizeof(si.addr);\n\n    status = getsockname(sd, addr, &addrlen);\n    if (status < 0) {\n        return \"unknown\";\n    }\n\n    return nc_unresolve_addr(addr, addrlen);\n}\n"
  },
  {
    "path": "src/nc_util.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_UTIL_H_\n#define _NC_UTIL_H_\n\n#include <stdarg.h>\n\n#ifdef __GNUC__\n# define NC_GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__)\n#else\n# define NC_GCC_VERSION 0\n#endif\n#if NC_GCC_VERSION >= 2007\n#define NC_ATTRIBUTE_FORMAT(type, idx, first) __attribute__ ((format(type, idx, first)))\n#else\n#define NC_ATTRIBUTE_FORMAT(type, idx, first)\n#endif\n\n\n#define LF                  (uint8_t) 10\n#define CR                  (uint8_t) 13\n#define CRLF                \"\\x0d\\x0a\"\n#define CRLF_LEN            (sizeof(\"\\x0d\\x0a\") - 1)\n\n#define NELEMS(a)           ((sizeof(a)) / sizeof((a)[0]))\n\n#define MIN(a, b)           ((a) < (b) ? (a) : (b))\n#define MAX(a, b)           ((a) > (b) ? (a) : (b))\n\n#define SQUARE(d)           ((d) * (d))\n#define VAR(s, s2, n)       (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1))\n#define STDDEV(s, s2, n)    (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n))))\n\n#define NC_INET4_ADDRSTRLEN (sizeof(\"255.255.255.255\") - 1)\n#define NC_INET6_ADDRSTRLEN \\\n    (sizeof(\"ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255\") - 1)\n#define NC_INET_ADDRSTRLEN  MAX(NC_INET4_ADDRSTRLEN, NC_INET6_ADDRSTRLEN)\n#define NC_UNIX_ADDRSTRLEN  \\\n    (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))\n\n#define NC_MAXHOSTNAMELEN   256\n\n/*\n * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral\n * type (uintmax_t) in ascii, including the null terminator '\\0'\n *\n * From stdint.h, we have:\n * # define UINT8_MAX\t(255)\n * # define UINT16_MAX\t(65535)\n * # define UINT32_MAX\t(4294967295U)\n * # define UINT64_MAX\t(__UINT64_C(18446744073709551615))\n */\n#define NC_UINT8_MAXLEN     (3 + 1)\n#define NC_UINT16_MAXLEN    (5 + 1)\n#define NC_UINT32_MAXLEN    (10 + 1)\n#define NC_UINT64_MAXLEN    (20 + 1)\n#define NC_UINTMAX_MAXLEN   NC_UINT64_MAXLEN\n\n/*\n * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2\n * of 2.\n */\n#define NC_ALIGNMENT        sizeof(unsigned long) /* platform word */\n#define NC_ALIGN(d, n)      (((d) + (n - 1)) & ~(n - 1))\n#define NC_ALIGN_PTR(p, n)  \\\n    (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1))\n\n/*\n * Wrapper to workaround well known, safe, implicit type conversion when\n * invoking system calls.\n */\n#define nc_gethostname(_name, _len) \\\n    gethostname((char *)_name, (size_t)_len)\n\n#define nc_atoi(_line, _n)          \\\n    _nc_atoi((uint8_t *)_line, (size_t)_n)\n\nint nc_set_blocking(int sd);\nint nc_set_nonblocking(int sd);\nint nc_set_reuseaddr(int sd);\nint nc_set_reuseport(int sd);\nint nc_set_tcpnodelay(int sd);\nint nc_set_linger(int sd, int timeout);\nint nc_set_sndbuf(int sd, int size);\nint nc_set_rcvbuf(int sd, int size);\nint nc_set_tcpkeepalive(int sd);\nint nc_get_soerror(int sd);\nint nc_get_sndbuf(int sd);\nint nc_get_rcvbuf(int sd);\n\nint _nc_atoi(const uint8_t *line, size_t n);\nbool nc_valid_port(int n);\n\n/*\n * Memory allocation and free wrappers.\n *\n * These wrappers enables us to loosely detect double free, dangling\n * pointer access and zero-byte alloc.\n */\n#define nc_alloc(_s)                    \\\n    _nc_alloc((size_t)(_s), __FILE__, __LINE__)\n\n#define nc_zalloc(_s)                   \\\n    _nc_zalloc((size_t)(_s), __FILE__, __LINE__)\n\n#define nc_calloc(_n, _s)               \\\n    _nc_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__)\n\n#define nc_realloc(_p, _s)              \\\n    _nc_realloc(_p, (size_t)(_s), __FILE__, __LINE__)\n\n#define nc_free(_p) do {                \\\n    _nc_free(_p, __FILE__, __LINE__);   \\\n    (_p) = NULL;                        \\\n} while (0)\n\nvoid *_nc_alloc(size_t size, const char *name, int line);\nvoid *_nc_zalloc(size_t size, const char *name, int line);\nvoid *_nc_calloc(size_t nmemb, size_t size, const char *name, int line);\nvoid *_nc_realloc(void *ptr, size_t size, const char *name, int line);\nvoid _nc_free(void *ptr, const char *name, int line);\n\n/*\n * Wrappers to send or receive n byte message on a blocking\n * socket descriptor.\n */\n#define nc_sendn(_s, _b, _n)    \\\n    _nc_sendn(_s, _b, (size_t)(_n))\n\n#define nc_recvn(_s, _b, _n)    \\\n    _nc_recvn(_s, _b, (size_t)(_n))\n\n/*\n * Wrappers to read or write data to/from (multiple) buffers\n * to a file or socket descriptor.\n */\n#define nc_read(_d, _b, _n)     \\\n    read(_d, _b, (size_t)(_n))\n\n#define nc_readv(_d, _b, _n)    \\\n    readv(_d, _b, (int)(_n))\n\n#define nc_write(_d, _b, _n)    \\\n    write(_d, _b, (size_t)(_n))\n\n#define nc_writev(_d, _b, _n)   \\\n    writev(_d, _b, (int)(_n))\n\nssize_t _nc_sendn(int sd, const void *vptr, size_t n);\nssize_t _nc_recvn(int sd, void *vptr, size_t n);\n\n/*\n * Wrappers for defining custom assert based on whether macro\n * NC_ASSERT_PANIC or NC_ASSERT_LOG was defined at the moment\n * ASSERT was called.\n */\n#ifdef NC_ASSERT_PANIC\n\n#define ASSERT(_x) do {                         \\\n    if (!(_x)) {                                \\\n        nc_assert(#_x, __FILE__, __LINE__, 1);  \\\n    }                                           \\\n} while (0)\n\n#define NOT_REACHED() ASSERT(0)\n\n#elif NC_ASSERT_LOG\n\n#define ASSERT(_x) do {                         \\\n    if (!(_x)) {                                \\\n        nc_assert(#_x, __FILE__, __LINE__, 0);  \\\n    }                                           \\\n} while (0)\n\n#define NOT_REACHED() ASSERT(0)\n\n#else\n\n#define ASSERT(_x)\n\n#define NOT_REACHED()\n\n#endif\n\nvoid nc_assert(const char *cond, const char *file, int line, int panic);\nvoid nc_stacktrace(int skip_count);\nvoid nc_stacktrace_fd(int fd);\n\nint _scnprintf(char *buf, size_t size, const char *fmt, ...) NC_ATTRIBUTE_FORMAT(printf, 3, 4);\nint _vscnprintf(char *buf, size_t size, const char *fmt, va_list args);\nint64_t nc_usec_now(void);\nint64_t nc_msec_now(void);\n\n/*\n * Address resolution for internet (ipv4 and ipv6) and unix domain\n * socket address.\n */\n\nstruct sockinfo {\n    int       family;              /* socket address family */\n    socklen_t addrlen;             /* socket address length */\n    union {\n        struct sockaddr_in  in;    /* ipv4 socket address */\n        struct sockaddr_in6 in6;   /* ipv6 socket address */\n        struct sockaddr_un  un;    /* unix domain address */\n    } addr;\n};\n\nint nc_resolve(const struct string *name, int port, struct sockinfo *si);\nconst char *nc_unresolve_addr(struct sockaddr *addr, socklen_t addrlen);\nconst char *nc_unresolve_peer_desc(int sd);\nconst char *nc_unresolve_desc(int sd);\n\n#endif\n"
  },
  {
    "path": "src/proto/Makefile.am",
    "content": "MAINTAINERCLEANFILES = Makefile.in\n\nAM_CPPFLAGS = -I $(top_srcdir)/src\n\nAM_CFLAGS = -Wall -Wshadow\nAM_CFLAGS += -Wno-unused-parameter -Wno-unused-value\n\nnoinst_LIBRARIES = libproto.a\n\nnoinst_HEADERS = nc_proto.h\n\nlibproto_a_SOURCES =\t\t\t\\\n\tnc_memcache.c\t\t\t\\\n\tnc_redis.c\n"
  },
  {
    "path": "src/proto/nc_memcache.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <ctype.h>\n\n#include <nc_core.h>\n#include <nc_proto.h>\n\n/*\n * From memcache protocol specification:\n *\n * Data stored by memcached is identified with the help of a key. A key\n * is a text string which should uniquely identify the data for clients\n * that are interested in storing and retrieving it.  Currently the\n * length limit of a key is set at 250 characters (of course, normally\n * clients wouldn't need to use such long keys); the key must not include\n * control characters or whitespace.\n */\n#define MEMCACHE_MAX_KEY_LENGTH 250\n\n/*\n * Return true, if the memcache command is a storage command, otherwise\n * return false\n */\nstatic bool\nmemcache_storage(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_MC_SET:\n    case MSG_REQ_MC_CAS:\n    case MSG_REQ_MC_ADD:\n    case MSG_REQ_MC_REPLACE:\n    case MSG_REQ_MC_APPEND:\n    case MSG_REQ_MC_PREPEND:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the memcache command is a cas command, otherwise\n * return false\n */\nstatic bool\nmemcache_cas(const struct msg *r)\n{\n    if (r->type == MSG_REQ_MC_CAS) {\n        return true;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the memcache command is a retrieval command, otherwise\n * return false\n */\nstatic bool\nmemcache_retrieval(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_MC_GET:\n    case MSG_REQ_MC_GETS:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the memcache command should be fragmented,\n * otherwise return false.\n *\n * The only supported memcache commands that can have multiple keys\n * are get/gets. Both are multigets, and the latter returns CAS token with the\n * value.\n *\n * Fragmented requests are assumed to be slower due to the fact that they need\n * to allocate an array to track which key went to which server,\n * so avoid them when possible.\n */\nstatic bool\nmemcache_should_fragment(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_MC_GET:\n    case MSG_REQ_MC_GETS:\n        /*\n         * A memcache get for a single key is only sent to one server.\n         * Fragmenting it would work but be less efficient.\n         */\n        return array_n(r->keys) != 1;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the memcache command is a arithmetic command, otherwise\n * return false\n */\nstatic bool\nmemcache_arithmetic(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_MC_INCR:\n    case MSG_REQ_MC_DECR:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the memcache command is a delete command, otherwise\n * return false\n */\nstatic bool\nmemcache_delete(const struct msg *r)\n{\n    if (r->type == MSG_REQ_MC_DELETE) {\n        return true;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the memcache command is a touch command, otherwise\n * return false\n */\nstatic bool\nmemcache_touch(const struct msg *r)\n{\n    if (r->type == MSG_REQ_MC_TOUCH) {\n        return true;\n    }\n\n    return false;\n}\n\nvoid\nmemcache_parse_req(struct msg *r)\n{\n    struct mbuf *b;\n    uint8_t *p, *m;\n    uint8_t ch;\n    enum {\n        SW_START,\n        SW_REQ_TYPE,\n        SW_SPACES_BEFORE_KEY,\n        SW_KEY,\n        SW_SPACES_BEFORE_KEYS,\n        SW_SPACES_BEFORE_FLAGS,\n        SW_FLAGS,\n        SW_SPACES_BEFORE_EXPIRY,\n        SW_EXPIRY,\n        SW_SPACES_BEFORE_VLEN,\n        SW_VLEN,\n        SW_SPACES_BEFORE_CAS,\n        SW_CAS,\n        SW_RUNTO_VAL,\n        SW_VAL,\n        SW_SPACES_BEFORE_NUM,\n        SW_NUM,\n        SW_RUNTO_CRLF,\n        SW_CRLF,\n        SW_NOREPLY,\n        SW_AFTER_NOREPLY,\n        SW_ALMOST_DONE,\n        SW_SENTINEL\n    } state;\n\n    state = r->state;\n    b = STAILQ_LAST(&r->mhdr, mbuf, next);\n\n    ASSERT(r->request);\n    ASSERT(!r->redis);\n    ASSERT(state >= SW_START && state < SW_SENTINEL);\n    ASSERT(b != NULL);\n    ASSERT(b->pos <= b->last);\n\n    /* validate the parsing maker */\n    ASSERT(r->pos != NULL);\n    ASSERT(r->pos >= b->pos && r->pos <= b->last);\n\n    for (p = r->pos; p < b->last; p++) {\n        ch = *p;\n\n        switch (state) {\n\n        case SW_START:\n            if (ch == ' ') {\n                break;\n            }\n\n            if (!islower(ch)) {\n                goto error;\n            }\n\n            /* req_start <- p; type_start <- p */\n            r->token = p;\n            state = SW_REQ_TYPE;\n\n            break;\n\n        case SW_REQ_TYPE:\n            if (ch == ' ' || ch == CR) {\n                /* type_end = p - 1 */\n                m = r->token;\n                r->token = NULL;\n                r->type = MSG_UNKNOWN;\n                r->narg++;\n\n                switch (p - m) {\n\n                case 3:\n                    if (str4cmp(m, 'g', 'e', 't', ' ')) {\n                        r->type = MSG_REQ_MC_GET;\n                        break;\n                    }\n\n                    if (str4cmp(m, 's', 'e', 't', ' ')) {\n                        r->type = MSG_REQ_MC_SET;\n                        break;\n                    }\n\n                    if (str4cmp(m, 'a', 'd', 'd', ' ')) {\n                        r->type = MSG_REQ_MC_ADD;\n                        break;\n                    }\n\n                    if (str4cmp(m, 'c', 'a', 's', ' ')) {\n                        r->type = MSG_REQ_MC_CAS;\n                        break;\n                    }\n\n                    break;\n\n                case 4:\n                    if (str4cmp(m, 'g', 'e', 't', 's')) {\n                        r->type = MSG_REQ_MC_GETS;\n                        break;\n                    }\n\n                    if (str4cmp(m, 'i', 'n', 'c', 'r')) {\n                        r->type = MSG_REQ_MC_INCR;\n                        break;\n                    }\n\n                    if (str4cmp(m, 'd', 'e', 'c', 'r')) {\n                        r->type = MSG_REQ_MC_DECR;\n                        break;\n                    }\n\n                    if (str4cmp(m, 'q', 'u', 'i', 't')) {\n                        r->type = MSG_REQ_MC_QUIT;\n                        r->quit = 1;\n                        break;\n                    }\n\n                    break;\n\n                case 5:\n                    if (str5cmp(m, 't', 'o', 'u', 'c', 'h')) {\n                      r->type = MSG_REQ_MC_TOUCH;\n                      break;\n                    }\n\n                    break;\n\n                case 6:\n                    if (str6cmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) {\n                        r->type = MSG_REQ_MC_APPEND;\n                        break;\n                    }\n\n                    if (str6cmp(m, 'd', 'e', 'l', 'e', 't', 'e')) {\n                        r->type = MSG_REQ_MC_DELETE;\n                        break;\n                    }\n\n                    break;\n\n                case 7:\n                    if (str7cmp(m, 'p', 'r', 'e', 'p', 'e', 'n', 'd')) {\n                        r->type = MSG_REQ_MC_PREPEND;\n                        break;\n                    }\n\n                    if (str7cmp(m, 'r', 'e', 'p', 'l', 'a', 'c', 'e')) {\n                        r->type = MSG_REQ_MC_REPLACE;\n                        break;\n                    }\n\n                    if (str7cmp(m, 'v', 'e', 'r', 's', 'i', 'o', 'n')) {\n                        r->type = MSG_REQ_MC_VERSION;\n                        if (!msg_set_placeholder_key(r)) {\n                            goto enomem;\n                        }\n                        break;\n                    }\n\n                    break;\n                }\n\n                switch (r->type) {\n                case MSG_REQ_MC_GET:\n                case MSG_REQ_MC_GETS:\n                case MSG_REQ_MC_DELETE:\n                case MSG_REQ_MC_CAS:\n                case MSG_REQ_MC_SET:\n                case MSG_REQ_MC_ADD:\n                case MSG_REQ_MC_REPLACE:\n                case MSG_REQ_MC_APPEND:\n                case MSG_REQ_MC_PREPEND:\n                case MSG_REQ_MC_INCR:\n                case MSG_REQ_MC_DECR:\n                case MSG_REQ_MC_TOUCH:\n                    if (ch == CR) {\n                        goto error;\n                    }\n                    state = SW_SPACES_BEFORE_KEY;\n                    break;\n\n                case MSG_REQ_MC_VERSION:\n                case MSG_REQ_MC_QUIT:\n                    p = p - 1; /* go back by 1 byte */\n                    state = SW_CRLF;\n                    break;\n\n                case MSG_UNKNOWN:\n                    goto error;\n\n                default:\n                    NOT_REACHED();\n                }\n\n            } else if (!islower(ch)) {\n                goto error;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_KEY:\n            if (ch != ' ') {\n                p = p - 1; /* go back by 1 byte */\n                r->token = NULL;\n                state = SW_KEY;\n            }\n            break;\n\n        case SW_KEY:\n            if (r->token == NULL) {\n                r->token = p;\n            }\n            if (ch == ' ' || ch == CR) {\n                struct keypos *kpos;\n                int keylen = p - r->token;\n                if (keylen > MEMCACHE_MAX_KEY_LENGTH) {\n                    log_error(\"parsed bad req %\"PRIu64\" of type %d with key \"\n                              \"prefix '%.*s...' and length %d that exceeds \"\n                              \"maximum key length\", r->id, r->type, 16,\n                              r->token, (int)(p - r->token));\n                    goto error;\n                } else if (keylen == 0) {\n                    log_error(\"parsed bad req %\"PRIu64\" of type %d with an \"\n                              \"empty key\", r->id, r->type);\n                    goto error;\n                }\n\n                kpos = array_push(r->keys);\n                if (kpos == NULL) {\n                    goto enomem;\n                }\n                kpos->start = r->token;\n                kpos->end = p;\n\n                r->narg++;\n                r->token = NULL;\n\n                /* get next state */\n                if (memcache_storage(r)) {\n                    state = SW_SPACES_BEFORE_FLAGS;\n                } else if (memcache_arithmetic(r) || memcache_touch(r) ) {\n                    state = SW_SPACES_BEFORE_NUM;\n                } else if (memcache_retrieval(r)) {\n                    state = SW_SPACES_BEFORE_KEYS;\n                } else {\n                    /* delete, etc. */\n                    state = SW_RUNTO_CRLF;\n                }\n\n                if (ch == CR) {\n                    if (memcache_storage(r) || memcache_arithmetic(r)) {\n                        goto error;\n                    }\n                    p = p - 1; /* go back by 1 byte */\n                }\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_KEYS:\n            ASSERT(memcache_retrieval(r));\n            switch (ch) {\n            case ' ':\n                break;\n\n            case CR:\n                state = SW_ALMOST_DONE;\n                break;\n\n            default:\n                r->token = NULL;\n                p = p - 1; /* go back by 1 byte */\n                state = SW_KEY;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_FLAGS:\n            if (ch != ' ') {\n                if (!isdigit(ch)) {\n                    goto error;\n                }\n                /* flags_start <- p; flags <- ch - '0' */\n                r->token = p;\n                state = SW_FLAGS;\n            }\n\n            break;\n\n        case SW_FLAGS:\n            if (isdigit(ch)) {\n                /* flags <- flags * 10 + (ch - '0') */\n                ;\n            } else if (ch == ' ') {\n                /* flags_end <- p - 1 */\n                r->token = NULL;\n                state = SW_SPACES_BEFORE_EXPIRY;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_EXPIRY:\n            if (ch != ' ') {\n                if (!isdigit(ch)) {\n                    goto error;\n                }\n                /* expiry_start <- p; expiry <- ch - '0' */\n                r->token = p;\n                state = SW_EXPIRY;\n            }\n\n            break;\n\n        case SW_EXPIRY:\n            if (isdigit(ch)) {\n                /* expiry <- expiry * 10 + (ch - '0') */\n                ;\n            } else if (ch == ' ') {\n                /* expiry_end <- p - 1 */\n                r->token = NULL;\n                state = SW_SPACES_BEFORE_VLEN;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_VLEN:\n            if (ch != ' ') {\n                if (!isdigit(ch)) {\n                    goto error;\n                }\n                /* vlen_start <- p */\n                r->vlen = (uint32_t)(ch - '0');\n                state = SW_VLEN;\n            }\n\n            break;\n\n        case SW_VLEN:\n            if (isdigit(ch)) {\n                r->vlen = r->vlen * 10 + (uint32_t)(ch - '0');\n            } else if (memcache_cas(r)) {\n                if (ch != ' ') {\n                    goto error;\n                }\n                /* vlen_end <- p - 1 */\n                p = p - 1; /* go back by 1 byte */\n                r->token = NULL;\n                state = SW_SPACES_BEFORE_CAS;\n            } else if (ch == ' ' || ch == CR) {\n                /* vlen_end <- p - 1 */\n                p = p - 1; /* go back by 1 byte */\n                r->token = NULL;\n                state = SW_RUNTO_CRLF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_CAS:\n            if (ch != ' ') {\n                if (!isdigit(ch)) {\n                    goto error;\n                }\n                /* cas_start <- p; cas <- ch - '0' */\n                r->token = p;\n                state = SW_CAS;\n            }\n\n            break;\n\n        case SW_CAS:\n            if (isdigit(ch)) {\n                /* cas <- cas * 10 + (ch - '0') */\n                ;\n            } else if (ch == ' ' || ch == CR) {\n                /* cas_end <- p - 1 */\n                p = p - 1; /* go back by 1 byte */\n                r->token = NULL;\n                state = SW_RUNTO_CRLF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n\n        case SW_RUNTO_VAL:\n            switch (ch) {\n            case LF:\n                /* val_start <- p + 1 */\n                state = SW_VAL;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_VAL:\n            m = p + r->vlen;\n            if (m >= b->last) {\n                ASSERT(r->vlen >= (uint32_t)(b->last - p));\n                r->vlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m; /* move forward by vlen bytes */\n                break;\n            }\n            switch (*m) {\n            case CR:\n                /* val_end <- p - 1 */\n                p = m; /* move forward by vlen bytes */\n                state = SW_ALMOST_DONE;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_NUM:\n            if (ch != ' ') {\n                if (!(isdigit(ch) || ch == '-')) {\n                    goto error;\n                }\n                /* num_start <- p; num <- ch - '0'  */\n                r->token = p;\n                state = SW_NUM;\n            }\n\n            break;\n\n        case SW_NUM:\n            if (isdigit(ch)) {\n                /* num <- num * 10 + (ch - '0') */\n                ;\n            } else if (ch == ' ' || ch == CR) {\n                r->token = NULL;\n                /* num_end <- p - 1 */\n                p = p - 1; /* go back by 1 byte */\n                state = SW_RUNTO_CRLF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_RUNTO_CRLF:\n            switch (ch) {\n            case ' ':\n                break;\n\n            case 'n':\n                if (memcache_storage(r) || memcache_arithmetic(r) || memcache_delete(r) || memcache_touch(r)) {\n                    /* noreply_start <- p */\n                    r->token = p;\n                    state = SW_NOREPLY;\n                } else {\n                    goto error;\n                }\n\n                break;\n\n            case CR:\n                if (memcache_storage(r)) {\n                    state = SW_RUNTO_VAL;\n                } else {\n                    state = SW_ALMOST_DONE;\n                }\n\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_NOREPLY:\n            switch (ch) {\n            case ' ':\n            case CR:\n                m = r->token;\n                if (((p - m) == 7) && str7cmp(m, 'n', 'o', 'r', 'e', 'p', 'l', 'y')) {\n                    ASSERT(memcache_storage(r) || memcache_arithmetic(r) || memcache_delete(r) || memcache_touch(r));\n                    r->token = NULL;\n                    /* noreply_end <- p - 1 */\n                    r->noreply = 1;\n                    state = SW_AFTER_NOREPLY;\n                    p = p - 1; /* go back by 1 byte */\n                } else {\n                    goto error;\n                }\n            }\n\n            break;\n\n        case SW_AFTER_NOREPLY:\n            switch (ch) {\n            case ' ':\n                break;\n\n            case CR:\n                if (memcache_storage(r)) {\n                    state = SW_RUNTO_VAL;\n                } else {\n                    state = SW_ALMOST_DONE;\n                }\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_CRLF:\n            switch (ch) {\n            case ' ':\n                break;\n\n            case CR:\n                state = SW_ALMOST_DONE;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ALMOST_DONE:\n            switch (ch) {\n            case LF:\n                /* req_end <- p */\n                goto done;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_SENTINEL:\n        default:\n            NOT_REACHED();\n            break;\n\n        }\n    }\n\n    /*\n     * At this point, buffer from b->pos to b->last has been parsed completely\n     * but we haven't been able to reach to any conclusion. Normally, this\n     * means that we have to parse again starting from the state we are in\n     * after more data has been read. The newly read data is either read into\n     * a new mbuf, if existing mbuf is full (b->last == b->end) or into the\n     * existing mbuf.\n     *\n     * The only exception to this is when the existing mbuf is full (b->last\n     * is at b->end) and token marker is set, which means that we have to\n     * copy the partial token into a new mbuf and parse again with more data\n     * read into new mbuf.\n     */\n    ASSERT(p == b->last);\n    r->pos = p;\n    r->state = state;\n\n    if (b->last == b->end && r->token != NULL) {\n        r->pos = r->token;\n        r->token = NULL;\n        r->result = MSG_PARSE_REPAIR;\n    } else {\n        r->result = MSG_PARSE_AGAIN;\n    }\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed req %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\ndone:\n    ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL);\n    r->pos = p + 1;\n    ASSERT(r->pos <= b->last);\n    r->state = SW_START;\n    r->result = MSG_PARSE_OK;\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed req %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\nenomem:\n    r->result = MSG_PARSE_ERROR;\n    r->state = state;\n\n    log_hexdump(LOG_INFO, b->pos, mbuf_length(b), \"out of memory on parse req %\"PRIu64\" \"\n                \"res %d type %d state %d\", r->id, r->result, r->type, r->state);\n\n    return;\n\nerror:\n    r->result = MSG_PARSE_ERROR;\n    r->state = state;\n    errno = EINVAL;\n\n    log_hexdump(LOG_INFO, b->pos, mbuf_length(b), \"parsed bad req %\"PRIu64\" \"\n                \"res %d type %d state %d\", r->id, r->result, r->type,\n                r->state);\n}\n\nvoid\nmemcache_parse_rsp(struct msg *r)\n{\n    struct mbuf *b;\n    uint8_t *p, *m;\n    uint8_t ch;\n    enum {\n        SW_START,\n        SW_RSP_NUM,\n        SW_RSP_STR,\n        SW_SPACES_BEFORE_KEY,\n        SW_KEY,\n        SW_SPACES_BEFORE_FLAGS,     /* 5 */\n        SW_FLAGS,\n        SW_SPACES_BEFORE_VLEN,\n        SW_VLEN,\n        SW_RUNTO_VAL,\n        SW_VAL,                     /* 10 */\n        SW_VAL_LF,\n        SW_END,\n        SW_RUNTO_CRLF,\n        SW_CRLF,\n        SW_ALMOST_DONE,             /* 15 */\n        SW_SENTINEL\n    } state;\n\n    state = r->state;\n    b = STAILQ_LAST(&r->mhdr, mbuf, next);\n\n    ASSERT(!r->request);\n    ASSERT(!r->redis);\n    ASSERT(state >= SW_START && state < SW_SENTINEL);\n    ASSERT(b != NULL);\n    ASSERT(b->pos <= b->last);\n\n    /* validate the parsing marker */\n    ASSERT(r->pos != NULL);\n    ASSERT(r->pos >= b->pos && r->pos <= b->last);\n\n    for (p = r->pos; p < b->last; p++) {\n        ch = *p;\n\n        switch (state) {\n        case SW_START:\n            if (isdigit(ch)) {\n                state = SW_RSP_NUM;\n            } else {\n                state = SW_RSP_STR;\n            }\n            p = p - 1; /* go back by 1 byte */\n\n            break;\n\n        case SW_RSP_NUM:\n            if (r->token == NULL) {\n                /* rsp_start <- p; type_start <- p */\n                r->token = p;\n            }\n\n            if (isdigit(ch)) {\n                /* num <- num * 10 + (ch - '0') */\n                ;\n            } else if (ch == ' ' || ch == CR) {\n                /* type_end <- p - 1 */\n                r->token = NULL;\n                r->type = MSG_RSP_MC_NUM;\n                p = p - 1; /* go back by 1 byte */\n                state = SW_CRLF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_RSP_STR:\n            if (r->token == NULL) {\n                /* rsp_start <- p; type_start <- p */\n                r->token = p;\n            }\n\n            if (ch == ' ' || ch == CR) {\n                /* type_end <- p - 1 */\n                m = r->token;\n                /* r->token = NULL; */\n                r->type = MSG_UNKNOWN;\n\n                switch (p - m) {\n                case 3:\n                    if (str4cmp(m, 'E', 'N', 'D', '\\r')) {\n                        r->type = MSG_RSP_MC_END;\n                        /* end_start <- m; end_end <- p - 1 */\n                        r->end = m;\n                        break;\n                    }\n\n                    break;\n\n                case 5:\n                    if (str5cmp(m, 'V', 'A', 'L', 'U', 'E')) {\n                        /*\n                         * Encompasses responses for 'get', 'gets' and\n                         * 'cas' command.\n                         */\n                        r->type = MSG_RSP_MC_VALUE;\n                        break;\n                    }\n\n                    if (str5cmp(m, 'E', 'R', 'R', 'O', 'R')) {\n                        r->type = MSG_RSP_MC_ERROR;\n                        break;\n                    }\n\n                    break;\n\n                case 6:\n                    if (str6cmp(m, 'S', 'T', 'O', 'R', 'E', 'D')) {\n                        r->type = MSG_RSP_MC_STORED;\n                        break;\n                    }\n\n                    if (str6cmp(m, 'E', 'X', 'I', 'S', 'T', 'S')) {\n                        r->type = MSG_RSP_MC_EXISTS;\n                        break;\n                    }\n\n                    break;\n\n                case 7:\n                    if (str7cmp(m, 'D', 'E', 'L', 'E', 'T', 'E', 'D')) {\n                        r->type = MSG_RSP_MC_DELETED;\n                        break;\n                    }\n\n                    if (str7cmp(m, 'T', 'O', 'U', 'C', 'H', 'E', 'D')) {\n                        r->type = MSG_RSP_MC_TOUCHED;\n                        break;\n                    }\n\n                    if (str7cmp(m, 'V', 'E', 'R', 'S', 'I', 'O', 'N')) {\n                        r->type = MSG_RSP_MC_VERSION;\n                        break;\n                    }\n\n                    break;\n\n                case 9:\n                    if (str9cmp(m, 'N', 'O', 'T', '_', 'F', 'O', 'U', 'N', 'D')) {\n                        r->type = MSG_RSP_MC_NOT_FOUND;\n                        break;\n                    }\n\n                    break;\n\n                case 10:\n                    if (str10cmp(m, 'N', 'O', 'T', '_', 'S', 'T', 'O', 'R', 'E', 'D')) {\n                        r->type = MSG_RSP_MC_NOT_STORED;\n                        break;\n                    }\n\n                    break;\n\n                case 12:\n                    if (str12cmp(m, 'C', 'L', 'I', 'E', 'N', 'T', '_', 'E', 'R', 'R', 'O', 'R')) {\n                        r->type = MSG_RSP_MC_CLIENT_ERROR;\n                        break;\n                    }\n\n                    if (str12cmp(m, 'S', 'E', 'R', 'V', 'E', 'R', '_', 'E', 'R', 'R', 'O', 'R')) {\n                        r->type = MSG_RSP_MC_SERVER_ERROR;\n                        break;\n                    }\n\n                    break;\n                }\n\n                switch (r->type) {\n                case MSG_UNKNOWN:\n                    goto error;\n\n                case MSG_RSP_MC_STORED:\n                case MSG_RSP_MC_NOT_STORED:\n                case MSG_RSP_MC_EXISTS:\n                case MSG_RSP_MC_NOT_FOUND:\n                case MSG_RSP_MC_DELETED:\n                case MSG_RSP_MC_TOUCHED:\n                    state = SW_CRLF;\n                    break;\n\n                case MSG_RSP_MC_END:\n                    state = SW_CRLF;\n                    break;\n\n                case MSG_RSP_MC_VALUE:\n                    state = SW_SPACES_BEFORE_KEY;\n                    break;\n\n                case MSG_RSP_MC_ERROR:\n                    state = SW_CRLF;\n                    break;\n\n                case MSG_RSP_MC_CLIENT_ERROR:\n                case MSG_RSP_MC_SERVER_ERROR:\n                case MSG_RSP_MC_VERSION:\n                    state = SW_RUNTO_CRLF;\n                    break;\n\n                default:\n                    NOT_REACHED();\n                }\n\n                p = p - 1; /* go back by 1 byte */\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_KEY:\n            if (ch != ' ') {\n                state = SW_KEY;\n                p = p - 1; /* go back by 1 byte */\n            }\n\n            break;\n\n        case SW_KEY:\n            if (ch == ' ') {\n                /* r->token = NULL; */\n                state = SW_SPACES_BEFORE_FLAGS;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_FLAGS:\n            if (ch != ' ') {\n                if (!isdigit(ch)) {\n                    goto error;\n                }\n                state = SW_FLAGS;\n                p = p - 1; /* go back by 1 byte */\n            }\n\n            break;\n\n        case SW_FLAGS:\n            if (r->token == NULL) {\n                /* flags_start <- p */\n                /* r->token = p; */\n            }\n\n            if (isdigit(ch)) {\n                /* flags <- flags * 10 + (ch - '0') */\n                ;\n            } else if (ch == ' ') {\n                /* flags_end <- p - 1 */\n                /* r->token = NULL; */\n                state = SW_SPACES_BEFORE_VLEN;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_SPACES_BEFORE_VLEN:\n            if (ch != ' ') {\n                if (!isdigit(ch)) {\n                    goto error;\n                }\n                p = p - 1; /* go back by 1 byte */\n                state = SW_VLEN;\n                r->vlen = 0;\n            }\n\n            break;\n\n        case SW_VLEN:\n            if (isdigit(ch)) {\n                r->vlen = r->vlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == ' ' || ch == CR) {\n                /* vlen_end <- p - 1 */\n                p = p - 1; /* go back by 1 byte */\n                /* r->token = NULL; */\n                state = SW_RUNTO_CRLF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_RUNTO_VAL:\n            switch (ch) {\n            case LF:\n                /* val_start <- p + 1 */\n                state = SW_VAL;\n                r->token = NULL;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_VAL:\n            m = p + r->vlen;\n            if (m >= b->last) {\n                ASSERT(r->vlen >= (uint32_t)(b->last - p));\n                r->vlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m; /* move forward by vlen bytes */\n                break;\n            }\n            switch (*m) {\n            case CR:\n                /* val_end <- p - 1 */\n                p = m; /* move forward by vlen bytes */\n                state = SW_VAL_LF;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_VAL_LF:\n            switch (ch) {\n            case LF:\n                /* state = SW_END; */\n                state = SW_RSP_STR;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_END:\n            if (r->token == NULL) {\n                if (ch != 'E') {\n                    goto error;\n                }\n                /* end_start <- p */\n                r->token = p;\n            } else if (ch == CR) {\n                /* end_end <- p */\n                m = r->token;\n                r->token = NULL;\n\n                switch (p - m) {\n                case 3:\n                    if (str4cmp(m, 'E', 'N', 'D', '\\r')) {\n                        r->end = m;\n                        state = SW_ALMOST_DONE;\n                    }\n                    break;\n\n                default:\n                    goto error;\n                }\n            }\n\n            break;\n\n        case SW_RUNTO_CRLF:\n            switch (ch) {\n            case CR:\n                if (r->type == MSG_RSP_MC_VALUE) {\n                    state = SW_RUNTO_VAL;\n                } else {\n                    state = SW_ALMOST_DONE;\n                }\n\n                break;\n\n            default:\n                break;\n            }\n\n            break;\n\n        case SW_CRLF:\n            switch (ch) {\n            case ' ':\n                break;\n\n            case CR:\n                state = SW_ALMOST_DONE;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ALMOST_DONE:\n            switch (ch) {\n            case LF:\n                /* rsp_end <- p */\n                goto done;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_SENTINEL:\n        default:\n            NOT_REACHED();\n            break;\n\n        }\n    }\n\n    ASSERT(p == b->last);\n    r->pos = p;\n    r->state = state;\n\n    if (b->last == b->end && r->token != NULL) {\n        if (state <= SW_RUNTO_VAL || state == SW_CRLF || state == SW_ALMOST_DONE) {\n            r->state = SW_START;\n        }\n        r->pos = r->token;\n        r->token = NULL;\n        r->result = MSG_PARSE_REPAIR;\n    } else {\n        r->result = MSG_PARSE_AGAIN;\n    }\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed rsp %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\ndone:\n    ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL);\n    r->pos = p + 1;\n    ASSERT(r->pos <= b->last);\n    r->state = SW_START;\n    r->token = NULL;\n    r->result = MSG_PARSE_OK;\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed rsp %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\nerror:\n    r->result = MSG_PARSE_ERROR;\n    r->state = state;\n    errno = EINVAL;\n\n    log_hexdump(LOG_INFO, b->pos, mbuf_length(b), \"parsed bad rsp %\"PRIu64\" \"\n                \"res %d type %d state %d\", r->id, r->result, r->type,\n                r->state);\n}\n\nbool\nmemcache_failure(const struct msg *r)\n{\n    return false;\n}\n\nstatic rstatus_t\nmemcache_append_key(struct msg *r, const uint8_t *key, uint32_t keylen)\n{\n    struct mbuf *mbuf;\n    struct keypos *kpos;\n\n    mbuf = msg_ensure_mbuf(r, keylen + 2);\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n\n    kpos = array_push(r->keys);\n    if (kpos == NULL) {\n        return NC_ENOMEM;\n    }\n\n    kpos->start = mbuf->last;\n    kpos->end = mbuf->last + keylen;\n    mbuf_copy(mbuf, key, keylen);\n    r->mlen += keylen;\n\n    mbuf_copy(mbuf, (const uint8_t *)\" \", 1);\n    r->mlen += 1;\n    return NC_OK;\n}\n\n/*\n * read the comment in proto/nc_redis.c\n */\nstatic rstatus_t\nmemcache_fragment_retrieval(struct msg *r, uint32_t nserver,\n                            struct msg_tqh *frag_msgq,\n                            uint32_t key_step)\n{\n    struct mbuf *mbuf;\n    struct msg **sub_msgs;\n    uint32_t i;\n    rstatus_t status;\n\n    sub_msgs = nc_zalloc(nserver * sizeof(*sub_msgs));\n    if (sub_msgs == NULL) {\n        return NC_ENOMEM;\n    }\n\n    ASSERT(r->frag_seq == NULL);\n    r->frag_seq = nc_alloc(array_n(r->keys) * sizeof(*r->frag_seq));\n    if (r->frag_seq == NULL) {\n        nc_free(sub_msgs);\n        return NC_ENOMEM;\n    }\n\n    mbuf = STAILQ_FIRST(&r->mhdr);\n    mbuf->pos = mbuf->start;\n\n    /*\n     * This code is based on the assumption that 'gets ' is located\n     * in a contiguous location.\n     * This is always true because we have capped our MBUF_MIN_SIZE at 512 and\n     * whenever we have multiple messages, we copy the tail message into a new mbuf\n     */\n    for (; *(mbuf->pos) != ' ';) {          /* eat get/gets  */\n        mbuf->pos++;\n    }\n    mbuf->pos++;\n\n    r->frag_id = msg_gen_frag_id();\n    r->nfrag = 0;\n    r->frag_owner = r;\n\n    /* Build up the key1 key2 ... to be sent to a given server at index idx */\n    for (i = 0; i < array_n(r->keys); i++) {        /* for each  key */\n        struct msg *sub_msg;\n        struct keypos *kpos = array_get(r->keys, i);\n        uint32_t idx = msg_backend_idx(r, kpos->start, kpos->end - kpos->start);\n        ASSERT(idx < nserver);\n\n        if (sub_msgs[idx] == NULL) {\n            sub_msgs[idx] = msg_get(r->owner, r->request, r->redis);\n            if (sub_msgs[idx] == NULL) {\n                nc_free(sub_msgs);\n                return NC_ENOMEM;\n            }\n        }\n        r->frag_seq[i] = sub_msg = sub_msgs[idx];\n\n        sub_msg->narg++;\n        status = memcache_append_key(sub_msg, kpos->start, kpos->end - kpos->start);\n        if (status != NC_OK) {\n            nc_free(sub_msgs);\n            return status;\n        }\n    }\n    /*\n     * prepend mget header, and forward the get[s] key1 key2\\r\\n\n     * to the corresponding server(s)\n     */\n    for (i = 0; i < nserver; i++) {\n        struct msg *sub_msg = sub_msgs[i];\n        if (sub_msg == NULL) {\n            continue;\n        }\n\n        /* prepend get/gets */\n        if (r->type == MSG_REQ_MC_GET) {\n            status = msg_prepend(sub_msg, (const uint8_t *)\"get \", 4);\n        } else if (r->type == MSG_REQ_MC_GETS) {\n            status = msg_prepend(sub_msg, (const uint8_t *)\"gets \", 5);\n        }\n        if (status != NC_OK) {\n            nc_free(sub_msgs);\n            return status;\n        }\n\n        /* append \\r\\n */\n        status = msg_append(sub_msg, (const uint8_t *)CRLF, CRLF_LEN);\n        if (status != NC_OK) {\n            nc_free(sub_msgs);\n            return status;\n        }\n\n        sub_msg->type = r->type;\n        sub_msg->frag_id = r->frag_id;\n        sub_msg->frag_owner = r->frag_owner;\n\n        TAILQ_INSERT_TAIL(frag_msgq, sub_msg, m_tqe);\n        r->nfrag++;\n    }\n\n    nc_free(sub_msgs);\n    return NC_OK;\n}\n\nrstatus_t\nmemcache_fragment(struct msg *r, uint32_t nserver, struct msg_tqh *frag_msgq)\n{\n    if (memcache_should_fragment(r)) {\n        return memcache_fragment_retrieval(r, nserver, frag_msgq, 1);\n    }\n    return NC_OK;\n}\n\n/*\n * Pre-coalesce handler is invoked when the message is a response to\n * the fragmented multi vector request - 'get' or 'gets' and all the\n * responses to the fragmented request vector hasn't been received\n */\nvoid\nmemcache_pre_coalesce(struct msg *r)\n{\n    struct msg *pr = r->peer; /* peer request */\n    struct mbuf *mbuf;\n\n    ASSERT(!r->request);\n    ASSERT(pr->request);\n\n    if (pr->frag_id == 0) {\n        /* do nothing, if not a response to a fragmented request */\n        return;\n    }\n\n    pr->frag_owner->nfrag_done++;\n    switch (r->type) {\n\n    case MSG_RSP_MC_VALUE:\n    case MSG_RSP_MC_END:\n\n        /*\n         * Readjust responses of the fragmented message vector by not\n         * including the end marker for all\n         */\n\n        ASSERT(r->end != NULL);\n\n        for (;;) {\n            mbuf = STAILQ_LAST(&r->mhdr, mbuf, next);\n            ASSERT(mbuf != NULL);\n\n            /*\n             * We cannot assert that end marker points to the last mbuf\n             * Consider a scenario where end marker points to the\n             * penultimate mbuf and the last mbuf only contains spaces\n             * and CRLF: mhdr -> [...END] -> [\\r\\n]\n             */\n\n            if (r->end >= mbuf->pos && r->end < mbuf->last) {\n                /* end marker is within this mbuf */\n                r->mlen -= (uint32_t)(mbuf->last - r->end);\n                mbuf->last = r->end;\n                break;\n            }\n\n            /* end marker is not in this mbuf */\n            r->mlen -= mbuf_length(mbuf);\n            mbuf_remove(&r->mhdr, mbuf);\n            mbuf_put(mbuf);\n        }\n\n        break;\n\n    default:\n        /*\n         * Valid responses for a fragmented requests are MSG_RSP_MC_VALUE or,\n         * MSG_RSP_MC_END. For an invalid response, we send out SERVER_ERRROR\n         * with EINVAL errno\n         */\n        mbuf = STAILQ_FIRST(&r->mhdr);\n        log_hexdump(LOG_ERR, mbuf->pos, mbuf_length(mbuf), \"rsp fragment \"\n                    \"with unknown type %d\", r->type);\n        pr->error = 1;\n        pr->err = EINVAL;\n        break;\n    }\n}\n\n/*\n * Copy one response from src to dst and return bytes copied\n */\nstatic rstatus_t\nmemcache_copy_bulk(struct msg *dst, struct msg *src)\n{\n    struct mbuf *mbuf, *nbuf;\n    const uint8_t *p;\n    const uint8_t *last;\n    uint32_t len = 0;\n    uint32_t bytes = 0;\n    uint32_t i = 0;\n\n    for (mbuf = STAILQ_FIRST(&src->mhdr);\n         mbuf && mbuf_empty(mbuf);\n         mbuf = STAILQ_FIRST(&src->mhdr)) {\n\n        mbuf_remove(&src->mhdr, mbuf);\n        mbuf_put(mbuf);\n    }\n\n    mbuf = STAILQ_FIRST(&src->mhdr);\n    if (mbuf == NULL) {\n        return NC_OK;           /* key not exists */\n    }\n    p = mbuf->pos;\n    last = mbuf->last;\n\n    /*\n     * get : VALUE key flags len\\r\\nval\\r\\n\n     * gets: VALUE key flags len cas\\r\\nval\\r\\n\n     */\n    ASSERT(*p == 'V');\n    i = 0;\n    while (p < last) { /*  eat 'VALUE key flags '  */\n        if (*p == ' ') {\n            i++;\n            if (i >= 3) {\n                p++;\n                break;\n            }\n        }\n        p++;\n    }\n\n    len = 0;\n    for (; p < last && isdigit(*p); p++) {\n        len = len * 10 + (uint32_t)(*p - '0');\n    }\n\n    for (; p < last && ('\\r' != *p); p++) { /* eat cas for gets */\n        ;\n    }\n    /* \"*p\" should be pointing to '\\r' */\n\n    len += CRLF_LEN * 2;\n    len += (p - mbuf->pos);\n\n    if (p >= last) {\n        log_error(\"Saw memcache value response where header was not \"\n                  \"parsed or header length %d unexpectedly exceeded mbuf size limit\",\n                  (int)(p - mbuf->pos));\n        return NC_ERROR;\n    }\n\n\n    bytes = len;\n\n    /* copy len bytes to dst */\n    for (; mbuf;) {\n        if (mbuf_length(mbuf) <= len) {   /* steal this mbuf from src to dst */\n            nbuf = STAILQ_NEXT(mbuf, next);\n            mbuf_remove(&src->mhdr, mbuf);\n            mbuf_insert(&dst->mhdr, mbuf);\n            len -= mbuf_length(mbuf);\n            mbuf = nbuf;\n        } else {                        /* split it */\n            nbuf = mbuf_get();\n            if (nbuf == NULL) {\n                return NC_ENOMEM;\n            }\n            mbuf_copy(nbuf, mbuf->pos, len);\n            mbuf_insert(&dst->mhdr, nbuf);\n            mbuf->pos += len;\n            break;\n        }\n    }\n\n    dst->mlen += bytes;\n    src->mlen -= bytes;\n    log_debug(LOG_VVERB, \"memcache_copy_bulk copy bytes: %d\", bytes);\n    return NC_OK;\n}\n\n/*\n * Post-coalesce handler is invoked when the message is a response to\n * the fragmented multi vector request - 'get' or 'gets' and all the\n * responses to the fragmented request vector has been received and\n * the fragmented request is consider to be done\n */\nvoid\nmemcache_post_coalesce(struct msg *request)\n{\n    struct msg *response = request->peer;\n    struct msg *sub_msg;\n    uint32_t i;\n    rstatus_t status;\n\n    ASSERT(!response->request);\n    ASSERT(request->request && (request->frag_owner == request));\n    if (request->error || request->ferror) {\n        response->owner->err = 1;\n        return;\n    }\n\n    for (i = 0; i < array_n(request->keys); i++) {      /* for each key */\n        sub_msg = request->frag_seq[i]->peer;           /* get its peer response */\n        if (sub_msg == NULL) {\n            response->owner->err = 1;\n            return;\n        }\n        status = memcache_copy_bulk(response, sub_msg);\n        if (status != NC_OK) {\n            response->owner->err = 1;\n            return;\n        }\n    }\n\n    /* append END\\r\\n */\n    status = msg_append(response, (const uint8_t *)\"END\\r\\n\", 5);\n    if (status != NC_OK) {\n        response->owner->err = 1;\n        return;\n    }\n}\n\nvoid\nmemcache_post_connect(struct context *ctx, struct conn *conn, struct server *server)\n{\n}\n\nvoid\nmemcache_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg)\n{\n}\n\nrstatus_t\nmemcache_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn)\n{\n    NOT_REACHED();\n    return NC_OK;\n}\n\nrstatus_t\nmemcache_reply(struct msg *r)\n{\n    NOT_REACHED();\n    return NC_OK;\n}\n\n"
  },
  {
    "path": "src/proto/nc_proto.h",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef _NC_PROTO_H_\n#define _NC_PROTO_H_\n\n#include <nc_core.h>\n\n#ifdef NC_LITTLE_ENDIAN\n\n#define str4cmp(m, c0, c1, c2, c3)                                                          \\\n    (*(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0))\n\n#define str5cmp(m, c0, c1, c2, c3, c4)                                                      \\\n    (str4cmp(m, c0, c1, c2, c3) && (m[4] == c4))\n\n#define str6cmp(m, c0, c1, c2, c3, c4, c5)                                                  \\\n    (str4cmp(m, c0, c1, c2, c3) &&                                                          \\\n        (((uint32_t *) m)[1] & 0xffff) == ((c5 << 8) | c4))\n\n#define str7cmp(m, c0, c1, c2, c3, c4, c5, c6)                                              \\\n    (str6cmp(m, c0, c1, c2, c3, c4, c5) && (m[6] == c6))\n\n#define str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                                          \\\n    (str4cmp(m, c0, c1, c2, c3) &&                                                          \\\n        (((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)))\n\n#define str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                                      \\\n    (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && m[8] == c8)\n\n#define str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)                                 \\\n    (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) &&                                          \\\n        (((uint32_t *) m)[2] & 0xffff) == ((c9 << 8) | c8))\n\n#define str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)                            \\\n    (str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && (m[10] == c10))\n\n#define str12cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)                       \\\n    (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) &&                                          \\\n        (((uint32_t *) m)[2] == ((c11 << 24) | (c10 << 16) | (c9 << 8) | c8)))\n\n#else\n\n#define str4cmp(m, c0, c1, c2, c3)                                                          \\\n    (m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3)\n\n#define str5cmp(m, c0, c1, c2, c3, c4)                                                      \\\n    (str4cmp(m, c0, c1, c2, c3) && (m[4] == c4))\n\n#define str6cmp(m, c0, c1, c2, c3, c4, c5)                                                  \\\n    (str5cmp(m, c0, c1, c2, c3, c4) && m[5] == c5)\n\n#define str7cmp(m, c0, c1, c2, c3, c4, c5, c6)                                              \\\n    (str6cmp(m, c0, c1, c2, c3, c4, c5) && m[6] == c6)\n\n#define str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                                          \\\n    (str7cmp(m, c0, c1, c2, c3, c4, c5, c6) && m[7] == c7)\n\n#define str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                                      \\\n    (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && m[8] == c8)\n\n#define str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)                                 \\\n    (str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && m[9] == c9)\n\n#define str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)                            \\\n    (str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && m[10] == c10)\n\n#define str12cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)                       \\\n    (str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && m[11] == c11)\n\n#endif\n\n#define str3icmp(m, c0, c1, c2)                                                             \\\n    ((m[0] == c0 || m[0] == (c0 ^ 0x20)) &&                                                 \\\n     (m[1] == c1 || m[1] == (c1 ^ 0x20)) &&                                                 \\\n     (m[2] == c2 || m[2] == (c2 ^ 0x20)))\n\n#define str4icmp(m, c0, c1, c2, c3)                                                         \\\n    (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20)))\n\n#define str5icmp(m, c0, c1, c2, c3, c4)                                                     \\\n    (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20)))\n\n#define str6icmp(m, c0, c1, c2, c3, c4, c5)                                                 \\\n    (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20)))\n\n#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6)                                             \\\n    (str6icmp(m, c0, c1, c2, c3, c4, c5) &&                                                 \\\n     (m[6] == c6 || m[6] == (c6 ^ 0x20)))\n\n#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                                         \\\n    (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) &&                                             \\\n     (m[7] == c7 || m[7] == (c7 ^ 0x20)))\n\n#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                                     \\\n    (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) &&                                         \\\n     (m[8] == c8 || m[8] == (c8 ^ 0x20)))\n\n#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)                                \\\n    (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) &&                                     \\\n     (m[9] == c9 || m[9] == (c9 ^ 0x20)))\n\n#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)                           \\\n    (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) &&                                \\\n     (m[10] == c10 || m[10] == (c10 ^ 0x20)))\n\n#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)                      \\\n    (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) &&                           \\\n     (m[11] == c11 || m[11] == (c11 ^ 0x20)))\n\n#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12)                 \\\n    (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) &&                      \\\n     (m[12] == c12 || m[12] == (c12 ^ 0x20)))\n\n#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13)            \\\n    (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) &&                 \\\n     (m[13] == c13 || m[13] == (c13 ^ 0x20)))\n\n#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14)       \\\n    (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) &&            \\\n     (m[14] == c14 || m[14] == (c14 ^ 0x20)))\n\n#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15)  \\\n    (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) &&       \\\n     (m[15] == c15 || m[15] == (c15 ^ 0x20)))\n\n#define str17icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16)  \\\n    (str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) &&       \\\n     (m[16] == c16 || m[16] == (c16 ^ 0x20)))\n\nvoid memcache_parse_req(struct msg *r);\nvoid memcache_parse_rsp(struct msg *r);\nbool memcache_failure(const struct msg *r);\nvoid memcache_pre_coalesce(struct msg *r);\nvoid memcache_post_coalesce(struct msg *r);\nrstatus_t memcache_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn);\nrstatus_t memcache_fragment(struct msg *r, uint32_t nserver, struct msg_tqh *frag_msgq);\nrstatus_t memcache_reply(struct msg *r);\nvoid memcache_post_connect(struct context *ctx, struct conn *conn, struct server *server);\nvoid memcache_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg);\n\nvoid redis_parse_req(struct msg *r);\nvoid redis_parse_rsp(struct msg *r);\nbool redis_failure(const struct msg *r);\nvoid redis_pre_coalesce(struct msg *r);\nvoid redis_post_coalesce(struct msg *r);\nrstatus_t redis_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn);\nrstatus_t redis_fragment(struct msg *r, uint32_t nserver, struct msg_tqh *frag_msgq);\nrstatus_t redis_reply(struct msg *r);\nvoid redis_post_connect(struct context *ctx, struct conn *conn, struct server *server);\nvoid redis_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg);\n\n#endif\n"
  },
  {
    "path": "src/proto/nc_redis.c",
    "content": "/*\n * twemproxy - A fast and lightweight proxy for memcached protocol.\n * Copyright (C) 2011 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <ctype.h>\n#include <math.h>\n\n#include <nc_core.h>\n#include <nc_proto.h>\n\n#define RSP_STRING(ACTION)                                                          \\\n    ACTION( ok,               \"+OK\\r\\n\"                                           ) \\\n    ACTION( pong,             \"+PONG\\r\\n\"                                         ) \\\n    ACTION( invalid_password, \"-ERR invalid password\\r\\n\"                         ) \\\n    ACTION( auth_required,    \"-NOAUTH Authentication required\\r\\n\"               ) \\\n    ACTION( no_password,      \"-ERR Client sent AUTH, but no password is set\\r\\n\" ) \\\n\n#define DEFINE_ACTION(_var, _str) static struct string rsp_##_var = string(_str);\n    RSP_STRING( DEFINE_ACTION )\n#undef DEFINE_ACTION\n\nstatic rstatus_t redis_handle_auth_req(struct msg *request, struct msg *response);\n\n/*\n * Return true, if the redis command take no key, otherwise\n * return false\n */\nstatic bool\nredis_argz(const struct msg *r)\n{\n    switch (r->type) {\n    /* TODO: PING has an optional argument, emulate that? */\n    case MSG_REQ_REDIS_PING:\n    case MSG_REQ_REDIS_QUIT:\n    case MSG_REQ_REDIS_COMMAND:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command accepts no arguments, otherwise\n * return false\n */\nstatic bool\nredis_arg0(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_PERSIST:\n    case MSG_REQ_REDIS_PTTL:\n    case MSG_REQ_REDIS_TTL:\n    case MSG_REQ_REDIS_TYPE:\n    case MSG_REQ_REDIS_DUMP:\n\n    case MSG_REQ_REDIS_DECR:\n    case MSG_REQ_REDIS_GET:\n    case MSG_REQ_REDIS_GETDEL:\n    case MSG_REQ_REDIS_INCR:\n    case MSG_REQ_REDIS_STRLEN:\n\n    case MSG_REQ_REDIS_HGETALL:\n    case MSG_REQ_REDIS_HKEYS:\n    case MSG_REQ_REDIS_HLEN:\n    case MSG_REQ_REDIS_HVALS:\n\n    case MSG_REQ_REDIS_LLEN:\n\n    case MSG_REQ_REDIS_SCARD:\n    case MSG_REQ_REDIS_SMEMBERS:\n\n    case MSG_REQ_REDIS_ZCARD:\n        /* TODO: Support emulating 2-arg username+password auth by just checking password? */\n    case MSG_REQ_REDIS_AUTH:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command accepts exactly 1 argument, otherwise\n * return false\n */\nstatic bool\nredis_arg1(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_EXPIRE:\n    case MSG_REQ_REDIS_EXPIREAT:\n    case MSG_REQ_REDIS_PEXPIRE:\n    case MSG_REQ_REDIS_PEXPIREAT:\n    case MSG_REQ_REDIS_MOVE:\n\n    case MSG_REQ_REDIS_APPEND:\n    case MSG_REQ_REDIS_DECRBY:\n    case MSG_REQ_REDIS_GETBIT:\n    case MSG_REQ_REDIS_GETSET:\n    case MSG_REQ_REDIS_INCRBY:\n    case MSG_REQ_REDIS_INCRBYFLOAT:\n    case MSG_REQ_REDIS_SETNX:\n\n    case MSG_REQ_REDIS_HEXISTS:\n    case MSG_REQ_REDIS_HGET:\n    case MSG_REQ_REDIS_HSTRLEN:\n\n    case MSG_REQ_REDIS_LINDEX:\n    case MSG_REQ_REDIS_RPOPLPUSH:\n\n    case MSG_REQ_REDIS_SISMEMBER:\n\n    case MSG_REQ_REDIS_ZRANK:\n    case MSG_REQ_REDIS_ZREVRANK:\n    case MSG_REQ_REDIS_ZSCORE:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command accepts exactly 2 arguments, otherwise\n * return false\n */\nstatic bool\nredis_arg2(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_GETRANGE:\n    case MSG_REQ_REDIS_PSETEX:\n    case MSG_REQ_REDIS_SETBIT:\n    case MSG_REQ_REDIS_SETEX:\n    case MSG_REQ_REDIS_SETRANGE:\n\n    case MSG_REQ_REDIS_HINCRBY:\n    case MSG_REQ_REDIS_HINCRBYFLOAT:\n    case MSG_REQ_REDIS_HSETNX:\n\n    case MSG_REQ_REDIS_LRANGE:\n    case MSG_REQ_REDIS_LREM:\n    case MSG_REQ_REDIS_LSET:\n    case MSG_REQ_REDIS_LTRIM:\n\n    case MSG_REQ_REDIS_SMOVE:\n\n    case MSG_REQ_REDIS_ZCOUNT:\n    case MSG_REQ_REDIS_ZLEXCOUNT:\n    case MSG_REQ_REDIS_ZINCRBY:\n    case MSG_REQ_REDIS_ZREMRANGEBYLEX:\n    case MSG_REQ_REDIS_ZREMRANGEBYRANK:\n    case MSG_REQ_REDIS_ZREMRANGEBYSCORE:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command accepts exactly 3 arguments, otherwise\n * return false\n */\nstatic bool\nredis_arg3(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_LINSERT:\n    case MSG_REQ_REDIS_LMOVE:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command operates on one key and accepts 0 or more arguments, otherwise\n * return false\n */\nstatic bool\nredis_argn(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_SORT:\n    case MSG_REQ_REDIS_COPY:\n\n    case MSG_REQ_REDIS_BITCOUNT:\n    case MSG_REQ_REDIS_BITPOS:\n    case MSG_REQ_REDIS_BITFIELD:\n        /* TODO: Support REDIS_BITOP operation destkey key ... and add tests - this requires handling key in a position other than the first one */\n\n    case MSG_REQ_REDIS_EXISTS:\n    case MSG_REQ_REDIS_GETEX:\n    case MSG_REQ_REDIS_SET:\n\n    case MSG_REQ_REDIS_HDEL:\n    case MSG_REQ_REDIS_HMGET:\n    case MSG_REQ_REDIS_HMSET:\n    case MSG_REQ_REDIS_HSCAN:\n    case MSG_REQ_REDIS_HSET:\n    case MSG_REQ_REDIS_HRANDFIELD:\n\n    case MSG_REQ_REDIS_LPUSH:\n    case MSG_REQ_REDIS_LPUSHX:\n    case MSG_REQ_REDIS_RPUSH:\n    case MSG_REQ_REDIS_RPUSHX:\n    case MSG_REQ_REDIS_LPOP:\n    case MSG_REQ_REDIS_RPOP:\n    case MSG_REQ_REDIS_LPOS:\n\n    case MSG_REQ_REDIS_SADD:\n    case MSG_REQ_REDIS_SDIFF:\n    case MSG_REQ_REDIS_SDIFFSTORE:\n    case MSG_REQ_REDIS_SINTER:\n    case MSG_REQ_REDIS_SINTERSTORE:\n    case MSG_REQ_REDIS_SREM:\n    case MSG_REQ_REDIS_SUNION:\n    case MSG_REQ_REDIS_SUNIONSTORE:\n    case MSG_REQ_REDIS_SRANDMEMBER:\n    case MSG_REQ_REDIS_SSCAN:\n    case MSG_REQ_REDIS_SPOP:\n    case MSG_REQ_REDIS_SMISMEMBER:\n\n    case MSG_REQ_REDIS_PFADD:\n    case MSG_REQ_REDIS_PFMERGE:\n    case MSG_REQ_REDIS_PFCOUNT:\n\n    case MSG_REQ_REDIS_ZADD:\n    case MSG_REQ_REDIS_ZDIFF:\n    case MSG_REQ_REDIS_ZDIFFSTORE:\n    case MSG_REQ_REDIS_ZINTER:\n    case MSG_REQ_REDIS_ZINTERSTORE:\n    case MSG_REQ_REDIS_ZMSCORE:\n    case MSG_REQ_REDIS_ZPOPMAX:\n    case MSG_REQ_REDIS_ZPOPMIN:\n    case MSG_REQ_REDIS_ZRANDMEMBER:\n    case MSG_REQ_REDIS_ZRANGE:\n    case MSG_REQ_REDIS_ZRANGEBYLEX:\n    case MSG_REQ_REDIS_ZRANGEBYSCORE:\n    case MSG_REQ_REDIS_ZRANGESTORE:\n    case MSG_REQ_REDIS_ZREM:\n    case MSG_REQ_REDIS_ZREVRANGE:\n    case MSG_REQ_REDIS_ZREVRANGEBYLEX:\n    case MSG_REQ_REDIS_ZREVRANGEBYSCORE:\n    case MSG_REQ_REDIS_ZSCAN:\n    case MSG_REQ_REDIS_ZUNION:\n    case MSG_REQ_REDIS_ZUNIONSTORE:\n\n    case MSG_REQ_REDIS_GEODIST:\n    case MSG_REQ_REDIS_GEOPOS:\n    case MSG_REQ_REDIS_GEOHASH:\n    case MSG_REQ_REDIS_GEOADD:\n    case MSG_REQ_REDIS_GEORADIUS:\n    case MSG_REQ_REDIS_GEORADIUSBYMEMBER:\n    case MSG_REQ_REDIS_GEOSEARCH:\n    case MSG_REQ_REDIS_GEOSEARCHSTORE:\n\n    case MSG_REQ_REDIS_RESTORE:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command is a vector command accepting one or\n * more keys, otherwise return false\n */\nstatic bool\nredis_argx(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_MGET:\n    case MSG_REQ_REDIS_DEL:\n    case MSG_REQ_REDIS_UNLINK:\n    case MSG_REQ_REDIS_TOUCH:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command is a vector command accepting one or\n * more key-value pairs, otherwise return false\n */\nstatic bool\nredis_argkvx(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_MSET:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis command is either EVAL or EVALSHA. These commands\n * have a special format with exactly 2 arguments, followed by one or more keys,\n * followed by zero or more arguments (the documentation online seems to suggest\n * that at least one argument is required, but that shouldn't be the case).\n */\nstatic bool\nredis_argeval(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_EVAL:\n    case MSG_REQ_REDIS_EVALSHA:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\nstatic bool\nredis_nokey(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_REQ_REDIS_LOLWUT:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Return true, if the redis response is an error response i.e. a simple\n * string whose first character is '-', otherwise return false.\n */\nstatic bool\nredis_error(const struct msg *r)\n{\n    switch (r->type) {\n    case MSG_RSP_REDIS_ERROR:\n    case MSG_RSP_REDIS_ERROR_ERR:\n    case MSG_RSP_REDIS_ERROR_OOM:\n    case MSG_RSP_REDIS_ERROR_BUSY:\n    case MSG_RSP_REDIS_ERROR_NOAUTH:\n    case MSG_RSP_REDIS_ERROR_LOADING:\n    case MSG_RSP_REDIS_ERROR_BUSYKEY:\n    case MSG_RSP_REDIS_ERROR_MISCONF:\n    case MSG_RSP_REDIS_ERROR_NOSCRIPT:\n    case MSG_RSP_REDIS_ERROR_READONLY:\n    case MSG_RSP_REDIS_ERROR_WRONGTYPE:\n    case MSG_RSP_REDIS_ERROR_EXECABORT:\n    case MSG_RSP_REDIS_ERROR_MASTERDOWN:\n    case MSG_RSP_REDIS_ERROR_NOREPLICAS:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * Reference: http://redis.io/topics/protocol\n *\n * Redis >= 1.2 uses the unified protocol to send requests to the Redis\n * server. In the unified protocol all the arguments sent to the server\n * are binary safe and every request has the following general form:\n *\n *   *<number of arguments> CR LF\n *   $<number of bytes of argument 1> CR LF\n *   <argument data> CR LF\n *   ...\n *   $<number of bytes of argument N> CR LF\n *   <argument data> CR LF\n *\n * Before the unified request protocol, redis protocol for requests supported\n * the following commands\n * 1). Inline commands: simple commands where arguments are just space\n *     separated strings. No binary safeness is possible.\n * 2). Bulk commands: bulk commands are exactly like inline commands, but\n *     the last argument is handled in a special way in order to allow for\n *     a binary-safe last argument.\n *\n * Nutcracker only supports the Redis unified protocol for requests.\n */\nvoid\nredis_parse_req(struct msg *r)\n{\n    struct mbuf *b;\n    uint8_t *p, *m;\n    uint8_t ch;\n    enum {\n        SW_START,\n        SW_NARG,\n        SW_NARG_LF,\n        SW_REQ_TYPE_LEN,\n        SW_REQ_TYPE_LEN_LF,\n        SW_REQ_TYPE,\n        SW_REQ_TYPE_LF,\n        SW_KEY_LEN,\n        SW_KEY_LEN_LF,\n        SW_KEY,\n        SW_KEY_LF,\n        SW_ARG1_LEN,\n        SW_ARG1_LEN_LF,\n        SW_ARG1,\n        SW_ARG1_LF,\n        SW_ARG2_LEN,\n        SW_ARG2_LEN_LF,\n        SW_ARG2,\n        SW_ARG2_LF,\n        SW_ARG3_LEN,\n        SW_ARG3_LEN_LF,\n        SW_ARG3,\n        SW_ARG3_LF,\n        SW_ARGN_LEN,\n        SW_ARGN_LEN_LF,\n        SW_ARGN,\n        SW_ARGN_LF,\n        SW_SENTINEL\n    } state;\n\n    state = r->state;\n    b = STAILQ_LAST(&r->mhdr, mbuf, next);\n\n    ASSERT(r->request);\n    ASSERT(state >= SW_START && state < SW_SENTINEL);\n    ASSERT(b != NULL);\n    ASSERT(b->pos <= b->last);\n\n    /* validate the parsing maker */\n    ASSERT(r->pos != NULL);\n    ASSERT(r->pos >= b->pos && r->pos <= b->last);\n\n    for (p = r->pos; p < b->last; p++) {\n        ch = *p;\n\n        switch (state) {\n\n        case SW_START:\n            ASSERT(r->token == NULL);\n            if (ch != '*') {\n                /* redis commands are always arrays */\n                goto error;\n            }\n            r->token = p;\n            /* req_start <- p */\n            r->narg_start = p;\n            r->rnarg = 0;\n            state = SW_NARG;\n\n            break;\n\n        case SW_NARG:\n            /* SW_NARG: The number of arguments in the redis command array */\n            ASSERT(r->token != NULL);\n            if (isdigit(ch)) {\n                r->rnarg = r->rnarg * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if (r->rnarg == 0) {\n                    goto error;\n                }\n                r->narg = r->rnarg;\n                r->narg_end = p;\n                r->token = NULL;\n                state = SW_NARG_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_NARG_LF:\n            switch (ch) {\n            case LF:\n                state = SW_REQ_TYPE_LEN;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_REQ_TYPE_LEN:\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                r->token = p;\n                r->rlen = 0;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if (r->rlen == 0 || r->rnarg == 0) {\n                    goto error;\n                }\n                r->rnarg--;\n                r->token = NULL;\n                state = SW_REQ_TYPE_LEN_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_REQ_TYPE_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_REQ_TYPE;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_REQ_TYPE:\n            if (r->token == NULL) {\n                r->token = p;\n            }\n\n            m = r->token + r->rlen;\n            if (m >= b->last) {\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p = m; /* move forward by rlen bytes */\n            r->rlen = 0;\n            m = r->token;\n            r->token = NULL;\n            r->type = MSG_UNKNOWN;\n\n            switch (p - m) {\n\n            case 3:\n                if (str3icmp(m, 'g', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_GET;\n                    break;\n                }\n\n                if (str3icmp(m, 's', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_SET;\n                    break;\n                }\n\n                if (str3icmp(m, 't', 't', 'l')) {\n                    r->type = MSG_REQ_REDIS_TTL;\n                    break;\n                }\n\n                if (str3icmp(m, 'd', 'e', 'l')) {\n                    r->type = MSG_REQ_REDIS_DEL;\n                    break;\n                }\n\n                break;\n\n            case 4:\n                if (str4icmp(m, 'p', 't', 't', 'l')) {\n                    r->type = MSG_REQ_REDIS_PTTL;\n                    break;\n                }\n\n                if (str4icmp(m, 'd', 'e', 'c', 'r')) {\n                    r->type = MSG_REQ_REDIS_DECR;\n                    break;\n                }\n\n                if (str4icmp(m, 'd', 'u', 'm', 'p')) {\n                    r->type = MSG_REQ_REDIS_DUMP;\n                    break;\n                }\n\n                if (str4icmp(m, 'h', 'd', 'e', 'l')) {\n                    r->type = MSG_REQ_REDIS_HDEL;\n                    break;\n                }\n\n                if (str4icmp(m, 'h', 'g', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_HGET;\n                    break;\n                }\n\n                if (str4icmp(m, 'h', 'l', 'e', 'n')) {\n                    r->type = MSG_REQ_REDIS_HLEN;\n                    break;\n                }\n\n                if (str4icmp(m, 'h', 's', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_HSET;\n                    break;\n                }\n\n                if (str4icmp(m, 'i', 'n', 'c', 'r')) {\n                    r->type = MSG_REQ_REDIS_INCR;\n                    break;\n                }\n\n                if (str4icmp(m, 'l', 'l', 'e', 'n')) {\n                    r->type = MSG_REQ_REDIS_LLEN;\n                    break;\n                }\n\n                if (str4icmp(m, 'l', 'p', 'o', 'p')) {\n                    r->type = MSG_REQ_REDIS_LPOP;\n                    break;\n                }\n\n                if (str4icmp(m, 'l', 'p', 'o', 's')) {\n                    r->type = MSG_REQ_REDIS_LPOS;\n                    break;\n                }\n\n                if (str4icmp(m, 'l', 'r', 'e', 'm')) {\n                    r->type = MSG_REQ_REDIS_LREM;\n                    break;\n                }\n\n                if (str4icmp(m, 'l', 's', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_LSET;\n                    break;\n                }\n\n                if (str4icmp(m, 'r', 'p', 'o', 'p')) {\n                    r->type = MSG_REQ_REDIS_RPOP;\n                    break;\n                }\n\n                if (str4icmp(m, 's', 'a', 'd', 'd')) {\n                    r->type = MSG_REQ_REDIS_SADD;\n                    break;\n                }\n\n                if (str4icmp(m, 's', 'p', 'o', 'p')) {\n                    r->type = MSG_REQ_REDIS_SPOP;\n                    break;\n                }\n\n                if (str4icmp(m, 's', 'r', 'e', 'm')) {\n                    r->type = MSG_REQ_REDIS_SREM;\n                    break;\n                }\n\n                if (str4icmp(m, 't', 'y', 'p', 'e')) {\n                    r->type = MSG_REQ_REDIS_TYPE;\n                    break;\n                }\n\n                if (str4icmp(m, 'm', 'g', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_MGET;\n                    break;\n                }\n                if (str4icmp(m, 'm', 's', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_MSET;\n                    break;\n                }\n\n                if (str4icmp(m, 'z', 'a', 'd', 'd')) {\n                    r->type = MSG_REQ_REDIS_ZADD;\n                    break;\n                }\n\n                if (str4icmp(m, 'z', 'r', 'e', 'm')) {\n                    r->type = MSG_REQ_REDIS_ZREM;\n                    break;\n                }\n\n                if (str4icmp(m, 'e', 'v', 'a', 'l')) {\n                    r->type = MSG_REQ_REDIS_EVAL;\n                    break;\n                }\n\n                if (str4icmp(m, 's', 'o', 'r', 't')) {\n                    r->type = MSG_REQ_REDIS_SORT;\n                    break;\n                }\n\n                if (str4icmp(m, 'p', 'i', 'n', 'g')) {\n                    r->type = MSG_REQ_REDIS_PING;\n                    r->noforward = 1;\n                    break;\n                }\n\n                if (str4icmp(m, 'q', 'u', 'i', 't')) {\n                    r->type = MSG_REQ_REDIS_QUIT;\n                    r->quit = 1;\n                    break;\n                }\n\n                if (str4icmp(m, 'a', 'u', 't', 'h')) {\n                    r->type = MSG_REQ_REDIS_AUTH;\n                    r->noforward = 1;\n                    break;\n                }\n\n                if (str4icmp(m, 'm', 'o', 'v', 'e')) {\n                    r->type = MSG_REQ_REDIS_MOVE;\n                    r->noforward = 1;\n                    break;\n                }\n\n                if (str4icmp(m, 'c', 'o', 'p', 'y')) {\n                    r->type = MSG_REQ_REDIS_COPY;\n                    break;\n                }\n\n                break;\n\n            case 5:\n                if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) {\n                    r->type = MSG_REQ_REDIS_HKEYS;\n                    break;\n                }\n\n                if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_HMGET;\n                    break;\n                }\n\n                if (str5icmp(m, 'h', 'm', 's', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_HMSET;\n                    break;\n                }\n\n                if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) {\n                    r->type = MSG_REQ_REDIS_HVALS;\n                    break;\n                }\n\n                if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) {\n                    r->type = MSG_REQ_REDIS_HSCAN;\n                    break;\n                }\n\n                if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) {\n                    r->type = MSG_REQ_REDIS_LPUSH;\n                    break;\n                }\n\n                if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) {\n                    r->type = MSG_REQ_REDIS_LTRIM;\n                    break;\n                }\n\n                if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) {\n                    r->type = MSG_REQ_REDIS_RPUSH;\n                    break;\n                }\n\n                if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) {\n                    r->type = MSG_REQ_REDIS_SCARD;\n                    break;\n                }\n\n                if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) {\n                    r->type = MSG_REQ_REDIS_SDIFF;\n                    break;\n                }\n\n                if (str5icmp(m, 's', 'e', 't', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_SETEX;\n                    break;\n                }\n\n                if (str5icmp(m, 's', 'e', 't', 'n', 'x')) {\n                    r->type = MSG_REQ_REDIS_SETNX;\n                    break;\n                }\n\n                if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) {\n                    r->type = MSG_REQ_REDIS_SMOVE;\n                    break;\n                }\n\n                if (str5icmp(m, 's', 's', 'c', 'a', 'n')) {\n                    r->type = MSG_REQ_REDIS_SSCAN;\n                    break;\n                }\n\n                if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) {\n                    r->type = MSG_REQ_REDIS_ZCARD;\n                    break;\n                }\n\n                if (str5icmp(m, 'z', 'd', 'i', 'f', 'f')) {\n                    r->type = MSG_REQ_REDIS_ZDIFF;\n                    break;\n                }\n\n                if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) {\n                    r->type = MSG_REQ_REDIS_ZRANK;\n                    break;\n                }\n\n                if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) {\n                    r->type = MSG_REQ_REDIS_ZSCAN;\n                    break;\n                }\n\n                if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) {\n                    r->type = MSG_REQ_REDIS_PFADD;\n                    break;\n                }\n\n                if (str5icmp(m, 'g', 'e', 't', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_GETEX;\n                    break;\n                }\n\n                if (str5icmp(m, 't', 'o', 'u', 'c', 'h')) {\n                    r->type = MSG_REQ_REDIS_TOUCH;\n                    break;\n                }\n\n                if (str5icmp(m, 'l', 'm', 'o', 'v', 'e')) {\n                    r->type = MSG_REQ_REDIS_LMOVE;\n                    break;\n                }\n\n                break;\n\n            case 6:\n                if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) {\n                    r->type = MSG_REQ_REDIS_APPEND;\n                    break;\n                }\n\n                if (str6icmp(m, 'b', 'i', 't', 'p', 'o', 's')) {\n                    r->type = MSG_REQ_REDIS_BITPOS;\n                    break;\n                }\n\n                if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) {\n                    r->type = MSG_REQ_REDIS_DECRBY;\n                    break;\n                }\n\n                if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) {\n                    r->type = MSG_REQ_REDIS_EXISTS;\n                    break;\n                }\n\n                if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_EXPIRE;\n                    break;\n                }\n\n                if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) {\n                    r->type = MSG_REQ_REDIS_GETBIT;\n                    break;\n                }\n\n                if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) {\n                    r->type = MSG_REQ_REDIS_GETSET;\n                    break;\n                }\n\n                if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_PSETEX;\n                    break;\n                }\n\n                if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) {\n                    r->type = MSG_REQ_REDIS_HSETNX;\n                    break;\n                }\n\n                if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) {\n                    r->type = MSG_REQ_REDIS_INCRBY;\n                    break;\n                }\n\n                if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_LINDEX;\n                    break;\n                }\n\n                if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) {\n                    r->type = MSG_REQ_REDIS_LPUSHX;\n                    break;\n                }\n\n                if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) {\n                    r->type = MSG_REQ_REDIS_LRANGE;\n                    break;\n                }\n\n                if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) {\n                    r->type = MSG_REQ_REDIS_RPUSHX;\n                    break;\n                }\n\n                if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) {\n                    r->type = MSG_REQ_REDIS_SETBIT;\n                    break;\n                }\n\n                if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_SINTER;\n                    break;\n                }\n\n                if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) {\n                    r->type = MSG_REQ_REDIS_STRLEN;\n                    break;\n                }\n\n                if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) {\n                    r->type = MSG_REQ_REDIS_SUNION;\n                    break;\n                }\n\n                if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) {\n                    r->type = MSG_REQ_REDIS_ZCOUNT;\n                    break;\n                }\n\n                if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZRANGE;\n                    break;\n                }\n\n                if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZSCORE;\n                    break;\n                }\n\n                if (str6icmp(m, 'g', 'e', 'o', 'p', 'o', 's')) {\n                    r->type = MSG_REQ_REDIS_GEOPOS;\n                    break;\n                }\n\n                if (str6icmp(m, 'g', 'e', 'o', 'a', 'd', 'd')) {\n                    r->type = MSG_REQ_REDIS_GEOADD;\n                    break;\n                }\n\n                if (str6icmp(m, 'g', 'e', 't', 'd', 'e', 'l')) {\n                    r->type = MSG_REQ_REDIS_GETDEL;\n                    break;\n                }\n\n                if (str6icmp(m, 'z', 'u', 'n', 'i', 'o', 'n')) {\n                    r->type = MSG_REQ_REDIS_ZUNION;\n                    break;\n                }\n\n                if (str6icmp(m, 'z', 'i', 'n', 't', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_ZINTER;\n                    break;\n                }\n\n                if (str6icmp(m, 'u', 'n', 'l', 'i', 'n', 'k')) {\n                    r->type = MSG_REQ_REDIS_UNLINK;\n                    break;\n                }\n\n                if (str6icmp(m, 'l', 'o', 'l', 'w', 'u', 't')) {\n                    r->type = MSG_REQ_REDIS_LOLWUT;\n                    if (!msg_set_placeholder_key(r)) {\n                        goto enomem;\n                    }\n                    break;\n                }\n\n                break;\n\n            case 7:\n                if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) {\n                    r->type = MSG_REQ_REDIS_PERSIST;\n                    break;\n                }\n\n                if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_PEXPIRE;\n                    break;\n                }\n\n                if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) {\n                    r->type = MSG_REQ_REDIS_HEXISTS;\n                    break;\n                }\n\n                if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) {\n                    r->type = MSG_REQ_REDIS_HGETALL;\n                    break;\n                }\n\n                if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) {\n                    r->type = MSG_REQ_REDIS_HINCRBY;\n                    break;\n                }\n\n                if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) {\n                    r->type = MSG_REQ_REDIS_LINSERT;\n                    break;\n                }\n\n                if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) {\n                    r->type = MSG_REQ_REDIS_ZINCRBY;\n                    break;\n                }\n\n                if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) {\n                    r->type = MSG_REQ_REDIS_EVALSHA;\n                    break;\n                }\n\n                if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_RESTORE;\n                    break;\n                }\n\n                if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) {\n                    r->type = MSG_REQ_REDIS_PFCOUNT;\n                    break;\n                }\n\n                if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) {\n                    r->type = MSG_REQ_REDIS_PFMERGE;\n                    break;\n                }\n\n                if (str7icmp(m, 'z', 'm', 's', 'c', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZMSCORE;\n                    break;\n                }\n\n                if (str7icmp(m, 'z', 'p', 'o', 'p', 'm', 'i', 'n')) {\n                    r->type = MSG_REQ_REDIS_ZPOPMIN;\n                    break;\n                }\n\n                if (str7icmp(m, 'z', 'p', 'o', 'p', 'm', 'a', 'x')) {\n                    r->type = MSG_REQ_REDIS_ZPOPMAX;\n                    break;\n                }\n\n                if (str7icmp(m, 'g', 'e', 'o', 'd', 'i', 's', 't')) {\n                    r->type = MSG_REQ_REDIS_GEODIST;\n                    break;\n                }\n\n                if (str7icmp(m, 'g', 'e', 'o', 'h', 'a', 's', 'h')) {\n                    r->type = MSG_REQ_REDIS_GEOHASH;\n                    break;\n                }\n\n                if (str7icmp(m, 'h', 's', 't', 'r', 'l', 'e', 'n')) {\n                    r->type = MSG_REQ_REDIS_HSTRLEN;\n                    break;\n                }\n\n                if (str7icmp(m, 'c', 'o', 'm', 'm', 'a', 'n', 'd')) {\n                    r->type = MSG_REQ_REDIS_COMMAND;\n                    if (!msg_set_placeholder_key(r)) {\n                        goto enomem;\n                    }\n                    break;\n                }\n\n                break;\n\n            case 8:\n                if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) {\n                    r->type = MSG_REQ_REDIS_EXPIREAT;\n                    break;\n                }\n\n                if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) {\n                    r->type = MSG_REQ_REDIS_BITCOUNT;\n                    break;\n                }\n\n                if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) {\n                    r->type = MSG_REQ_REDIS_GETRANGE;\n                    break;\n                }\n\n                if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) {\n                    r->type = MSG_REQ_REDIS_SETRANGE;\n                    break;\n                }\n\n                if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) {\n                    r->type = MSG_REQ_REDIS_SMEMBERS;\n                    break;\n                }\n\n                if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) {\n                    r->type = MSG_REQ_REDIS_ZREVRANK;\n                    break;\n                }\n\n                if (str8icmp(m, 'b', 'i', 't', 'f', 'i', 'e', 'l', 'd')) {\n                    r->type = MSG_REQ_REDIS_BITFIELD;\n                    break;\n                }\n\n                break;\n\n            case 9:\n                if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) {\n                    r->type = MSG_REQ_REDIS_PEXPIREAT;\n                    break;\n                }\n\n                if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) {\n                    r->type = MSG_REQ_REDIS_RPOPLPUSH;\n                    break;\n                }\n\n                if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_SISMEMBER;\n                    break;\n                }\n\n                if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZREVRANGE;\n                    break;\n                }\n\n                if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) {\n                    r->type = MSG_REQ_REDIS_ZLEXCOUNT;\n                    break;\n                }\n\n                if (str9icmp(m, 'g', 'e', 'o', 's', 'e', 'a', 'r', 'c', 'h')) {\n                    r->type = MSG_REQ_REDIS_GEOSEARCH;\n                    break;\n                }\n\n                if (str9icmp(m, 'g', 'e', 'o', 'r', 'a', 'd', 'i', 'u', 's')) {\n                    r->type = MSG_REQ_REDIS_GEORADIUS;\n                    break;\n                }\n\n                break;\n\n            case 10:\n                if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_SDIFFSTORE;\n                    break;\n                }\n\n                if (str10icmp(m, 'h', 'r', 'a', 'n', 'd', 'f', 'i', 'e', 'l', 'd')) {\n                    r->type = MSG_REQ_REDIS_HRANDFIELD;\n                    break;\n                }\n\n                if (str10icmp(m, 's', 'm', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_SMISMEMBER;\n                    break;\n                }\n\n                if (str10icmp(m, 'z', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZDIFFSTORE;\n                    break;\n                }\n\n\n                break;\n\n            case 11:\n                if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) {\n                    r->type = MSG_REQ_REDIS_INCRBYFLOAT;\n                    break;\n                }\n\n                if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_SINTERSTORE;\n                    break;\n                }\n\n                if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_SRANDMEMBER;\n                    break;\n                }\n\n                if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_SUNIONSTORE;\n                    break;\n                }\n\n                if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZINTERSTORE;\n                    break;\n                }\n\n                if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZUNIONSTORE;\n                    break;\n                }\n\n                if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_ZRANGEBYLEX;\n                    break;\n                }\n\n                if (str11icmp(m, 'z', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_ZRANDMEMBER;\n                    break;\n                }\n\n                if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZRANGESTORE;\n                    break;\n                }\n\n                break;\n\n            case 12:\n                if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) {\n                    r->type = MSG_REQ_REDIS_HINCRBYFLOAT;\n                    break;\n                }\n\n\n                break;\n\n            case 13:\n                if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZRANGEBYSCORE;\n                    break;\n                }\n\n                break;\n\n            case 14:\n                if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_ZREMRANGEBYLEX;\n                    break;\n                }\n\n                if (str14icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {\n                    r->type = MSG_REQ_REDIS_ZREVRANGEBYLEX;\n                    break;\n                }\n                if (str14icmp(m, 'g', 'e', 'o', 's', 'e', 'a', 'r', 'c', 'h', 's', 't', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_GEOSEARCHSTORE;\n                    break;\n                }\n\n\n                break;\n\n            case 15:\n                if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) {\n                    r->type = MSG_REQ_REDIS_ZREMRANGEBYRANK;\n                    break;\n                }\n\n                break;\n\n            case 16:\n                if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZREMRANGEBYSCORE;\n                    break;\n                }\n\n                if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {\n                    r->type = MSG_REQ_REDIS_ZREVRANGEBYSCORE;\n                    break;\n                }\n                break;\n\n            case 17:\n                if (str17icmp(m, 'g', 'e', 'o', 'r', 'a', 'd', 'i', 'u', 's', 'b', 'y', 'm', 'e', 'm', 'b', 'e', 'r')) {\n                    r->type = MSG_REQ_REDIS_GEORADIUSBYMEMBER;\n                    break;\n                }\n\n            default:\n                break;\n            }\n\n            if (r->type == MSG_UNKNOWN) {\n                log_error(\"parsed unsupported command '%.*s'\", (int)(p - m), m);\n                goto error;\n            }\n\n            log_debug(LOG_VERB, \"parsed command '%.*s'\", (int)(p - m), m);\n\n            state = SW_REQ_TYPE_LF;\n            break;\n\n        case SW_REQ_TYPE_LF:\n            switch (ch) {\n            case LF:\n                if (redis_argz(r)) {\n                    if (r->narg != 1) {\n                        /* It's an error to provide more than one argument. */\n                        goto error;\n                    }\n                    goto done;\n                } else if (redis_nokey(r)) {\n                    if (r->narg == 1) {\n                        goto done;\n                    }\n                    state = SW_ARGN_LEN;\n                } else if (r->narg == 1) {\n                    goto error;\n                } else if (redis_argeval(r)) {\n                    state = SW_ARG1_LEN;\n                } else {\n                    state = SW_KEY_LEN;\n                }\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_KEY_LEN:\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                r->token = p;\n                r->rlen = 0;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if (r->rlen >= mbuf_data_size()) {\n                    log_error(\"parsed bad req %\"PRIu64\" of type %d with key \"\n                              \"length %d that greater than or equal to maximum\"\n                              \" redis key length of %zu\", r->id, r->type,\n                              r->rlen, mbuf_data_size());\n                    goto error;\n                }\n                if (r->rnarg == 0) {\n                    goto error;\n                }\n                r->rnarg--;\n                r->token = NULL;\n                state = SW_KEY_LEN_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_KEY_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_KEY;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_KEY:\n            if (r->token == NULL) {\n                r->token = p;\n            }\n\n            m = r->token + r->rlen;\n            if (m >= b->last) {\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            } else {        /* got a key */\n                struct keypos *kpos;\n\n                p = m;      /* move forward by rlen bytes */\n                r->rlen = 0;\n                m = r->token;\n                r->token = NULL;\n\n                kpos = array_push(r->keys);\n                if (kpos == NULL) {\n                    goto enomem;\n                }\n                kpos->start = m;\n                kpos->end = p;\n\n                state = SW_KEY_LF;\n            }\n\n            break;\n\n        case SW_KEY_LF:\n            switch (ch) {\n            case LF:\n                if (redis_arg0(r)) {\n                    if (r->rnarg != 0) {\n                        goto error;\n                    }\n                    goto done;\n                } else if (redis_arg1(r)) {\n                    if (r->rnarg != 1) {\n                        goto error;\n                    }\n                    state = SW_ARG1_LEN;\n                } else if (redis_arg2(r)) {\n                    if (r->rnarg != 2) {\n                        goto error;\n                    }\n                    state = SW_ARG1_LEN;\n                } else if (redis_arg3(r)) {\n                    if (r->rnarg != 3) {\n                        goto error;\n                    }\n                    state = SW_ARG1_LEN;\n                } else if (redis_argn(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_ARG1_LEN;\n                } else if (redis_argx(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_KEY_LEN;\n                } else if (redis_argkvx(r)) {\n                    if (r->narg % 2 == 0) {\n                        goto error;\n                    }\n                    state = SW_ARG1_LEN;\n                } else if (redis_argeval(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_ARGN_LEN;\n                } else {\n                    goto error;\n                }\n\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG1_LEN:\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                r->rlen = 0;\n                r->token = p;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1 || r->rnarg == 0) {\n                    goto error;\n                }\n                r->rnarg--;\n                r->token = NULL;\n                state = SW_ARG1_LEN_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG1_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_ARG1;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG1:\n            m = p + r->rlen;\n            if (m >= b->last) {\n                r->rlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p = m; /* move forward by rlen bytes */\n            r->rlen = 0;\n\n            state = SW_ARG1_LF;\n\n            break;\n\n        case SW_ARG1_LF:\n            switch (ch) {\n            case LF:\n                if (redis_arg1(r)) {\n                    if (r->rnarg != 0) {\n                        goto error;\n                    }\n                    goto done;\n                } else if (redis_arg2(r)) {\n                    if (r->rnarg != 1) {\n                        goto error;\n                    }\n                    state = SW_ARG2_LEN;\n                } else if (redis_arg3(r)) {\n                    if (r->rnarg != 2) {\n                        goto error;\n                    }\n                    state = SW_ARG2_LEN;\n                } else if (redis_argn(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_ARGN_LEN;\n                } else if (redis_argeval(r)) {\n                    if (r->rnarg < 2) {\n                        goto error;\n                    }\n                    state = SW_ARG2_LEN;\n                } else if (redis_argkvx(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_KEY_LEN;\n                } else {\n                    goto error;\n                }\n\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG2_LEN:\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                r->rlen = 0;\n                r->token = p;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1 || r->rnarg == 0) {\n                    goto error;\n                }\n                r->rnarg--;\n                r->token = NULL;\n                state = SW_ARG2_LEN_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG2_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_ARG2;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG2:\n            if (r->token == NULL && redis_argeval(r)) {\n                /*\n                 * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must\n                 * be tokenized and stored in contiguous memory.\n                 */\n                r->token = p;\n            }\n\n            m = p + r->rlen;\n            if (m >= b->last) {\n                /* \n                 * For EVAL/EVALHASH, the r->token has been assigned a value.  When\n                 * m >= b->last happens will need to repair mbuf.  \n                 * \n                 * At the end of redis_parse_req, r->token will be used to choose\n                 * the start (p) for the next call to redis_parse_req and clear\n                 * r->token when repairing this and adding more data.\n                 * \n                 * So, only when r->token == NULL we need to calculate r->rlen again.\n                 */\n                if (r->token == NULL) {\n                    r->rlen -= (uint32_t)(b->last - p);\n                }\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p = m; /* move forward by rlen bytes */\n            r->rlen = 0;\n\n            if (redis_argeval(r)) {\n                uint32_t nkey;\n                uint8_t *chp;\n\n                /*\n                 * For EVAL/EVALSHA, we need to find the integer value of this\n                 * argument. It tells us the number of keys in the script, and\n                 * we need to error out if number of keys is 0. At this point,\n                 * both p and m point to the end of the argument and r->token\n                 * points to the start.\n                 */\n                if (p - r->token < 1) {\n                    goto error;\n                }\n\n                for (nkey = 0, chp = r->token; chp < p; chp++) {\n                    if (isdigit(*chp)) {\n                        nkey = nkey * 10 + (uint32_t)(*chp - '0');\n                    } else {\n                        goto error;\n                    }\n                }\n                if (nkey == 0) {\n                    goto error;\n                }\n\n                r->token = NULL;\n            }\n\n            state = SW_ARG2_LF;\n\n            break;\n\n        case SW_ARG2_LF:\n            switch (ch) {\n            case LF:\n                if (redis_arg2(r)) {\n                    if (r->rnarg != 0) {\n                        goto error;\n                    }\n                    goto done;\n                } else if (redis_arg3(r)) {\n                    if (r->rnarg != 1) {\n                        goto error;\n                    }\n                    state = SW_ARG3_LEN;\n                } else if (redis_argn(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_ARGN_LEN;\n                } else if (redis_argeval(r)) {\n                    if (r->rnarg < 1) {\n                        goto error;\n                    }\n                    state = SW_KEY_LEN;\n                } else {\n                    goto error;\n                }\n\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG3_LEN:\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                r->rlen = 0;\n                r->token = p;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1 || r->rnarg == 0) {\n                    goto error;\n                }\n                r->rnarg--;\n                r->token = NULL;\n                state = SW_ARG3_LEN_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG3_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_ARG3;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARG3:\n            m = p + r->rlen;\n            if (m >= b->last) {\n                r->rlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p = m; /* move forward by rlen bytes */\n            r->rlen = 0;\n            state = SW_ARG3_LF;\n\n            break;\n\n        case SW_ARG3_LF:\n            switch (ch) {\n            case LF:\n                if (redis_arg3(r)) {\n                    if (r->rnarg != 0) {\n                        goto error;\n                    }\n                    goto done;\n                } else if (redis_argn(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_ARGN_LEN;\n                } else {\n                    goto error;\n                }\n\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARGN_LEN:\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                r->rlen = 0;\n                r->token = p;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1 || r->rnarg == 0) {\n                    goto error;\n                }\n                r->rnarg--;\n                r->token = NULL;\n                state = SW_ARGN_LEN_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_ARGN_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_ARGN;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_ARGN:\n            m = p + r->rlen;\n            if (m >= b->last) {\n                r->rlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p = m; /* move forward by rlen bytes */\n            r->rlen = 0;\n            state = SW_ARGN_LF;\n\n            break;\n\n        case SW_ARGN_LF:\n            switch (ch) {\n            case LF:\n                if (redis_argn(r) || redis_argeval(r) || redis_nokey(r)) {\n                    if (r->rnarg == 0) {\n                        goto done;\n                    }\n                    state = SW_ARGN_LEN;\n                } else {\n                    goto error;\n                }\n\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_SENTINEL:\n        default:\n            NOT_REACHED();\n            break;\n        }\n    }\n\n    ASSERT(p == b->last);\n    r->pos = p;\n    r->state = state;\n\n    if (b->last == b->end && r->token != NULL) {\n        r->pos = r->token;\n        r->token = NULL;\n        r->result = MSG_PARSE_REPAIR;\n    } else {\n        r->result = MSG_PARSE_AGAIN;\n    }\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed req %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\ndone:\n    ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL);\n    r->pos = p + 1;\n    ASSERT(r->pos <= b->last);\n    r->state = SW_START;\n    r->token = NULL;\n    r->result = MSG_PARSE_OK;\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed req %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\nenomem:\n    r->result = MSG_PARSE_ERROR;\n    r->state = state;\n\n    log_hexdump(LOG_INFO, b->pos, mbuf_length(b), \"out of memory on parse req %\"PRIu64\" \"\n                \"res %d type %d state %d\", r->id, r->result, r->type, r->state);\n\n    return;\n\nerror:\n    r->result = MSG_PARSE_ERROR;\n    r->state = state;\n    errno = EINVAL;\n\n    log_hexdump(LOG_INFO, b->pos, mbuf_length(b), \"parsed bad req %\"PRIu64\" \"\n                \"res %d type %d state %d\", r->id, r->result, r->type,\n                r->state);\n}\n\n/*\n * Reference: http://redis.io/topics/protocol\n *\n * Redis will reply to commands with different kinds of replies. It is\n * possible to check the kind of reply from the first byte sent by the\n * server:\n *  - with a single line reply the first byte of the reply will be \"+\"\n *  - with an error message the first byte of the reply will be \"-\"\n *  - with an integer number the first byte of the reply will be \":\"\n *  - with bulk reply the first byte of the reply will be \"$\"\n *  - with multi-bulk reply the first byte of the reply will be \"*\"\n *\n * 1). Status reply (or single line reply) is in the form of a single line\n *     string starting with \"+\" terminated by \"\\r\\n\".\n * 2). Error reply are similar to status replies. The only difference is\n *     that the first byte is \"-\" instead of \"+\".\n * 3). Integer reply is just a CRLF terminated string representing an\n *     integer, and prefixed by a \":\" byte.\n * 4). Bulk reply is used by server to return a single binary safe string.\n *     The first reply line is a \"$\" byte followed by the number of bytes\n *     of the actual reply, followed by CRLF, then the actual data bytes,\n *     followed by additional two bytes for the final CRLF. If the requested\n *     value does not exist the bulk reply will use the special value '-1'\n *     as the data length.\n * 5). Multi-bulk reply is used by the server to return many binary safe\n *     strings (bulks) with the initial line indicating how many bulks that\n *     will follow. The first byte of a multi bulk reply is always *.\n */\nvoid\nredis_parse_rsp(struct msg *r)\n{\n    struct mbuf *b;\n    uint8_t *p, *m;\n    uint8_t ch;\n\n    enum {\n        SW_START,\n        SW_STATUS,\n        SW_ERROR,\n        SW_INTEGER_START,\n        SW_SIMPLE,\n        SW_BULK,\n        SW_BULK_LF,\n        SW_BULK_ARG,\n        SW_BULK_ARG_LF,\n        SW_MULTIBULK,\n        SW_MULTIBULK_NARG_LF,\n        SW_MULTIBULK_ARGN_LEN,\n        SW_MULTIBULK_ARGN_LEN_LF,\n        SW_MULTIBULK_ARGN,\n        SW_MULTIBULK_ARGN_LF,\n        SW_RUNTO_CRLF,\n        SW_ALMOST_DONE,\n        SW_SENTINEL\n    } state;\n\n    state = r->state;\n    b = STAILQ_LAST(&r->mhdr, mbuf, next);\n\n    ASSERT(!r->request);\n    ASSERT(state >= SW_START && state < SW_SENTINEL);\n    ASSERT(b != NULL);\n    ASSERT(b->pos <= b->last);\n\n    /* validate the parsing marker */\n    ASSERT(r->pos != NULL);\n    ASSERT(r->pos >= b->pos && r->pos <= b->last);\n\n    for (p = r->pos; p < b->last; p++) {\n        ch = *p;\n\n        switch (state) {\n        case SW_START:\n            r->type = MSG_UNKNOWN;\n            r->rnarg = 1;\n            r->is_top_level = 1;\n\n            switch (ch) {\n            case '+':\n                p = p - 1; /* go back by 1 byte */\n                r->type = MSG_RSP_REDIS_STATUS;\n                state = SW_STATUS;\n                break;\n\n            case '-':\n                r->type = MSG_RSP_REDIS_ERROR;\n                p = p - 1; /* go back by 1 byte */\n                state = SW_ERROR;\n                break;\n\n            case ':':\n                r->type = MSG_RSP_REDIS_INTEGER;\n                r->integer = 0;\n                state = SW_INTEGER_START;\n                break;\n\n            case '$':\n                r->type = MSG_RSP_REDIS_BULK;\n                p = p - 1; /* go back by 1 byte */\n                state = SW_BULK;\n                break;\n\n            case '*':\n                r->type = MSG_RSP_REDIS_MULTIBULK;\n                p = p - 1; /* go back by 1 byte */\n                state = SW_MULTIBULK;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_STATUS:\n            /* rsp_start <- p */\n            state = SW_RUNTO_CRLF;\n            break;\n\n        case SW_ERROR:\n            if (r->token == NULL) {\n                if (ch != '-') {\n                    goto error;\n                }\n              /* rsp_start <- p */\n              r->token = p;\n            }\n            if (ch == ' ' || ch == CR) {\n                m = r->token;\n                r->token = NULL;\n                switch (p - m) {\n\n                case 4:\n                    /*\n                     * -ERR no such key\\r\\n\n                     * -ERR syntax error\\r\\n\n                     * -ERR source and destination objects are the same\\r\\n\n                     * -ERR index out of range\\r\\n\n                     */\n                    if (str4cmp(m, '-', 'E', 'R', 'R')) {\n                        r->type = MSG_RSP_REDIS_ERROR_ERR;\n                        break;\n                    }\n\n                    /* -OOM command not allowed when used memory > 'maxmemory'.\\r\\n */\n                    if (str4cmp(m, '-', 'O', 'O', 'M')) {\n                        r->type = MSG_RSP_REDIS_ERROR_OOM;\n                        break;\n                    }\n\n                    break;\n\n                case 5:\n                    /* -BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\\r\\n\" */\n                    if (str5cmp(m, '-', 'B', 'U', 'S', 'Y')) {\n                        r->type = MSG_RSP_REDIS_ERROR_BUSY;\n                        break;\n                    }\n\n                    break;\n\n                case 7:\n                    /* -NOAUTH Authentication required.\\r\\n */\n                    if (str7cmp(m, '-', 'N', 'O', 'A', 'U', 'T', 'H')) {\n                        r->type = MSG_RSP_REDIS_ERROR_NOAUTH;\n                        break;\n                    }\n\n                    break;\n\n                case 8:\n                    /* rsp: \"-LOADING Redis is loading the dataset in memory\\r\\n\" */\n                    if (str8cmp(m, '-', 'L', 'O', 'A', 'D', 'I', 'N', 'G')) {\n                        r->type = MSG_RSP_REDIS_ERROR_LOADING;\n                        break;\n                    }\n\n                    /* -BUSYKEY Target key name already exists.\\r\\n */\n                    if (str8cmp(m, '-', 'B', 'U', 'S', 'Y', 'K', 'E', 'Y')) {\n                        r->type = MSG_RSP_REDIS_ERROR_BUSYKEY;\n                        break;\n                    }\n\n                    /* \"-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.\\r\\n\" */\n                    if (str8cmp(m, '-', 'M', 'I', 'S', 'C', 'O', 'N', 'F')) {\n                        r->type = MSG_RSP_REDIS_ERROR_MISCONF;\n                        break;\n                    }\n\n                    break;\n\n                case 9:\n                    /* -NOSCRIPT No matching script. Please use EVAL.\\r\\n */\n                    if (str9cmp(m, '-', 'N', 'O', 'S', 'C', 'R', 'I', 'P', 'T')) {\n                        r->type = MSG_RSP_REDIS_ERROR_NOSCRIPT;\n                        break;\n                    }\n\n                    /* -READONLY You can't write against a read only slave.\\r\\n */\n                    if (str9cmp(m, '-', 'R', 'E', 'A', 'D', 'O', 'N', 'L', 'Y')) {\n                        r->type = MSG_RSP_REDIS_ERROR_READONLY;\n                        break;\n                    }\n\n                    break;\n\n                case 10:\n                    /* -WRONGTYPE Operation against a key holding the wrong kind of value\\r\\n */\n                    if (str10cmp(m, '-', 'W', 'R', 'O', 'N', 'G', 'T', 'Y', 'P', 'E')) {\n                        r->type = MSG_RSP_REDIS_ERROR_WRONGTYPE;\n                        break;\n                    }\n\n                    /* -EXECABORT Transaction discarded because of previous errors.\\r\\n\" */\n                    if (str10cmp(m, '-', 'E', 'X', 'E', 'C', 'A', 'B', 'O', 'R', 'T')) {\n                        r->type = MSG_RSP_REDIS_ERROR_EXECABORT;\n                        break;\n                    }\n\n                    break;\n\n                case 11:\n                    /* -MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\\r\\n */\n                    if (str11cmp(m, '-', 'M', 'A', 'S', 'T', 'E', 'R', 'D', 'O', 'W', 'N')) {\n                        r->type = MSG_RSP_REDIS_ERROR_MASTERDOWN;\n                        break;\n                    }\n\n                    /* -NOREPLICAS Not enough good slaves to write.\\r\\n */\n                    if (str11cmp(m, '-', 'N', 'O', 'R', 'E', 'P', 'L', 'I', 'C', 'A', 'S')) {\n                        r->type = MSG_RSP_REDIS_ERROR_NOREPLICAS;\n                        break;\n                    }\n\n                    break;\n                }\n                if (ch == '\\r') {\n                    state = SW_ALMOST_DONE;\n                } else {\n                    /* Read remaining characters until '\\r' */\n                    state = SW_RUNTO_CRLF;\n                }\n            }\n\n            break;\n\n        case SW_SIMPLE:\n            if (ch == CR) {\n                ASSERT(r->rnarg > 0);\n                r->rnarg--;\n                state = SW_MULTIBULK_ARGN_LF;\n            }\n            break;\n\n        case SW_INTEGER_START:\n            if (ch == CR) {\n                state = SW_ALMOST_DONE;\n            } else if (ch == '-') {\n                ;\n            } else if (isdigit(ch)) {\n                r->integer = r->integer * 10 + (uint32_t)(ch - '0');\n            } else {\n                goto error;\n            }\n            break;\n\n        case SW_RUNTO_CRLF:\n            switch (ch) {\n            case CR:\n                state = SW_ALMOST_DONE;\n                break;\n\n            default:\n                break;\n            }\n\n            break;\n\n        case SW_ALMOST_DONE:\n            switch (ch) {\n            case LF:\n                /* rsp_end <- p */\n                goto done;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_BULK:\n            /*\n             * SW_BULK is used for top-level bulk string replies.\n             * Within an array, SW_MULTIBULK_ARG... helpers are used\n             * to parse bulk strings instead.\n             */\n            if (r->token == NULL) {\n                if (ch != '$') {\n                    goto error;\n                }\n                /* rsp_start <- p */\n                r->token = p;\n                r->rlen = 0;\n            } else if (ch == '-') {\n                /* handles null bulk reply = '$-1' */\n                state = SW_RUNTO_CRLF;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1) {\n                    goto error;\n                }\n                r->token = NULL;\n                state = SW_BULK_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_BULK_LF:\n            switch (ch) {\n            case LF:\n                state = SW_BULK_ARG;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_BULK_ARG:\n            m = p + r->rlen;\n            if (m >= b->last) {\n                r->rlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p = m; /* move forward by rlen bytes */\n            r->rlen = 0;\n\n            state = SW_BULK_ARG_LF;\n\n            break;\n\n        case SW_BULK_ARG_LF:\n            switch (ch) {\n            case LF:\n                goto done;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_MULTIBULK:\n            if (r->token == NULL) {\n                if (ch != '*') {\n                    goto error;\n                }\n                r->vlen = 0;\n                r->token = p;\n                /* rsp_start <- p */\n                if (r->is_top_level) {\n                    r->narg_start = p;\n                }\n            } else if (ch == '-') {\n                p = p-1;\n                r->token = NULL;\n                /*\n                 * This is a null array (e.g. from BLPOP). Don't increment rnarg\n                 * https://redis.io/topics/protocol\n                 */\n                r->vlen = 1;\n                state = SW_MULTIBULK_ARGN_LEN;\n            } else if (isdigit(ch)) {\n                r->vlen = r->vlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1) {\n                    goto error;\n                }\n\n                if (r->is_top_level) {\n                    /* For multiget responses, we may need to know the number of responses to combine them. */\n                    r->narg = r->vlen;\n                    r->narg_end = p;\n                }\n                r->is_top_level = 0;\n                ASSERT(r->rnarg > 0);\n                r->rnarg += r->vlen - 1;\n                r->token = NULL;\n\n                /*\n                 * The stack is always initialized before transitioning\n                 * to another state.\n                 */\n                state = SW_MULTIBULK_NARG_LF;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_MULTIBULK_NARG_LF:\n            switch (ch) {\n            case LF:\n                if (r->rnarg == 0) {\n                    /* response is '*0\\r\\n' */\n                    goto done;\n                }\n\n                state = SW_MULTIBULK_ARGN_LEN;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_MULTIBULK_ARGN_LEN:\n            if (r->token == NULL) {\n                /*\n                 * From: http://redis.io/topics/protocol, a multi bulk reply\n                 * is used to return an array of other replies. Every element\n                 * of a multi bulk reply can be of any kind, including a\n                 * nested multi bulk reply.\n                 *\n                 * Here, we only handle a multi bulk reply element that\n                 * are either integer reply or bulk reply.\n                 *\n                 * there is a special case for sscan/hscan/zscan, these command\n                 * replay a nested multi-bulk with a number and a multi bulk like this:\n                 *\n                 * - multi-bulk\n                 *    - cursor\n                 *    - multi-bulk\n                 *       - val1\n                 *       - val2\n                 *       - val3\n                 *\n                 * in this case, there is only one sub-multi-bulk,\n                 * and it's the last element of parent,\n                 * we can handle it like tail-recursive.\n                 *\n                 */\n                if (ch == '*') {    /* for sscan/hscan/zscan only */\n                    p = p - 1;      /* go back by 1 byte */\n                    state = SW_MULTIBULK;\n                    break;\n                }\n\n                if (ch == ':' || ch == '+' || ch == '-') {\n                    /* handles not-found reply = '$-1' or integer reply = ':<num>' */\n                    /* and *2\\r\\n$2\\r\\nr0\\r\\n+OK\\r\\n or *1\\r\\n+OK\\r\\n */\n                    state = SW_SIMPLE;\n                    break;\n                }\n\n                if (ch != '$') {\n                    goto error;\n                }\n\n                r->token = p;\n                r->rlen = 0;\n            } else if (isdigit(ch)) {\n                r->rlen = r->rlen * 10 + (uint32_t)(ch - '0');\n            } else if (ch == '-') {\n                ;\n            } else if (ch == CR) {\n                if ((p - r->token) <= 1 || r->rnarg == 0) {\n                    goto error;\n                }\n\n                if ((r->rlen == 1 && (p - r->token) == 3)) {\n                    r->rlen = 0;\n                    state = SW_MULTIBULK_ARGN_LF;\n                } else {\n                    state = SW_MULTIBULK_ARGN_LEN_LF;\n                }\n                ASSERT(r->rnarg > 0);\n                r->rnarg--;\n                r->token = NULL;\n            } else {\n                goto error;\n            }\n\n            break;\n\n        case SW_MULTIBULK_ARGN_LEN_LF:\n            switch (ch) {\n            case LF:\n                state = SW_MULTIBULK_ARGN;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_MULTIBULK_ARGN:\n            m = p + r->rlen;\n            if (m >= b->last) {\n                r->rlen -= (uint32_t)(b->last - p);\n                m = b->last - 1;\n                p = m;\n                break;\n            }\n\n            if (*m != CR) {\n                goto error;\n            }\n\n            p += r->rlen; /* move forward by rlen bytes */\n            r->rlen = 0;\n\n            state = SW_MULTIBULK_ARGN_LF;\n\n            break;\n\n        case SW_MULTIBULK_ARGN_LF:\n            switch (ch) {\n            case LF:\n                if (r->rnarg == 0) {\n                    goto done;\n                }\n\n                state = SW_MULTIBULK_ARGN_LEN;\n                break;\n\n            default:\n                goto error;\n            }\n\n            break;\n\n        case SW_SENTINEL:\n        default:\n            NOT_REACHED();\n            break;\n        }\n    }\n\n    ASSERT(p == b->last);\n    r->pos = p;\n    r->state = state;\n\n    if (b->last == b->end && r->token != NULL) {\n        r->pos = r->token;\n        r->token = NULL;\n        r->result = MSG_PARSE_REPAIR;\n    } else {\n        r->result = MSG_PARSE_AGAIN;\n    }\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed rsp %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\ndone:\n    ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL);\n    r->pos = p + 1;\n    ASSERT(r->pos <= b->last);\n    r->state = SW_START;\n    r->token = NULL;\n    r->result = MSG_PARSE_OK;\n\n    log_hexdump(LOG_VERB, b->pos, mbuf_length(b), \"parsed rsp %\"PRIu64\" res %d \"\n                \"type %d state %d rpos %d of %d\", r->id, r->result, r->type,\n                r->state, (int)(r->pos - b->pos), (int)(b->last - b->pos));\n    return;\n\nerror:\n    r->result = MSG_PARSE_ERROR;\n    r->state = state;\n    errno = EINVAL;\n\n    log_hexdump(LOG_INFO, b->pos, mbuf_length(b), \"parsed bad rsp %\"PRIu64\" \"\n                \"res %d type %d state %d\", r->id, r->result, r->type,\n                r->state);\n}\n\n/*\n * Return true, if redis replies with a transient server failure response,\n * otherwise return false\n *\n * Transient failures on redis are scenarios when it is temporarily\n * unresponsive and responds with the following protocol specific error\n * reply:\n * -OOM, when redis is out-of-memory\n * -BUSY, when redis is busy\n * -LOADING when redis is loading dataset into memory\n *\n * See issue: https://github.com/twitter/twemproxy/issues/369\n */\nbool\nredis_failure(const struct msg *r)\n{\n    ASSERT(!r->request);\n\n    switch (r->type) {\n    case MSG_RSP_REDIS_ERROR_OOM:\n    case MSG_RSP_REDIS_ERROR_BUSY:\n    case MSG_RSP_REDIS_ERROR_LOADING:\n        return true;\n\n    default:\n        break;\n    }\n\n    return false;\n}\n\n/*\n * copy one bulk from src to dst\n *\n * if dst == NULL, we just eat the bulk\n *\n * */\nstatic rstatus_t\nredis_copy_bulk(struct msg *dst, struct msg *src)\n{\n    struct mbuf *mbuf, *nbuf;\n    uint8_t *p;\n    uint32_t len = 0;\n    uint32_t bytes = 0;\n    rstatus_t status;\n\n    for (mbuf = STAILQ_FIRST(&src->mhdr);\n         mbuf && mbuf_empty(mbuf);\n         mbuf = STAILQ_FIRST(&src->mhdr)) {\n\n        mbuf_remove(&src->mhdr, mbuf);\n        mbuf_put(mbuf);\n    }\n\n    mbuf = STAILQ_FIRST(&src->mhdr);\n    if (mbuf == NULL) {\n        return NC_ERROR;\n    }\n\n    p = mbuf->pos;\n    ASSERT(*p == '$');\n    p++;\n\n    if (p[0] == '-' && p[1] == '1') {\n        len = 1 + 2 + CRLF_LEN;             /* $-1\\r\\n */\n        p = mbuf->pos + len;\n    } else {\n        len = 0;\n        for (; p < mbuf->last && isdigit(*p); p++) {\n            len = len * 10 + (uint32_t)(*p - '0');\n        }\n        len += CRLF_LEN * 2;\n        len += (p - mbuf->pos);\n    }\n    bytes = len;\n\n    /* copy len bytes to dst */\n    for (; mbuf;) {\n        if (mbuf_length(mbuf) <= len) {     /* steal this buf from src to dst */\n            nbuf = STAILQ_NEXT(mbuf, next);\n            mbuf_remove(&src->mhdr, mbuf);\n            if (dst != NULL) {\n                mbuf_insert(&dst->mhdr, mbuf);\n            } else {\n                mbuf_put(mbuf);\n            }\n            len -= mbuf_length(mbuf);\n            mbuf = nbuf;\n        } else {                             /* split it */\n            if (dst != NULL) {\n                status = msg_append(dst, mbuf->pos, len);\n                if (status != NC_OK) {\n                    return status;\n                }\n            }\n            mbuf->pos += len;\n            break;\n        }\n    }\n\n    if (dst != NULL) {\n        dst->mlen += bytes;\n    }\n    src->mlen -= bytes;\n    log_debug(LOG_VVERB, \"redis_copy_bulk copy bytes: %d\", bytes);\n    return NC_OK;\n}\n\n/*\n * Pre-coalesce handler is invoked when the message is a response to\n * the fragmented multi vector request - 'mget' or 'del' and all the\n * responses to the fragmented request vector hasn't been received\n */\nvoid\nredis_pre_coalesce(struct msg *r)\n{\n    struct msg *pr = r->peer; /* peer request */\n    struct mbuf *mbuf;\n\n    ASSERT(!r->request);\n    ASSERT(pr->request);\n\n    if (pr->frag_id == 0) {\n        /* do nothing, if not a response to a fragmented request */\n        return;\n    }\n    pr->frag_owner->nfrag_done++;\n\n    switch (r->type) {\n    case MSG_RSP_REDIS_INTEGER:\n        /* only redis 'del' fragmented request sends back integer reply */\n        ASSERT(pr->type == MSG_REQ_REDIS_DEL || pr->type == MSG_REQ_REDIS_TOUCH || pr->type == MSG_REQ_REDIS_UNLINK);\n\n        mbuf = STAILQ_FIRST(&r->mhdr);\n        /*\n         * Our response parser guarantees that the integer reply will be\n         * completely encapsulated in a single mbuf and we should skip over\n         * all the mbuf contents and discard it as the parser has already\n         * parsed the integer reply and stored it in msg->integer\n         */\n        ASSERT(mbuf == STAILQ_LAST(&r->mhdr, mbuf, next));\n        ASSERT(r->mlen == mbuf_length(mbuf));\n\n        r->mlen -= mbuf_length(mbuf);\n        mbuf_rewind(mbuf);\n\n        /* accumulate the integer value in frag_owner of peer request */\n        pr->frag_owner->integer += r->integer;\n        break;\n\n    case MSG_RSP_REDIS_MULTIBULK:\n        /* only redis 'mget' fragmented request sends back multi-bulk reply */\n        ASSERT(pr->type == MSG_REQ_REDIS_MGET);\n\n        mbuf = STAILQ_FIRST(&r->mhdr);\n        /*\n         * Muti-bulk reply can span over multiple mbufs and in each reply\n         * we should skip over the narg token. Our response parser\n         * guarantees that the narg token and the immediately following\n         * '\\r\\n' will exist in a contiguous region in the first mbuf\n         */\n        ASSERT(r->narg_start == mbuf->pos);\n        ASSERT(r->narg_start < r->narg_end);\n\n        r->narg_end += CRLF_LEN;\n        r->mlen -= (uint32_t)(r->narg_end - r->narg_start);\n        mbuf->pos = r->narg_end;\n\n        break;\n\n    case MSG_RSP_REDIS_STATUS:\n        if (pr->type == MSG_REQ_REDIS_MSET) {       /* MSET segments */\n            mbuf = STAILQ_FIRST(&r->mhdr);\n            r->mlen -= mbuf_length(mbuf);\n            mbuf_rewind(mbuf);\n        }\n        break;\n\n    default:\n        /*\n         * Valid responses for a fragmented request are MSG_RSP_REDIS_INTEGER or,\n         * MSG_RSP_REDIS_MULTIBULK. For an invalid response, we send out -ERR\n         * with EINVAL errno\n         */\n        mbuf = STAILQ_FIRST(&r->mhdr);\n        log_hexdump(LOG_ERR, mbuf->pos, mbuf_length(mbuf), \"rsp fragment \"\n                    \"with unknown type %d\", r->type);\n        pr->error = 1;\n        pr->err = EINVAL;\n        break;\n    }\n}\n\nstatic rstatus_t\nredis_append_key(struct msg *r, const uint8_t *key, uint32_t keylen)\n{\n    uint32_t len;\n    struct mbuf *mbuf;\n    uint8_t printbuf[32];\n    struct keypos *kpos;\n\n    /* 1. keylen */\n    len = (uint32_t)nc_snprintf(printbuf, sizeof(printbuf), \"$%d\\r\\n\", keylen);\n    mbuf = msg_ensure_mbuf(r, len);\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n    mbuf_copy(mbuf, printbuf, len);\n    r->mlen += len;\n\n    /* 2. key */\n    mbuf = msg_ensure_mbuf(r, keylen);\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n\n    kpos = array_push(r->keys);\n    if (kpos == NULL) {\n        return NC_ENOMEM;\n    }\n\n    kpos->start = mbuf->last;\n    kpos->end = mbuf->last + keylen;\n    mbuf_copy(mbuf, key, keylen);\n    r->mlen += keylen;\n\n    /* 3. CRLF */\n    mbuf = msg_ensure_mbuf(r, CRLF_LEN);\n    if (mbuf == NULL) {\n        return NC_ENOMEM;\n    }\n    mbuf_copy(mbuf, (uint8_t *)CRLF, CRLF_LEN);\n    r->mlen += (uint32_t)CRLF_LEN;\n\n    return NC_OK;\n}\n\n/*\n * input a msg, return a msg chain.\n * nserver is the number of backend redis/memcache server\n *\n * the original msg will be fragmented into at most nserver fragments.\n * all the keys map to the same backend will group into one fragment.\n *\n * frag_id:\n * a unique fragment id for all fragments of the message vector. including the orig msg.\n *\n * frag_owner:\n * All fragments of the message use frag_owner point to the orig msg\n *\n * frag_seq:\n * the map from each key to it's fragment, (only in the orig msg)\n *\n * For example, a message vector with 3 keys:\n *\n *     get key1 key2 key3\n *\n * suppose we have 2 backend server, and the map is:\n *\n *     key1  => backend 0\n *     key2  => backend 1\n *     key3  => backend 0\n *\n * it will fragment like this:\n *\n *   +-----------------+\n *   |  msg vector     |\n *   |(original msg)   |\n *   |key1, key2, key3 |\n *   +-----------------+\n *\n *                                             frag_owner\n *                        /--------------------------------------+\n *       frag_owner      /                                       |\n *     /-----------+    | /------------+ frag_owner              |\n *     |           |    | |            |                         |\n *     |           v    v v            |                         |\n *   +--------------------+     +---------------------+     +----+----------------+\n *   |   frag_id = 10     |     |   frag_id = 10      |     |   frag_id = 10      |\n *   |     nfrag = 3      |     |      nfrag = 0      |     |      nfrag = 0      |\n *   | frag_seq = x x x   |     |     key1, key3      |     |         key2        |\n *   +------------|-|-|---+     +---------------------+     +---------------------+\n *                | | |          ^    ^                          ^\n *                | \\ \\          |    |                          |\n *                |  \\ ----------+    |                          |\n *                +---\\---------------+                          |\n *                     ------------------------------------------+\n *\n */\nstatic rstatus_t\nredis_fragment_argx(struct msg *r, uint32_t nserver, struct msg_tqh *frag_msgq,\n                    uint32_t key_step)\n{\n    struct mbuf *mbuf;\n    struct msg **sub_msgs;\n    uint32_t i;\n    rstatus_t status;\n    struct array *keys = r->keys;\n\n    ASSERT(array_n(keys) == (r->narg - 1) / key_step);\n\n    sub_msgs = nc_zalloc(nserver * sizeof(*sub_msgs));\n    if (sub_msgs == NULL) {\n        return NC_ENOMEM;\n    }\n\n    ASSERT(r->frag_seq == NULL);\n    r->frag_seq = nc_alloc(array_n(keys) * sizeof(*r->frag_seq));\n    if (r->frag_seq == NULL) {\n        nc_free(sub_msgs);\n        return NC_ENOMEM;\n    }\n\n    mbuf = STAILQ_FIRST(&r->mhdr);\n    mbuf->pos = mbuf->start;\n\n    /*\n     * This code is based on the assumption that '*narg\\r\\n$4\\r\\nMGET\\r\\n' is located\n     * in a contiguous location.\n     * This is always true because we have capped our MBUF_MIN_SIZE at 512 and\n     * whenever we have multiple messages, we copy the tail message into a new mbuf\n     */\n    for (i = 0; i < 3; i++) {                 /* eat *narg\\r\\n$4\\r\\nMGET\\r\\n */\n        for (; *(mbuf->pos) != '\\n';) {\n            mbuf->pos++;\n        }\n        mbuf->pos++;\n    }\n\n    r->frag_id = msg_gen_frag_id();\n    r->nfrag = 0;\n    r->frag_owner = r;\n\n    /* Build up the key1 key2 ... to be sent to a given server at index idx */\n    for (i = 0; i < array_n(keys); i++) {        /* for each key */\n        struct msg *sub_msg;\n        struct keypos *kpos = array_get(keys, i);\n        uint32_t idx = msg_backend_idx(r, kpos->start, kpos->end - kpos->start);\n        ASSERT(idx < nserver);\n\n        if (sub_msgs[idx] == NULL) {\n            sub_msgs[idx] = msg_get(r->owner, r->request, r->redis);\n            if (sub_msgs[idx] == NULL) {\n                nc_free(sub_msgs);\n                return NC_ENOMEM;\n            }\n        }\n        r->frag_seq[i] = sub_msg = sub_msgs[idx];\n\n        sub_msg->narg++;\n        status = redis_append_key(sub_msg, kpos->start, kpos->end - kpos->start);\n        if (status != NC_OK) {\n            nc_free(sub_msgs);\n            return status;\n        }\n\n        if (key_step == 1) {                            /* mget,del */\n            continue;\n        } else {                                        /* mset */\n            status = redis_copy_bulk(NULL, r);          /* eat key */\n            if (status != NC_OK) {\n                nc_free(sub_msgs);\n                return status;\n            }\n\n            status = redis_copy_bulk(sub_msg, r);\n            if (status != NC_OK) {\n                nc_free(sub_msgs);\n                return status;\n            }\n\n            sub_msg->narg++;\n        }\n    }\n\n    /*\n     * prepend mget header, and forward the command (command type+key(s)+suffix)\n     * to the corresponding server(s)\n     */\n    for (i = 0; i < nserver; i++) {\n        struct msg *sub_msg = sub_msgs[i];\n        if (sub_msg == NULL) {\n            continue;\n        }\n\n        if (r->type == MSG_REQ_REDIS_MGET) {\n            status = msg_prepend_format(sub_msg, \"*%d\\r\\n$4\\r\\nmget\\r\\n\",\n                                        sub_msg->narg + 1);\n        } else if (r->type == MSG_REQ_REDIS_DEL) {\n            status = msg_prepend_format(sub_msg, \"*%d\\r\\n$3\\r\\ndel\\r\\n\",\n                                        sub_msg->narg + 1);\n        } else if (r->type == MSG_REQ_REDIS_MSET) {\n            status = msg_prepend_format(sub_msg, \"*%d\\r\\n$4\\r\\nmset\\r\\n\",\n                                        sub_msg->narg + 1);\n        } else if (r->type == MSG_REQ_REDIS_TOUCH) {\n            status = msg_prepend_format(sub_msg, \"*%d\\r\\n$5\\r\\ntouch\\r\\n\",\n                                        sub_msg->narg + 1);\n        } else if (r->type == MSG_REQ_REDIS_UNLINK) {\n            status = msg_prepend_format(sub_msg, \"*%d\\r\\n$6\\r\\nunlink\\r\\n\",\n                                        sub_msg->narg + 1);\n        } else {\n            NOT_REACHED();\n        }\n        if (status != NC_OK) {\n            nc_free(sub_msgs);\n            return status;\n        }\n\n        sub_msg->type = r->type;\n        sub_msg->frag_id = r->frag_id;\n        sub_msg->frag_owner = r->frag_owner;\n\n        TAILQ_INSERT_TAIL(frag_msgq, sub_msg, m_tqe);\n        r->nfrag++;\n    }\n\n    nc_free(sub_msgs);\n    return NC_OK;\n}\n\nrstatus_t\nredis_fragment(struct msg *r, uint32_t nserver, struct msg_tqh *frag_msgq)\n{\n    if (1 == array_n(r->keys)){\n        return NC_OK;\n    }\n\n    switch (r->type) {\n    case MSG_REQ_REDIS_MGET:\n    case MSG_REQ_REDIS_DEL:\n    case MSG_REQ_REDIS_TOUCH:\n    case MSG_REQ_REDIS_UNLINK:\n        return redis_fragment_argx(r, nserver, frag_msgq, 1);\n\n        /* TODO: MSETNX - instead of responding with OK, respond with 1 if all fragments respond with 1 */\n    case MSG_REQ_REDIS_MSET:\n        return redis_fragment_argx(r, nserver, frag_msgq, 2);\n\n    default:\n        return NC_OK;\n    }\n}\n\nrstatus_t\nredis_reply(struct msg *r)\n{\n    struct conn *c_conn;\n    struct msg *response = r->peer;\n\n    ASSERT(response != NULL && response->owner != NULL);\n\n    c_conn = response->owner;\n    if (r->type == MSG_REQ_REDIS_AUTH) {\n        return redis_handle_auth_req(r, response);\n    }\n\n    if (!conn_authenticated(c_conn)) {\n        return msg_append(response, rsp_auth_required.data, rsp_auth_required.len);\n    }\n\n    switch (r->type) {\n    case MSG_REQ_REDIS_PING:\n        return msg_append(response, rsp_pong.data, rsp_pong.len);\n\n    default:\n        NOT_REACHED();\n        return NC_ERROR;\n    }\n}\n\nvoid\nredis_post_coalesce_mset(struct msg *request)\n{\n    rstatus_t status;\n    struct msg *response = request->peer;\n\n    status = msg_append(response, rsp_ok.data, rsp_ok.len);\n    if (status != NC_OK) {\n        response->error = 1;        /* mark this msg as err */\n        response->err = errno;\n    }\n}\n\nvoid\nredis_post_coalesce_del_or_touch(struct msg *request)\n{\n    struct msg *response = request->peer;\n    rstatus_t status;\n\n    status = msg_prepend_format(response, \":%d\\r\\n\", request->integer);\n    if (status != NC_OK) {\n        response->error = 1;\n        response->err = errno;\n    }\n}\n\nstatic void\nredis_post_coalesce_mget(struct msg *request)\n{\n    struct msg *response = request->peer;\n    struct msg *sub_msg;\n    rstatus_t status;\n    uint32_t i;\n\n    status = msg_prepend_format(response, \"*%d\\r\\n\", request->narg - 1);\n    if (status != NC_OK) {\n        /*\n         * the fragments is still in c_conn->omsg_q, we have to discard all of them,\n         * we just close the conn here\n         */\n        response->owner->err = 1;\n        return;\n    }\n\n    for (i = 0; i < array_n(request->keys); i++) {      /* for each key */\n        sub_msg = request->frag_seq[i]->peer;           /* get it's peer response */\n        if (sub_msg == NULL) {\n            response->owner->err = 1;\n            return;\n        }\n        status = redis_copy_bulk(response, sub_msg);\n        if (status != NC_OK) {\n            response->owner->err = 1;\n            return;\n        }\n    }\n}\n\n/*\n * Post-coalesce handler is invoked when the message is a response to\n * the fragmented multi vector request - 'mget' or 'del' and all the\n * responses to the fragmented request vector has been received and\n * the fragmented request is consider to be done\n */\nvoid\nredis_post_coalesce(struct msg *r)\n{\n    struct msg *pr = r->peer; /* peer response */\n\n    ASSERT(!pr->request);\n    ASSERT(r->request && (r->frag_owner == r));\n    if (r->error || r->ferror) {\n        /* do nothing, if msg is in error */\n        return;\n    }\n\n    switch (r->type) {\n    case MSG_REQ_REDIS_MGET:\n        return redis_post_coalesce_mget(r);\n\n    case MSG_REQ_REDIS_DEL:\n    case MSG_REQ_REDIS_TOUCH:\n    case MSG_REQ_REDIS_UNLINK:\n        return redis_post_coalesce_del_or_touch(r);\n\n    case MSG_REQ_REDIS_MSET:\n        return redis_post_coalesce_mset(r);\n\n    default:\n        NOT_REACHED();\n    }\n}\n\nstatic rstatus_t\nredis_handle_auth_req(struct msg *req, struct msg *rsp)\n{\n    struct conn *conn = (struct conn *)rsp->owner;\n    const struct server_pool *pool;\n    const struct keypos *kpos;\n    const uint8_t *key;\n    uint32_t keylen;\n    bool valid;\n\n    ASSERT(conn->client && !conn->proxy);\n\n    pool = (const struct server_pool *)conn->owner;\n\n    if (!pool->require_auth) {\n        /*\n         * AUTH command from the client in absence of a redis_auth:\n         * directive should be treated as an error\n         */\n        return msg_append(rsp, rsp_no_password.data, rsp_no_password.len);\n    }\n\n    kpos = array_get(req->keys, 0);\n    key = kpos->start;\n    keylen = (uint32_t)(kpos->end - kpos->start);\n    valid = (keylen == pool->redis_auth.len) &&\n            (memcmp(pool->redis_auth.data, key, keylen) == 0);\n    if (valid) {\n        conn->authenticated = 1;\n        return msg_append(rsp, rsp_ok.data, rsp_ok.len);\n    }\n\n    /*\n     * Password in the AUTH command doesn't match the one configured in\n     * redis_auth: directive\n     *\n     * We mark the connection has unauthenticated until the client\n     * reauthenticates with the correct password\n     */\n    conn->authenticated = 0;\n    return msg_append(rsp, rsp_invalid_password.data, rsp_invalid_password.len);\n}\n\nrstatus_t\nredis_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn)\n{\n    rstatus_t status;\n    struct msg *msg;\n    struct server_pool *pool;\n\n    ASSERT(!s_conn->client && !s_conn->proxy);\n    ASSERT(!conn_authenticated(s_conn));\n\n    pool = c_conn->owner;\n\n    msg = msg_get(c_conn, true, c_conn->redis);\n    if (msg == NULL) {\n        c_conn->err = errno;\n        return NC_ENOMEM;\n    }\n\n    status = msg_prepend_format(msg, \"*2\\r\\n$4\\r\\nAUTH\\r\\n$%d\\r\\n%s\\r\\n\",\n                                pool->redis_auth.len, pool->redis_auth.data);\n    if (status != NC_OK) {\n        msg_put(msg);\n        return status;\n    }\n\n    msg->swallow = 1;\n    s_conn->enqueue_inq(ctx, s_conn, msg);\n    s_conn->authenticated = 1;\n\n    return NC_OK;\n}\n\nvoid\nredis_post_connect(struct context *ctx, struct conn *conn, struct server *server)\n{\n    rstatus_t status;\n    struct server_pool *pool = server->owner;\n    struct msg *msg;\n    int digits;\n\n    ASSERT(!conn->client && conn->connected);\n    ASSERT(conn->redis);\n\n    /*\n     * By default, every connection to redis uses the database DB 0. You\n     * can select a different one on a per-connection basis by sending\n     * a request 'SELECT <redis_db>', where <redis_db> is the configured\n     * on a per pool basis in the configuration\n     */\n    if (pool->redis_db <= 0) {\n        return;\n    }\n\n    /*\n     * Create a fake client message and add it to the pipeline. We force this\n     * message to be head of queue as it might already contain a command\n     * that triggered the connect.\n     */\n    msg = msg_get(conn, true, conn->redis);\n    if (msg == NULL) {\n        return;\n    }\n\n    digits = (pool->redis_db >= 10) ? (int)log10(pool->redis_db) + 1 : 1;\n    status = msg_prepend_format(msg, \"*2\\r\\n$6\\r\\nSELECT\\r\\n$%d\\r\\n%d\\r\\n\", digits, pool->redis_db);\n    if (status != NC_OK) {\n        msg_put(msg);\n        return;\n    }\n    msg->type = MSG_REQ_REDIS_SELECT;\n    msg->result = MSG_PARSE_OK;\n    msg->swallow = 1;\n    msg->owner = NULL;\n\n    /* enqueue as head and send */\n    req_server_enqueue_imsgq_head(ctx, conn, msg);\n    msg_send(ctx, conn);\n\n    log_debug(LOG_NOTICE, \"sent 'SELECT %d' to %s | %s\", pool->redis_db,\n              pool->name.data, server->name.data);\n}\n\nvoid\nredis_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg)\n{\n    if (pmsg != NULL && pmsg->type == MSG_REQ_REDIS_SELECT &&\n        msg != NULL && redis_error(msg)) {\n        struct server* conn_server;\n        struct server_pool* conn_pool;\n        struct mbuf* rsp_buffer;\n        uint8_t message[128];\n        size_t copy_len;\n\n        /*\n         * Get a substring from the message so that the initial - and the trailing\n         * \\r\\n is removed.\n         */\n        conn_server = (struct server*)conn->owner;\n        conn_pool = conn_server->owner;\n        rsp_buffer = STAILQ_LAST(&msg->mhdr, mbuf, next);\n        copy_len = MIN(mbuf_length(rsp_buffer) - 3, sizeof(message) - 1);\n\n        nc_memcpy(message, &rsp_buffer->start[1], copy_len);\n        message[copy_len] = 0;\n\n        log_warn(\"SELECT %d failed on %s | %s: %s\",\n                 conn_pool->redis_db, conn_pool->name.data,\n                 conn_server->name.data, message);\n    }\n}\n"
  },
  {
    "path": "src/test_all.c",
    "content": "#include <nc_hashkit.h>\n#include <nc_conf.h>\n#include <nc_util.h>\n#include <proto/nc_proto.h>\n#include <stdio.h>\n\nstatic int failures = 0;\nstatic int successes = 0;\n\nstatic void expect_same_int(int expected, int actual, const char* message) {\n    if (expected != actual) {\n        printf(\"FAIL Expected %d, got %d (%s)\\n\", expected, actual, message);\n        failures++;\n    } else {\n        /* printf(\"PASS (%s)\\n\", message); */\n        successes++;\n    }\n}\n\nstatic void expect_same_uint32_t(uint32_t expected, uint32_t actual, const char* message) {\n    if (expected != actual) {\n        printf(\"FAIL Expected %u, got %u (%s)\\n\", (unsigned int) expected,\n                (unsigned int) actual, message);\n        failures++;\n    } else {\n        /* printf(\"PASS (%s)\\n\", message); */\n        successes++;\n    }\n}\n\nstatic void expect_same_ptr(const void *expected, const void *actual, const char* message) {\n    if (expected != actual) {\n        printf(\"FAIL Expected %p, got %p (%s)\\n\", expected, actual, message);\n        failures++;\n    } else {\n        /* printf(\"PASS (%s)\\n\", message); */\n        successes++;\n    }\n}\n\nstatic void test_hash_algorithms(void) {\n    /* refer to libmemcached tests/hash_results.h */\n    expect_same_uint32_t(2297466611U, hash_one_at_a_time(\"apple\", 5), \"should have expected one_at_a_time hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(3195025439U, hash_md5(\"apple\", 5), \"should have expected md5 hash for key \\\"apple\\\"\");\n\n    expect_same_uint32_t(3662830516U, hash_crc16(\"apple\", 5), \"should have expected crc16 hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(10542U,      hash_crc32(\"apple\", 5), \"should have expected crc32 hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(2838417488U, hash_crc32a(\"apple\", 5), \"should have expected crc32a hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(67176023U,   hash_fnv1_32(\"apple\", 5), \"should have expected fnv1_32 hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(280767167U,  hash_fnv1a_32(\"apple\", 5), \"should have expected fnv1a_32 hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(473199127U,  hash_fnv1_64(\"apple\", 5), \"should have expected fnv1_64 hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(1488911807U, hash_fnv1a_64(\"apple\", 5), \"should have expected fnv1a_64 hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(3738850110U, hash_hsieh(\"apple\", 5), \"should have expected hsieh hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(1442444624U, hash_jenkins(\"apple\", 5), \"should have expected jenkins hash for key \\\"apple\\\"\");\n    expect_same_uint32_t(4142305122U, hash_murmur(\"apple\", 5), \"should have expected murmur hash for key \\\"apple\\\"\");\n    /* The above have exactly the same result as libmemcached/tests/hash_results.h */\n\n    expect_same_uint32_t(3853726576U, ketama_hash(\"server1-8\", strlen(\"server1-8\"), 0), \"should have expected ketama_hash for server1-8 index 0\");\n    expect_same_uint32_t(2667054752U, ketama_hash(\"server1-8\", strlen(\"server1-8\"), 3), \"should have expected ketama_hash for server1-8 index 3\");\n}\n\nstatic void test_config_parsing(void) {\n    const char* conf_file = \"../conf/nutcracker.yml\";\n    struct conf * conf = conf_create(conf_file);\n    if (conf == NULL) {\n        printf(\"FAIL could not parse %s (this test should be run within src/ folder)\\n\", conf_file);\n        failures++;\n    } else {\n        printf(\"PASS parsed %s\\n\", conf_file);\n\n        conf_destroy(conf);\n        successes++;\n    }\n}\n\nstatic void test_redis_parse_req_success_case(const char* data, int expected_type) {\n    const int original_failures = failures;\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in redis_parse_req */\n\n    struct msg *req = msg_get(&fake_client, 1, 1);\n    req->state = SW_START;\n    req->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&req->mhdr);\n    ASSERT(STAILQ_EMPTY(&req->mhdr));\n    mbuf_insert(&req->mhdr, m);\n    req->pos = m->start;\n\n    redis_parse_req(req);\n    expect_same_ptr(m->last, req->pos, \"redis_parse_req: expected req->pos to be m->last\");\n    expect_same_int(SW_START, req->state, \"redis_parse_req: expected full buffer to be parsed\");\n    expect_same_int(expected_type, req->type, \"redis_parse_req: expected request type to be parsed\");\n    expect_same_int(0, fake_client.err, \"redis_parse_req: expected no connection error\");\n\n    msg_put(req);\n    /* mbuf_put(m); */\n    if (failures > original_failures) {\n        fprintf(stderr, \"test_redis_parse_req_success_case failed for (%s)\", data);\n    }\n}\n\n/* Test support for https://redis.io/topics/protocol */\nstatic void test_redis_parse_req_success(void) {\n    /* Redis requests from clients are serialized as arrays before sending them (* is array length, $ is string length) */\n\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\neval\\r\\n$10\\r\\nreturn 123\\r\\n$1\\r\\n1\\r\\n$1\\r\\n1\\r\\n\", MSG_REQ_REDIS_EVAL);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_EVAL);\n\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nappend\\r\\n$3\\r\\n999\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_APPEND);\n    test_redis_parse_req_success_case(\"*2\\r\\n$8\\r\\nbitcount\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_BITCOUNT);\n    test_redis_parse_req_success_case(\"*4\\r\\n$8\\r\\nbitcount\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$1\\r\\n1\\r\\n\", MSG_REQ_REDIS_BITCOUNT);\n    test_redis_parse_req_success_case(\"*1\\r\\n$7\\r\\nCOMMAND\\r\\n\", MSG_REQ_REDIS_COMMAND);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\ndecr\\r\\n$7\\r\\ncounter\\r\\n\", MSG_REQ_REDIS_DECR);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\ndecrby\\r\\n$7\\r\\ncounter\\r\\n$3\\r\\n100\\r\\n\", MSG_REQ_REDIS_DECRBY);\n    test_redis_parse_req_success_case(\"*2\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_DEL);\n    test_redis_parse_req_success_case(\"*3\\r\\n$3\\r\\ndel\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_DEL);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\ndump\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_DUMP);\n    test_redis_parse_req_success_case(\"*2\\r\\n$6\\r\\nexists\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_EXISTS);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nexpire\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n0\\r\\n\", MSG_REQ_REDIS_EXPIRE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$8\\r\\nexpireat\\r\\n$3\\r\\nfoo\\r\\n$10\\r\\n1282463464\\r\\n\", MSG_REQ_REDIS_EXPIREAT);\n    test_redis_parse_req_success_case(\"*2\\r\\n$3\\r\\nGET\\r\\n$3\\r\\nkey\\r\\n\", MSG_REQ_REDIS_GET);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\ngetbit\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n\", MSG_REQ_REDIS_GETBIT);\n    test_redis_parse_req_success_case(\"*4\\r\\n$8\\r\\ngetrange\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$1\\r\\n2\\r\\n\", MSG_REQ_REDIS_GETRANGE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\ngetset\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_GETSET);\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\nhdel\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_HDEL);\n    test_redis_parse_req_success_case(\"*3\\r\\n$7\\r\\nhexists\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n\", MSG_REQ_REDIS_HEXISTS);\n    test_redis_parse_req_success_case(\"*3\\r\\n$4\\r\\nhget\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n\", MSG_REQ_REDIS_HGET);\n    test_redis_parse_req_success_case(\"*2\\r\\n$7\\r\\nhgetall\\r\\n$4\\r\\nhfoo\\r\\n\", MSG_REQ_REDIS_HGETALL);\n    test_redis_parse_req_success_case(\"*4\\r\\n$7\\r\\nhincrby\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n$3\\r\\n100\\r\\n\", MSG_REQ_REDIS_HINCRBY);\n    test_redis_parse_req_success_case(\"*4\\r\\n$12\\r\\nhincrbyfloat\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n$6\\r\\n100.12\\r\\n\", MSG_REQ_REDIS_HINCRBYFLOAT);\n    test_redis_parse_req_success_case(\"*2\\r\\n$5\\r\\nhkeys\\r\\n$4\\r\\nhfoo\\r\\n\", MSG_REQ_REDIS_HKEYS);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nhlen\\r\\n$4\\r\\nhfoo\\r\\n\", MSG_REQ_REDIS_HLEN);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\nhmget\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n\", MSG_REQ_REDIS_HMGET);\n    test_redis_parse_req_success_case(\"*4\\r\\n$5\\r\\nhmget\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n$6\\r\\n1dleif\\r\\n\", MSG_REQ_REDIS_HMGET);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_HMSET);\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\nhset\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\n1dleif\\r\\n$3\\r\\nrab\\r\\n\", MSG_REQ_REDIS_HSET);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\nhsetnx\\r\\n$4\\r\\nhfoo\\r\\n$6\\r\\nfield1\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_HSETNX);\n    test_redis_parse_req_success_case(\"*2\\r\\n$5\\r\\nhvals\\r\\n$4\\r\\nhfoo\\r\\n\", MSG_REQ_REDIS_HVALS);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nincr\\r\\n$7\\r\\ncounter\\r\\n\", MSG_REQ_REDIS_INCR);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nincrby\\r\\n$7\\r\\ncounter\\r\\n$3\\r\\n100\\r\\n\", MSG_REQ_REDIS_INCRBY);\n    test_redis_parse_req_success_case(\"*3\\r\\n$11\\r\\nincrbyfloat\\r\\n$7\\r\\ncounter\\r\\n$5\\r\\n10.10\\r\\n\", MSG_REQ_REDIS_INCRBYFLOAT);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nlindex\\r\\n$4\\r\\nlfoo\\r\\n$1\\r\\n0\\r\\n\", MSG_REQ_REDIS_LINDEX);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_LINSERT);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nllen\\r\\n$4\\r\\nlfoo\\r\\n\", MSG_REQ_REDIS_LLEN);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nLLEN\\r\\n$6\\r\\nmylist\\r\\n\", MSG_REQ_REDIS_LLEN);  /* LLEN command */\n    test_redis_parse_req_success_case(\"*1\\r\\n$6\\r\\nLOLWUT\\r\\n\", MSG_REQ_REDIS_LOLWUT);\n    test_redis_parse_req_success_case(\"*2\\r\\n$6\\r\\nLOLWUT\\r\\n$2\\r\\n40\\r\\n\", MSG_REQ_REDIS_LOLWUT);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nlpop\\r\\n$4\\r\\nlfoo\\r\\n\", MSG_REQ_REDIS_LPOP);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\nlpush\\r\\n$4\\r\\nlfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_LPUSH);\n    test_redis_parse_req_success_case(\"*4\\r\\n$5\\r\\nlpush\\r\\n$4\\r\\nlfoo\\r\\n$3\\r\\nbaq\\r\\n$3\\r\\nbap\\r\\n\", MSG_REQ_REDIS_LPUSH);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_LPUSH);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nlpushx\\r\\n$4\\r\\nlfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_LPUSHX);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\nlrange\\r\\n$4\\r\\nlfoo\\r\\n$1\\r\\n0\\r\\n$1\\r\\n2\\r\\n\", MSG_REQ_REDIS_LRANGE);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\nlrange\\r\\n$4\\r\\nlfoo\\r\\n$1\\r\\n0\\r\\n$1\\r\\n3\\r\\n\", MSG_REQ_REDIS_LRANGE);\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\nlrem\\r\\n$4\\r\\nlfoo\\r\\n$1\\r\\n2\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_LREM);\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\nlset\\r\\n$4\\r\\nlfoo\\r\\n$1\\r\\n0\\r\\n$3\\r\\nbaq\\r\\n\", MSG_REQ_REDIS_LSET);\n    test_redis_parse_req_success_case(\"*4\\r\\n$5\\r\\nltrim\\r\\n$4\\r\\nlfoo\\r\\n$1\\r\\n0\\r\\n$1\\r\\n2\\r\\n\", MSG_REQ_REDIS_LTRIM);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_MGET);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nMGET\\r\\n$1\\r\\nx\\r\\n\", MSG_REQ_REDIS_MGET);\n    test_redis_parse_req_success_case(\"*3\\r\\n$4\\r\\nMGET\\r\\n$1\\r\\nx\\r\\n$10\\r\\nabcdefghij\\r\\n\", MSG_REQ_REDIS_MGET);\n    test_redis_parse_req_success_case(\"*3\\r\\n$4\\r\\nmget\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_MGET);\n    test_redis_parse_req_success_case(\"*3\\r\\n$4\\r\\nmget\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_MGET);\n    test_redis_parse_req_success_case(\"*2\\r\\n$7\\r\\npersist\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_PERSIST);\n    test_redis_parse_req_success_case(\"*3\\r\\n$7\\r\\npexpire\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n0\\r\\n\", MSG_REQ_REDIS_PEXPIRE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\npfadd\\r\\n$7\\r\\n{pfoo}2\\r\\n$3\\r\\nbas\\r\\n\", MSG_REQ_REDIS_PFADD);\n    test_redis_parse_req_success_case(\"*4\\r\\n$5\\r\\npfadd\\r\\n$4\\r\\npfoo\\r\\n$3\\r\\nbar\\r\\n$3\\r\\nbas\\r\\n\", MSG_REQ_REDIS_PFADD);\n    test_redis_parse_req_success_case(\"*2\\r\\n$7\\r\\npfcount\\r\\n$4\\r\\npfoo\\r\\n\", MSG_REQ_REDIS_PFCOUNT);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_PFMERGE);\n    test_redis_parse_req_success_case(\"*1\\r\\n$4\\r\\nPING\\r\\n\", MSG_REQ_REDIS_PING);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\npsetex\\r\\n$3\\r\\nfoo\\r\\n$4\\r\\n1000\\r\\n$3\\r\\noof\\r\\n\", MSG_REQ_REDIS_PSETEX);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\npttl\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_PTTL);\n    test_redis_parse_req_success_case(\"*4\\r\\n$7\\r\\nrestore\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n0\\r\\n$3\\r\\noof\\r\\n\", MSG_REQ_REDIS_RESTORE);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\nrpop\\r\\n$4\\r\\nlfoo\\r\\n\", MSG_REQ_REDIS_RPOP);\n    test_redis_parse_req_success_case(\"*3\\r\\n$9\\r\\nrpoplpush\\r\\n$6\\r\\n{lfoo}\\r\\n$7\\r\\n{lfoo}2\\r\\n\", MSG_REQ_REDIS_RPOPLPUSH);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\nrpush\\r\\n$4\\r\\nlfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_RPUSH);\n    test_redis_parse_req_success_case(\"*4\\r\\n$5\\r\\nrpush\\r\\n$4\\r\\nlfoo\\r\\n$3\\r\\nbat\\r\\n$3\\r\\nbau\\r\\n\", MSG_REQ_REDIS_RPUSH);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nrpushx\\r\\n$4\\r\\nlfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_RPUSHX);\n    test_redis_parse_req_success_case(\"*3\\r\\n$4\\r\\nsadd\\r\\n$7\\r\\n{sfoo}2\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_SADD);\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\nsadd\\r\\n$4\\r\\nsfoo\\r\\n$3\\r\\nbar\\r\\n$3\\r\\nbas\\r\\n\", MSG_REQ_REDIS_SADD);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_SADD);\n    test_redis_parse_req_success_case(\"*2\\r\\n$5\\r\\nscard\\r\\n$4\\r\\nsfoo\\r\\n\", MSG_REQ_REDIS_SCARD);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\nsdiff\\r\\n$6\\r\\n{sfoo}\\r\\n$7\\r\\n{sfoo}2\\r\\n\", MSG_REQ_REDIS_SDIFF);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_SDIFFSTORE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$3\\r\\nSET\\r\\n$10\\r\\nkey4567890\\r\\n$5\\r\\nVALUE\\r\\n\", MSG_REQ_REDIS_SET);\n    test_redis_parse_req_success_case(\"*3\\r\\n$3\\r\\nset\\r\\n$3\\r\\nbar\\r\\n$3\\r\\nrab\\r\\n\", MSG_REQ_REDIS_SET);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\nsetbit\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$1\\r\\n1\\r\\n\", MSG_REQ_REDIS_SETBIT);\n    test_redis_parse_req_success_case(\"*4\\r\\n$5\\r\\nsetex\\r\\n$3\\r\\nfoo\\r\\n$4\\r\\n1000\\r\\n$3\\r\\noof\\r\\n\", MSG_REQ_REDIS_SETEX);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\nsetnx\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\noof\\r\\n\", MSG_REQ_REDIS_SETNX);\n    test_redis_parse_req_success_case(\"*4\\r\\n$8\\r\\nsetrange\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$3\\r\\noof\\r\\n\", MSG_REQ_REDIS_SETRANGE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nsinter\\r\\n$6\\r\\n{sfoo}\\r\\n$7\\r\\n{sfoo}2\\r\\n\", MSG_REQ_REDIS_SINTER);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_SINTERSTORE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$9\\r\\nsismember\\r\\n$4\\r\\nsfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_SISMEMBER);\n    test_redis_parse_req_success_case(\"*2\\r\\n$8\\r\\nsmembers\\r\\n$4\\r\\nsfoo\\r\\n\", MSG_REQ_REDIS_SMEMBERS);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_SMOVE);\n    test_redis_parse_req_success_case(\"*2\\r\\n$11\\r\\nsrandmember\\r\\n$4\\r\\nsfoo\\r\\n\", MSG_REQ_REDIS_SRANDMEMBER);\n    test_redis_parse_req_success_case(\"*3\\r\\n$11\\r\\nsrandmember\\r\\n$4\\r\\nsfoo\\r\\n$1\\r\\n2\\r\\n\", MSG_REQ_REDIS_SRANDMEMBER);\n    test_redis_parse_req_success_case(\"*3\\r\\n$4\\r\\nsrem\\r\\n$4\\r\\nsfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_SREM);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_SREM);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nsunion\\r\\n$6\\r\\n{sfoo}\\r\\n$7\\r\\n{sfoo}2\\r\\n\", MSG_REQ_REDIS_SUNION);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_SUNIONSTORE);\n    test_redis_parse_req_success_case(\"*2\\r\\n$3\\r\\nttl\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_TTL);\n    test_redis_parse_req_success_case(\"*2\\r\\n$4\\r\\ntype\\r\\n$3\\r\\nfoo\\r\\n\", MSG_REQ_REDIS_TYPE);\n    test_redis_parse_req_success_case(\"*4\\r\\n$4\\r\\nzadd\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\n100\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_ZADD);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_ZADD);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_ZADD);\n    test_redis_parse_req_success_case(\"*2\\r\\n$5\\r\\nzcard\\r\\n$4\\r\\nzfoo\\r\\n\", MSG_REQ_REDIS_ZCARD);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\nzcount\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\n100\\r\\n$3\\r\\n101\\r\\n\", MSG_REQ_REDIS_ZCOUNT);\n    test_redis_parse_req_success_case(\"*4\\r\\n$7\\r\\nzincrby\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\n100\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_ZINCRBY);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_ZINTERSTORE);\n    test_redis_parse_req_success_case(\"*4\\r\\n$9\\r\\nzlexcount\\r\\n$4\\r\\nzfoo\\r\\n$1\\r\\n-\\r\\n$1\\r\\n+\\r\\n\", MSG_REQ_REDIS_ZLEXCOUNT);\n    test_redis_parse_req_success_case(\"*4\\r\\n$6\\r\\nzrange\\r\\n$4\\r\\nzfoo\\r\\n$1\\r\\n0\\r\\n$1\\r\\n3\\r\\n\", MSG_REQ_REDIS_ZRANGE);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_ZRANGE);\n    test_redis_parse_req_success_case(\"*4\\r\\n$11\\r\\nzrangebylex\\r\\n$4\\r\\nzfoo\\r\\n$1\\r\\n-\\r\\n$1\\r\\n+\\r\\n\", MSG_REQ_REDIS_ZRANGEBYLEX);\n    test_redis_parse_req_success_case(\"*4\\r\\n$13\\r\\nzrangebyscore\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\n100\\r\\n$3\\r\\n101\\r\\n\", MSG_REQ_REDIS_ZRANGEBYSCORE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$5\\r\\nzrank\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_ZRANK);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_ZREM);\n    test_redis_parse_req_success_case(\"*4\\r\\n$14\\r\\nzremrangebylex\\r\\n$4\\r\\nzfoo\\r\\n$1\\r\\n-\\r\\n$1\\r\\n+\\r\\n\", MSG_REQ_REDIS_ZREMRANGEBYLEX);\n    test_redis_parse_req_success_case(\"*4\\r\\n$15\\r\\nzremrangebyrank\\r\\n$4\\r\\nzfoo\\r\\n$1\\r\\n0\\r\\n$1\\r\\n1\\r\\n\", MSG_REQ_REDIS_ZREMRANGEBYRANK);\n    test_redis_parse_req_success_case(\"*4\\r\\n$16\\r\\nzremrangebyscore\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\n100\\r\\n$3\\r\\n101\\r\\n\", MSG_REQ_REDIS_ZREMRANGEBYSCORE);\n    test_redis_parse_req_success_case(\"*4\\r\\n$9\\r\\nzrevrange\\r\\n$4\\r\\nzfoo\\r\\n$1\\r\\n0\\r\\n$1\\r\\n2\\r\\n\", MSG_REQ_REDIS_ZREVRANGE);\n    test_redis_parse_req_success_case(\"*3\\r\\n$8\\r\\nzrevrank\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_ZREVRANK);\n    test_redis_parse_req_success_case(\"*3\\r\\n$6\\r\\nzscore\\r\\n$4\\r\\nzfoo\\r\\n$3\\r\\nbar\\r\\n\", MSG_REQ_REDIS_ZSCORE);\n    test_redis_parse_req_success_case(\"*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\", MSG_REQ_REDIS_ZUNIONSTORE);\n}\n\nstatic void test_redis_parse_rsp_success_case(const char* data, int expected) {\n    int original_failures = failures;\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in redis_parse_rsp */\n\n    struct msg *rsp = msg_get(&fake_client, 0, 1);\n    rsp->state = SW_START;\n    rsp->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&rsp->mhdr);\n    ASSERT(STAILQ_EMPTY(&rsp->mhdr));\n    mbuf_insert(&rsp->mhdr, m);\n    rsp->pos = m->start;\n    errno = 0;\n\n    redis_parse_rsp(rsp);\n    expect_same_ptr(m->last, rsp->pos, \"redis_parse_rsp: expected rsp->pos to be m->last\");\n    expect_same_int(SW_START, rsp->state, \"redis_parse_rsp: expected full buffer to be parsed\");\n    expect_same_int(expected, rsp->type, \"redis_parse_rsp: expected response type to be parsed\");\n    expect_same_int(0, errno, \"redis_parse_rsp: expected errno=0\");\n    expect_same_uint32_t(1, rsp->rnarg ? rsp->rnarg : 1, \"expected remaining args to be 0 or 1\");\n\n    msg_put(rsp);\n    /* mbuf_put(m); */\n    if (failures > original_failures) {\n        fprintf(stderr, \"test_redis_parse_rsp_success_case failed for (%s)\", data);\n    }\n}\n\n/* Test support for https://redis.io/topics/protocol */\nstatic void test_redis_parse_rsp_success(void) {\n    /* Error message without a space */\n    test_redis_parse_rsp_success_case(\"-CUSTOMERR\\r\\n\", MSG_RSP_REDIS_ERROR);\n    /* Error message */\n    test_redis_parse_rsp_success_case(\"-Error message\\r\\n\", MSG_RSP_REDIS_ERROR);\n    /* Error message without a space */\n    test_redis_parse_rsp_success_case(\"+OK\\r\\n\", MSG_RSP_REDIS_STATUS);\n    /* bulk string */\n    test_redis_parse_rsp_success_case(\"$6\\r\\nfoobar\\r\\n\", MSG_RSP_REDIS_BULK);\n    /* empty bulk string */\n    test_redis_parse_rsp_success_case(\"$0\\r\\n\\r\\n\", MSG_RSP_REDIS_BULK);\n    /* null value */\n    test_redis_parse_rsp_success_case(\"$-1\\r\\n\", MSG_RSP_REDIS_BULK);\n    /* empty array */\n    test_redis_parse_rsp_success_case(\"*0\\r\\n\", MSG_RSP_REDIS_MULTIBULK);\n    /* array with 2 bulk strings */\n    test_redis_parse_rsp_success_case(\"*2\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\",\n            MSG_RSP_REDIS_MULTIBULK);\n    /* array with 3 integers */\n    test_redis_parse_rsp_success_case(\"*3\\r\\n:1\\r\\n:2\\r\\n:3\\r\\n\",\n            MSG_RSP_REDIS_MULTIBULK);\n    /* null array for BLPOP */\n    test_redis_parse_rsp_success_case(\"*-1\\r\\n\", MSG_RSP_REDIS_MULTIBULK);\n    /*\n     * Test support for parsing arrays of arrays.\n     * They can be returned by COMMAND, EVAL, etc.\n     */\n    test_redis_parse_rsp_success_case(\"*2\\r\\n\"\n            \"*3\\r\\n\"\n            \":1\\r\\n\"\n            \":2\\r\\n\"\n            \":3\\r\\n\"\n            \"*2\\r\\n\"\n            \"+Foo\\r\\n\"\n            \"-Bar\\r\\n\", MSG_RSP_REDIS_MULTIBULK);  /* array of 2 arrays */\n}\n\nstatic void test_redis_parse_rsp_failure_case(const char* data) {\n    int original_failures = failures;\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in redis_parse_rsp */\n\n    struct msg *rsp = msg_get(&fake_client, 0, 1);\n    rsp->state = SW_START;\n    rsp->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&rsp->mhdr);\n    ASSERT(STAILQ_EMPTY(&rsp->mhdr));\n    mbuf_insert(&rsp->mhdr, m);\n    rsp->pos = m->start;\n    errno = 0;\n\n    redis_parse_rsp(rsp);\n    expect_same_ptr(m->start, rsp->pos, \"redis_parse_rsp: expected rsp->pos to be m->start\");\n    expect_same_int(MSG_PARSE_ERROR, rsp->result, \"redis_parse_rsp: expected MSG_PARSE_ERROR\");\n    expect_same_int(EINVAL, errno, \"redis_parse_rsp: expected errno=EINVAL\");\n\n    msg_put(rsp);\n    /* mbuf_put(m); */\n    if (failures > original_failures) {\n        fprintf(stderr, \"test_redis_parse_rsp_failure_case failed for (%s)\", data);\n    }\n}\n\n\n/* Test support for https://redis.io/topics/protocol */\nstatic void test_redis_parse_rsp_failure(void) {\n    test_redis_parse_rsp_failure_case(\"*\\r\\n\");\n    test_redis_parse_rsp_failure_case(\":x\\r\\n\");\n    test_redis_parse_rsp_failure_case(\"$6\\r\\nfoobarr\\r\\n\");\n    test_redis_parse_rsp_failure_case(\"$6\\r\\nfoobar\\n\\n\");\n    test_redis_parse_rsp_failure_case(\"$0\\r\\nx\\r\\n\");\n    test_redis_parse_rsp_failure_case(\"$0\\n\");\n    test_redis_parse_rsp_failure_case(\"*2\\r\\n\"\n            \"*3\\r\\n\"\n            \":1\\r\\n\"\n            \":2\\r\\n\"\n            \":3\\r\\n\"\n            \"*2\\r\\n\"\n            \"\\r\\n\");\n}\n\nstatic void test_memcache_parse_rsp_success_case(const char* data, int expected) {\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in memcache_parse_rsp */\n    const int original_failures = failures;\n\n    struct msg *rsp = msg_get(&fake_client, 0, 0);\n    rsp->state = SW_START;\n    rsp->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&rsp->mhdr);\n    ASSERT(STAILQ_EMPTY(&rsp->mhdr));\n    mbuf_insert(&rsp->mhdr, m);\n    rsp->pos = m->start;\n    errno=0;\n\n    memcache_parse_rsp(rsp);\n    expect_same_ptr(m->last, rsp->pos, \"memcache_parse_rsp: expected rsp->pos to be m->last\");\n    expect_same_int(SW_START, rsp->state, \"memcache_parse_rsp: expected state to be SW_START after parsing full buffer\");\n    expect_same_int(expected, rsp->type, \"memcache_parse_rsp: expected response type to be parsed\");\n    expect_same_int(0, fake_client.err, \"memcache_parse_rsp: expected no connection error\");\n    expect_same_int(0, rsp->request, \"memcache_parse_rsp: expected response\");\n    expect_same_int(0, rsp->error, \"memcache_parse_rsp: expected no error\");\n    expect_same_int(0, rsp->swallow, \"memcache_parse_rsp: expected swallow=0\");\n    expect_same_int(0, errno, \"memcache_parse_rsp: expected errno=0\");\n\n    msg_put(rsp);\n    /* mbuf_put(m); */\n    if (original_failures != failures) {\n        printf(\"Saw test failures for test_memcache_parse_rsp_success_case (%s)\\n\",\n               data);\n    }\n}\n\nstatic void test_memcache_parse_rsp_success(void) {\n    test_memcache_parse_rsp_success_case(\"0\\r\\n\", MSG_RSP_MC_NUM);\n    /* The number returned by the server may be space-padded at the end */\n    test_memcache_parse_rsp_success_case(\"0  \\r\\n\", MSG_RSP_MC_NUM);\n    test_memcache_parse_rsp_success_case(\"9223372036854775807\\r\\n\", MSG_RSP_MC_NUM);\n    test_memcache_parse_rsp_success_case(\"DELETED\\r\\n\", MSG_RSP_MC_DELETED);\n    test_memcache_parse_rsp_success_case(\"END\\r\\n\", MSG_RSP_MC_END);\n    test_memcache_parse_rsp_success_case(\"ERROR\\r\\n\", MSG_RSP_MC_ERROR);\n    test_memcache_parse_rsp_success_case(\"EXISTS\\r\\n\", MSG_RSP_MC_EXISTS);\n    test_memcache_parse_rsp_success_case(\"NOT_FOUND\\r\\n\", MSG_RSP_MC_NOT_FOUND);\n    test_memcache_parse_rsp_success_case(\"STORED\\r\\n\", MSG_RSP_MC_STORED);\n    test_memcache_parse_rsp_success_case(\"TOUCHED\\r\\n\", MSG_RSP_MC_TOUCHED);\n    test_memcache_parse_rsp_success_case(\"VALUE key 0 2\\r\\nab\\r\\nEND\\r\\n\", MSG_RSP_MC_END);\n    test_memcache_parse_rsp_success_case(\"VALUE key 0 2\\r\\nab\\r\\nVALUE key2 0 2\\r\\ncd\\r\\nEND\\r\\n\", MSG_RSP_MC_END);\n    test_memcache_parse_rsp_success_case(\"VERSION 1.5.22\\r\\n\", MSG_RSP_MC_VERSION);\n}\n\nstatic void test_memcache_parse_rsp_failure_case(const char* data) {\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in memcache_parse_rsp */\n    const int original_failures = failures;\n\n    struct msg *rsp = msg_get(&fake_client, 0, 0);\n    rsp->state = SW_START;\n    rsp->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&rsp->mhdr);\n    ASSERT(STAILQ_EMPTY(&rsp->mhdr));\n    mbuf_insert(&rsp->mhdr, m);\n    rsp->pos = m->start;\n    errno = 0;\n\n    memcache_parse_rsp(rsp);\n    expect_same_ptr(m->start, rsp->pos, \"memcache_parse_rsp: expected rsp->pos to be m->start\");\n    expect_same_int(0, rsp->type, \"memcache_parse_rsp: expected response type to be parsed\");\n    expect_same_int(MSG_PARSE_ERROR, rsp->result, \"memcache_parse_rsp: expected MSG_PARSE_ERROR\");\n    expect_same_int(EINVAL, errno, \"memcache_parse_rsp: expected EINVAL\");\n\n    msg_put(rsp);\n    /* mbuf_put(m); */\n    if (original_failures != failures) {\n        printf(\"Saw test failures for test_memcache_parse_rsp_success_case (%s)\\n\",\n               data);\n    }\n}\n\n\nstatic void test_memcache_parse_rsp_failure(void) {\n    test_memcache_parse_rsp_failure_case(\"\\r\\n\");\n    test_memcache_parse_rsp_failure_case(\"ENDD\\r\\n\");\n    test_memcache_parse_rsp_failure_case(\"\\r\");\n    test_memcache_parse_rsp_failure_case(\"-1\\r\\n\");\n}\n\nstatic void test_memcache_parse_req_success_case(const char* data, int expected) {\n    const int original_failures = failures;\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in memcache_parse_req */\n    /* in the test cases, the substring noreply only occurs for valid noreply requests */\n    const int expected_noreply = strstr(data, \" noreply\") != NULL;\n\n    struct msg *req = msg_get(&fake_client, 1, 0);\n    req->state = SW_START;\n    req->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&req->mhdr);\n    ASSERT(STAILQ_EMPTY(&req->mhdr));\n    mbuf_insert(&req->mhdr, m);\n    req->pos = m->start;\n\n    memcache_parse_req(req);\n    expect_same_ptr(m->last, req->pos, \"memcache_parse_req: expected req->pos to be m->last\");\n    expect_same_int(SW_START, req->state, \"memcache_parse_req: expected state to be SW_START after parsing full buffer\");\n    expect_same_int(expected, req->type, \"memcache_parse_req: expected response type to be parsed\");\n    expect_same_int(expected_noreply, req->noreply, \"memcache_parse_req: unexpected noreply value\");\n    expect_same_int(0, req->noforward, \"memcache_parse_req: unexpected noforward value\");\n    expect_same_int(1, req->request, \"memcache_parse_req: expected request\");\n    expect_same_int(0, req->error, \"memcache_parse_req: expected no error\");\n    expect_same_int(strstr(data, \"quit\\r\\n\") != NULL ? 1 : 0, req->quit,\n            \"memcache_parse_req: unexpected quit value\");\n    expect_same_int(0, fake_client.err, \"memcache_parse_req: expected no connection error\");\n\n    msg_put(req);\n    /* mbuf_put(m); */\n    if (original_failures != failures) {\n        printf(\"Saw test failures for test_memcache_parse_req_success_case (%s)\\n\",\n               data);\n    }\n}\n\nstatic void test_memcache_parse_req_success(void) {\n    test_memcache_parse_req_success_case(\"add key 0 600 5\\r\\nvalue\\r\\n\", MSG_REQ_MC_ADD);\n    /* Can add any binary data such as '\\n' */\n    test_memcache_parse_req_success_case(\"add key 0 0 1 noreply\\r\\n\\n\\r\\n\", MSG_REQ_MC_ADD);\n    test_memcache_parse_req_success_case(\"append key 0 600 5\\r\\nvalue\\r\\n\", MSG_REQ_MC_APPEND);\n    test_memcache_parse_req_success_case(\"append key 0 1 0 noreply\\r\\n\\r\\n\", MSG_REQ_MC_APPEND);\n    test_memcache_parse_req_success_case(\"cas key 0 600 5 123456\\r\\nvalue\\r\\n\", MSG_REQ_MC_CAS);\n    test_memcache_parse_req_success_case(\"cas key 0 1 1 1 noreply\\r\\nx\\r\\n\", MSG_REQ_MC_CAS);\n    test_memcache_parse_req_success_case(\"decr key 0\\r\\n\", MSG_REQ_MC_DECR);\n    test_memcache_parse_req_success_case(\"decr key 0 noreply\\r\\n\", MSG_REQ_MC_DECR);\n    test_memcache_parse_req_success_case(\"delete a noreply\\r\\n\", MSG_REQ_MC_DELETE);\n    test_memcache_parse_req_success_case(\"delete key\\r\\n\", MSG_REQ_MC_DELETE);\n    /* TODO https://github.com/twitter/twemproxy/issues/631 gat/gats */\n    /* test_memcache_parse_req_success_case(\"gat 3600 key\\r\\n\", MSG_REQ_MC_GAT); */\n    test_memcache_parse_req_success_case(\"get a b xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\r\\n\", MSG_REQ_MC_GET);\n    test_memcache_parse_req_success_case(\"get key\\r\\n\", MSG_REQ_MC_GET);\n    test_memcache_parse_req_success_case(\"gets u\\r\\n\", MSG_REQ_MC_GETS);\n    test_memcache_parse_req_success_case(\"incr key 1\\r\\n\", MSG_REQ_MC_INCR);\n    test_memcache_parse_req_success_case(\"incr key 9223372036854775807 noreply\\r\\n\", MSG_REQ_MC_INCR);\n    test_memcache_parse_req_success_case(\"prepend key 0 600 5\\r\\nvalue\\r\\n\", MSG_REQ_MC_PREPEND);\n    test_memcache_parse_req_success_case(\"prepend key 0 600 0 noreply\\r\\n\\r\\n\", MSG_REQ_MC_PREPEND);\n    test_memcache_parse_req_success_case(\"quit\\r\\n\", MSG_REQ_MC_QUIT);\n    test_memcache_parse_req_success_case(\"replace key 0 600 5\\r\\nvalue\\r\\n\", MSG_REQ_MC_REPLACE);\n    test_memcache_parse_req_success_case(\"replace key 0 9 0 noreply\\r\\n\\r\\n\", MSG_REQ_MC_REPLACE);\n    test_memcache_parse_req_success_case(\"set key 0 5 10 noreply\\r\\nvalue12345\\r\\n\", MSG_REQ_MC_SET);\n    test_memcache_parse_req_success_case(\"set key 0 600 5\\r\\nvalue\\r\\n\", MSG_REQ_MC_SET);\n    test_memcache_parse_req_success_case(\"touch key 12345\\r\\n\", MSG_REQ_MC_TOUCH);\n    test_memcache_parse_req_success_case(\"touch key 12345 noreply\\r\\n\", MSG_REQ_MC_TOUCH);\n    test_memcache_parse_req_success_case(\"version\\r\\n\", MSG_REQ_MC_VERSION);\n}\n\nstatic void test_memcache_parse_req_failure_case(const char* data) {\n    const int original_failures = failures;\n    struct conn fake_client = {0};\n    struct mbuf *m = mbuf_get();\n    const int SW_START = 0;  /* Same as SW_START in memcache_parse_req */\n\n    struct msg *req = msg_get(&fake_client, 1, 0);\n    req->state = SW_START;\n    req->token = NULL;\n    const size_t datalen = strlen(data);\n\n    /* Copy data into the message */\n    mbuf_copy(m, (const uint8_t*)data, datalen);\n    /* Insert a single buffer into the message mbuf header */\n    STAILQ_INIT(&req->mhdr);\n    ASSERT(STAILQ_EMPTY(&req->mhdr));\n    mbuf_insert(&req->mhdr, m);\n    req->pos = m->start;\n    errno = 0;\n\n    memcache_parse_req(req);\n\n    expect_same_ptr(m->start, req->pos, \"memcache_parse_rsp: expected rsp->pos to be m->start\");\n    expect_same_int(MSG_PARSE_ERROR, req->result, \"memcache_parse_rsp: expected MSG_PARSE_ERROR\");\n    expect_same_int(EINVAL, errno, \"memcache_parse_rsp: expected EINVAL\");\n\n    msg_put(req);\n    /* mbuf_put(m); */\n    if (original_failures != failures) {\n        printf(\"Saw test failures for test_memcache_parse_req_success_case (%s)\\n\",\n               data);\n    }\n}\n\nstatic void test_memcache_parse_req_failure(void) {\n    /* key length exceeds 250 */\n    test_memcache_parse_req_failure_case(\"add xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 0 600 5\\r\\nvalue\\r\\n\");\n    test_memcache_parse_req_failure_case(\"add\\r\\n\");\n    test_memcache_parse_req_failure_case(\"get\\r\\n\");\n    test_memcache_parse_req_failure_case(\"get \\r\\n\");\n    test_memcache_parse_req_failure_case(\"get key\\r\\r\");\n    test_memcache_parse_req_failure_case(\"append key 0 600\\r\\n\");\n    test_memcache_parse_req_failure_case(\"cas key 0 600 5 \\r\\n\");\n    test_memcache_parse_req_failure_case(\"decr key 0 extra\\r\\n\");\n    test_memcache_parse_req_failure_case(\"decr key\\r\\n\");\n    test_memcache_parse_req_failure_case(\"delete \\r\\n\");\n    test_memcache_parse_req_failure_case(\"DELETE key\\r\\n\");\n    test_memcache_parse_req_failure_case(\"gets\\r\\n\");\n    test_memcache_parse_req_failure_case(\"incr\\r\\n\");\n    test_memcache_parse_req_failure_case(\"incr key 0notanint\\r\\n\");\n    test_memcache_parse_req_failure_case(\"prepend key 0 600 5\\r\\nvalueextra\\r\\n\");\n    test_memcache_parse_req_failure_case(\"prepend key 0 600 0 noreply\\r\\r\");\n    /* test_memcache_parse_req_failure_case(\"quit unknownarg\\r\\n\"); */\n    test_memcache_parse_req_failure_case(\"replace key 0 9 ?\\r\\n\\r\\n\");\n    test_memcache_parse_req_failure_case(\"set key 0 5 10 noreply\\r\\nvalue12345\\r\\r\");\n    test_memcache_parse_req_failure_case(\"set key 0 600 5\\r\\nvaluee\\r\\n\");\n    test_memcache_parse_req_failure_case(\"touch missingarg\\r\\n\");\n    test_memcache_parse_req_failure_case(\"version extra\\r\\n\");\n}\n\nint main(int argc, char **argv) {\n    struct instance nci = {0};\n    nci.mbuf_chunk_size = MBUF_SIZE;\n    mbuf_init(&nci);\n    msg_init();\n    log_init(7, NULL);\n\n    test_hash_algorithms();\n    test_config_parsing();\n    test_redis_parse_rsp_success();\n    test_redis_parse_req_success();\n    test_memcache_parse_rsp_success();\n    test_memcache_parse_req_success();\n    printf(\"Starting tests of request/response parsing failures\\n\");\n    test_memcache_parse_rsp_failure();\n    test_memcache_parse_req_failure();\n    test_redis_parse_rsp_failure();\n    printf(\"%d successes, %d failures\\n\", successes, failures);\n\n    msg_deinit();\n    mbuf_deinit();\n    log_deinit();\n\n    return failures > 0 ? 1 : 0;\n}\n"
  },
  {
    "path": "test_in_docker.sh",
    "content": "#!/usr/bin/env bash\n# Main ci script for nutcracker tests\nset -xeu\n\nfunction print_usage() {\n    echo \"Usage: $0 [REDIS_VER]\" 1>&2\n    echo \"e.g.   $0 3.2.12\" 1>&2\n    exit 1\n}\n\nREDIS_VER=3.2.11\nif [[ \"$#\" > 1 ]]; then\n    echo \"Too many arguments\" 1>&2\n    print_usage\nelif [[ \"$#\" > 0 ]]; then\n\tREDIS_VER=\"$1\"\nfi\n\nPACKAGE_NAME=\"nutcrackerci\"\n\nTAG=$( git describe --always )\nDOCKER_IMG_NAME=twemproxy-build-$PACKAGE_NAME-$REDIS_VER-$TAG\n\nrm -rf twemproxy\n\nDOCKER_TAG=twemproxy-$PACKAGE_NAME-$REDIS_VER:$TAG\n\ndocker build -f ci/Dockerfile \\\n   --tag $DOCKER_TAG \\\n   --build-arg=REDIS_VER=$REDIS_VER \\\n   .\n\n# Run c unit tests\nUNIT_TEST_FAIL=no\nif ! docker run \\\n   --rm \\\n   -e REDIS_VER=$REDIS_VER \\\n   --workdir=/usr/src/twemproxy/src \\\n   --name=$DOCKER_IMG_NAME \\\n   --entrypoint=/bin/sh \\\n   $DOCKER_TAG \\\n   -c 'make test_all && ./test_all'; then\n\n    UNIT_TEST_FAIL=yes\nfi\n\n# Run nose tests\ndocker run \\\n   --rm \\\n   -e REDIS_VER=$REDIS_VER \\\n   --name=$DOCKER_IMG_NAME \\\n   $DOCKER_TAG \\\n   nosetests -v test_redis test_memcache test_system\n\nif [ $UNIT_TEST_FAIL = yes ]; then\n    echo \"See earlier output, unit tests failed\" 1>&2\n    exit 1\nfi\n"
  },
  {
    "path": "tests/.gitignore",
    "content": "*.pyc\n*.out\n*.log\n"
  },
  {
    "path": "tests/README.rst",
    "content": "Python testing facilities for twemproxy, this test suite is based on https://github.com/idning/redis-mgr\n\nTesting in docker\n=================\n\nThe script `test_in_docker.sh` can be run to run all of twemproxy's compiler checks, C unit tests, and this folder's integration tests in docker.\n\n    REDIS_VERSION=6.2.4\n    ./test_in_docker.sh $REDIS_VERSION\n\nusage\n=====\n\nInformation on setting up integration tests, running integration tests, and creating new integration tests is below.\n\n1. install dependencies (redis-py must be 3.0 or newer)::\n\n    pip install nose\n    pip install git+https://github.com/andymccurdy/redis-py.git@3.5.3\n    pip install git+https://github.com/linsomniac/python-memcached.git#egg=memcache\n\n2. copy binaries to _binaries/::\n\n    _binaries/\n    |-- nutcracker\n    |-- redis-benchmark\n    |-- redis-check-aof\n    |-- redis-check-dump\n    |-- redis-cli\n    |-- redis-sentinel\n    |-- redis-server\n    |-- memcached\n\n3. run with nosetests (or ./nosetests_verbose.sh)::\n\n    $ python3 -m nose -v\n    test_del.test_multi_delete_on_readonly ... ok\n    test_mget.test_mget ... ok\n\n    ----------------------------------------------------------------------\n    Ran 2 tests in 4.483s\n\n    OK\n\n4. add a case::\n\n    cp tests/test_del.py tests/test_xxx.py\n    vim tests/test_xxx.py\n\n\n\nvariables\n=========\n::\n\n    export T_VERBOSE=9 will start nutcracker with '-v 9'  (default:4)\n    export T_MBUF=512  will start nutcracker whit '-m 512' (default:521)\n    export T_LARGE=10000 will test 10000 keys for mget/mset (default:1000)\n\nT_LOGFILE:\n\n- to put test log on stderr::\n\n    export T_LOGFILE=-\n\n- to put test log on t.log::\n\n    export T_LOGFILE=t.log\n\n  or::\n\n    unset T_LOGFILE\n\n\nnotes\n=====\n\n- After all of the integration tests, you may get a core dump because we have a case in test_signal which will send SEGV to nutcracker\n\n- If tests are failing, you may have to `pkill` redis-server, redis-sentinel, or nutcracker\n\nUnit tests\n==========\n\nSee src/test_all.c - unit tests are separate from these integration tests and do not require python. They can be compiled and run with `make check`.\n\nTo view the output of the failing tests, run `cd src; make test_all; ./test_all`.\n"
  },
  {
    "path": "tests/conf/conf.py",
    "content": "import os\nimport sys\n\nPWD = os.path.dirname(os.path.realpath(__file__))\nWORKDIR = os.path.join(PWD,  '../')\n\nBINARYS = {\n    'REDIS_SERVER_BINS'   : os.path.join(WORKDIR, '_binaries/redis-*'),\n    'REDIS_CLI'           : os.path.join(WORKDIR, '_binaries/redis-cli'),\n    'MEMCACHED_BINS'      : os.path.join(WORKDIR, '_binaries/memcached'),\n    'NUTCRACKER_BINS'     : os.path.join(WORKDIR, '_binaries/nutcracker'),\n}\n\n"
  },
  {
    "path": "tests/conf/control.sh",
    "content": "#!/bin/bash\n\nstart() \n{\n    stop\n    ulimit -c unlimited\n\n    pushd . > /dev/null\n\n    cd `dirname $$0`\n    ${startcmd}\n    popd\n}\n\nstop() \n{\n    pkill -9 -f '${runcmd}'\n}\n\ncase C\"$$1\" in\n    C)\n        echo \"Usage: $$0 {start|stop}\"\n        ;; \n    Cstart)\n        start\n        echo \"Done!\"\n        ;;\n    Cstop)\n        stop\n        echo \"Done!\"\n        ;;\n    C*)\n        echo \"Usage: $$0 {start|stop}\"\n        ;;\nesac\n\n\n\n"
  },
  {
    "path": "tests/conf/redis.conf",
    "content": "\n# Redis configuration file example\n# Note on units: when memory size is needed, it is possible to specify\n# it in the usual form of 1k 5GB 4M and so forth:\n#\n# 1k => 1000 bytes\n# 1kb => 1024 bytes\n# 1m => 1000000 bytes\n# 1mb => 1024*1024 bytes\n# 1g => 1000000000 bytes\n# 1gb => 1024*1024*1024 bytes\n#\n# units are case insensitive so 1GB 1Gb 1gB are all the same.\n\n# By default Redis does not run as a daemon. Use 'yes' if you need it.\n# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.\ndaemonize yes\n\n# When running daemonized, Redis writes a pid file in /var/run/redis.pid by\n# default. You can specify a custom pid file location here.\npidfile ${pidfile}\n\n# Accept connections on the specified port, default is 6379.\n# If port 0 is specified Redis will not listen on a TCP socket.\nport ${port}\n\n# If you want you can bind a single interface, if the bind option is not\n# specified all the interfaces will listen for incoming connections.\n#\n# bind 127.0.0.1\n\n# Specify the path for the unix socket that will be used to listen for\n# incoming connections. There is no default, so Redis will not listen\n# on a unix socket when not specified.\n#\n# unixsocket /tmp/redis.sock\n# unixsocketperm 755\n\n# Close the connection after a client is idle for N seconds (0 to disable)\ntimeout 0\n\n# TCP keepalive.\n#\n# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence\n# of communication. This is useful for two reasons:\n#\n# 1) Detect dead peers.\n# 2) Take the connection alive from the point of view of network\n#    equipment in the middle.\n#\n# On Linux, the specified value (in seconds) is the period used to send ACKs.\n# Note that to close the connection the double of the time is needed.\n# On other kernels the period depends on the kernel configuration.\n#\n# A reasonable value for this option is 60 seconds.\ntcp-keepalive 60\n\n# Specify the server verbosity level.\n# This can be one of:\n# debug (a lot of information, useful for development/testing)\n# verbose (many rarely useful info, but not a mess like the debug level)\n# notice (moderately verbose, what you want in production probably)\n# warning (only very important / critical messages are logged)\nloglevel notice\n\n# Specify the log file name. Also 'stdout' can be used to force\n# Redis to log on the standard output. Note that if you use standard\n# output for logging but daemonize, logs will be sent to /dev/null\nlogfile ${logfile}\n\n# To enable logging to the system logger, just set 'syslog-enabled' to yes,\n# and optionally update the other syslog parameters to suit your needs.\n# syslog-enabled no\n\n# Specify the syslog identity.\n# syslog-ident redis\n\n# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.\n# syslog-facility local0\n\n# Set the number of databases. The default database is DB 0, you can select\n# a different one on a per-connection basis using SELECT <dbid> where\n# dbid is a number between 0 and 'databases'-1\ndatabases 16\n\n################################ SNAPSHOTTING  #################################\n#\n# Save the DB on disk:\n#\n#   save <seconds> <changes>\n#\n#   Will save the DB if both the given number of seconds and the given\n#   number of write operations against the DB occurred.\n#\n#   In the example below the behaviour will be to save:\n#   after 900 sec (15 min) if at least 1 key changed\n#   after 300 sec (5 min) if at least 10 keys changed\n#   after 60 sec if at least 10000 keys changed\n#\n#   Note: you can disable saving at all commenting all the \"save\" lines.\n#\n#   It is also possible to remove all the previously configured save\n#   points by adding a save directive with a single empty string argument\n#   like in the following example:\n#\n\n#save 900 1\n#save 300 10\n#save 60 10000\nsave \"\"\n\n# By default Redis will stop accepting writes if RDB snapshots are enabled\n# (at least one save point) and the latest background save failed.\n# This will make the user aware (in an hard way) that data is not persisting\n# on disk properly, otherwise chances are that no one will notice and some\n# distater will happen.\n#\n# If the background saving process will start working again Redis will\n# automatically allow writes again.\n#\n# However if you have setup your proper monitoring of the Redis server\n# and persistence, you may want to disable this feature so that Redis will\n# continue to work as usually even if there are problems with disk,\n# permissions, and so forth.\nstop-writes-on-bgsave-error yes\n\n# Compress string objects using LZF when dump .rdb databases?\n# For default that's set to 'yes' as it's almost always a win.\n# If you want to save some CPU in the saving child set it to 'no' but\n# the dataset will likely be bigger if you have compressible values or keys.\nrdbcompression yes\n\n# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.\n# This makes the format more resistant to corruption but there is a performance\n# hit to pay (around 10%) when saving and loading RDB files, so you can disable it\n# for maximum performances.\n#\n# RDB files created with checksum disabled have a checksum of zero that will\n# tell the loading code to skip the check.\nrdbchecksum yes\n\n# The filename where to dump the DB\ndbfilename dump.rdb\n\n# The working directory.\n#\n# The DB will be written inside this directory, with the filename specified\n# above using the 'dbfilename' configuration directive.\n# \n# The Append Only File will also be created inside this directory.\n# \n# Note that you must specify a directory here, not a file name.\ndir ${dir}\n\n################################# REPLICATION #################################\n\n# Master-Slave replication. Use slaveof to make a Redis instance a copy of\n# another Redis server. Note that the configuration is local to the slave\n# so for example it is possible to configure the slave to save the DB with a\n# different interval, or to listen to another port, and so on.\n#\n# slaveof <masterip> <masterport>\n\n# If the master is password protected (using the \"requirepass\" configuration\n# directive below) it is possible to tell the slave to authenticate before\n# starting the replication synchronization process, otherwise the master will\n# refuse the slave request.\n#\n# masterauth <master-password>\n\n# When a slave loses its connection with the master, or when the replication\n# is still in progress, the slave can act in two different ways:\n#\n# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will\n#    still reply to client requests, possibly with out of date data, or the\n#    data set may just be empty if this is the first synchronization.\n#\n# 2) if slave-serve-stale-data is set to 'no' the slave will reply with\n#    an error \"SYNC with master in progress\" to all the kind of commands\n#    but to INFO and SLAVEOF.\n#\nslave-serve-stale-data yes\n\n# You can configure a slave instance to accept writes or not. Writing against\n# a slave instance may be useful to store some ephemeral data (because data\n# written on a slave will be easily deleted after resync with the master) but\n# may also cause problems if clients are writing to it because of a\n# misconfiguration.\n#\n# Since Redis 2.6 by default slaves are read-only.\n#\n# Note: read only slaves are not designed to be exposed to untrusted clients\n# on the internet. It's just a protection layer against misuse of the instance.\n# Still a read only slave exports by default all the administrative commands\n# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve\n# security of read only slaves using 'rename-command' to shadow all the\n# administrative / dangerous commands.\nslave-read-only yes\n\n# Slaves send PINGs to server in a predefined interval. It's possible to change\n# this interval with the repl_ping_slave_period option. The default value is 10\n# seconds.\n#\n# repl-ping-slave-period 10\n\n# The following option sets a timeout for both Bulk transfer I/O timeout and\n# master data or ping response timeout. The default value is 60 seconds.\n#\n# It is important to make sure that this value is greater than the value\n# specified for repl-ping-slave-period otherwise a timeout will be detected\n# every time there is low traffic between the master and the slave.\n#\nrepl-timeout 120\n\n# Disable TCP_NODELAY on the slave socket after SYNC?\n#\n# If you select \"yes\" Redis will use a smaller number of TCP packets and\n# less bandwidth to send data to slaves. But this can add a delay for\n# the data to appear on the slave side, up to 40 milliseconds with\n# Linux kernels using a default configuration.\n#\n# If you select \"no\" the delay for data to appear on the slave side will\n# be reduced but more bandwidth will be used for replication.\n#\n# By default we optimize for low latency, but in very high traffic conditions\n# or when the master and slaves are many hops away, turning this to \"yes\" may\n# be a good idea.\nrepl-disable-tcp-nodelay no\n\n# Set the replication backlog size. The backlog is a buffer that accumulates\n# slave data when slaves are disconnected for some time, so that when a slave\n# wants to reconnect again, often a full resync is not needed, but a partial\n# resync is enough, just passing the portion of data the slave missed while\n# disconnected.\n#\n# The biggest the replication backlog, the longer the time the slave can be\n# disconnected and later be able to perform a partial resynchronization.\n#\n# The backlog is only allocated once there is at least a slave connected.\n#\nrepl-backlog-size 64mb\n\n# After a master has no longer connected slaves for some time, the backlog\n# will be freed. The following option configures the amount of seconds that\n# need to elapse, starting from the time the last slave disconnected, for\n# the backlog buffer to be freed.\n#\n# A value of 0 means to never release the backlog.\n#\n# repl-backlog-ttl 3600\n\n# The slave priority is an integer number published by Redis in the INFO output.\n# It is used by Redis Sentinel in order to select a slave to promote into a\n# master if the master is no longer working correctly.\n#\n# A slave with a low priority number is considered better for promotion, so\n# for instance if there are three slaves with priority 10, 100, 25 Sentinel will\n# pick the one wtih priority 10, that is the lowest.\n#\n# However a special priority of 0 marks the slave as not able to perform the\n# role of master, so a slave with priority of 0 will never be selected by\n# Redis Sentinel for promotion.\n#\n# By default the priority is 100.\nslave-priority 100\n\n# It is possible for a master to stop accepting writes if there are less than\n# N slaves connected, having a lag less or equal than M seconds.\n#\n# The N slaves need to be in \"online\" state.\n#\n# The lag in seconds, that must be <= the specified value, is calculated from\n# the last ping received from the slave, that is usually sent every second.\n#\n# This option does not GUARANTEES that N replicas will accept the write, but\n# will limit the window of exposure for lost writes in case not enough slaves\n# are available, to the specified number of seconds.\n#\n# For example to require at least 3 slaves with a lag <= 10 seconds use:\n#\n# min-slaves-to-write 3\n# min-slaves-max-lag 10\n#\n# Setting one or the other to 0 disables the feature.\n#\n# By default min-slaves-to-write is set to 0 (feature disabled) and\n# min-slaves-max-lag is set to 10.\n\n################################## SECURITY ###################################\n\n# Require clients to issue AUTH <PASSWORD> before processing any other\n# commands.  This might be useful in environments in which you do not trust\n# others with access to the host running redis-server.\n#\n# This should stay commented out for backward compatibility and because most\n# people do not need auth (e.g. they run their own servers).\n# \n# Warning: since Redis is pretty fast an outside user can try up to\n# 150k passwords per second against a good box. This means that you should\n# use a very strong password otherwise it will be very easy to break.\n#\n# requirepass foobared\n\n# Command renaming.\n#\n# It is possible to change the name of dangerous commands in a shared\n# environment. For instance the CONFIG command may be renamed into something\n# hard to guess so that it will still be available for internal-use tools\n# but not available for general clients.\n#\n# Example:\n#\n# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52\n#\n# It is also possible to completely kill a command by renaming it into\n# an empty string:\n#\n# rename-command CONFIG \"\"\n#\n# Please note that changing the name of commands that are logged into the\n# AOF file or transmitted to slaves may cause problems.\n\n################################### LIMITS ####################################\n\n# Set the max number of connected clients at the same time. By default\n# this limit is set to 10000 clients, however if the Redis server is not\n# able to configure the process file limit to allow for the specified limit\n# the max number of allowed clients is set to the current file limit\n# minus 32 (as Redis reserves a few file descriptors for internal uses).\n#\n# Once the limit is reached Redis will close all the new connections sending\n# an error 'max number of clients reached'.\n#\n# maxclients 10000\n\n# Don't use more memory than the specified amount of bytes.\n# When the memory limit is reached Redis will try to remove keys\n# accordingly to the eviction policy selected (see maxmemmory-policy).\n#\n# If Redis can't remove keys according to the policy, or if the policy is\n# set to 'noeviction', Redis will start to reply with errors to commands\n# that would use more memory, like SET, LPUSH, and so on, and will continue\n# to reply to read-only commands like GET.\n#\n# This option is usually useful when using Redis as an LRU cache, or to set\n# an hard memory limit for an instance (using the 'noeviction' policy).\n#\n# WARNING: If you have slaves attached to an instance with maxmemory on,\n# the size of the output buffers needed to feed the slaves are subtracted\n# from the used memory count, so that network problems / resyncs will\n# not trigger a loop where keys are evicted, and in turn the output\n# buffer of slaves is full with DELs of keys evicted triggering the deletion\n# of more keys, and so forth until the database is completely emptied.\n#\n# In short... if you have slaves attached it is suggested that you set a lower\n# limit for maxmemory so that there is some free RAM on the system for slave\n# output buffers (but this is not needed if the policy is 'noeviction').\n#\nmaxmemory 5368709120\n\n# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory\n# is reached. You can select among five behaviors:\n# \n# volatile-lru -> remove the key with an expire set using an LRU algorithm\n# allkeys-lru -> remove any key accordingly to the LRU algorithm\n# volatile-random -> remove a random key with an expire set\n# allkeys-random -> remove a random key, any key\n# volatile-ttl -> remove the key with the nearest expire time (minor TTL)\n# noeviction -> don't expire at all, just return an error on write operations\n# \n# Note: with any of the above policies, Redis will return an error on write\n#       operations, when there are not suitable keys for eviction.\n#\n#       At the date of writing this commands are: set setnx setex append\n#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd\n#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby\n#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby\n#       getset mset msetnx exec sort\n#\n# The default is:\n#\nmaxmemory-policy volatile-lru\n\n# LRU and minimal TTL algorithms are not precise algorithms but approximated\n# algorithms (in order to save memory), so you can select as well the sample\n# size to check. For instance for default Redis will check three keys and\n# pick the one that was used less recently, you can change the sample size\n# using the following configuration directive.\n#\nmaxmemory-samples 3\n\n############################## APPEND ONLY MODE ###############################\n\n# By default Redis asynchronously dumps the dataset on disk. This mode is\n# good enough in many applications, but an issue with the Redis process or\n# a power outage may result into a few minutes of writes lost (depending on\n# the configured save points).\n#\n# The Append Only File is an alternative persistence mode that provides\n# much better durability. For instance using the default data fsync policy\n# (see later in the config file) Redis can lose just one second of writes in a\n# dramatic event like a server power outage, or a single write if something\n# wrong with the Redis process itself happens, but the operating system is\n# still running correctly.\n#\n# AOF and RDB persistence can be enabled at the same time without problems.\n# If the AOF is enabled on startup Redis will load the AOF, that is the file\n# with the better durability guarantees.\n#\n# Please check http://redis.io/topics/persistence for more information.\n\nappendonly yes\n\n# The name of the append only file (default: \"appendonly.aof\")\n# appendfilename appendonly.aof\n\n# The fsync() call tells the Operating System to actually write data on disk\n# instead to wait for more data in the output buffer. Some OS will really flush \n# data on disk, some other OS will just try to do it ASAP.\n#\n# Redis supports three different modes:\n#\n# no: don't fsync, just let the OS flush the data when it wants. Faster.\n# always: fsync after every write to the append only log . Slow, Safest.\n# everysec: fsync only one time every second. Compromise.\n#\n# The default is \"everysec\", as that's usually the right compromise between\n# speed and data safety. It's up to you to understand if you can relax this to\n# \"no\" that will let the operating system flush the output buffer when\n# it wants, for better performances (but if you can live with the idea of\n# some data loss consider the default persistence mode that's snapshotting),\n# or on the contrary, use \"always\" that's very slow but a bit safer than\n# everysec.\n#\n# More details please check the following article:\n# http://antirez.com/post/redis-persistence-demystified.html\n#\n# If unsure, use \"everysec\".\n\n# appendfsync always\nappendfsync everysec\n# appendfsync no\n\n# When the AOF fsync policy is set to always or everysec, and a background\n# saving process (a background save or AOF log background rewriting) is\n# performing a lot of I/O against the disk, in some Linux configurations\n# Redis may block too long on the fsync() call. Note that there is no fix for\n# this currently, as even performing fsync in a different thread will block\n# our synchronous write(2) call.\n#\n# In order to mitigate this problem it's possible to use the following option\n# that will prevent fsync() from being called in the main process while a\n# BGSAVE or BGREWRITEAOF is in progress.\n#\n# This means that while another child is saving, the durability of Redis is\n# the same as \"appendfsync none\". In practical terms, this means that it is\n# possible to lose up to 30 seconds of log in the worst scenario (with the\n# default Linux settings).\n# \n# If you have latency problems turn this to \"yes\". Otherwise leave it as\n# \"no\" that is the safest pick from the point of view of durability.\nno-appendfsync-on-rewrite no\n\n# Automatic rewrite of the append only file.\n# Redis is able to automatically rewrite the log file implicitly calling\n# BGREWRITEAOF when the AOF log size grows by the specified percentage.\n# \n# This is how it works: Redis remembers the size of the AOF file after the\n# latest rewrite (if no rewrite has happened since the restart, the size of\n# the AOF at startup is used).\n#\n# This base size is compared to the current size. If the current size is\n# bigger than the specified percentage, the rewrite is triggered. Also\n# you need to specify a minimal size for the AOF file to be rewritten, this\n# is useful to avoid rewriting the AOF file even if the percentage increase\n# is reached but it is still pretty small.\n#\n# Specify a percentage of zero in order to disable the automatic AOF\n# rewrite feature.\n\nauto-aof-rewrite-percentage 0\nauto-aof-rewrite-min-size 64mb\n\n################################ LUA SCRIPTING  ###############################\n\n# Max execution time of a Lua script in milliseconds.\n#\n# If the maximum execution time is reached Redis will log that a script is\n# still in execution after the maximum allowed time and will start to\n# reply to queries with an error.\n#\n# When a long running script exceed the maximum execution time only the\n# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be\n# used to stop a script that did not yet called write commands. The second\n# is the only way to shut down the server in the case a write commands was\n# already issue by the script but the user don't want to wait for the natural\n# termination of the script.\n#\n# Set it to 0 or a negative value for unlimited execution without warnings.\nlua-time-limit 5000\n\n################################ REDIS CLUSTER  ###############################\n#\n# Normal Redis instances can't be part of a Redis Cluster; only nodes that are\n# started as cluster nodes can. In order to start a Redis instance as a\n# cluster node enable the cluster support uncommenting the following:\n#\n# cluster-enabled yes\n\n# Every cluster node has a cluster configuration file. This file is not\n# intended to be edited by hand. It is created and updated by Redis nodes.\n# Every Redis Cluster node requires a different cluster configuration file.\n# Make sure that instances running in the same system does not have\n# overlapping cluster configuration file names.\n#\n# cluster-config-file nodes-6379.conf\n\n# Cluster node timeout is the amount of milliseconds a node must be unreachable \n# for it to be considered in failure state.\n# Most other internal time limits are multiple of the node timeout.\n#\n# cluster-node-timeout 15000\n\n# In order to setup your cluster make sure to read the documentation\n# available at http://redis.io web site.\n\n################################## SLOW LOG ###################################\n\n# The Redis Slow Log is a system to log queries that exceeded a specified\n# execution time. The execution time does not include the I/O operations\n# like talking with the client, sending the reply and so forth,\n# but just the time needed to actually execute the command (this is the only\n# stage of command execution where the thread is blocked and can not serve\n# other requests in the meantime).\n# \n# You can configure the slow log with two parameters: one tells Redis\n# what is the execution time, in microseconds, to exceed in order for the\n# command to get logged, and the other parameter is the length of the\n# slow log. When a new command is logged the oldest one is removed from the\n# queue of logged commands.\n\n# The following time is expressed in microseconds, so 1000000 is equivalent\n# to one second. Note that a negative number disables the slow log, while\n# a value of zero forces the logging of every command.\nslowlog-log-slower-than 10000\n\n# There is no limit to this length. Just be aware that it will consume memory.\n# You can reclaim memory used by the slow log with SLOWLOG RESET.\nslowlog-max-len 128\n\n############################# Event notification ##############################\n\n# Redis can notify Pub/Sub clients about events happening in the key space.\n# This feature is documented at http://redis.io/topics/keyspace-events\n# \n# For instance if keyspace events notification is enabled, and a client\n# performs a DEL operation on key \"foo\" stored in the Database 0, two\n# messages will be published via Pub/Sub:\n#\n# PUBLISH __keyspace@0__:foo del\n# PUBLISH __keyevent@0__:del foo\n#\n# It is possible to select the events that Redis will notify among a set\n# of classes. Every class is identified by a single character:\n#\n#  K     Keyspace events, published with __keyspace@<db>__ prefix.\n#  E     Keyevent events, published with __keyevent@<db>__ prefix.\n#  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...\n#  \\     String commands\n#  l     List commands\n#  s     Set commands\n#  h     Hash commands\n#  z     Sorted set commands\n#  x     Expired events (events generated every time a key expires)\n#  e     Evicted events (events generated when a key is evicted for maxmemory)\n#  A     Alias for g\\lshzxe, so that the \"AKE\" string means all the events.\n#\n#  The \"notify-keyspace-events\" takes as argument a string that is composed\n#  by zero or multiple characters. The empty string means that notifications\n#  are disabled at all.\n#\n#  Example: to enable list and generic events, from the point of view of the\n#           event name, use:\n#\n#  notify-keyspace-events Elg\n#\n#  Example 2: to get the stream of the expired keys subscribing to channel\n#             name __keyevent@0__:expired use:\n#\n#  notify-keyspace-events Ex\n#\n#  By default all notifications are disabled because most users don't need\n#  this feature and the feature has some overhead. Note that if you don't\n#  specify at least one of K or E, no events will be delivered.\nnotify-keyspace-events \"\"\n\n############################### ADVANCED CONFIG ###############################\n\n# Hashes are encoded using a memory efficient data structure when they have a\n# small number of entries, and the biggest entry does not exceed a given\n# threshold. These thresholds can be configured using the following directives.\nhash-max-ziplist-entries 512\nhash-max-ziplist-value 64\n\n# Similarly to hashes, small lists are also encoded in a special way in order\n# to save a lot of space. The special representation is only used when\n# you are under the following limits:\nlist-max-ziplist-entries 512\nlist-max-ziplist-value 64\n\n# Sets have a special encoding in just one case: when a set is composed\n# of just strings that happens to be integers in radix 10 in the range\n# of 64 bit signed integers.\n# The following configuration setting sets the limit in the size of the\n# set in order to use this special memory saving encoding.\nset-max-intset-entries 512\n\n# Similarly to hashes and lists, sorted sets are also specially encoded in\n# order to save a lot of space. This encoding is only used when the length and\n# elements of a sorted set are below the following limits:\nzset-max-ziplist-entries 128\nzset-max-ziplist-value 64\n\n# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in\n# order to help rehashing the main Redis hash table (the one mapping top-level\n# keys to values). The hash table implementation Redis uses (see dict.c)\n# performs a lazy rehashing: the more operation you run into an hash table\n# that is rehashing, the more rehashing \"steps\" are performed, so if the\n# server is idle the rehashing is never complete and some more memory is used\n# by the hash table.\n# \n# The default is to use this millisecond 10 times every second in order to\n# active rehashing the main dictionaries, freeing memory when possible.\n#\n# If unsure:\n# use \"activerehashing no\" if you have hard latency requirements and it is\n# not a good thing in your environment that Redis can reply form time to time\n# to queries with 2 milliseconds delay.\n#\n# use \"activerehashing yes\" if you don't have such hard requirements but\n# want to free memory asap when possible.\nactiverehashing yes\n\n# The client output buffer limits can be used to force disconnection of clients\n# that are not reading data from the server fast enough for some reason (a\n# common reason is that a Pub/Sub client can't consume messages as fast as the\n# publisher can produce them).\n#\n# The limit can be set differently for the three different classes of clients:\n#\n# normal -> normal clients\n# slave  -> slave clients and MONITOR clients\n# pubsub -> clients subcribed to at least one pubsub channel or pattern\n#\n# The syntax of every client-output-buffer-limit directive is the following:\n#\n# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>\n#\n# A client is immediately disconnected once the hard limit is reached, or if\n# the soft limit is reached and remains reached for the specified number of\n# seconds (continuously).\n# So for instance if the hard limit is 32 megabytes and the soft limit is\n# 16 megabytes / 10 seconds, the client will get disconnected immediately\n# if the size of the output buffers reach 32 megabytes, but will also get\n# disconnected if the client reaches 16 megabytes and continuously overcomes\n# the limit for 10 seconds.\n#\n# By default normal clients are not limited because they don't receive data\n# without asking (in a push way), but just after a request, so only\n# asynchronous clients may create a scenario where data is requested faster\n# than it can read.\n#\n# Instead there is a default limit for pubsub and slave clients, since\n# subscribers and slaves receive data in a push fashion.\n#\n# Both the hard or the soft limit can be disabled by setting them to zero.\nclient-output-buffer-limit normal 0 0 0\nclient-output-buffer-limit slave 256mb 64mb 60\nclient-output-buffer-limit pubsub 32mb 8mb 60\n\n# Redis calls an internal function to perform many background tasks, like\n# closing connections of clients in timeot, purging expired keys that are\n# never requested, and so forth.\n#\n# Not all tasks are perforemd with the same frequency, but Redis checks for\n# tasks to perform accordingly to the specified \"hz\" value.\n#\n# By default \"hz\" is set to 10. Raising the value will use more CPU when\n# Redis is idle, but at the same time will make Redis more responsive when\n# there are many keys expiring at the same time, and timeouts may be\n# handled with more precision.\n#\n# The range is between 1 and 500, however a value over 100 is usually not\n# a good idea. Most users should use the default of 10 and raise this up to\n# 100 only in environments where very low latency is required.\nhz 10\n\n# When a child rewrites the AOF file, if the following option is enabled\n# the file will be fsync-ed every 32 MB of data generated. This is useful\n# in order to commit the file to the disk more incrementally and avoid\n# big latency spikes.\naof-rewrite-incremental-fsync yes\n\n################################## INCLUDES ###################################\n\n# Include one or more other config files here.  This is useful if you\n# have a standard template that goes to all Redis server but also need\n# to customize a few per-server settings.  Include files can include\n# other files, so use this wisely.\n#\n# include /path/to/local.conf\n# include /path/to/other.conf\n"
  },
  {
    "path": "tests/conf/sentinel.conf",
    "content": "# Example sentinel.conf\n\n# port <sentinel-port>\n# The port that this sentinel instance will run on\nport ${port}\n\n# By default Redis does not run as a daemon. Use 'yes' if you need it.\n# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.\ndaemonize yes\n\n# When running daemonized, Redis writes a pid file in /var/run/redis.pid by\n# default. You can specify a custom pid file location here.\npidfile ${pidfile}\n\n# Specify the log file name. Also 'stdout' can be used to force\n# Redis to log on the standard output. Note that if you use standard\n# output for logging but daemonize, logs will be sent to /dev/null\nlogfile ${logfile}\n\n# sentinel announce-ip <ip>\n# sentinel announce-port <port>\n#\n# The above two configuration directives are useful in environments where,\n# because of NAT, Sentinel is reachable from outside via a non-local address.\n#\n# When announce-ip is provided, the Sentinel will claim the specified IP address\n# in HELLO messages used to gossip its presence, instead of auto-detecting the\n# local address as it usually does.\n#\n# Similarly when announce-port is provided and is valid and non-zero, Sentinel\n# will announce the specified TCP port.\n#\n# The two options don't need to be used together, if only announce-ip is\n# provided, the Sentinel will announce the specified IP and the server port\n# as specified by the \"port\" option. If only announce-port is provided, the\n# Sentinel will announce the auto-detected local IP and the specified port.\n#\n# Example:\n#\n# sentinel announce-ip 1.2.3.4\n\n# dir <working-directory>\n# Every long running process should have a well-defined working directory.\n# For Redis Sentinel to chdir to /tmp at startup is the simplest thing\n# for the process to don't interfere with administrative tasks such as\n# unmounting filesystems.\ndir ${dir}\n\n"
  },
  {
    "path": "tests/lib/server_modules.py",
    "content": "#!/usr/bin/env python\n#coding: utf-8\n#file   : server_modules.py\n#author : ning\n#date   : 2014-02-24 13:00:28\n\nimport os\nimport sys\n\nfrom utils import *\nimport conf\n\nif sys.version_info[0] < 3:\n    # Give a clear error message instead of a confusing one.\n    sys.stderr.write(\"Error: must use python 3 to run these nosetests, e.g. python3 -m nose [options] test_modules\\n\")\n    sys.exit(2)\n\nclass Base:\n    '''\n    Sub class should implement:\n    _alive, _pre_deploy, status, and init self.args\n    '''\n    def __init__(self, name, host, port, path):\n        self.args = {\n            'name'      : name,\n            'host'      : host,\n            'port'      : port,\n            'path'      : path,\n\n            #startcmd and runcmd will used to generate the control script\n            #used for the start cmd\n            'startcmd'  : '',\n            #process name you see in `ps -aux`, used this to generate stop cmd\n            'runcmd'    : '',\n            'logfile'   : '',\n        }\n\n    def __str__(self):\n        return TT('[$name:$host:$port]', self.args)\n\n    def deploy(self):\n        logging.info('deploy %s' % self)\n        self._run(TTCMD('mkdir -p $path/bin &&  \\\n                      mkdir -p $path/conf && \\\n                      mkdir -p $path/log &&  \\\n                      mkdir -p $path/data',\n                     self.args))\n\n        self._pre_deploy()\n        self._gen_control_script()\n\n    def _gen_control_script(self):\n        content = open(os.path.join(WORKDIR, 'conf/control.sh'), 'r').read()\n        content = TT(content, self.args)\n\n        control_filename = TT('${path}/${name}_control', self.args)\n\n        fout = open(control_filename, 'w+')\n        fout.write(content)\n        fout.close()\n        os.chmod(control_filename, 0o755)\n\n    def start(self):\n        if self._alive():\n            logging.warning('%s already running' % (self))\n            return\n\n        logging.debug('starting %s' % self)\n        t1 = time.time()\n        sleeptime = .1\n\n        cmd = TT(\"cd $path && ./${name}_control start\", self.args)\n        self._run(cmd)\n\n        while not self._alive():\n            lets_sleep(sleeptime)\n            if sleeptime < 5:\n                sleeptime *= 2\n            else:\n                sleeptime = 5.0\n                logging.warning('%s still not alive' % self)\n\n        t2 = time.time()\n        logging.info('%s start ok in %.2f seconds' %(self, t2-t1))\n\n    def stop(self):\n        if not self._alive():\n            logging.warning('%s already stop' %(self))\n            return\n\n        cmd = TT(\"cd $path && ./${name}_control stop\", self.args)\n        self._run(cmd)\n\n        t1 = time.time()\n        while self._alive():\n            lets_sleep()\n        t2 = time.time()\n        logging.info('%s stop ok in %.2f seconds' %(self, t2-t1))\n\n    def pid(self):\n        cmd = TT(\"pgrep -f '^$runcmd'\", self.args)\n        return self._run(cmd)\n\n    def status(self):\n        logging.warning(\"status: not implement\")\n\n    def _alive(self):\n        logging.warning(\"_alive: not implement\")\n\n    def _run(self, raw_cmd):\n        ret = system(raw_cmd, logging.debug)\n        logging.debug('return : [%d] [%s] ' % (len(ret), shorten(ret)))\n        return ret\n\n    def clean(self):\n        cmd = TT(\"rm -rf $path\", self.args)\n        self._run(cmd)\n\n    def host(self):\n        return self.args['host']\n\n    def port(self):\n        return self.args['port']\n\nclass RedisServer(Base):\n    def __init__(self, host, port, path, cluster_name, server_name, auth = None):\n        Base.__init__(self, 'redis', host, port, path)\n\n        self.args['startcmd']     = TT('bin/redis-server conf/redis.conf', self.args)\n        self.args['runcmd']       = TT('redis-server \\\\*:$port', self.args)\n        self.args['conf']         = TT('$path/conf/redis.conf', self.args)\n        self.args['pidfile']      = TT('$path/log/redis.pid', self.args)\n        self.args['logfile']      = TT('$path/log/redis.log', self.args)\n        self.args['dir']          = TT('$path/data', self.args)\n        self.args['REDIS_CLI']    = conf.BINARYS['REDIS_CLI']\n\n        self.args['cluster_name'] = cluster_name\n        self.args['server_name']  = server_name\n        self.args['auth']         = auth\n\n    def _info_dict(self):\n        cmd = TT('$REDIS_CLI -h $host -p $port INFO', self.args)\n        if self.args['auth']:\n            cmd = TT('$REDIS_CLI -h $host -p $port -a $auth INFO', self.args)\n        info = self._run(cmd)\n\n        info = [line.split(':', 1) for line in info.split('\\r\\n')\n                if not line.startswith('#')]\n        info = [i for i in info if len(i) > 1]\n        return defaultdict(str, info) #this is a defaultdict, be Notice\n\n    def _ping(self):\n        cmd = TT('$REDIS_CLI -h $host -p $port PING', self.args)\n        if self.args['auth']:\n            cmd = TT('$REDIS_CLI -h $host -p $port -a $auth PING', self.args)\n        return self._run(cmd)\n\n    def _alive(self):\n        return strstr(self._ping(), 'PONG')\n\n    def _gen_conf(self):\n        content = open(os.path.join(WORKDIR, 'conf/redis.conf'), 'r').read()\n        content = TT(content, self.args)\n        if self.args['auth']:\n            content += '\\r\\nrequirepass %s' % self.args['auth']\n        return content\n\n    def _pre_deploy(self):\n        self.args['BINS'] = conf.BINARYS['REDIS_SERVER_BINS']\n        self._run(TT('cp $BINS $path/bin/', self.args))\n\n        fout = open(TT('$path/conf/redis.conf', self.args), 'w+')\n        fout.write(self._gen_conf())\n        fout.close()\n\n    def status(self):\n        uptime = self._info_dict()['uptime_in_seconds']\n        if uptime:\n            logging.info('%s uptime %s seconds' % (self, uptime))\n        else:\n            logging.error('%s is down' % self)\n\n    def isslaveof(self, master_host, master_port):\n        info = self._info_dict()\n        if info['master_host'] == master_host and \\\n           int(info['master_port']) == master_port:\n            logging.debug('already slave of %s:%s' % (master_host, master_port))\n            return True\n\n    def slaveof(self, master_host, master_port):\n        cmd = 'SLAVEOF %s %s' % (master_host, master_port)\n        return self.rediscmd(cmd)\n\n    def rediscmd(self, cmd):\n        args = copy.deepcopy(self.args)\n        args['cmd'] = cmd\n        cmd = TT('$REDIS_CLI -h $host -p $port $cmd', args)\n        logging.info('%s %s' % (self, cmd))\n        return self._run(cmd)\n\nclass RedisSentinel(RedisServer):\n    def __init__(self, host, port, path, cluster_name, server_name, masters, quorum, down_time, auth = None):\n        RedisServer.__init__(self, host, port, path, cluster_name, server_name, auth)\n\n        self.masters = masters\n        self.quorum = quorum\n        self.down_time = down_time\n\n        self.args['startcmd']     = TT('bin/redis-sentinel conf/sentinel.conf', self.args)\n        self.args['runcmd']       = TT('redis-sentinel \\\\*:$port', self.args)\n        self.args['conf']         = TT('$path/conf/sentinel.conf', self.args)\n        self.args['pidfile']      = TT('$path/log/sentinel.pid', self.args)\n        self.args['logfile']      = TT('$path/log/sentinel.log', self.args)\n\n    def _gen_conf_section(self):\n        template = '''\nsentinel monitor $server_name $host $port %d\nsentinel down-after-milliseconds $server_name %d\nsentinel parallel-syncs $server_name 1\nsentinel failover-timeout $server_name 180000\n''' % (self.quorum, self.down_time)\n        cfg = '\\n'.join([TT(template, master.args) for master in self.masters])\n        return cfg\n\n    def _gen_conf(self):\n        content = open(os.path.join(WORKDIR, 'conf/sentinel.conf'), 'r').read()\n        content = TT(content, self.args)\n        if self.args['auth']:\n            content += '\\r\\nrequirepass %s' % self.args['auth']\n        return content + self._gen_conf_section()\n\n    def _pre_deploy(self):\n        self.args['BINS'] = conf.BINARYS['REDIS_SERVER_BINS']\n        self._run(TT('cp $BINS $path/bin/', self.args))\n\n        fout = open(TT('$path/conf/sentinel.conf', self.args), 'w+')\n        fout.write(self._gen_conf())\n        fout.close()\n\n    def failover(self, server_name):\n        cmd = 'SENTINEL FAILOVER %s' % server_name\n        return self.rediscmd(cmd)\n\nclass Memcached(Base):\n    def __init__(self, host, port, path, cluster_name, server_name):\n        Base.__init__(self, 'memcached', host, port, path)\n\n        self.args['startcmd']     = TT('bin/memcached -d -p $port', self.args)\n        self.args['runcmd']       = self.args['startcmd']\n\n        self.args['cluster_name'] = cluster_name\n        self.args['server_name']  = server_name\n\n    def _alive(self):\n        cmd = TT('echo \"stats\" | socat - TCP:$host:$port', self.args)\n        ret = self._run(cmd)\n        return strstr(ret, 'END')\n\n    def _pre_deploy(self):\n        self.args['BINS'] = conf.BINARYS['MEMCACHED_BINS']\n        self._run(TT('cp $BINS $path/bin/', self.args))\n\nclass NutCracker(Base):\n    def __init__(self, host, port, path, cluster_name, masters, mbuf=512,\n            verbose=5, is_redis=True, redis_auth=None, sentinels=None):\n        Base.__init__(self, 'nutcracker', host, port, path)\n\n        self.masters = masters\n        self.sentinels = sentinels\n\n        self.args['mbuf']        = mbuf\n        self.args['verbose']     = verbose\n        self.args['redis_auth']  = redis_auth\n        self.args['conf']        = TT('$path/conf/nutcracker.conf', self.args)\n        self.args['pidfile']     = TT('$path/log/nutcracker.pid', self.args)\n        self.args['logfile']     = TT('$path/log/nutcracker.log', self.args)\n        self.args['status_port'] = self.args['port'] + 1000\n\n        self.args['startcmd'] = TTCMD('bin/nutcracker -d -c $conf -o $logfile \\\n                                       -p $pidfile -s $status_port            \\\n                                       -v $verbose -m $mbuf -i 1', self.args)\n        self.args['runcmd']   = TTCMD('bin/nutcracker -d -c $conf -o $logfile \\\n                                       -p $pidfile -s $status_port', self.args)\n\n        self.args['cluster_name']= cluster_name\n        self.args['is_redis']= str(is_redis).lower()\n\n    def _alive(self):\n        return self._info_dict()\n\n    def _gen_conf_section(self, servers):\n        template = '    - $host:$port:1 $server_name'\n        cfg = '\\n'.join([TT(template, server.args) for server in servers])\n        return cfg\n\n    def _gen_conf(self):\n        content = '''\n$cluster_name:\n  listen: 0.0.0.0:$port\n  hash: fnv1a_64\n  distribution: modula\n  preconnect: true\n  auto_eject_hosts: false\n  redis: $is_redis\n  backlog: 512\n  timeout: 400\n  client_connections: 0\n  server_connections: 1\n  server_retry_timeout: 2000\n  server_failure_limit: 2\n  servers:\n'''\n        if self.args['redis_auth']:\n            content = content.replace('redis: $is_redis',\n                    'redis: $is_redis\\r\\n  redis_auth: $redis_auth')\n        content = TT(content, self.args)\n        content += self._gen_conf_section(self.masters)\n        if self.args['is_redis'] and self.sentinels:\n            content += '''\n  sentinels:\n'''\n            content += self._gen_conf_section(self.sentinels)\n        return content\n\n    def _pre_deploy(self):\n        self.args['BINS'] = conf.BINARYS['NUTCRACKER_BINS']\n        self._run(TT('cp $BINS $path/bin/', self.args))\n\n        fout = open(TT('$path/conf/nutcracker.conf', self.args), 'w+')\n        fout.write(self._gen_conf())\n        fout.close()\n\n    def version(self):\n        #This is nutcracker-0.4.0\n        s = self._run(TT('$BINS --version', self.args))\n        return s.strip().replace('This is nutcracker-', '')\n\n    def _info_dict(self):\n        try:\n            c = telnetlib.Telnet(self.args['host'], self.args['status_port'])\n            ret = c.read_all()\n            return json_decode(ret)\n        except Exception as e:\n            logging.debug('can not get _info_dict of nutcracker, \\\n                          [Exception: %s]' % (e, ))\n            return None\n\n    def reconfig(self, masters, sentinels=None):\n        self.masters = masters\n        if sentinels:\n            self.sentinels = sentinels\n        self.stop()\n        self.deploy()\n        self.start()\n        logging.info('proxy %s:%s is updated' % (self.args['host'], self.args['port']))\n\n    def logfile(self):\n        return self.args['logfile']\n\n    def cleanlog(self):\n        cmd = TT(\"rm '$logfile'\", self.args)\n        self._run(cmd)\n\n    def signal(self, signo):\n        self.args['signo'] = signo\n        cmd = TT(\"pkill -$signo -f '^$runcmd'\", self.args)\n        self._run(cmd)\n\n    def reload(self):\n        self.signal('USR1')\n\n    def set_config(self, content):\n        fout = open(TT('$path/conf/nutcracker.conf', self.args), 'w+')\n        fout.write(content)\n        fout.close()\n\n        self.reload()\n\n"
  },
  {
    "path": "tests/lib/utils.py",
    "content": "import os\nimport re\nimport sys\nimport time\nimport copy\nimport _thread\nimport socket\nimport threading\nimport logging\nimport inspect\nimport argparse\nimport telnetlib\nimport redis\nimport random\nimport redis\nimport json\nimport glob\nimport subprocess\n\nfrom collections import defaultdict\nfrom argparse import RawTextHelpFormatter\n\nfrom string import Template\n\nPWD = os.path.dirname(os.path.realpath(__file__))\nWORKDIR = os.path.join(PWD,  '../')\n\ndef getenv(key, default):\n    if key in os.environ:\n        return os.environ[key]\n    return default\n\nlogfile = getenv('T_LOGFILE', 'log/t.log')\nif logfile == '-':\n    logging.basicConfig(level=logging.DEBUG,\n        format=\"%(asctime)-15s [%(threadName)s] [%(levelname)s] %(message)s\")\nelse:\n    logging.basicConfig(filename=logfile, level=logging.DEBUG,\n        format=\"%(asctime)-15s [%(threadName)s] [%(levelname)s] %(message)s\")\n\nlogging.info(\"test running\")\n\ndef strstr(s1, s2):\n    return s1.find(s2) != -1\n\ndef lets_sleep(SLEEP_TIME = 0.1):\n    time.sleep(SLEEP_TIME)\n\ndef TT(template, args): #todo: modify all\n    return Template(template).substitute(args)\n\ndef TTCMD(template, args): #todo: modify all\n    '''\n    Template for cmd (we will replace all spaces)\n    '''\n    ret = TT(template, args)\n    return re.sub(' +', ' ', ret)\n\ndef nothrow(ExceptionToCheck=Exception, logger=None):\n    def deco_retry(f):\n        def f_retry(*args, **kwargs):\n            try:\n                return f(*args, **kwargs)\n            except ExceptionToCheck as e:\n                if logger:\n                    logger.info(e)\n                else:\n                    print(str(e))\n        return f_retry  # true decorator\n    return deco_retry\n\n@nothrow(Exception)\ndef test_nothrow():\n    raise Exception('exception: xx')\n\ndef json_encode(j):\n    return json.dumps(j, indent=4, cls=MyEncoder)\n\ndef json_decode(j):\n    if isinstance(j, bytes):\n        j = str(j, encoding=\"utf-8\")\n\n    return json.loads(j)\n\n#commands does not work on windows..\ndef system(cmd, log_fun=logging.info):\n    if log_fun: log_fun(cmd)\n    r = subprocess.getoutput(cmd)\n    return r\n\ndef shorten(s, l=80):\n    if len(s)<=l:\n        return s\n    return s[:l-3] + '...'\n\ndef assert_true(a):\n    assert a, 'assert fail: except true, got %s' % a\n\ndef assert_equal(a, b):\n    assert a == b, 'assert fail: %s vs %s' % (shorten(str(a)), shorten(str(b)))\n\ndef assert_raises(exception_cls, callable, *args, **kwargs):\n    try:\n        callable(*args, **kwargs)\n    except exception_cls as e:\n        return e\n    except Exception as e:\n        assert False, 'assert_raises %s but raised: %s' % (exception_cls, e)\n    assert False, 'assert_raises %s but nothing raise' % (exception_cls)\n\ndef assert_fail(err_response, callable, *args, **kwargs):\n    try:\n        callable(*args, **kwargs)\n    except Exception as e:\n        assert re.search(err_response, str(e)), \\\n               'assert \"%s\" but got \"%s\"' % (err_response, e)\n        return\n\n    assert False, 'assert_fail %s but nothing raise' % (err_response)\n\nif __name__ == \"__main__\":\n    test_nothrow()\n"
  },
  {
    "path": "tests/log/.gitignore",
    "content": "*.log\n"
  },
  {
    "path": "tests/nosetests_verbose.sh",
    "content": "#!/bin/bash -xeu\n# A simple utility script to run tests with extremely verbose output.\n\nif [[ $# == 0 ]]; then\n\techo \"Usage: $0 test_a [test_b, ...]\" 1>&2\n\texit 1\nfi\n\n# Print test logging to stderr\nexport T_LOGFILE=-\n\npython3 -m nose -v --nologcapture --nocapture \"$@\"\n"
  },
  {
    "path": "tests/test_memcache/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_memcache/test_gets.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport sys\nimport redis\nimport memcache\n\nPWD = os.path.dirname(os.path.realpath(__file__))\nWORKDIR = os.path.join(PWD,  '../')\nsys.path.append(os.path.join(WORKDIR, 'lib/'))\nsys.path.append(os.path.join(WORKDIR, 'conf/'))\nimport conf\n\nfrom server_modules import *\nfrom utils import *\n\nCLUSTER_NAME = 'ntest'\nall_mc= [\n        Memcached('127.0.0.1', 2200, '/tmp/r/memcached-2200/', CLUSTER_NAME, 'mc-2200'),\n        Memcached('127.0.0.1', 2201, '/tmp/r/memcached-2201/', CLUSTER_NAME, 'mc-2201'),\n    ]\n\nnc_verbose = int(getenv('T_VERBOSE', 4))\nmbuf = int(getenv('T_MBUF', 512))\nlarge = int(getenv('T_LARGE', 1000))\n\nnc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME,\n                all_mc, mbuf=mbuf, verbose=nc_verbose, is_redis=False)\n\ndef setup():\n    for r in all_mc:\n        r.deploy()\n        r.stop()\n        r.start()\n\n    nc.deploy()\n    nc.stop()\n    nc.start()\n\ndef teardown():\n    for r in all_mc:\n        r.stop()\n    assert(nc._alive())\n    nc.stop()\n\ndef getconn():\n    host_port = '%s:%s' % (nc.host(), nc.port())\n    return memcache.Client([host_port])\n\ndef test_basic():\n    conn = getconn()\n    conn.set('k', 'v')\n    assert('v' == conn.get('k'))\n\n    conn.set(\"key\", \"1\")\n    for i in range(10):\n        conn.incr(\"key\")\n        assert(str(i+2) == conn.get('key'))\n\n    conn.delete(\"key\")\n    assert(None == conn.get('key'))\n\ndefault_kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(10)}\ndef test_mget_mset(kv=default_kv):\n    conn = getconn()\n    conn.set_multi(kv)\n    keys = sorted(kv.keys())\n\n    assert(conn.get_multi(keys) == kv)\n\n    #del\n    conn.delete_multi(keys)\n    #mget again\n    vals = conn.get_multi(keys)\n    assert({} == vals)\n\ndef test_mget_mset_large():\n    for cnt in range(179, large, 179):\n        #print 'test', cnt\n        kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(cnt)}\n        test_mget_mset(kv)\n\ndef test_mget_mset_key_not_exists(kv=default_kv):\n    conn = getconn()\n    conn.set_multi(kv)\n\n    keys = list(kv.keys())\n    keys2 = ['x-'+k for k in keys]\n    keys = keys + keys2\n    random.shuffle(keys)\n\n    for i in range(2):\n        #mget to check\n        vals = conn.get_multi(keys)\n        for i, k in enumerate(keys):\n            if k in kv:\n                assert(kv[k] == vals[k])\n            else:\n                assert(k not in vals)\n\n    #del\n    conn.delete_multi(keys)\n    #mget again\n    vals = conn.get_multi(keys)\n    assert({} == vals)\n\n"
  },
  {
    "path": "tests/test_redis/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_redis/common.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport sys\nimport redis\nimport time\n\nPWD = os.path.dirname(os.path.realpath(__file__))\nWORKDIR = os.path.join(PWD,'../')\nsys.path.append(os.path.join(WORKDIR,'lib/'))\nsys.path.append(os.path.join(WORKDIR,'conf/'))\n\nimport conf\n\nfrom server_modules import *\nfrom utils import *\n\nCLUSTER_NAME = 'ntest'\nnc_verbose = int(getenv('T_VERBOSE', 5))\nmbuf = int(getenv('T_MBUF', 512))\nlarge = int(getenv('T_LARGE', 1000))\n\nall_redis = [\n        RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100'),\n        RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101'),\n        ]\n\nnc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME,\n                all_redis, mbuf=mbuf, verbose=nc_verbose)\n\ndef setup():\n    print('setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose))\n    for r in all_redis + [nc]:\n        r.clean()\n        r.deploy()\n        r.stop()\n        r.start()\n\ndef teardown():\n    all_alive = True\n    for r in all_redis + [nc]:\n        if not r._alive():\n            all_alive = False\n        r.stop()\n    assert(all_alive)\n\ndefault_kv = {b'kkk-%d' % i : b'vvv-%d' % i for i in range(10)}\n\ndef getconn():\n    for r in all_redis:\n        c = redis.Redis(r.host(), r.port())\n        c.flushdb()\n\n    r = redis.Redis(nc.host(), nc.port())\n    return r\n\n"
  },
  {
    "path": "tests/test_redis/test_auth.py",
    "content": "#!/usr/bin/env python3\n\nfrom .common import *\n\nall_redis = [\n    RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/',\n                CLUSTER_NAME, 'redis-2100', auth = 'hellopasswd'),\n    RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/',\n                CLUSTER_NAME, 'redis-2101', auth = 'hellopasswd'),\n]\n\nnc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME,\n                all_redis, mbuf=mbuf, verbose=nc_verbose,\n                redis_auth = 'hellopasswd')\n\nnc_badpass = NutCracker('127.0.0.1', 4101, '/tmp/r/nutcracker-4101', CLUSTER_NAME,\n                        all_redis, mbuf=mbuf, verbose=nc_verbose,\n                        redis_auth = 'badpasswd')\nnc_nopass = NutCracker('127.0.0.1', 4102, '/tmp/r/nutcracker-4102', CLUSTER_NAME,\n                       all_redis, mbuf=mbuf, verbose=nc_verbose)\n\ndef setup():\n    print('setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose))\n    for r in all_redis + [nc, nc_badpass, nc_nopass]:\n        r.clean()\n        r.deploy()\n        r.stop()\n        r.start()\n\ndef teardown():\n    for r in all_redis + [nc, nc_badpass, nc_nopass]:\n        assert(r._alive())\n        r.stop()\n\ndefault_kv = {bytes('kkk-%s' % i, encoding='utf-8') : bytes('vvv-%s' % i, encoding='utf-8') for i in range(10)}\n\ndef getconn():\n    r = redis.Redis(nc.host(), nc.port())\n    return r\n\n'''\n\ncases:\n\n\nredis       proxy       case\n1           1           test_auth_basic\n1           bad         test_badpass_on_proxy\n1           0           test_nopass_on_proxy\n0           0           already tested on other case\n0           1\n\n'''\n\ndef test_auth_basic():\n    # we hope to have same behavior when the server is redis or twemproxy\n    conns = [\n         redis.Redis(all_redis[0].host(), all_redis[0].port()),\n         redis.Redis(nc.host(), nc.port()),\n    ]\n\n    for r in conns:\n        assert_fail('Authentication required', r.ping)\n        assert_fail('Authentication required', r.set, 'k', 'v')\n        assert_fail('Authentication required', r.get, 'k')\n\n        # bad passwd\n        assert_fail('invalid password|WRONGPASS', r.execute_command, 'AUTH', 'badpasswd')\n\n        # everything is ok after auth\n        r.execute_command('AUTH', 'hellopasswd')\n        r.set('k', 'v')\n        assert(r.ping() == True)\n        assert_equal(b'v', r.get('k'))\n\n        # auth fail here, should we return ok or not => we will mark the conn state as not authed\n        assert_fail('invalid password|WRONGPASS', r.execute_command, 'AUTH', 'badpasswd')\n        # https://redis.io/commands/auth changed in redis 6.0.0 and auth now appears to be additive for valid credentials?\n        # We can get the redis version by invoking a shell command, but not going to bother. Just assert that it if it throws, it's for the expected reason.\n        try:\n            r.ping()\n        except Exception as e:\n            assert re.search('Authentication required', str(e))\n        try:\n            r.get('k')\n        except Exception as e:\n            assert re.search('Authentication required', str(e))\n\ndef test_nopass_on_proxy():\n    r = redis.Redis(nc_nopass.host(), nc_nopass.port())\n\n    # if you config pass on redis but not on twemproxy,\n    # twemproxy will reply ok for ping, but once you do get/set, you will get errmsg from redis\n    assert(r.ping() == True)\n    assert_fail('Authentication required', r.set, 'k', 'v')\n    assert_fail('Authentication required', r.get, 'k')\n\n    # proxy has no pass, when we try to auth\n    assert_fail('Client sent AUTH, but no password is set', r.execute_command, 'AUTH', 'anypasswd')\n    pass\n\ndef test_badpass_on_proxy():\n    r = redis.Redis(nc_badpass.host(), nc_badpass.port())\n\n    assert_fail('Authentication required', r.ping)\n    assert_fail('Authentication required', r.set, 'k', 'v')\n    assert_fail('Authentication required', r.get, 'k')\n\n    # we can auth with bad pass (twemproxy will say ok for this)\n    r.execute_command('AUTH', 'badpasswd')\n    # after that, we still got NOAUTH for get/set (return from redis-server)\n    assert(r.ping() == True)\n    assert_fail('Authentication required', r.set, 'k', 'v')\n    assert_fail('Authentication required', r.get, 'k')\n\ndef setup_and_wait():\n    time.sleep(60*60)\n"
  },
  {
    "path": "tests/test_redis/test_basic.py",
    "content": "#!/usr/bin/env python\n#coding: utf-8\n\nfrom nose.tools import nottest\nfrom .common import *\n\ndef test_setget():\n    r = getconn()\n\n    rst = r.set('k', 'v')\n    assert_equal(True, rst)\n    assert_equal(b'v', r.get('k'))\n\ndef test_msetnx():\n    r = getconn()\n\n    # https://redis.io/commands/msetnx\n    # MSETNX is not supported because the keys can get sent to different backends, which is not supported.\n    normalized_kv = {str(key, encoding='utf-8'): val for key, val in default_kv.items()}\n    assert_fail('Socket closed|Connection closed', r.msetnx, normalized_kv)\n\ndef test_null_key():\n    r = getconn()\n    rst = r.set('', 'v')\n    assert_equal(b'v', r.get(''))\n\n    rst = r.set('', '')\n    assert_equal(b'', r.get(''))\n\n    kv = {'' : 'val', 'k': 'v'}\n    ret = r.mset(kv)\n    assert_equal(b'val', r.get(''))\n\ndef test_ping_quit():\n    r = getconn()\n    assert(r.ping() == True)\n\n    #get set\n    rst = r.set('k', 'v')\n    assert_equal(b'v', r.get('k'))\n\n    assert_fail('Socket closed|Connection closed', r.execute_command, 'QUIT')\n\ndef test_slow_req_lua():\n    r = getconn()\n    pipe = r.pipeline(transaction=False)\n    pipe.eval(\"local x=0;for i = 1,300000000,1 do x = x+ i; end; return x\", 1, 'tmp')\n    assert_fail('timed out', pipe.execute)\n\ndef test_fast_req_lua():\n    r = getconn()\n    pipe = r.pipeline(transaction=False)\n    pipe.eval(\"local x=0;for i = 1,100,1 do x = x+ i; end; return x\", 1, 'tmp')\n    assert_equal([5050], pipe.execute())\n\n# Disabled because this uses a lot of memory and would sometimes complete before the timeout.\n@nottest\ndef disabled_test_slow_req():\n    r = getconn()\n\n    kv = {'mkkk-%s' % i : 'mvvv-%s' % i for i in range(500000)}\n\n    pipe = r.pipeline(transaction=False)\n    pipe.set('key-1', 'v1')\n    pipe.get('key-1')\n    pipe.hmset('xxx', kv)\n    pipe.get('key-2')\n    pipe.get('key-3')\n\n    assert_fail('timed out', pipe.execute)\n\ndef test_signal():\n    #init\n    nc.cleanlog()\n    time.sleep(.1)\n    nc.signal('HUP')\n\n    nc.signal('HUP')\n    nc.signal('TTIN')\n    nc.signal('TTOU')\n    nc.signal('SEGV')\n\n    time.sleep(.3)\n    log = open(nc.logfile(), 'r').read()\n\n    assert(strstr(log, 'HUP'))\n    assert(strstr(log, 'TTIN'))\n    assert(strstr(log, 'TTOU'))\n    assert(strstr(log, 'SEGV'))\n\n    #recover\n    nc.start()\n\ndef test_nc_stats():\n    nc.stop() #reset counters\n    nc.start()\n    r = getconn()\n    kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(10)}\n    for k, v in list(kv.items()):\n        r.set(k, v)\n        r.get(k)\n\n    def get_stat(name):\n        time.sleep(1)\n        stat = nc._info_dict()\n        #pprint(stat)\n        if name in ['client_connections', 'client_eof', 'client_err', \\\n                    'forward_error', 'fragments', 'server_ejects']:\n            return stat[CLUSTER_NAME][name]\n\n        #sum num of each server\n        ret = 0\n        for k, v in list(stat[CLUSTER_NAME].items()):\n            if type(v) == dict:\n                ret += v[name]\n        return ret\n\n    assert(get_stat('requests') == 20)\n    assert(get_stat('responses') == 20)\n\n    ##### mget\n    keys = list(kv.keys())\n    r.mget(keys)\n\n    #for version<=0.3.0\n    #assert(get_stat('requests') == 30)\n    #assert(get_stat('responses') == 30)\n\n    #for mget-improve\n    assert(get_stat('requests') == 22)\n    assert(get_stat('responses') == 22)\n\ndef test_issue_323():\n    # do on redis\n    r = all_redis[0]\n    c = redis.Redis(r.host(), r.port())\n    assert_equal([1, b'OK'], c.eval(\"return {1, redis.call('set', 'x', '1')}\", 1, 'tmp'))\n\n    # do on twemproxy\n    c = getconn()\n    assert_equal([1, b'OK'], c.eval(\"return {1, redis.call('set', 'x', '1')}\", 1, 'tmp'))\n\n    # Test processing deeply nested multibulk responses\n    assert_equal([[[[[[[[[[[[[[[[[[[[b'value']]]]]]]]]]]]]]]]]]], b'other'], c.eval(\"return {{{{{{{{{{{{{{{{{{{{'value'}}}}}}}}}}}}}}}}}}}, 'other'}\", 1, 'tmp'))\n\ndef setup_and_wait():\n    time.sleep(60*60)\n\n"
  },
  {
    "path": "tests/test_redis/test_commands.py",
    "content": "#!/usr/bin/env python\n#coding: utf-8\n\nfrom .common import *\n\ndef test_linsert():\n    r = getconn()\n\n    r.rpush('mylist', 'Hello')\n    r.rpush('mylist', 'World')\n    r.linsert('mylist', 'BEFORE', 'World', 'There')\n\n    rst = r.lrange('mylist', 0, -1)\n    assert_equal([b'Hello', b'There', b'World'], rst)\n\ndef test_exists():\n    r = getconn()\n\n    r.set('exists1', 'foo')\n    assert_equal(1, r.exists('exists1'))\n    assert_equal(0, r.exists('doesnotexist'))\n\ndef test_lpush_lrange():\n    r = getconn()\n\n    vals = [b'vvv-%d' % i for i in range(10) ]\n    assert([] == r.lrange('mylist', 0, -1))\n\n    r.lpush('mylist', *vals)\n    rst = r.lrange('mylist', 0, -1)\n\n    assert(10 == len(rst))\n\ndef test_hscan():\n    r = getconn()\n\n    kv = {b'kkk-%d' % i : b'vvv-%d' % i for i in range(10)}\n    r.hmset('a', kv)\n\n    cursor, dic = r.hscan('a')\n    assert(str(cursor) == '0')\n    assert(dic == kv)\n\n    cursor, dic = r.hscan('a', match='kkk-5')\n    assert(str(cursor) == '0')\n    assert(dic == {b'kkk-5': b'vvv-5'})\n\ndef test_hscan_large():\n    r = getconn()\n\n    kv = {b'x'* 100 + (b'kkk-%d' % i) : (b'vvv-%d' % i)\n          for i in range(1000)}\n    r.hmset('a', kv)\n\n    cursor = '0'\n    dic = {}\n    while True:\n        cursor, t = r.hscan('a', cursor, count=10)\n        for k, v in list(t.items()):\n            dic[k] = v\n\n        if '0' == str(cursor):\n            break\n\n    assert(dic == kv)\n\n    cursor, dic = r.hscan('a', '0', match='*kkk-5*', count=1000)\n    if str(cursor) == '0':\n        assert(len(dic) == 111)\n    else:\n        assert(len(dic) == 111)\n\n        #again.\n        cursor, dic = r.hscan('a', cursor, match='*kkk-5*', count=1000)\n        assert(str(cursor) == '0')\n        assert(len(dic) == 0)\n\ndef test_zscan():\n    r = getconn()\n\n    r.zadd('a', {'a': 1, 'b': 2, 'c': 3})\n\n    cursor, pairs = r.zscan('a')\n    assert_equal(0, cursor)\n    assert_equal({(b'a', 1), (b'b', 2), (b'c', 3)}, set(pairs))\n\n    cursor, pairs = r.zscan('a', match='a')\n    assert_equal(0, cursor)\n    assert_equal({(b'a', 1)}, set(pairs))\n\ndef test_sscan():\n    r = getconn()\n\n    r.sadd('a', 1, 2, 3)\n\n    cursor, members = r.sscan('a')\n    assert_equal(0, cursor)\n    assert_equal({b'1', b'2', b'3'}, set(members))\n\n    cursor, members = r.sscan('a', match='1')\n    assert_equal('0', str(cursor))\n    assert_equal({b'1'}, set(members))\n\n"
  },
  {
    "path": "tests/test_redis/test_mget_large_binary.py",
    "content": "#!/usr/bin/env python\n#coding: utf-8\n\nfrom .common import *\nfrom .test_mget_mset import test_mget_mset as _mget_mset\n\n#force to use large mbuf, we need to copy the setup/teardown here..\n\nmbuf = 64*1024\n\nnc = NutCracker(nc.host(), nc.port(), '/tmp/r/nutcracker-4100', CLUSTER_NAME,\n                all_redis, mbuf=mbuf, verbose=nc_verbose)\n\ndef setup():\n    print('special setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose))\n    for r in all_redis + [nc]:\n        r.deploy()\n        r.stop()\n        r.start()\n\ndef teardown():\n    for r in all_redis + [nc]:\n        assert(r._alive())\n        r.stop()\n\n######################################################\ndef test_mget_binary_value(cnt=5):\n    kv = {}\n    for i in range(cnt):\n        kv[bytes('kkk-%s' % i, encoding='utf-8')] = os.urandom(1024*1024*16+1024) #16M\n    for i in range(cnt):\n        kv[bytes('kkk2-%s' % i, encoding='utf-8')] = b''\n    _mget_mset(kv)\n\n"
  },
  {
    "path": "tests/test_redis/test_mget_mset.py",
    "content": "#!/usr/bin/env python3\n\nfrom .common import *\n\ndef test_mget_mset(kv=default_kv):\n    r = getconn()\n\n    def insert_by_pipeline():\n        pipe = r.pipeline(transaction=False)\n        for k, v in list(kv.items()):\n            pipe.set(k, v)\n        pipe.execute()\n\n    def insert_by_mset():\n        ret = r.mset(**kv)\n\n    #insert_by_mset() #only the mget-imporve branch support this\n    try:\n        insert_by_mset() #only the mget-imporve branch support this\n    except:\n        insert_by_pipeline()\n\n    keys = list(kv.keys())\n\n    #mget to check\n    vals = r.mget(keys)\n    for i, k in enumerate(keys):\n        assert_equal(kv[k], vals[i])\n\n    #del\n    assert (len(keys) == r.delete(*keys) )\n\n    #mget again\n    vals = r.mget(keys)\n\n    for i, k in enumerate(keys):\n        assert(None == vals[i])\n\ndef test_mget_mset_on_key_not_exist(kv=default_kv):\n    r = getconn()\n\n    def insert_by_pipeline():\n        pipe = r.pipeline(transaction=False)\n        for k, v in list(kv.items()):\n            pipe.set(k, v)\n        pipe.execute()\n\n    def insert_by_mset():\n        ret = r.mset(**kv)\n\n    try:\n        insert_by_mset() #only the mget-imporve branch support this\n    except:\n        insert_by_pipeline()\n\n    keys = list(kv.keys())\n    keys2 = [b'x-'+k for k in keys]\n    keys = keys + keys2\n    random.shuffle(keys)\n\n    #mget to check\n    vals = r.mget(keys)\n    for i, k in enumerate(keys):\n        if k in kv:\n            assert_equal(kv[k], vals[i])\n        else:\n            assert_equal(None, vals[i])\n\n    #del\n    assert (len(kv) == r.delete(*keys) )\n\n    #mget again\n    vals = r.mget(keys)\n\n    for i, k in enumerate(keys):\n        assert(None == vals[i])\n\ndef test_mget_mset_large():\n    for cnt in range(171, large, 171):\n        kv = {b'kkk-%d' % i : b'vvv-%d' % i for i in range(cnt)}\n        test_mget_mset(kv)\n\ndef test_mget_special_key(cnt=5):\n    #key length = 512-48-1\n    kv = {}\n    for i in range(cnt):\n        k = b'kkk-%d' % i\n        k = k + b'x'*(512-48-1-len(k))\n        kv[k] = b'vvv'\n\n    test_mget_mset(kv)\n\ndef test_mget_special_key_2(cnt=5):\n    #key length = 512-48-2\n    kv = {}\n    for i in range(cnt):\n        k = b'kkk-%d' % i\n        k = k + b'x'*(512-48-2-len(k))\n        kv[k] = b'vvv'*9\n\n    test_mget_mset(kv)\n\ndef test_mget_on_backend_down():\n    #one backend down\n\n    r = redis.Redis(nc.host(), nc.port())\n    assert_equal(None, r.get('key-2'))\n    assert_equal(None, r.get('key-1'))\n\n    all_redis[0].stop()\n\n    assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-1')\n    assert_fail('Connection refused|reset by peer|Broken pipe', r.get, 'key-1')\n    assert_equal(None, r.get('key-2'))\n\n    keys = ['key-1', 'key-2', 'kkk-3']\n    assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, *keys)\n\n    #all backend down\n    all_redis[1].stop()\n    r = redis.Redis(nc.host(), nc.port())\n\n    assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-1')\n    assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-2')\n\n    keys = ['key-1', 'key-2', 'kkk-3']\n    assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, *keys)\n\n    for r in all_redis:\n        r.start()\n\ndef test_mset_on_backend_down():\n    all_redis[0].stop()\n    r = redis.Redis(nc.host(),nc.port())\n\n    assert_fail('Connection refused|Broken pipe',r.mset,default_kv)\n\n    all_redis[1].stop()\n    assert_fail('Connection refused|Broken pipe',r.mset,default_kv)\n\n    for r in all_redis:\n        r.start()\n\ndef test_mget_pipeline():\n    r = getconn()\n\n    pipe = r.pipeline(transaction=False)\n    for k,v in list(default_kv.items()):\n        pipe.set(k,v)\n    keys = list(default_kv.keys())\n    pipe.mget(keys)\n    kv = {}\n    for i in range(large):\n        kv[b'kkk-%d' % i] = os.urandom(100)\n    for k,v in list(kv.items()):\n        pipe.set(k,v)\n    for k in list(kv.keys()):\n        pipe.get(k)\n    rst = pipe.execute()\n\n    #print rst\n    #check the result\n    keys = list(default_kv.keys())\n\n    #mget to check\n    vals = r.mget(keys)\n    for i, k in enumerate(keys):\n        assert(kv[k] == vals[i])\n\n    #del\n    assert (len(keys) == r.delete(*keys) )\n\n    #mget again\n    vals = r.mget(keys)\n\n    for i, k in enumerate(keys):\n        assert(None == vals[i])\n\ndef test_multi_delete_normal():\n    r = getconn()\n\n    for i in range(100):\n        r.set('key-%s'%i, 'val-%s'%i)\n\n    for i in range(100):\n        assert_equal(bytes('val-%s'%i, encoding='utf-8'), r.get('key-%s'%i) )\n\n    keys = ['key-%s'%i for i in range(100)]\n    assert_equal(100, r.delete(*keys))\n\n    for i in range(100):\n        assert_equal(None, r.get('key-%s'%i) )\n\ndef test_multi_delete_on_readonly():\n    all_redis[0].slaveof(all_redis[1].args['host'], all_redis[1].args['port'])\n\n    r = redis.Redis(nc.host(), nc.port())\n\n    # got \"You can't write against a read only (replica)\"\n    assert_fail('READONLY|Invalid|You can\\'t write against a read only', r.delete, 'key-1')\n    assert_equal(0, r.delete('key-2'))\n    assert_fail('READONLY|Invalid|You can\\'t write against a read only', r.delete, 'key-3')\n\n    keys = ['key-1', 'key-2', 'kkk-3']\n    assert_fail('Invalid argument', r.delete, *keys)     # got \"Invalid argument\"\n\ndef test_multi_delete_on_backend_down():\n    r = redis.Redis(nc.host(), nc.port())\n    assert_equal(None, r.get('key-2'))\n\n    # one backend down\n    all_redis[0].stop()\n\n    try:\n        # Saw this fail in redis 6.2.2 spuriously in GitHub actions with a timeout.\n        # Continue to assert that subsequent commands will recover.\n        assert_fail('Connection refused|reset by peer|Broken pipe|Connection timed out', r.delete, 'key-1')\n        assert_equal(None, r.get('key-2'))\n\n        keys = ['key-1', 'key-2', 'kkk-3']\n        assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, *keys)\n\n        #all backend down\n        all_redis[1].stop()\n        r = redis.Redis(nc.host(), nc.port())\n\n        assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-1')\n        assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-2')\n\n        keys = ['key-1', 'key-2', 'kkk-3']\n        assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, *keys)\n    finally:\n        # Start is idempotent.\n        for r in all_redis:\n            r.start()\n\n\ndef test_multi_delete_20140525():\n    r = getconn()\n\n    cnt = 126\n    keys = ['key-%s'%i for i in range(cnt)]\n    pipe = r.pipeline(transaction=False)\n    pipe.mget(keys)\n    pipe.delete(*keys)\n    pipe.execute()\n\n\n"
  },
  {
    "path": "tests/test_redis/test_pipeline.py",
    "content": "#!/usr/bin/env python3\n\nfrom .common import *\n\ndef test_pipeline():\n    r = getconn()\n\n    pipe = r.pipeline(transaction = False)\n\n    pipe.set('a', 'a1').get('a').zadd('z', {'z1': 1}).zadd('z', {'z2': 4})\n    pipe.zincrby('z', 1, 'z1').zrange('z', 0, 5, withscores=True)\n\n    assert_equal(\n        [\n            True,\n            b'a1',\n            True,\n            True,\n            2.0,\n            [(b'z1', 2.0), (b'z2', 4)],\n        ],\n        pipe.execute()\n    )\n\ndef test_invalid_pipeline():\n    r = getconn()\n\n    pipe = r.pipeline(transaction = False)\n\n    pipe.set('a', 1).set('b', 2).lpush('a', 3).set('d', 4).get('a')\n    result = pipe.execute(raise_on_error = False)\n\n    assert_equal(True, result[0])\n    assert_equal(True, result[1])\n\n    # we can't lpush to a key that's a string value, so this should\n    # be a ResponseError exception\n    assert isinstance(result[2], redis.ResponseError)\n\n    # since this isn't a transaction, the other commands after the\n    # error are still executed\n    assert_equal(True, result[3])\n    assert_equal(b'1', result[4])\n\n    # make sure the pipe was restored to a working state\n    assert pipe.set('z', 'zzz').execute() == [True]\n\ndef test_parse_error_raised():\n    r = getconn()\n\n    pipe = r.pipeline(transaction = False)\n\n    # the zrem is invalid because we don't pass any keys to it\n    pipe.set('a', 1).zrem('b').set('b', 2)\n    result = pipe.execute(raise_on_error = False)\n\n    assert result[0]\n    assert isinstance(result[1], redis.ResponseError)\n    assert result[2]\n"
  },
  {
    "path": "tests/test_redis/test_protocol.py",
    "content": "#!/usr/bin/env python3\nfrom .common import *\nfrom pprint import pprint\n\ndef get_conn():\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((nc.host(), nc.port()))\n    s.settimeout(.3)\n    return s\n\ndef _test(req, resp, sleep=0):\n    s = get_conn()\n\n    # Send a single byte at a time\n    for i in req:\n        s.sendall(bytes([i]))\n        time.sleep(sleep)\n\n    s.settimeout(.3)\n\n    data = s.recv(10000)\n    assert_equal(resp, data)\n\ndef test_slow():\n    req = b'*1\\r\\n$4\\r\\nPING\\r\\n'\n    resp = b'+PONG\\r\\n'\n\n    if large > 1000:\n        sleep = 1\n    else:\n        sleep = .1\n\n    _test(req, resp, sleep)\n\ndef test_pingpong():\n    req = b'*1\\r\\n$4\\r\\nPING\\r\\n'\n    resp = b'+PONG\\r\\n'\n    _test(req, resp)\n    # Sanity check there's no error\n    info = nc._info_dict()\n    assert_equal(0, info['ntest']['client_err'])\n\n# twemproxy for redis doesn't appear to have any code to send +OK\\r\\n, it just disconnects.\ndef test_quit():\n    req = b'*1\\r\\n$4\\r\\nQUIT\\r\\n'\n    # NOTE: Nutcracker doesn't appear to have any code to send +OK\\r\\n, it just disconnects.\n    # +OK\\r\\n would also be valid.\n    resp = b''\n    _test(req, resp)\n\n# twemproxy for redis doesn't appear to have any code to send +OK\\r\\n, it just disconnects.\n# If it doesn't try to send anything, there's no client_err.\ndef test_quit_without_recv():\n    req = b'*1\\r\\n$4\\r\\nQUIT\\r\\n'\n    resp = b'+OK\\r\\n'\n    s = get_conn()\n\n    s.sendall(req)\n    s.close()\n    time.sleep(0.1)\n    info = nc._info_dict()\n    assert_equal(0, info['ntest']['client_err'])\n\ndef _test_bad(req):\n    s = get_conn()\n\n    s.sendall(req)\n    data = s.recv(10000)\n\n    assert_equal(b'', s.recv(1000))  # peer is closed\n\ndef test_badreq():\n    reqs = [\n        # '*1\\r\\n$3\\r\\nPING\\r\\n',\n        b'\\r\\n',\n        # '*3abcdefg\\r\\n',\n        b'*3\\r\\n*abcde\\r\\n',\n\n        b'*4\\r\\n$4\\r\\nMSET\\r\\n$1\\r\\nA\\r\\n$1\\r\\nA\\r\\n$1\\r\\nA\\r\\n',\n        b'*2\\r\\n$4\\r\\nMSET\\r\\n$1\\r\\nA\\r\\n',\n        # '*3\\r\\n$abcde\\r\\n',\n        # '*3\\r\\n$3abcde\\r\\n',\n        # '*3\\r\\n$3\\r\\nabcde\\r\\n',\n    ]\n\n    for req in reqs:\n        _test_bad(req)\n\n\ndef test_wrong_argc():\n    s = get_conn()\n\n    s.sendall(b'*1\\r\\n$3\\r\\nGET\\r\\n')\n    assert_equal(b'', s.recv(1000))  # peer is closed\n"
  },
  {
    "path": "tests/test_system/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_system/test_reload.py",
    "content": "#!/usr/bin/env python3\n#file   : test_reload.py\n#author : ning\n#date   : 2014-09-03 12:28:16\n\nimport os\nimport sys\nimport redis\n\nPWD = os.path.dirname(os.path.realpath(__file__))\nWORKDIR = os.path.join(PWD,'../')\nsys.path.append(os.path.join(WORKDIR,'lib/'))\nsys.path.append(os.path.join(WORKDIR,'conf/'))\n\nimport conf\n\nfrom server_modules import *\nfrom utils import *\nfrom nose import with_setup\n\n# Reload support with SIGUSR1 was at one planned to be added in 0.4.2 of twitter/twemproxy but has not actually been added.\nVERSION_SUPPORTING_RELOAD = '99.99.99'\nCLUSTER_NAME = 'ntest'\nnc_verbose = int(getenv('T_VERBOSE', 5))\nmbuf = int(getenv('T_MBUF', 512))\nlarge = int(getenv('T_LARGE', 1000))\n\nT_RELOAD_DELAY = 3 + 1\n\nall_redis = [\n        RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100'),\n        RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101'),\n    ]\n\nnc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME,\n                all_redis, mbuf=mbuf, verbose=nc_verbose)\n\ndef _setup():\n    print('setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose))\n    for r in all_redis + [nc]:\n        r.deploy()\n        r.stop()\n        r.start()\n\ndef _teardown():\n    for r in all_redis + [nc]:\n        assert(r._alive())\n        r.stop()\n\ndef get_tcp_conn(host, port):\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((host, port))\n    s.settimeout(.3)\n    return s\n\ndef send_cmd(s, req, resp):\n    s.sendall(req)\n    data = s.recv(10000)\n    assert(data == resp)\n\n@with_setup(_setup, _teardown)\ndef test_reload_with_old_conf():\n    if nc.version() < VERSION_SUPPORTING_RELOAD:\n        print('Ignore test_reload for version %s' % nc.version())\n        return\n    pid = nc.pid()\n    # print 'old pid:', pid\n    r = redis.Redis(nc.host(), nc.port())\n    r.set('k', 'v')\n\n    conn = get_tcp_conn(nc.host(), nc.port())\n    send_cmd(conn, '*2\\r\\n$3\\r\\nGET\\r\\n$1\\r\\nk\\r\\n', '$1\\r\\nv\\r\\n')\n\n    # nc.reload() is same as nc.stop() and nc.start()\n    nc.reload()\n    time.sleep(.01)  #it need time for the old process fork new process.\n\n    # the old connection is still ok in T_RELOAD_DELAY seconds\n    send_cmd(conn, '*2\\r\\n$3\\r\\nGET\\r\\n$1\\r\\nk\\r\\n', '$1\\r\\nv\\r\\n')\n\n    # conn2 should connect to new instance\n    conn2 = get_tcp_conn(nc.host(), nc.port())\n    send_cmd(conn2, '*2\\r\\n$3\\r\\nGET\\r\\n$1\\r\\nk\\r\\n', '$1\\r\\nv\\r\\n')\n\n    # the old connection is still ok in T_RELOAD_DELAY seconds\n    send_cmd(conn, '*2\\r\\n$3\\r\\nGET\\r\\n$1\\r\\nk\\r\\n', '$1\\r\\nv\\r\\n')\n\n    time.sleep(T_RELOAD_DELAY)\n    assert(pid != nc.pid())\n\n    # assert the old connection is closed.\n    send_cmd(conn, '*2\\r\\n$3\\r\\nGET\\r\\n$1\\r\\nk\\r\\n', '')\n\n    # conn2 should survive\n    send_cmd(conn2, '*2\\r\\n$3\\r\\nGET\\r\\n$1\\r\\nk\\r\\n', '$1\\r\\nv\\r\\n')\n\n    r = redis.Redis(nc.host(), nc.port())\n    rst = r.set('k', 'v')\n    assert(r.get('k') == 'v')\n\n@with_setup(_setup, _teardown)\ndef test_new_port():\n    if nc.version() < VERSION_SUPPORTING_RELOAD:\n        print('Ignore test_reload for version %s' % nc.version())\n        return\n    r = redis.Redis(nc.host(), nc.port())\n    r.set('k', 'v')\n\n    content = '''\nreload_test:\n  listen: 0.0.0.0:4101\n  hash: fnv1a_64\n  distribution: modula\n  redis: true\n  timeout: 400\n  servers:\n    - 127.0.0.1:2100:1 redis-2100\n    - 127.0.0.1:2101:1 redis-2101\n'''\n\n    nc.set_config(content)\n    time.sleep(T_RELOAD_DELAY)\n\n    r1 = redis.Redis(nc.host(), nc.port())\n    r2 = redis.Redis(nc.host(), 4101)\n\n    assert_fail('Connection refused', r1.get, 'k')\n    assert(r2.get('k') == 'v')\n\n@with_setup(_setup, _teardown)\ndef test_pool_add_del():\n    if nc.version() < VERSION_SUPPORTING_RELOAD:\n        print('Ignore test_reload for version %s' % nc.version())\n        return\n\n    r = redis.Redis(nc.host(), nc.port())\n\n    r.set('k', 'v')\n    content = '''\nreload_test:\n  listen: 0.0.0.0:4100\n  hash: fnv1a_64\n  distribution: modula\n  redis: true\n  servers:\n    - 127.0.0.1:2100:1 redis-2100\n    - 127.0.0.1:2101:1 redis-2101\n\nreload_test2:\n  listen: 0.0.0.0:4101\n  hash: fnv1a_64\n  distribution: modula\n  redis: true\n  servers:\n    - 127.0.0.1:2100:1 redis-2100\n    - 127.0.0.1:2101:1 redis-2101\n'''\n\n    nc.set_config(content)\n    time.sleep(T_RELOAD_DELAY)\n\n    r1 = redis.Redis(nc.host(), nc.port())\n    r2 = redis.Redis(nc.host(), 4101)\n\n    assert(r1.get('k') == 'v')\n    assert(r2.get('k') == 'v')\n\n    content = '''\nreload_test:\n  listen: 0.0.0.0:4102\n  hash: fnv1a_64\n  distribution: modula\n  redis: true\n  preconnect: true\n  servers:\n    - 127.0.0.1:2100:1 redis-2100\n    - 127.0.0.1:2101:1 redis-2101\n'''\n    nc.set_config(content)\n    time.sleep(T_RELOAD_DELAY)\n    pid = nc.pid()\n    print(system('ls -l /proc/%s/fd/' % pid))\n\n    r3 = redis.Redis(nc.host(), 4102)\n\n    assert_fail('Connection refused', r1.get, 'k')\n    assert_fail('Connection refused', r2.get, 'k')\n    assert(r3.get('k') == 'v')\n\n    fds = system('ls -l /proc/%s/fd/' % pid)\n    sockets = [s for s in fds.split('\\n') if strstr(s, 'socket:') ]\n    # pool + stat + 2 backend + 1 client\n    assert(len(sockets) == 5)\n\n"
  }
]